diff --git a/.cargo/config b/.cargo/config index e291ebf3e9..7f9d89b452 100644 --- a/.cargo/config +++ b/.cargo/config @@ -1,4 +1,4 @@ [alias] test-gen-llvm = "test -p test_gen" test-gen-dev = "test -p roc_gen_dev -p test_gen --no-default-features --features gen-dev" -test-gen-wasm = "test -p test_gen --no-default-features --features gen-wasm" +test-gen-wasm = "test -p roc_gen_wasm -p test_gen --no-default-features --features gen-wasm" diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 63e7fc9638..c5f2a8d123 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -17,13 +17,13 @@ jobs: - uses: actions/checkout@v2 with: ref: "trunk" - clean: "true" + clean: "true" - name: Earthly version run: earthly --version - name: on trunk; prepare a self-contained benchmark folder - run: ./ci/safe-earthly.sh --build-arg BENCH_SUFFIX=trunk +prep-bench-folder + run: ./ci/safe-earthly.sh --build-arg BENCH_SUFFIX=trunk +prep-bench-folder - uses: actions/checkout@v2 with: diff --git a/.gitignore b/.gitignore index 4efe9d201e..87635559c0 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ zig-cache *.o *.tmp -# llvm human-readable output +# llvm human-readable output *.ll *.bc diff --git a/AUTHORS b/AUTHORS index 7d5a1e502f..41dcc89d13 100644 --- a/AUTHORS +++ b/AUTHORS @@ -61,3 +61,5 @@ Shahn Hogan Tankor Smash Matthias Devlamynck Jan Van Bruggen +Mats Sigge <> +Drew Lazzeri diff --git a/BUILDING_FROM_SOURCE.md b/BUILDING_FROM_SOURCE.md index 86373b26c0..7bae697da1 100644 --- a/BUILDING_FROM_SOURCE.md +++ b/BUILDING_FROM_SOURCE.md @@ -15,7 +15,7 @@ To run the test suite (via `cargo test`), you additionally need to install: * [`valgrind`](https://www.valgrind.org/) (needs special treatment to [install on macOS](https://stackoverflow.com/a/61359781) Alternatively, you can use `cargo test --no-fail-fast` or `cargo test -p specific_tests` to skip over the valgrind failures & tests. -For debugging LLVM IR, we use [DebugIR](https://github.com/vaivaswatha/debugir). This dependency is only required to build with the `--debug` flag, and for normal developtment you should be fine without it. +For debugging LLVM IR, we use [DebugIR](https://github.com/vaivaswatha/debugir). This dependency is only required to build with the `--debug` flag, and for normal developtment you should be fine without it. ### libcxb libraries @@ -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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2858e4402d..bbe7a3822e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,6 +22,15 @@ Earthly may temporarily use a lot of disk space, up to 90 GB. This disk space is - Before making your first pull request, definitely talk to an existing contributor on [Roc Zulip](https://roc.zulipchat.com) first about what you plan to do! This can not only avoid duplicated effort, it can also avoid making a whole PR only to discover it won't be accepted because the change doesn't fit with the goals of the language's design or implementation. - 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. +- All your commits need to be signed to prevent impersonation: + 1. If you have a Yubikey, follow [guide 1](https://dev.to/paulmicheli/using-your-yubikey-to-get-started-with-gpg-3h4k), [guide 2](https://dev.to/paulmicheli/using-your-yubikey-for-signed-git-commits-4l73) and skip the steps below. + 2. [Make a key to sign your commits.](https://docs.github.com/en/authentication/managing-commit-signature-verification/generating-a-new-gpg-key). + 3. [Configure git to use your key.](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key) + 4. Make git sign your commits automatically: + ``` + git config --global commit.gpgsign true + ``` + - You find good first issues [here](https://github.com/rtfeldman/roc/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). ## Can we do better? diff --git a/Cargo.lock b/Cargo.lock index 026b6830b1..2d7a1af35c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1306,12 +1306,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" @@ -1865,9 +1859,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 +2670,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 +2733,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" @@ -3204,6 +3185,17 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157" +[[package]] +name = "repl_test" +version = "0.1.0" +dependencies = [ + "indoc", + "roc_cli", + "roc_repl_cli", + "roc_test_utils", + "strip-ansi-escapes", +] + [[package]] name = "rkyv" version = "0.6.7" @@ -3248,12 +3240,13 @@ dependencies = [ "roc_builtins", "roc_can", "roc_collections", + "roc_error_macros", "roc_load", "roc_module", "roc_parse", "roc_problem", "roc_region", - "roc_reporting", + "roc_target", "roc_types", "roc_unify", "snafu", @@ -3283,6 +3276,7 @@ dependencies = [ "roc_reporting", "roc_solve", "roc_std", + "roc_target", "roc_types", "roc_unify", "serde_json", @@ -3297,6 +3291,7 @@ dependencies = [ "roc_collections", "roc_module", "roc_region", + "roc_target", "roc_types", ] @@ -3327,32 +3322,26 @@ dependencies = [ "const_format", "criterion 0.3.5 (git+https://github.com/Anton-4/criterion.rs)", "indoc", - "inkwell 0.1.0", - "libloading 0.7.1", "mimalloc", "pretty_assertions", "roc_build", "roc_builtins", "roc_can", "roc_collections", - "roc_constrain", "roc_docs", "roc_editor", + "roc_error_macros", "roc_fmt", - "roc_gen_llvm", "roc_linker", "roc_load", "roc_module", "roc_mono", "roc_parse", - "roc_problem", "roc_region", + "roc_repl_cli", "roc_reporting", - "roc_solve", - "roc_types", - "roc_unify", - "rustyline", - "rustyline-derive", + "roc_target", + "roc_test_utils", "serial_test", "target-lexicon", "tempfile", @@ -3414,6 +3403,7 @@ dependencies = [ "roc_module", "roc_parse", "roc_region", + "roc_target", "roc_types", "snafu", "tempfile", @@ -3469,6 +3459,10 @@ dependencies = [ "winit", ] +[[package]] +name = "roc_error_macros" +version = "0.1.0" + [[package]] name = "roc_fmt" version = "0.1.0" @@ -3494,14 +3488,15 @@ dependencies = [ "roc_builtins", "roc_can", "roc_collections", + "roc_error_macros", "roc_module", "roc_mono", "roc_parse", "roc_problem", "roc_region", - "roc_reporting", "roc_solve", "roc_std", + "roc_target", "roc_types", "roc_unify", "target-lexicon", @@ -3516,10 +3511,11 @@ dependencies = [ "morphic_lib", "roc_builtins", "roc_collections", + "roc_error_macros", "roc_module", "roc_mono", - "roc_reporting", "roc_std", + "roc_target", "target-lexicon", ] @@ -3530,10 +3526,11 @@ dependencies = [ "bumpalo", "roc_builtins", "roc_collections", + "roc_error_macros", "roc_module", "roc_mono", - "roc_reporting", "roc_std", + "roc_target", ] [[package]] @@ -3581,6 +3578,7 @@ dependencies = [ "roc_region", "roc_reporting", "roc_solve", + "roc_target", "roc_types", "roc_unify", "tempfile", @@ -3594,6 +3592,7 @@ dependencies = [ "bumpalo", "lazy_static", "roc_collections", + "roc_error_macros", "roc_ident", "roc_region", "snafu", @@ -3610,11 +3609,13 @@ dependencies = [ "roc_builtins", "roc_can", "roc_collections", + "roc_error_macros", "roc_module", "roc_problem", "roc_region", "roc_solve", "roc_std", + "roc_target", "roc_types", "roc_unify", "static_assertions", @@ -3652,6 +3653,69 @@ dependencies = [ [[package]] name = "roc_region" version = "0.1.0" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "roc_repl_cli" +version = "0.1.0" +dependencies = [ + "bumpalo", + "const_format", + "inkwell 0.1.0", + "libloading 0.7.1", + "roc_build", + "roc_builtins", + "roc_collections", + "roc_gen_llvm", + "roc_load", + "roc_mono", + "roc_parse", + "roc_repl_eval", + "roc_target", + "roc_types", + "rustyline", + "rustyline-derive", + "target-lexicon", +] + +[[package]] +name = "roc_repl_eval" +version = "0.1.0" +dependencies = [ + "bumpalo", + "roc_builtins", + "roc_can", + "roc_collections", + "roc_fmt", + "roc_load", + "roc_module", + "roc_mono", + "roc_parse", + "roc_region", + "roc_reporting", + "roc_target", + "roc_types", +] + +[[package]] +name = "roc_repl_wasm" +version = "0.1.0" +dependencies = [ + "bumpalo", + "js-sys", + "roc_builtins", + "roc_collections", + "roc_gen_wasm", + "roc_load", + "roc_parse", + "roc_repl_eval", + "roc_target", + "roc_types", + "wasm-bindgen", + "wasm-bindgen-futures", +] [[package]] name = "roc_reporting" @@ -3671,6 +3735,8 @@ dependencies = [ "roc_problem", "roc_region", "roc_solve", + "roc_target", + "roc_test_utils", "roc_types", "ven_pretty", ] @@ -3692,6 +3758,7 @@ dependencies = [ "roc_problem", "roc_region", "roc_solve", + "roc_target", "roc_types", "roc_unify", "tempfile", @@ -3707,6 +3774,13 @@ dependencies = [ "quickcheck_macros", ] +[[package]] +name = "roc_target" +version = "0.1.0" +dependencies = [ + "target-lexicon", +] + [[package]] name = "roc_test_utils" version = "0.1.0" @@ -3720,6 +3794,7 @@ version = "0.1.0" dependencies = [ "bumpalo", "roc_collections", + "roc_error_macros", "roc_module", "roc_region", "static_assertions", @@ -3730,6 +3805,7 @@ dependencies = [ name = "roc_unify" version = "0.1.0" dependencies = [ + "bitflags", "roc_collections", "roc_module", "roc_types", @@ -4253,6 +4329,7 @@ dependencies = [ "roc_reporting", "roc_solve", "roc_std", + "roc_target", "roc_types", "roc_unify", "target-lexicon", @@ -4274,6 +4351,7 @@ dependencies = [ "roc_load", "roc_module", "roc_mono", + "roc_target", "test_mono_macros", ] @@ -4321,17 +4399,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" @@ -4600,9 +4667,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", @@ -4610,9 +4677,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", @@ -4625,9 +4692,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", @@ -4637,9 +4704,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", @@ -4647,9 +4714,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", @@ -4660,9 +4727,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" diff --git a/Cargo.toml b/Cargo.toml index 234708adcb..874eeb705d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ members = [ "compiler/build", "compiler/arena_pool", "compiler/test_gen", + "compiler/roc_target", "vendor/ena", "vendor/inkwell", "vendor/pathfinding", @@ -30,7 +31,12 @@ members = [ "ast", "cli", "code_markup", + "error_macros", "reporting", + "repl_cli", + "repl_eval", + "repl_test", + "repl_wasm", "roc_std", "test_utils", "utils", diff --git a/Earthfile b/Earthfile index a98e1b4701..95517954a6 100644 --- a/Earthfile +++ b/Earthfile @@ -1,4 +1,4 @@ -FROM rust:1.57.0-slim-bullseye +FROM rust:1.57.0-slim-bullseye # make sure to update nixpkgs-unstable in sources.json too so that it uses the same rust version > search for cargo on unstable here: https://search.nixos.org/packages WORKDIR /earthbuild prep-debian: @@ -47,7 +47,7 @@ install-zig-llvm-valgrind-clippy-rustfmt: copy-dirs: FROM +install-zig-llvm-valgrind-clippy-rustfmt - COPY --dir cli cli_utils compiler docs editor ast code_markup utils test_utils reporting roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./ + COPY --dir cli cli_utils compiler docs editor ast code_markup error_macros utils test_utils reporting repl_cli repl_eval repl_test repl_wasm roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./ test-zig: FROM +install-zig-llvm-valgrind-clippy-rustfmt diff --git a/README.md b/README.md index ea09ac6ec2..c53721ef89 100644 --- a/README.md +++ b/README.md @@ -71,8 +71,6 @@ The core Roc language and standard library include no I/O operations, which give * A VR or [Arduino](https://www.arduino.cc/) platform can expose uncommon I/O operations supported by that hardware, while omitting common I/O operations that are unsupported (such as reading keyboard input from a terminal that doesn't exist). * A high-performance Web server written in Rust can be a Roc platform where all I/O operations are implemented in terms of Streams or Observables rather than a more traditional asynchronous abstraction like Futures or Promises. This would mean all code in that platform's ecosystem would be necessarily built on a common streaming abstraction. -Each Roc platform gets its own separate package repository, with packages built on top of the API that platform exposes. This means each platform has its own ecosystem where everything is built on top of the same shared set of platform-specific primitives. - ## Project Goals Roc is in relatively early stages of development. It's currently possible to build both platforms and applications (see the [examples](https://github.com/rtfeldman/roc/tree/trunk/examples) folder for some examples that aren't particularly organized at the moment), although [documentation](https://github.com/rtfeldman/roc/tree/trunk/compiler/builtins/docs) is in even earlier stages than the compiler itself. diff --git a/TUTORIAL.md b/TUTORIAL.md index d02ae8a929..fb6a1a52ba 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -291,7 +291,8 @@ Records are not objects; they don't have methods or inheritance, they just store We create the record when we write `{ birds: 5, iguanas: 7 }`. This defines a record with two *fields* - namely, the `birds` field and the `iguanas` field - and then assigns the number `5` to the `birds` field and the number `7` to the -`iguanas` field. +`iguanas` field. Order doesn't matter with record fields; we could have also specified +`iguanas` first and `birds` second, and Roc would consider it the exact same record. When we write `counts.birds`, it accesses the `birds` field of the `counts` record, and when we write `counts.iguanas` it accesses the `iguanas` field. When we use `==` @@ -328,6 +329,21 @@ values - including other records, or even functions! { birds: 4, nestedRecord: { someFunction: (\arg -> arg + 1), name: "Sam" } } ``` +### Record shorthands + +Roc has a couple of shorthands you can use to express some record-related operations more concisely. + +Instead of writing `\record -> record.x` we can write `.x` and it will evaluate to the same thing: +a function that takes a record and returns its `x` field. You can do this with any field you want. +For example: + +```elm +returnFoo = .foo + +returnFoo { foo: "hi!", bar: "blah" } +# returns "hi!" +``` + Whenever we're setting a field to be a def that has the same name as the field - for example, `{ x: x }` - we can shorten it to just writing the name of the def alone - for example, `{ x }`. We can do this with as many fields as we like, e.g. @@ -545,6 +561,15 @@ This makes two changes to our earlier `stoplightColor` / `stoplightStr` example. Any tag can be given a payload like this. A payload doesn't have to be a string; we could also have said (for example) `Custom { r: 40, g: 60, b: 80 }` to specify an RGB color instead of a string. Then in our `when` we could have written `Custom record ->` and then after the `->` used `record.r`, `record.g`, and `record.b` to access the `40`, `60`, `80` values. We could also have written `Custom { r, g, b } ->` to *destructure* the record, and then accessed these `r`, `g`, and `b` defs after the `->` instead. +A tag can also have a payload with more than one value. Instead of `Custom { r: 40, g: 60, b: 80 }` we could +write `Custom 40 60 80`. If we did that, then instead of destructuring a record with `Custom { r, g, b } ->` +inside a `when`, we would write `Custom r g b ->` to destructure the values directly out of the payload. + +We refer to whatever comes before a `->` in a `when` expression as a *pattern* - so for example, in the +`Custom description -> description` branch, `Custom description` would be a pattern. In programming, using +patterns in branching conditionals like `when` is known as [pattern matching](https://en.wikipedia.org/wiki/Pattern_matching). You may hear people say things like "let's pattern match on `Custom` here" as a way to +suggest making a `when` branch that begins with something like `Custom description ->`. + ## Lists Another thing we can do in Roc is to make a *list* of values. Here's an example: @@ -654,6 +679,24 @@ Instead, we're using a `when` to tell when we've got a string or a number, and t We could take this as far as we like, adding more different tags (e.g. `BoolElem True`) and then adding more branches to the `when` to handle them appropriately. +### Using tags as functions + +Let's say I want to apply a tag to a bunch of elements in a list. For example: + +```elm +List.map [ "a", "b", "c", ] \str -> Foo str +``` + +This is a perfectly reasonable way to write it, but I can also write it like this: + +```elm +List.map [ "a", "b", "c", ] Foo +``` + +These two versions compile to the same thing. As a convenience, Roc lets you specify +a tag name where a function is expected; when you do this, the compiler infers that you +want a function which uses all of its arguments as the payload to the given tag. + ### `List.any` and `List.all` There are several functions that work like `List.map` - they walk through each element of a list and do @@ -1007,17 +1050,153 @@ of the type annotation, or even the function's implementation! The only way to h Similarly, the only way to have a function whose type is `a -> a` is if the function's implementation returns its argument without modifying it in any way. This is known as [the identity function](https://en.wikipedia.org/wiki/Identity_function). -### Numeric types +## Numeric types -[ This part of the tutorial has not been written yet. Coming soon! ] +Roc has different numeric types that each have different tradeoffs. +They can all be broken down into two categories: [fractions](https://en.wikipedia.org/wiki/Fraction), +and [integers](https://en.wikipedia.org/wiki/Integer). In Roc we call these `Frac` and `Int` for short. -### Open and closed records +### Integers -[ This part of the tutorial has not been written yet. Coming soon! ] +Roc's integer types have two important characteristics: their *size* and their [*signedness*](https://en.wikipedia.org/wiki/Signedness). +Together, these two characteristics determine the range of numbers the integer type can represent. -### Open and closed tag unions +For example, the Roc type `U8` can represent the numbers 0 through 255, whereas the `I16` type can represent +the numbers -32768 through 32767. You can actually infer these ranges from their names (`U8` and `I16`) alone! -[ This part of the tutorial has not been written yet. Coming soon! ] +The `U` in `U8` indicates that it's *unsigned*, meaning that it can't have a minus [sign](https://en.wikipedia.org/wiki/Sign_(mathematics)), and therefore can't be negative. The fact that it's unsigned tells us immediately that +its lowest value is zero. The 8 in `U8` means it is 8 [bits](https://en.wikipedia.org/wiki/Bit) in size, which +means it has room to represent 2⁸ (which is equal to 256) different numbers. Since one of those 256 different numbers +is 0, we can look at `U8` and know that it goes from `0` (since it's unsigned) to `255` (2⁸ - 1, since it's 8 bits). + +If we change `U8` to `I8`, making it a *signed* 8-bit integer, the range changes. Because it's still 8 bits, it still +has room to represent 2⁸ (that is, 256) different numbers. However, now in addition to one of those 256 numbers +being zero, about half of rest will be negative, and the others positive. So instead of ranging from, say -255 +to 255 (which, counting zero, would represent 511 different numbers; too many to fit in 8 bits!) an `I8` value +ranges from -128 to 127. + +Notice that the negative extreme is `-128` versus `127` (not `128`) on the positive side. That's because of +needing room for zero; the slot for zero is taken from the positive range because zero doesn't have a minus sign. +So in general, you can find the lowest signed number by taking its total range (256 different numbers in the case +of an 8-bit integer) and dividing it in half (half of 256 is 128, so -128 is `I8`'s lowest number). To find the +highest number, take the positive version of the lowest number (so, convert `-128` to `128`) and then subtract 1 +to make room for zero (so, `128` becomes `127`; `I8` ranges from -128 to 127). + +Following this pattern, the 16 in `I16` means that it's a signed 16 bit integer. +That tells us it has room to represent 2¹⁶ (which is equal to 65536) different numbers. Half of 65536 is 32768, +so the lowest `I16` would be -32768, and the highest would be 32767. Knowing that, we can also quickly tell that +the lowest `U16` would be zero (since it always is for unsigned integers), and the higeest `U16` would be 65536. + +Choosing a size depends on your performance needs and the range of numbers you want to represent. Consider: + +* Larger integer sizes can represent a wider range of numbers. If you absolutely need to represent numbers in a certain range, make sure to pick an integer size that can hold them! +* Smaller integer sizes take up less memory. These savings rarely matters in variables and function arguments, but the sizes of integers that you use in data structures can add up. This can also affect whether those data structures fit in [cache lines](https://en.wikipedia.org/wiki/CPU_cache#Cache_performance), which can easily be a performance bottleneck. +* Certain processors work faster on some numeric sizes than others. There isn't even a general rule like "larger numeric sizes run slower" (or the reverse, for that matter) that applies to all processors. In fact, if the CPU is taking too long to run numeric calculations, you may find a performance improvement by experimenting with numeric sizes that are larger than otherwise necessary. However, in practice, doing this typically degrades overall performance, so be careful to measure properly! + +Here are the different fixed-size integer types that Roc supports: + +| Range | Type | Size | +| -----------------------------------------------------------: | :---- | :------- | +| `-128`
`127` | `I8` | 1 Byte | +| `0`
`255` | `U8` | 1 Byte | +| `-32_768`
`32_767` | `I16` | 2 Bytes | +| `0`
`65_535` | `U16` | 2 Bytes | +| `-2_147_483_648`
`2_147_483_647` | `I32` | 4 Bytes | +| `0`
(over 4 billion) `4_294_967_295` | `U32` | 4 Bytes | +| `-9_223_372_036_854_775_808`
`9_223_372_036_854_775_807` | `I64` | 8 Bytes | +| `0`
(over 18 quintillion) `18_446_744_073_709_551_615` | `U64` | 8 Bytes | +| `-170_141_183_460_469_231_731_687_303_715_884_105_728`
`170_141_183_460_469_231_731_687_303_715_884_105_727` | `I128` | 16 Bytes | +| `0`
(over 340 undecillion) `340_282_366_920_938_463_463_374_607_431_768_211_455` | `U128` | 16 Bytes | + +Roc also has one variable-size integer type: `Nat` (short for "natural number"). +The size of `Nat` is equal to the size of a memory address, which varies by system. +For example, when compiling for a 64-bit system, `Nat` works the same way as `U64`. +When compiling for a 32-bit system, it works the same way as `U32`. Most popular +computing devices today are 64-bit, so `Nat` is usually the same as `U64`, but +Web Assembly is typically 32-bit - so when running a Roc program built for Web Assembly, +`Nat` will work like a `U32` in that program. + +A common use for `Nat` is to store the length of a collection like a `List`; +there's a function `List.len : List * -> Nat` which returns the length of the given list. +64-bit systems can represent longer lists in memory than 32-bit systems can, +which is why the length of a list is represented as a `Nat`. + +If any operation would result in an integer that is either too big +or too small to fit in that range (e.g. calling `Int.maxI32 + 1`, which adds 1 to +the highest possible 32-bit integer), then the operation will *overflow*. +When an overflow occurs, the program will crash. + +As such, it's very important to design your integer operations not to exceed these bounds! + +### Fractions + +Roc has three fractional types: + +* `F32`, a 32-bit [floating-point number](https://en.wikipedia.org/wiki/IEEE_754) +* `F64`, a 64-bit [floating-point number](https://en.wikipedia.org/wiki/IEEE_754) +* `Dec`, a 128-bit decimal [fixed-point number](https://en.wikipedia.org/wiki/Fixed-point_arithmetic) + +These are different from integers in that they can represent numbers with fractional components, +such as 1.5 and -0.123. + +`Dec` is the best default choice for representing base-10 decimal numbers +like currency, because it is base-10 under the hood. In contrast, +`F64` and `F32` are base-2 under the hood, which can lead to decimal +precision loss even when doing addition and subtraction. For example, when +using `F64`, running 0.1 + 0.2 returns 0.3000000000000000444089209850062616169452667236328125, +whereas when using `Dec`, 0.1 + 0.2 returns 0.3. + +`F32` and `F64` have direct hardware support on common processors today. There is no hardware support +for fixed-point decimals, so under the hood, a `Dec` is an `I128`; operations on it perform +[base-10 fixed-point arithmetic](https://en.wikipedia.org/wiki/Fixed-point_arithmetic) +with 18 decimal places of precision. + +This means a `Dec` can represent whole numbers up to slightly over 170 +quintillion, along with 18 decimal places. (To be precise, it can store +numbers betwween `-170_141_183_460_469_231_731.687303715884105728` +and `170_141_183_460_469_231_731.687303715884105727`.) Why 18 +decimal places? It's the highest number of decimal places where you can still +convert any `U64] to a `Dec` without losing information. + +While the fixed-point `Dec` has a fixed range, the floating-point `F32` and `F64` do not. +Instead, outside of a certain range they start to lose precision instead of immediately overflowing +the way integers and `Dec` do. `F64` can represent [between 15 and 17 significant digits](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) before losing precision, whereas `F32` can only represent [between 6 and 9](https://en.wikipedia.org/wiki/Single-precision_floating-point_format#IEEE_754_single-precision_binary_floating-point_format:_binary32). + +There are some use cases where `F64` and `F32` can be better choices than `Dec` +despite their precision drawbacks. For example, in graphical applications they +can be a better choice for representing coordinates because they take up less memory, +various relevant calculations run faster, and decimal precision loss isn't as big a concern +when dealing with screen coordinates as it is when dealing with something like currency. + +### Num, Int, and Frac + +Some operations work on specific numeric types - such as `I64` or `Dec` - but operations support +multiple numeric types. For example, the `Num.abs` function works on any number, since you can +take the [absolute value](https://en.wikipedia.org/wiki/Absolute_value) of integers and fractions alike. +Its type is: + +```elm +abs : Num a -> Num a +``` + +This type says `abs` takes a number and then returns a number of the same type. That's because the +`Num` type is compatible with both integers and fractions. + +There's also an `Int` type which is only compatible with integers, and a `Frac` type which is only +compatible with fractions. For example: + +```elm +Num.xor : Int a, Int a -> Int a +``` + +```elm +Num.cos : Frac a -> Frac a +``` + +When you write a number literal in Roc, it has the type `Num *`. So you could call `Num.xor 1 1` +and also `Num.cos 1` and have them all work as expected; the number literal `1` has the type +`Num *`, which is compatible with the more constrained types `Int` and `Frac`. For the same reason, +you can pass number literals to functions expecting even more constrained types, like `I32` or `F64`. ## Interface modules @@ -1330,6 +1509,426 @@ Some important things to note about backpassing and `await`: * Backpassing syntax does not need to be used with `await` in particular. It can be used with any function. * Roc's compiler treats functions defined with backpassing exactly the same way as functions defined the other way. The only difference between `\text ->` and `text <-` is how they look, so feel free to use whichever looks nicer to you! +# Appendix: Advanced Concepts + +Here are some concepts you likely won't need as a beginner, but may want to know about eventually. +This is listed as an appendix rather than the main tutorial, to emphasize that it's totally fine +to stop reading here and go build things! + +## Open Records and Closed Records + +Let's say I write a function which takes a record with a `firstName` +and `lastName` field, and puts them together with a space in between: + +```swift +fullName = \user -> + "\(user.firstName) \(user.lastName)" +``` + +I can pass this function a record that has more fields than just +`firstName` and `lastName`, as long as it has *at least* both of those fields +(and both of them are strings). So any of these calls would work: + +* `fullName { firstName: "Sam", lastName: "Sample" }` +* `fullName { firstName: "Sam", lastName: "Sample", email: "blah@example.com" }` +* `fullName { age: 5, firstName: "Sam", things: 3, lastName: "Sample", role: Admin }` + +This `user` argument is an *open record* - that is, a description of a minimum set of fields +on a record, and their types. When a function takes an open record as an argument, +it's okay if you pass it a record with more fields than just the ones specified. + +In contrast, a *closed record* is one that requires an exact set of fields (and their types), +with no additional fields accepted. + +If we add a type annotation to this `fullName` function, we can choose to have it accept either +an open record or a closed record: + +```coffee +# Closed record +fullName : { firstName : Str, lastName : Str } -> Str +fullName = \user - > + "\(user.firstName) \(user.lastName)" +``` + +```coffee +# Open record (because of the `*`) +fullName : { firstName : Str, lastName : Str }* -> Str +fullName = \user - > + "\(user.firstName) \(user.lastName)" +``` + +The `*` in the type `{ firstName : Str, lastName : Str }*` is what makes it an open record type. +This `*` is the *wildcard type* we saw earlier with empty lists. (An empty list has the type `List *`, +in contrast to something like `List Str` which is a list of strings.) + +This is because record types can optionally end in a type variable. Just like how we can have `List *` +or `List a -> List a`, we can also have `{ first : Str, last : Str }*` or +`{ first : Str, last : Str }a -> { first: Str, last : Str }a`. The differences are that in `List a`, +the type variable is required and appears with a space after `List`; in a record, the type variable +is optional, and appears (with no space) immediately after `}`. + +If the type variable in a record type is a `*` (such as in `{ first : Str, last : Str }*`), then +it's an open record. If the type variable is missing, then it's a closed record. You can also specify +a closed record by putting a `{}` as the type variable (so for example, `{ email : Str }{}` is another way to write +`{ email : Str }`). In practice, closed records are basically always written without the `{}` on the end, +but later on we'll see a situation where putting types other than `*` in that spot can be useful. + +## Constrained Records + +The type variable can also be a named type variable, like so: + +```coffee +addHttps : { url : Str }a -> { url : Str }a +addHttps = \record -> + { record & url: "https://\(record.url)" } +``` + +This function uses *constrained records* in its type. The annotation is saying: +* This function takes a record which has at least a `url` field, and possibly others +* That `url` field has the type `Str` +* It returns a record of exactly the same type as the one it was given + +So if we give this function a record with five fields, it will return a record with those +same five fields. The only requirement is that one of those fields must be `url : Str`. + +In practice, constrained records appear in type annotations much less often than open or closed records do. + +Here's when you can typically expect to encounter these three flavors of type variables in records: + +- *Open records* are what the compiler infers when you use a record as an argument, or when destructuring it (for example, `{ x, y } =`). +- *Closed records* are what the compiler infers when you create a new record (for example, `{ x: 5, y: 6 }`) +- *Constrained records* are what the compiler infers when you do a record update (for example, `{ user & email: newEmail }`) + +Of note, you can pass a closed record to a function that accepts a smaller open record, but not the reverse. +So a function `{ a : Str, b : Bool }* -> Str` can accept an `{ a : Str, b : Bool, c : Bool }` record, +but a function `{ a : Str, b : Bool, c : Bool } -> Str` would not accept an `{ a : Str, b : Bool }*` record. + +This is because if a function accepts `{ a : Str, b : Bool, c : Bool }`, that means it might access the `c` +field of that record. So if you passed it a record that was not guaranteed to have all three of those fields +present (such as an `{ a : Str, b : Bool }*` record, which only guarantees that the fields `a` and `b` are present), +the function might try to access a `c` field at runtime that did not exist! + +## Type Variables in Record Annotations + +You can add type annotations to make record types less flexible than what the compiler infers, but not more +flexible. For example, you can use an annotation to tell the compiler to treat a record as closed when it would +be inferred as open (or constrained), but you can't use an annotation to make a record open when it would be +inferred as closed. + +If you like, you can always annotate your functions as accepting open records. However, in practice this may not +always be the nicest choice. For example, let's say you have a `User` type alias, like so: + +```coffee +User : + { + email : Str, + firstName : Str, + lastName : Str, + } +``` + +This defines `User` to be a closed record, which in practice is the most common way records named `User` +tend to be defined. + +If you want to have a function take a `User`, you might write its type like so: + +```elm +isValid : User -> Bool +``` + +If you want to have a function return a `User`, you might write its type like so: + +```elm +userFromEmail : Str -> User +``` + +A function which takes a user and returns a user might look like this: + +```elm +capitalizeNames : User -> User +``` + +This is a perfectly reasonable way to write all of these functions. However, I +might decide that I really want the `isValid` function to take an open record - +that is, a record with *at least* the fields of this `User` record, but possibly others as well. + +Since open records have a type variable (like `*` in `{ email : Str }*` or `a` in +`{ email : Str }a -> { email : Str }a`), in order to do this I'd need to add a +type variable to the `User` type alias: + +```coffee +User a : + { + email : Str, + firstName : Str, + lastName : Str, + }a +``` + +Notice that the `a` type variable appears not only in `User a` but also in `}a` at the end of the +record type! + +Using `User a` type alias, I can still write the same three functions, but now their types need to look different. +This is what the first one would look like: + +```elm +isValid : User * -> Bool +``` + +Here, the `User *` type alias substitutes `*` for the type variable `a` in the type alias, +which takes it from `{ email : Str, … }a` to `{ email : Str, … }*`. Now I can pass it any +record that has at least the fields in `User`, and possibly others as well, which was my goal. + +```elm +userFromEmail : Str -> User {} +``` + +Here, the `User {}` type alias substitutes `{}` for the type variable `a` in the type alias, +which takes it from `{ email : Str, … }a` to `{ email : Str, … }{}`. As noted earlier, +this is another way to specify a closed record: putting a `{}` after it, in the same place that +you'd find a `*` in an open record. + +> **Aside:** This works because you can form new record types by replacing the type variable with +> other record types. For example, `{ a : Str, b : Str }` can also be written `{ a : Str }{ b : Str }`. +> You can chain these more than once, e.g. `{ a : Str }{ b : Str }{ c : Str, d : Str }`. +> This is more useful when used with type annotations; for example, `{ a : Str, b : Str }User` describes +> a closed record consisting of all the fields in the closed record `User`, plus `a : Str` and `b : Str`. + +This function still returns the same record as it always did, it just needs to be annotated as +`User {}` now instead of just `User`, because the `User` type alias has a variable in it that must be +specified. + +The third function might need to use a named type variable: + +```elm +capitalizeNames : User a -> User a +``` + +If this function does a record update on the given user, and returns that - for example, if its +definition were `capitalizeNames = \user -> { user & email: "blah" }` - then it needs to use the +same named type variable for both the argument and return value. + +However, if returns a new `User` that it created from scratch, then its type could instead be: + +```elm +capitalizeNames : User * -> User {} +``` + +This says that it takes a record with at least the fields specified in the `User` type alias, +and possibly others...and then returns a record with exactly the fields specified in the `User` +type alias, and no others. + +These three examples illustrate why it's relatively uncommon to use open records for type aliases: +it makes a lot of types need to incorporate a type variable that otherwise they could omit, +all so that `isValid` can be given something that has not only the fields `User` has, but +some others as well. (In the case of a `User` record in particular, it may be that the extra +fields were included due to a mistake rather than on purpose, and accepting an open record could +prevent the compiler from raising an error that would have revealed the mistake.) + +That said, this is a useful technique to know about if you want to (for example) make a record +type that accumulates more and more fields as it progresses through a series of operations. + +## Open and Closed Tag Unions + +Just like how Roc has open records and closed records, it also has open and closed tag unions. + +The *open tag union* (or *open union* for short) `[ Foo Str, Bar Bool ]*` represents a tag that might +be `Foo Str` and might be `Bar Bool`, but might also be some other tag whose type isn't known at compile time. + +Because an open union represents possibilities that are impossible to know ahead of time, any `when` I use on a +`[ Foo Str, Bar Bool ]*` value must include a catch-all `_ ->` branch. Otherwise, if one of those +unknown tags were to come up, the `when` would not know what to do with it! For example: + +```coffee +example : [ Foo Str, Bar Bool ]* -> Bool +example = \tag -> + when tag is + Foo str -> Str.isEmpty str + Bar bool -> bool + _ -> False +``` + +In contrast, a *closed tag union* (or *closed union*) like `[ Foo Str, Bar Bool ]` (without the `*`) +represents an exhaustive set of possible tags. If I use a `when` on one of these, I can match on `Foo` +only and then on `Bar` only, with no need for a catch-all branch. For example: + +```coffee +example : [ Foo Str, Bar Bool ] -> Bool +example = \tag -> + when tag is + Foo str -> Str.isEmpty str + Bar bool -> bool +``` + +If we were to remove the type annotations from the previous two code examples, Roc would infer the same +types for them anyway. + +It would infer `tag : [ Foo Str, Bar Bool ]` for the latter example because the `when tag is` expression +only includes a `Foo Str` branch and a `Bar Bool` branch, and nothing else. Since the `when` doesn't handle +any other possibilities, these two tags must be the only possible ones the `tag` argument could be. + +It would infer `tag : [ Foo Str, Bar Bool ]*` for the former example because the `when tag is` expression +includes a `Foo Str` branch and a `Bar Bool` branch - meaning we know about at least those two specific +possibilities - but also a `_ ->` branch, indicating that there may be other tags we don't know about. Since +the `when` is flexible enough to handle all possible tags, `tag` gets inferred as an open union. + +Putting these together, whether a tag union is inferred to be open or closed depends on which possibilities +the implementation actually handles. + +> **Aside:** As with open and closed records, we can use type annotations to make tag union types less flexible +> than what would be inferred. If we added a `_ ->` branch to the second example above, the compiler would still +> accept `example : [ Foo Str, Bar Bool ] -> Bool` as the type annotation, even though the catch-all branch +> would permit the more flexible `example : [ Foo Str, Bar Bool ]* -> Bool` annotation instead. + +## Combining Open Unions + +When we make a new record, it's inferred to be a closed record. For example, in `foo { a: "hi" }`, +the type of `{ a: "hi" }` is inferred to be `{ a : Str }`. In contrast, when we make a new tag, it's inferred +to be an open union. So in `foo (Bar "hi")`, the type of `Bar "hi"` is inferred to be `[ Bar Str ]*`. + +This is because open unions can accumulate additional tags based on how they're used in the program, +whereas closed unions cannot. For example, let's look at this conditional: + +```elm +if x > 5 then + "foo" +else + 7 +``` + +This will be a type mismatch because the two branches have incompatible types. Strings and numbers are not +type-compatible! Now let's look at another example: + +```elm +if x > 5 then + Ok "foo" +else + Err "bar" +``` + +This shouldn't be a type mismatch, because we can see that the two branches are compatible; they are both +tags that could easily coexist in the same tag union. But if the compiler inferred the type of `Ok "foo"` to be +the closed union `[ Ok Str ]`, and likewise for `Err "bar"` and `[ Err Str ]`, then this would have to be +a type mismatch - because those two closed unions are incompatible. + +Instead, the compiler infers `Ok "foo"` to be the open union `[ Ok Str ]*`, and `Err "bar"` to be the open +union `[ Err Str ]*`. Then, when using them together in this conditional, the inferred type of the conditional +becomes `[ Ok Str, Err Str ]*` - that is, the combination of the unions in each of its branches. (Branches in +a `when` work the same way with open unions.) + +Earlier we saw how a function which accepts an open union must account for more possibilities, by including +catch-all `_ ->` patterns in its `when` expressions. So *accepting* an open union means you have more requirements. +In contrast, when you already *have* a value which is an open union, you have fewer requirements. A value +which is an open union (like `Ok "foo"`, which has the type `[ Ok Str ]*`) can be provided to anything that's +expecting a tag union (no matter whether it's open or closed), as long as the expected tag union includes at least +the tags in the open union you're providing. + +So if I have an `[ Ok Str ]*` value, I can pass it functions with any of these types (among others): + +* `[ Ok Str ]* -> Bool` +* `[ Ok Str ] -> Bool` +* `[ Ok Str, Err Bool ]* -> Bool` +* `[ Ok Str, Err Bool ] -> Bool` +* `[ Ok Str, Err Bool, Whatever ]* -> Bool` +* `[ Ok Str, Err Bool, Whatever ] -> Bool` +* `Result Str Bool -> Bool` +* `[ Err Bool, Whatever ]* -> Bool` + +That last one works because a function accepting an open union can accept any unrecognized tag, including +`Ok Str` - even though it is not mentioned as one of the tags in `[ Err Bool, Whatever ]*`! Remember, when +a function accepts an open tag union, any `when` branches on that union must include a catch-all `_ ->` branch, +which is the branch that will end up handling the `Ok Str` value we pass in. + +However, I could not pass an `[ Ok Str ]*` to a function with a *closed* tag union argument that did not +mention `Ok Str` as one of its tags. So if I tried to pass `[ Ok Str ]*` to a function with the type +`[ Err Bool, Whatever ] -> Str`, I would get a type mismatch - because a `when` in that function could +be handling the `Err Bool` possibility and the `Whatever` possibility, and since it would not necessarily have +a catch-all `_ ->` branch, it might not know what to do with an `Ok Str` if it received one. + +> **Note:** It wouldn't be accurate to say that a function which accepts an open union handles +> "all possible tags." For example, if I have a function `[ Ok Str ]* -> Bool` and I pass it +> `Ok 5`, that will still be a type mismatch. If you think about it, a `when` in that function might +> have the branch `Ok str ->` which assumes there's a string inside that `Ok`, and if `Ok 5` type-checked, +> then that assumption would be false and things would break! +> +> So `[ Ok Str ]*` is more restrictive than `[]*`. It's basically saying "this may or may not be an `Ok` tag, +> but if it is an `Ok` tag, then it's guaranteed to have a payload of exactly `Str`." + +In summary, here's a way to think about the difference between open unions in a value you have, compared to a value you're accepting: + +* If you *have* a closed union, that means it has all the tags it ever will, and can't accumulate more. +* If you *have* an open union, that means it can accumulate more tags through conditional branches. +* If you *accept* a closed union, that means you only have to handle the possibilities listed in the union. +* If you *accept* an open union, that means you have to handle the possibility that it has a tag you can't know about. + +## Type Variables in Tag Unions + +Earlier we saw these two examples, one with an open tag union and the other with a closed one: + +```coffee +example : [ Foo Str, Bar Bool ]* -> Bool +example = \tag -> + when tag is + Foo str -> Str.isEmpty str + Bar bool -> bool + _ -> False +``` + +```coffee +example : [ Foo Str, Bar Bool ] -> Bool +example = \tag -> + when tag is + Foo str -> Str.isEmpty str + Bar bool -> bool +``` + +Similarly to how there are open records with a `*`, closed records with nothing, +and constrained records with a named type variable, we can also have *constrained tag unions* +with a named type variable. Here's an example: + +```coffee +example : [ Foo Str, Bar Bool ]a -> [ Foo Str, Bar Bool ]a +example = \tag -> + when tag is + Foo str -> Bar (Str.isEmpty str) + Bar _ -> Bar False + other -> other +``` + +This type says that the `example` function will take either a `Foo Str` tag, or a `Bar Bool` tag, +or possibly another tag we don't know about at compile time - and it also says that the function's +return type is the same as the type of its argument. + +So if we give this function a `[ Foo Str, Bar Bool, Baz (List Str) ]` argument, then it will be guaranteed +to return a `[ Foo Str, Bar Bool, Baz (List Str) ]` value. This is more constrained than a function that +returned `[ Foo Str, Bar Bool ]*` because that would say it could return *any* other tag (in addition to +the `Foo Str` and `Bar Bool` we already know about). + +If we removed the type annotation from `example` above, Roc's compiler would infer the same type anyway. +This may be surprising if you look closely at the body of the function, because: + +* The return type includes `Foo Str`, but no branch explicitly returns `Foo`. Couldn't the return type be `[ Bar Bool ]a` instead? +* The argument type includes `Bar Bool` even though we never look at `Bar`'s payload. Couldn't the argument type be inferred to be `Bar *` instead of `Bar Bool`, since we never look at it? + +The reason it has this type is the `other -> other` branch. Take a look at that branch, and ask this question: +"What is the type of `other`?" There has to be exactly one answer! It can't be the case that `other` has one +type before the `->` and another type after it; whenever you see a named value in Roc, it is guaranteed to have +the same type everywhere it appears in that scope. + +For this reason, any time you see a function that only runs a `when` on its only argument, and that `when` +includes a branch like `x -> x` or `other -> other`, the function's argument type and return type must necessarily +be equivalent. + +> **Note:** Just like with records, you can also replace the type variable in tag union types with a concrete type. +> For example, `[ Foo Str ][ Bar Bool ][ Baz (List Str) ]` is equivalent to `[ Foo Str, Bar Bool, Baz (List Str) ]`. +> +> Also just like with records, you can use this to compose tag union type aliases. For example, you can write +> `NetworkError : [ Timeout, Disconnected ]` and then `Problem : [ InvalidInput, UnknownFormat ]NetworkError` + +## Phantom Types + +[ This part of the tutorial has not been written yet. Coming soon! ] + ## Operator Desugaring Table Here are various Roc expressions involving operators, and what they desugar to. diff --git a/ast/Cargo.toml b/ast/Cargo.toml index 9c3498821c..f0b82d7ca4 100644 --- a/ast/Cargo.toml +++ b/ast/Cargo.toml @@ -17,7 +17,8 @@ roc_problem = { path = "../compiler/problem" } roc_types = { path = "../compiler/types" } roc_unify = { path = "../compiler/unify"} roc_load = { path = "../compiler/load" } -roc_reporting = { path = "../reporting" } +roc_target = { path = "../compiler/roc_target" } +roc_error_macros = { path = "../error_macros" } arrayvec = "0.7.2" bumpalo = { version = "3.8.0", features = ["collections"] } libc = "0.2.106" diff --git a/ast/src/constrain.rs b/ast/src/constrain.rs index a1fd0e6a2c..f26eddd841 100644 --- a/ast/src/constrain.rs +++ b/ast/src/constrain.rs @@ -1985,7 +1985,7 @@ pub mod test_constrain { ident::Lowercase, symbol::{IdentIds, Interns, ModuleIds, Symbol}, }; - use roc_parse::parser::SyntaxError; + use roc_parse::parser::{SourceError, SyntaxError}; use roc_region::all::Region; use roc_types::{ pretty_print::{content_to_string, name_all_type_vars}, @@ -2128,7 +2128,7 @@ pub mod test_constrain { env: &mut Env<'a>, scope: &mut Scope, region: Region, - ) -> Result<(Expr2, Output), SyntaxError<'a>> { + ) -> Result<(Expr2, Output), SourceError<'a, SyntaxError<'a>>> { match roc_parse::test_helpers::parse_loc_with(arena, input.trim()) { Ok(loc_expr) => Ok(loc_expr_to_expr2(arena, loc_expr, env, scope, region)), Err(fail) => Err(fail), @@ -2325,7 +2325,7 @@ pub mod test_constrain { indoc!( r#" person = { name: "roc" } - + person "# ), @@ -2339,8 +2339,8 @@ pub mod test_constrain { indoc!( r#" person = { name: "roc" } - - { person & name: "bird" } + + { person & name: "bird" } "# ), "{ name : Str }", @@ -2462,7 +2462,7 @@ pub mod test_constrain { indoc!( r#" x = 1 - + \{} -> x "# ), diff --git a/ast/src/lang/core/def/def.rs b/ast/src/lang/core/def/def.rs index 8cd83f4257..aeea6e6a73 100644 --- a/ast/src/lang/core/def/def.rs +++ b/ast/src/lang/core/def/def.rs @@ -511,7 +511,7 @@ fn canonicalize_pending_def<'a>( // remove its generated name from the closure map. let references = env.closures.remove(&closure_symbol).unwrap_or_else(|| { - panic!( r"Tried to remove symbol {:?} from procedures, but it was not found: {:?}", closure_symbol, env.closures) + panic!( r"Tried to remove symbol {:?} from procedures, but it was not found: {:?}", closure_symbol, env.closures) }); // TODO should we re-insert this function into env.closures? @@ -680,7 +680,7 @@ fn canonicalize_pending_def<'a>( // remove its generated name from the closure map. let references = env.closures.remove(&closure_symbol).unwrap_or_else(|| { - panic!( r"Tried to remove symbol {:?} from procedures, but it was not found: {:?}", closure_symbol, env.closures) + panic!( r"Tried to remove symbol {:?} from procedures, but it was not found: {:?}", closure_symbol, env.closures) }); // TODO should we re-insert this function into env.closures? diff --git a/ast/src/lang/core/expr/expr_to_expr2.rs b/ast/src/lang/core/expr/expr_to_expr2.rs index a83fd39ed1..6295763f22 100644 --- a/ast/src/lang/core/expr/expr_to_expr2.rs +++ b/ast/src/lang/core/expr/expr_to_expr2.rs @@ -1,6 +1,8 @@ use bumpalo::Bump; -use roc_can::expr::Recursive; -use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; +use roc_can::expr::{IntValue, Recursive}; +use roc_can::num::{ + finish_parsing_base, finish_parsing_float, finish_parsing_num, ParsedNumResult, +}; use roc_can::operator::desugar_expr; use roc_collections::all::MutSet; use roc_module::symbol::Symbol; @@ -52,7 +54,7 @@ pub fn expr_to_expr2<'a>( match parse_expr { Float(string) => { match finish_parsing_float(string) { - Ok(float) => { + Ok((float, _bound)) => { let expr = Expr2::Float { number: FloatVal::F64(float), var: env.var_store.fresh(), @@ -73,10 +75,13 @@ pub fn expr_to_expr2<'a>( } } Num(string) => { - match finish_parsing_int(string) { - Ok(int) => { + match finish_parsing_num(string) { + Ok(ParsedNumResult::UnknownNum(int, _) | ParsedNumResult::Int(int, _)) => { let expr = Expr2::SmallInt { - number: IntVal::I64(int), + number: IntVal::I64(match int { + IntValue::U128(_) => todo!(), + IntValue::I128(n) => n as i64, // FIXME + }), var: env.var_store.fresh(), // TODO non-hardcode style: IntStyle::Decimal, @@ -85,6 +90,15 @@ pub fn expr_to_expr2<'a>( (expr, Output::default()) } + Ok(ParsedNumResult::Float(float, _)) => { + let expr = Expr2::Float { + number: FloatVal::F64(float), + var: env.var_store.fresh(), + text: PoolStr::new(string, env.pool), + }; + + (expr, Output::default()) + } Err((raw, error)) => { // emit runtime error let runtime_error = RuntimeError::InvalidInt( @@ -107,9 +121,12 @@ pub fn expr_to_expr2<'a>( is_negative, } => { match finish_parsing_base(string, *base, *is_negative) { - Ok(int) => { + Ok((int, _bound)) => { let expr = Expr2::SmallInt { - number: IntVal::I64(int), + number: IntVal::I64(match int { + IntValue::U128(_) => todo!(), + IntValue::I128(n) => n as i64, // FIXME + }), var: env.var_store.fresh(), // TODO non-hardcode style: IntStyle::from_base(*base), diff --git a/ast/src/lang/core/pattern.rs b/ast/src/lang/core/pattern.rs index 19a956bcd8..3bbefb7d83 100644 --- a/ast/src/lang/core/pattern.rs +++ b/ast/src/lang/core/pattern.rs @@ -3,8 +3,10 @@ #![allow(unused_imports)] use bumpalo::collections::Vec as BumpVec; -use roc_can::expr::unescape_char; -use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; +use roc_can::expr::{unescape_char, IntValue}; +use roc_can::num::{ + finish_parsing_base, finish_parsing_float, finish_parsing_num, ParsedNumResult, +}; use roc_collections::all::BumpMap; use roc_module::symbol::{Interns, Symbol}; use roc_parse::ast::{StrLiteral, StrSegment}; @@ -183,18 +185,35 @@ pub fn to_pattern2<'a>( let problem = MalformedPatternProblem::MalformedFloat; malformed_pattern(env, problem, region) } - Ok(float) => Pattern2::FloatLiteral(FloatVal::F64(float)), + Ok((float, _bound)) => Pattern2::FloatLiteral(FloatVal::F64(float)), }, ptype => unsupported_pattern(env, ptype, region), }, NumLiteral(string) => match pattern_type { - WhenBranch => match finish_parsing_int(string) { + WhenBranch => match finish_parsing_num(string) { Err(_error) => { let problem = MalformedPatternProblem::MalformedInt; malformed_pattern(env, problem, region) } - Ok(int) => Pattern2::NumLiteral(env.var_store.fresh(), int), + Ok(ParsedNumResult::UnknownNum(int, _bound)) => { + Pattern2::NumLiteral( + env.var_store.fresh(), + match int { + IntValue::U128(_) => todo!(), + IntValue::I128(n) => n as i64, // FIXME + }, + ) + } + Ok(ParsedNumResult::Int(int, _bound)) => { + Pattern2::IntLiteral(IntVal::I64(match int { + IntValue::U128(_) => todo!(), + IntValue::I128(n) => n as i64, // FIXME + })) + } + Ok(ParsedNumResult::Float(int, _bound)) => { + Pattern2::FloatLiteral(FloatVal::F64(int)) + } }, ptype => unsupported_pattern(env, ptype, region), }, @@ -209,7 +228,11 @@ pub fn to_pattern2<'a>( let problem = MalformedPatternProblem::MalformedBase(*base); malformed_pattern(env, problem, region) } - Ok(int) => { + Ok((int, _bound)) => { + let int = match int { + IntValue::U128(_) => todo!(), + IntValue::I128(n) => n as i64, // FIXME + }; if *is_negative { Pattern2::IntLiteral(IntVal::I64(-int)) } else { diff --git a/ast/src/lang/core/str.rs b/ast/src/lang/core/str.rs index 982e684629..dee65a3436 100644 --- a/ast/src/lang/core/str.rs +++ b/ast/src/lang/core/str.rs @@ -1,6 +1,6 @@ +use roc_error_macros::internal_error; use roc_module::{called_via::CalledVia, symbol::Symbol}; use roc_parse::ast::StrLiteral; -use roc_reporting::internal_error; use crate::{ ast_error::{ASTResult, UnexpectedASTNode}, diff --git a/ast/src/lang/rigids.rs b/ast/src/lang/rigids.rs index c629904dbd..d86aa5ec88 100644 --- a/ast/src/lang/rigids.rs +++ b/ast/src/lang/rigids.rs @@ -12,6 +12,7 @@ use roc_types::subs::Variable; #[derive(Debug)] pub struct Rigids { + // Rigid type variable = type variable where type is specified by the programmer pub names: PoolVec<(Option, Variable)>, // 8B padding: [u8; 1], } diff --git a/ast/src/module.rs b/ast/src/module.rs index e744760c42..f13028e8d1 100644 --- a/ast/src/module.rs +++ b/ast/src/module.rs @@ -3,6 +3,7 @@ use std::path::Path; use bumpalo::Bump; use roc_collections::all::MutMap; use roc_load::file::LoadedModule; +use roc_target::TargetInfo; pub fn load_module(src_file: &Path) -> LoadedModule { let subs_by_module = MutMap::default(); @@ -19,7 +20,7 @@ pub fn load_module(src_file: &Path) -> LoadedModule { ) }), subs_by_module, - 8, + TargetInfo::default_x86_64(), roc_can::builtins::builtin_defs_map, ); diff --git a/ast/src/roc_file.rs b/ast/src/roc_file.rs index 5379e9097a..5299921c62 100644 --- a/ast/src/roc_file.rs +++ b/ast/src/roc_file.rs @@ -116,13 +116,13 @@ mod test_file { indoc!( r#" interface Simple - exposes [ + exposes [ v, x ] imports [] - + v : Str - + v = "Value!" x : Int diff --git a/ast/src/solve_type.rs b/ast/src/solve_type.rs index 2c391ce637..6bff9cd93b 100644 --- a/ast/src/solve_type.rs +++ b/ast/src/solve_type.rs @@ -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 + } } } diff --git a/ci/bench-runner/src/main.rs b/ci/bench-runner/src/main.rs index b30e692541..efccc69497 100644 --- a/ci/bench-runner/src/main.rs +++ b/ci/bench-runner/src/main.rs @@ -65,7 +65,7 @@ fn finish(all_regressed_benches: HashSet, nr_repeat_benchmarks: usize) { r#" FAILED: The following benchmarks have shown a regression {:?} times: {:?} - + "#, nr_repeat_benchmarks, all_regressed_benches ); diff --git a/cli/Cargo.toml b/cli/Cargo.toml index c01da3f76d..dfffe25ee0 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -15,14 +15,11 @@ test = false bench = false [features] -default = ["target-aarch64", "target-x86_64", "target-wasm32", "llvm", "editor"] +default = ["target-aarch64", "target-x86_64", "target-wasm32", "editor"] wasm32-cli-run = ["target-wasm32", "run-wasm32"] i386-cli-run = ["target-x86"] -# This is a separate feature because when we generate docs on Netlify, -# it doesn't have LLVM installed. (Also, it doesn't need to do code gen.) -llvm = ["inkwell", "roc_gen_llvm", "roc_build/llvm"] editor = ["roc_editor"] run-wasm32 = ["wasmer", "wasmer-wasi"] @@ -50,29 +47,22 @@ roc_docs = { path = "../docs" } roc_parse = { path = "../compiler/parse" } roc_region = { path = "../compiler/region" } roc_module = { path = "../compiler/module" } -roc_problem = { path = "../compiler/problem" } -roc_types = { path = "../compiler/types" } roc_builtins = { path = "../compiler/builtins" } -roc_constrain = { path = "../compiler/constrain" } -roc_unify = { path = "../compiler/unify" } -roc_solve = { path = "../compiler/solve" } roc_mono = { path = "../compiler/mono" } roc_load = { path = "../compiler/load" } -roc_gen_llvm = { path = "../compiler/gen_llvm", optional = true } roc_build = { path = "../compiler/build", default-features = false } roc_fmt = { path = "../compiler/fmt" } +roc_target = { path = "../compiler/roc_target" } roc_reporting = { path = "../reporting" } +roc_error_macros = { path = "../error_macros" } roc_editor = { path = "../editor", optional = true } roc_linker = { path = "../linker" } +roc_repl_cli = { path = "../repl_cli" } clap = { version = "= 3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] } const_format = "0.2.22" -rustyline = { git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1" } -rustyline-derive = { git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1" } bumpalo = { version = "3.8.0", features = ["collections"] } -libloading = "0.7.1" mimalloc = { version = "0.1.26", default-features = false } -inkwell = { path = "../vendor/inkwell", optional = true } target-lexicon = "0.12.2" tempfile = "3.2.0" @@ -83,9 +73,9 @@ wasmer-wasi = { version = "2.0.0", optional = true } wasmer = { version = "2.0.0", default-features = false, features = ["default-cranelift", "default-universal"] } wasmer-wasi = "2.0.0" pretty_assertions = "1.0.0" +roc_test_utils = { path = "../test_utils" } indoc = "1.0.3" serial_test = "0.5.1" -tempfile = "3.2.0" criterion = { git = "https://github.com/Anton-4/criterion.rs"} cli_utils = { path = "../cli_utils" } diff --git a/cli/src/build.rs b/cli/src/build.rs index 38da872e87..4b0daee242 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -8,6 +8,7 @@ use roc_can::builtins::builtin_defs_map; use roc_collections::all::MutMap; use roc_load::file::LoadingProblem; use roc_mono::ir::OptLevel; +use roc_target::TargetInfo; use std::path::PathBuf; use std::time::{Duration, SystemTime}; use target_lexicon::Triple; @@ -58,7 +59,7 @@ pub fn build_file<'a>( target_valgrind: bool, ) -> Result> { let compilation_start = SystemTime::now(); - let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; + let target_info = TargetInfo::from(target); // Step 1: compile the app and generate the .o file let subs_by_module = MutMap::default(); @@ -72,7 +73,7 @@ pub fn build_file<'a>( stdlib, src_dir.as_path(), subs_by_module, - ptr_bytes, + target_info, builtin_defs_map, )?; @@ -105,6 +106,21 @@ pub fn build_file<'a>( // To do this we will need to preprocess files just for their exported symbols. // Also, we should no longer need to do this once we have platforms on // a package repository, as we can then get precompiled hosts from there. + + let exposed_values = loaded + .exposed_to_host + .values + .keys() + .map(|x| x.as_str(&loaded.interns).to_string()) + .collect(); + + let exposed_closure_types = loaded + .exposed_to_host + .closure_types + .iter() + .map(|x| x.as_str(&loaded.interns).to_string()) + .collect(); + let rebuild_thread = spawn_rebuild_thread( opt_level, surgically_link, @@ -112,11 +128,8 @@ pub fn build_file<'a>( host_input_path.clone(), binary_path.clone(), target, - loaded - .exposed_to_host - .keys() - .map(|x| x.as_str(&loaded.interns).to_string()) - .collect(), + exposed_values, + exposed_closure_types, target_valgrind, ); @@ -291,6 +304,7 @@ fn spawn_rebuild_thread( binary_path: PathBuf, target: &Triple, exported_symbols: Vec, + exported_closure_types: Vec, target_valgrind: bool, ) -> std::thread::JoinHandle { let thread_local_target = target.clone(); @@ -305,6 +319,7 @@ fn spawn_rebuild_thread( &thread_local_target, host_input_path.as_path(), exported_symbols, + exported_closure_types, target_valgrind, ) .unwrap(); @@ -342,7 +357,7 @@ pub fn check_file( // only used for generating errors. We don't do code generation, so hardcoding should be fine // we need monomorphization for when exhaustiveness checking - let ptr_bytes = 8; + let target_info = TargetInfo::default_x86_64(); // Step 1: compile the app and generate the .o file let subs_by_module = MutMap::default(); @@ -356,7 +371,7 @@ pub fn check_file( stdlib, src_dir.as_path(), subs_by_module, - ptr_bytes, + target_info, builtin_defs_map, )?; diff --git a/cli/src/format.rs b/cli/src/format.rs index 6eb75e2bd0..8ced266bce 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; use bumpalo::collections::Vec; use bumpalo::Bump; +use roc_error_macros::{internal_error, user_error}; use roc_fmt::def::fmt_def; use roc_fmt::module::fmt_module; use roc_fmt::Buf; @@ -11,17 +12,17 @@ use roc_parse::ast::{ TypeAnnotation, WhenBranch, }; use roc_parse::header::{ - AppHeader, Effects, ExposedName, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry, - PackageName, PlatformHeader, PlatformRequires, PlatformRigid, To, TypedIdent, + AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry, + PackageName, PlatformHeader, PlatformRequires, To, TypedIdent, }; use roc_parse::{ ast::{Def, Module}, + ident::UppercaseIdent, module::{self, module_defs}, parser::{Parser, SyntaxError}, state::State, }; -use roc_region::all::Loc; -use roc_reporting::{internal_error, user_error}; +use roc_region::all::{Loc, Region}; pub fn format(files: std::vec::Vec) { for file in files { @@ -110,8 +111,8 @@ struct Ast<'a> { } fn parse_all<'a>(arena: &'a Bump, src: &'a str) -> Result, SyntaxError<'a>> { - let (module, state) = - module::parse_header(arena, State::new(src.as_bytes())).map_err(SyntaxError::Header)?; + let (module, state) = module::parse_header(arena, State::new(src.as_bytes())) + .map_err(|e| SyntaxError::Header(e.problem))?; let (_, defs, _) = module_defs().parse(arena, state).map_err(|(_, e, _)| e)?; @@ -176,6 +177,7 @@ impl<'a> RemoveSpaces<'a> for Module<'a> { packages: header.packages.remove_spaces(arena), imports: header.imports.remove_spaces(arena), provides: header.provides.remove_spaces(arena), + provides_types: header.provides_types.map(|ts| ts.remove_spaces(arena)), to: header.to.remove_spaces(arena), before_header: &[], after_app_keyword: &[], @@ -197,14 +199,6 @@ impl<'a> RemoveSpaces<'a> for Module<'a> { packages: header.packages.remove_spaces(arena), imports: header.imports.remove_spaces(arena), provides: header.provides.remove_spaces(arena), - effects: Effects { - spaces_before_effects_keyword: &[], - spaces_after_effects_keyword: &[], - spaces_after_type_name: &[], - effect_shortname: header.effects.effect_shortname.remove_spaces(arena), - effect_type_name: header.effects.effect_type_name.remove_spaces(arena), - entries: header.effects.entries.remove_spaces(arena), - }, before_header: &[], after_platform_keyword: &[], before_requires: &[], @@ -219,6 +213,25 @@ impl<'a> RemoveSpaces<'a> for Module<'a> { after_provides: &[], }, }, + Module::Hosted { header } => Module::Hosted { + header: HostedHeader { + name: header.name.remove_spaces(arena), + exposes: header.exposes.remove_spaces(arena), + imports: header.imports.remove_spaces(arena), + generates: header.generates.remove_spaces(arena), + generates_with: header.generates_with.remove_spaces(arena), + before_header: &[], + after_hosted_keyword: &[], + before_exposes: &[], + after_exposes: &[], + before_imports: &[], + after_imports: &[], + before_generates: &[], + after_generates: &[], + before_with: &[], + after_with: &[], + }, + }, } } } @@ -285,7 +298,7 @@ impl<'a> RemoveSpaces<'a> for PlatformRequires<'a> { } } -impl<'a> RemoveSpaces<'a> for PlatformRigid<'a> { +impl<'a> RemoveSpaces<'a> for UppercaseIdent<'a> { fn remove_spaces(&self, _arena: &'a Bump) -> Self { *self } @@ -319,7 +332,7 @@ impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for Option { impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for Loc { fn remove_spaces(&self, arena: &'a Bump) -> Self { let res = self.value.remove_spaces(arena); - Loc::new(0, 0, 0, 0, res) + Loc::at(Region::zero(), res) } } diff --git a/cli/src/lib.rs b/cli/src/lib.rs index b8e5abc6fb..136f99eab7 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -18,7 +18,6 @@ use target_lexicon::{Architecture, OperatingSystem, Triple, X86_32Architecture}; pub mod build; mod format; -pub mod repl; pub use format::format; pub const CMD_BUILD: &str = "build"; diff --git a/cli/src/main.rs b/cli/src/main.rs index a59bd0465f..7fba8781c5 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,7 +1,7 @@ use roc_cli::build::check_file; use roc_cli::{ - build_app, docs, format, repl, 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, CMD_BUILD, CMD_CHECK, CMD_DOCS, CMD_EDIT, CMD_FORMAT, + CMD_REPL, CMD_VERSION, DIRECTORY_OR_FILES, FLAG_TIME, ROC_FILE, }; use roc_load::file::LoadingProblem; use std::fs::{self, FileType}; @@ -63,7 +63,7 @@ fn main() -> io::Result<()> { } } Some((CMD_REPL, _)) => { - repl::main()?; + roc_repl_cli::main()?; // Exit 0 if the repl exited normally Ok(0) diff --git a/cli/src/repl/debug.rs b/cli/src/repl/debug.rs deleted file mode 100644 index 8a26015036..0000000000 --- a/cli/src/repl/debug.rs +++ /dev/null @@ -1,109 +0,0 @@ - -// Check constraints -// -// Keep track of the used (in types or expectations) variables, and the declared variables (in -// flex_vars or rigid_vars fields of LetConstraint. These roc_collections should match: no duplicates -// and no variables that are used but not declared are allowed. -// -// There is one exception: the initial variable (that stores the type of the whole expression) is -// never declared, but is used. -pub fn assert_correct_variable_usage(constraint: &Constraint) { - // variables declared in constraint (flex_vars or rigid_vars) - // and variables actually used in constraints - let (declared, used) = variable_usage(constraint); - - let used: ImSet = used.into(); - let mut decl: ImSet = declared.rigid_vars.clone().into(); - - for var in declared.flex_vars.clone() { - decl.insert(var); - } - - let diff = used.clone().relative_complement(decl); - - // NOTE: this checks whether we're using variables that are not declared. For recursive type - // definitions, their rigid types are declared twice, which is correct! - if !diff.is_empty() { - println!("VARIABLE USAGE PROBLEM"); - - println!("used: {:?}", &used); - println!("rigids: {:?}", &declared.rigid_vars); - println!("flexs: {:?}", &declared.flex_vars); - - println!("difference: {:?}", &diff); - - panic!("variable usage problem (see stdout for details)"); - } -} - -#[derive(Default)] -pub struct SeenVariables { - pub rigid_vars: Vec, - pub flex_vars: Vec, -} - -pub fn variable_usage(con: &Constraint) -> (SeenVariables, Vec) { - let mut declared = SeenVariables::default(); - let mut used = ImSet::default(); - variable_usage_help(con, &mut declared, &mut used); - - used.remove(unsafe { &Variable::unsafe_test_debug_variable(1) }); - - let mut used_vec: Vec = used.into_iter().collect(); - used_vec.sort(); - - declared.rigid_vars.sort(); - declared.flex_vars.sort(); - - (declared, used_vec) -} - -fn variable_usage_help(con: &Constraint, declared: &mut SeenVariables, used: &mut ImSet) { - use Constraint::*; - - match con { - True | SaveTheEnvironment => (), - Eq(tipe, expectation, _, _) => { - for v in tipe.variables() { - used.insert(v); - } - - for v in expectation.get_type_ref().variables() { - used.insert(v); - } - } - Store(tipe, var, _, _) => { - for v in tipe.variables() { - used.insert(v); - } - - used.insert(*var); - } - Lookup(_, expectation, _) => { - for v in expectation.get_type_ref().variables() { - used.insert(v); - } - } - Pattern(_, _, tipe, pexpectation) => { - for v in tipe.variables() { - used.insert(v); - } - - for v in pexpectation.get_type_ref().variables() { - used.insert(v); - } - } - Let(letcon) => { - declared.rigid_vars.extend(letcon.rigid_vars.clone()); - declared.flex_vars.extend(letcon.flex_vars.clone()); - - variable_usage_help(&letcon.defs_constraint, declared, used); - variable_usage_help(&letcon.ret_constraint, declared, used); - } - And(constraints) => { - for sub in constraints { - variable_usage_help(sub, declared, used); - } - } - } -} \ No newline at end of file diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs deleted file mode 100644 index 4979f4bf46..0000000000 --- a/cli/src/repl/eval.rs +++ /dev/null @@ -1,1065 +0,0 @@ -use bumpalo::collections::Vec; -use bumpalo::Bump; -use libloading::Library; -use roc_builtins::bitcode::{FloatWidth, IntWidth}; -use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type}; -use roc_module::called_via::CalledVia; -use roc_module::ident::TagName; -use roc_module::symbol::{Interns, ModuleId, Symbol}; -use roc_mono::ir::ProcLayout; -use roc_mono::layout::{union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant}; -use roc_parse::ast::{AssignedField, Collection, Expr, StrLiteral}; -use roc_region::all::{Loc, Region}; -use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable}; - -struct Env<'a, 'env> { - arena: &'a Bump, - subs: &'env Subs, - ptr_bytes: u32, - interns: &'env Interns, - home: ModuleId, -} - -pub enum ToAstProblem { - FunctionLayout, -} - -/// JIT execute the given main function, and then wrap its results in an Expr -/// so we can display them to the user using the formatter. -/// -/// We need the original types in order to properly render records and tag unions, -/// because at runtime those are structs - that is, unlabeled memory offsets. -/// By traversing the type signature while we're traversing the layout, once -/// we get to a struct or tag, we know what the labels are and can turn them -/// back into the appropriate user-facing literals. -#[allow(clippy::too_many_arguments)] -pub unsafe fn jit_to_ast<'a>( - arena: &'a Bump, - lib: Library, - main_fn_name: &str, - layout: ProcLayout<'a>, - content: &Content, - interns: &Interns, - home: ModuleId, - subs: &Subs, - ptr_bytes: u32, -) -> Result, ToAstProblem> { - let env = Env { - arena, - subs, - ptr_bytes, - interns, - home, - }; - - match layout { - ProcLayout { - arguments: [], - result, - } => { - // this is a thunk - jit_to_ast_help(&env, lib, main_fn_name, &result, content) - } - _ => Err(ToAstProblem::FunctionLayout), - } -} - -fn jit_to_ast_help<'a>( - env: &Env<'a, '_>, - lib: Library, - main_fn_name: &str, - layout: &Layout<'a>, - content: &Content, -) -> Result, ToAstProblem> { - match layout { - Layout::Builtin(Builtin::Bool) => Ok(run_jit_function!(lib, main_fn_name, bool, |num| { - bool_to_ast(env, num, content) - })), - Layout::Builtin(Builtin::Int(int_width)) => { - use IntWidth::*; - - macro_rules! helper { - ($ty:ty) => { - run_jit_function!(lib, main_fn_name, $ty, |num| num_to_ast( - env, - number_literal_to_ast(env.arena, num), - content - )) - }; - } - - let result = match int_width { - U8 | I8 => { - // NOTE: this is does not handle 8-bit numbers yet - run_jit_function!(lib, main_fn_name, u8, |num| byte_to_ast(env, num, content)) - } - U16 => helper!(u16), - U32 => helper!(u32), - U64 => helper!(u64), - U128 => helper!(u128), - I16 => helper!(i16), - I32 => helper!(i32), - I64 => helper!(i64), - I128 => helper!(i128), - }; - - Ok(result) - } - Layout::Builtin(Builtin::Float(float_width)) => { - use FloatWidth::*; - - macro_rules! helper { - ($ty:ty) => { - run_jit_function!(lib, main_fn_name, $ty, |num| num_to_ast( - env, - number_literal_to_ast(env.arena, num), - content - )) - }; - } - - let result = match float_width { - F32 => helper!(f32), - F64 => helper!(f64), - F128 => todo!("F128 not implemented"), - }; - - Ok(result) - } - Layout::Builtin(Builtin::Str) => Ok(run_jit_function!( - lib, - main_fn_name, - &'static str, - |string: &'static str| { str_to_ast(env.arena, env.arena.alloc(string)) } - )), - Layout::Builtin(Builtin::List(elem_layout)) => Ok(run_jit_function!( - lib, - main_fn_name, - (*const u8, usize), - |(ptr, len): (*const u8, usize)| { list_to_ast(env, ptr, len, elem_layout, content) } - )), - Layout::Builtin(other) => { - todo!("add support for rendering builtin {:?} to the REPL", other) - } - Layout::Struct(field_layouts) => { - let ptr_to_ast = |ptr: *const u8| match content { - Content::Structure(FlatType::Record(fields, _)) => { - Ok(struct_to_ast(env, ptr, field_layouts, *fields)) - } - Content::Structure(FlatType::EmptyRecord) => Ok(struct_to_ast( - env, - ptr, - field_layouts, - RecordFields::empty(), - )), - Content::Structure(FlatType::TagUnion(tags, _)) => { - debug_assert_eq!(tags.len(), 1); - - let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); - - Ok(single_tag_union_to_ast( - env, - ptr, - field_layouts, - tag_name, - payload_vars, - )) - } - Content::Structure(FlatType::FunctionOrTagUnion(tag_name, _, _)) => { - let tag_name = &env.subs[*tag_name]; - - Ok(single_tag_union_to_ast( - env, - ptr, - field_layouts, - tag_name, - &[], - )) - } - Content::Structure(FlatType::Func(_, _, _)) => { - // a function with a struct as the closure environment - Err(ToAstProblem::FunctionLayout) - } - other => { - unreachable!( - "Something had a Struct layout, but instead of a Record or TagUnion type, it had: {:?}", - other - ); - } - }; - - let fields = [Layout::u64(), *layout]; - let layout = Layout::Struct(&fields); - - let result_stack_size = layout.stack_size(env.ptr_bytes); - - run_jit_function_dynamic_type!( - lib, - main_fn_name, - result_stack_size as usize, - |bytes: *const u8| { ptr_to_ast(bytes as *const u8) } - ) - } - Layout::Union(UnionLayout::NonRecursive(union_layouts)) => { - let union_layout = UnionLayout::NonRecursive(union_layouts); - - match content { - Content::Structure(FlatType::TagUnion(tags, _)) => { - debug_assert_eq!(union_layouts.len(), tags.len()); - - let tags_vec: std::vec::Vec<(TagName, std::vec::Vec)> = tags - .unsorted_iterator(env.subs, Variable::EMPTY_TAG_UNION) - .map(|(a, b)| (a.clone(), b.to_vec())) - .collect(); - - let tags_map: roc_collections::all::MutMap<_, _> = - tags_vec.iter().cloned().collect(); - - let union_variant = - union_sorted_tags_help(env.arena, tags_vec, None, env.subs, env.ptr_bytes); - - let size = layout.stack_size(env.ptr_bytes); - use roc_mono::layout::WrappedVariant::*; - match union_variant { - UnionVariant::Wrapped(variant) => { - match variant { - NonRecursive { - sorted_tag_layouts: tags_and_layouts, - } => { - Ok(run_jit_function_dynamic_type!( - lib, - main_fn_name, - size as usize, - |ptr: *const u8| { - // Because this is a `NonRecursive`, the tag ID is definitely after the data. - let offset = union_layout - .data_size_without_tag_id(env.ptr_bytes) - .unwrap(); - - let tag_id = match union_layout.tag_id_builtin() { - Builtin::Bool => { - *(ptr.add(offset as usize) as *const i8) as i64 - } - Builtin::Int(IntWidth::U8) => { - *(ptr.add(offset as usize) as *const i8) as i64 - } - Builtin::Int(IntWidth::U16) => { - *(ptr.add(offset as usize) as *const i16) as i64 - } - Builtin::Int(IntWidth::U64) => { - // used by non-recursive unions at the - // moment, remove if that is no longer the case - *(ptr.add(offset as usize) as *const i64) as i64 - } - _ => unreachable!("invalid tag id layout"), - }; - - // use the tag ID as an index, to get its name and layout of any arguments - let (tag_name, arg_layouts) = - &tags_and_layouts[tag_id as usize]; - - let tag_expr = tag_name_to_expr(env, tag_name); - let loc_tag_expr = - &*env.arena.alloc(Loc::at_zero(tag_expr)); - - let variables = &tags_map[tag_name]; - - debug_assert_eq!(arg_layouts.len(), variables.len()); - - // NOTE assumes the data bytes are the first bytes - let it = - variables.iter().copied().zip(arg_layouts.iter()); - let output = sequence_of_expr(env, ptr, it); - let output = output.into_bump_slice(); - - Expr::Apply(loc_tag_expr, output, CalledVia::Space) - } - )) - } - Recursive { - sorted_tag_layouts: tags_and_layouts, - } => { - Ok(run_jit_function_dynamic_type!( - lib, - main_fn_name, - size as usize, - |ptr: *const u8| { - // Because this is a `Wrapped`, the first 8 bytes encode the tag ID - let tag_id = *(ptr as *const i64); - - // use the tag ID as an index, to get its name and layout of any arguments - let (tag_name, arg_layouts) = - &tags_and_layouts[tag_id as usize]; - - let tag_expr = tag_name_to_expr(env, tag_name); - let loc_tag_expr = - &*env.arena.alloc(Loc::at_zero(tag_expr)); - - let variables = &tags_map[tag_name]; - - // because the arg_layouts include the tag ID, it is one longer - debug_assert_eq!( - arg_layouts.len() - 1, - variables.len() - ); - - // skip forward to the start of the first element, ignoring the tag id - let ptr = ptr.offset(8); - - let it = - variables.iter().copied().zip(&arg_layouts[1..]); - let output = sequence_of_expr(env, ptr, it); - let output = output.into_bump_slice(); - - Expr::Apply(loc_tag_expr, output, CalledVia::Space) - } - )) - } - _ => todo!(), - } - } - _ => unreachable!("any other variant would have a different layout"), - } - } - Content::Structure(FlatType::RecursiveTagUnion(_, _, _)) => { - todo!("print recursive tag unions in the REPL") - } - Content::Alias(_, _, actual) => { - let content = env.subs.get_content_without_compacting(*actual); - - jit_to_ast_help(env, lib, main_fn_name, layout, content) - } - other => unreachable!("Weird content for Union layout: {:?}", other), - } - } - Layout::Union(UnionLayout::Recursive(_)) - | Layout::Union(UnionLayout::NullableWrapped { .. }) - | Layout::Union(UnionLayout::NullableUnwrapped { .. }) - | Layout::Union(UnionLayout::NonNullableUnwrapped(_)) - | Layout::RecursivePointer => { - todo!("add support for rendering recursive tag unions in the REPL") - } - Layout::LambdaSet(lambda_set) => jit_to_ast_help( - env, - lib, - main_fn_name, - &lambda_set.runtime_representation(), - content, - ), - } -} - -fn tag_name_to_expr<'a>(env: &Env<'a, '_>, tag_name: &TagName) -> Expr<'a> { - match tag_name { - TagName::Global(_) => Expr::GlobalTag( - env.arena - .alloc_str(&tag_name.as_ident_str(env.interns, env.home)), - ), - TagName::Private(_) => Expr::PrivateTag( - env.arena - .alloc_str(&tag_name.as_ident_str(env.interns, env.home)), - ), - TagName::Closure(_) => unreachable!("User cannot type this"), - } -} - -fn ptr_to_ast<'a>( - env: &Env<'a, '_>, - ptr: *const u8, - layout: &Layout<'a>, - content: &Content, -) -> Expr<'a> { - macro_rules! helper { - ($ty:ty) => {{ - let num = unsafe { *(ptr as *const $ty) }; - - num_to_ast(env, number_literal_to_ast(env.arena, num), content) - }}; - } - - match layout { - Layout::Builtin(Builtin::Bool) => { - // TODO: bits are not as expected here. - // num is always false at the moment. - let num = unsafe { *(ptr as *const bool) }; - - bool_to_ast(env, num, content) - } - Layout::Builtin(Builtin::Int(int_width)) => { - use IntWidth::*; - - match int_width { - U8 => helper!(u8), - U16 => helper!(u16), - U32 => helper!(u32), - U64 => helper!(u64), - U128 => helper!(u128), - I8 => helper!(i8), - I16 => helper!(i16), - I32 => helper!(i32), - I64 => helper!(i64), - I128 => helper!(i128), - } - } - Layout::Builtin(Builtin::Float(float_width)) => { - use FloatWidth::*; - - match float_width { - F32 => helper!(f32), - F64 => helper!(f64), - F128 => todo!("F128 not implemented"), - } - } - Layout::Builtin(Builtin::List(elem_layout)) => { - // Turn the (ptr, len) wrapper struct into actual ptr and len values. - let len = unsafe { *(ptr.offset(env.ptr_bytes as isize) as *const usize) }; - let ptr = unsafe { *(ptr as *const *const u8) }; - - list_to_ast(env, ptr, len, elem_layout, content) - } - Layout::Builtin(Builtin::Str) => { - let arena_str = unsafe { *(ptr as *const &'static str) }; - - str_to_ast(env.arena, arena_str) - } - Layout::Struct(field_layouts) => match content { - Content::Structure(FlatType::Record(fields, _)) => { - struct_to_ast(env, ptr, field_layouts, *fields) - } - Content::Structure(FlatType::TagUnion(tags, _)) => { - debug_assert_eq!(tags.len(), 1); - - let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); - single_tag_union_to_ast(env, ptr, field_layouts, tag_name, payload_vars) - } - Content::Structure(FlatType::FunctionOrTagUnion(tag_name, _, _)) => { - let tag_name = &env.subs[*tag_name]; - single_tag_union_to_ast(env, ptr, field_layouts, tag_name, &[]) - } - Content::Structure(FlatType::EmptyRecord) => { - struct_to_ast(env, ptr, &[], RecordFields::empty()) - } - other => { - unreachable!( - "Something had a Struct layout, but instead of a Record type, it had: {:?}", - other - ); - } - }, - other => { - todo!( - "TODO add support for rendering pointer to {:?} in the REPL", - other - ); - } - } -} - -fn list_to_ast<'a>( - env: &Env<'a, '_>, - ptr: *const u8, - len: usize, - elem_layout: &Layout<'a>, - content: &Content, -) -> Expr<'a> { - let elem_content = match content { - Content::Structure(FlatType::Apply(Symbol::LIST_LIST, vars)) => { - debug_assert_eq!(vars.len(), 1); - - let elem_var_index = vars.into_iter().next().unwrap(); - let elem_var = env.subs[elem_var_index]; - - env.subs.get_content_without_compacting(elem_var) - } - other => { - unreachable!( - "Something had a Struct layout, but instead of a Record type, it had: {:?}", - other - ); - } - }; - - let arena = env.arena; - let mut output = Vec::with_capacity_in(len, arena); - let elem_size = elem_layout.stack_size(env.ptr_bytes) as usize; - - for index in 0..len { - let offset_bytes = index * elem_size; - let elem_ptr = unsafe { ptr.add(offset_bytes) }; - let loc_expr = &*arena.alloc(Loc { - value: ptr_to_ast(env, elem_ptr, elem_layout, elem_content), - region: Region::zero(), - }); - - output.push(loc_expr); - } - - let output = output.into_bump_slice(); - - Expr::List(Collection::with_items(output)) -} - -fn single_tag_union_to_ast<'a>( - env: &Env<'a, '_>, - ptr: *const u8, - field_layouts: &'a [Layout<'a>], - tag_name: &TagName, - payload_vars: &[Variable], -) -> Expr<'a> { - let arena = env.arena; - let tag_expr = tag_name_to_expr(env, tag_name); - - let loc_tag_expr = &*arena.alloc(Loc::at_zero(tag_expr)); - - let output = if field_layouts.len() == payload_vars.len() { - let it = payload_vars.iter().copied().zip(field_layouts); - sequence_of_expr(env, ptr as *const u8, it).into_bump_slice() - } else if field_layouts.is_empty() && !payload_vars.is_empty() { - // happens for e.g. `Foo Bar` where unit structures are nested and the inner one is dropped - let it = payload_vars.iter().copied().zip([&Layout::Struct(&[])]); - sequence_of_expr(env, ptr as *const u8, it).into_bump_slice() - } else { - unreachable!() - }; - - Expr::Apply(loc_tag_expr, output, CalledVia::Space) -} - -fn sequence_of_expr<'a, I>( - env: &Env<'a, '_>, - ptr: *const u8, - sequence: I, -) -> Vec<'a, &'a Loc>> -where - I: Iterator)>, - I: ExactSizeIterator)>, -{ - let arena = env.arena; - let subs = env.subs; - let mut output = Vec::with_capacity_in(sequence.len(), arena); - - // We'll advance this as we iterate through the fields - let mut field_ptr = ptr as *const u8; - - for (var, layout) in sequence { - let content = subs.get_content_without_compacting(var); - let expr = ptr_to_ast(env, field_ptr, layout, content); - let loc_expr = Loc::at_zero(expr); - - output.push(&*arena.alloc(loc_expr)); - - // Advance the field pointer to the next field. - field_ptr = unsafe { field_ptr.offset(layout.stack_size(env.ptr_bytes) as isize) }; - } - - output -} - -fn struct_to_ast<'a>( - env: &Env<'a, '_>, - ptr: *const u8, - field_layouts: &'a [Layout<'a>], - record_fields: RecordFields, -) -> Expr<'a> { - let arena = env.arena; - let subs = env.subs; - let mut output = Vec::with_capacity_in(field_layouts.len(), arena); - - let sorted_fields: Vec<_> = Vec::from_iter_in( - record_fields.sorted_iterator(env.subs, Variable::EMPTY_RECORD), - env.arena, - ); - - if sorted_fields.len() == 1 { - // this is a 1-field wrapper record around another record or 1-tag tag union - let (label, field) = sorted_fields.into_iter().next().unwrap(); - - let inner_content = env.subs.get_content_without_compacting(field.into_inner()); - - let loc_expr = &*arena.alloc(Loc { - value: ptr_to_ast(env, ptr, &Layout::Struct(field_layouts), inner_content), - region: Region::zero(), - }); - - let field_name = Loc { - value: &*arena.alloc_str(label.as_str()), - region: Region::zero(), - }; - let loc_field = Loc { - value: AssignedField::RequiredValue(field_name, &[], loc_expr), - region: Region::zero(), - }; - - let output = env.arena.alloc([loc_field]); - - Expr::Record(Collection::with_items(output)) - } else { - debug_assert_eq!(sorted_fields.len(), field_layouts.len()); - - // We'll advance this as we iterate through the fields - let mut field_ptr = ptr; - - for ((label, field), field_layout) in sorted_fields.into_iter().zip(field_layouts.iter()) { - let var = field.into_inner(); - - let content = subs.get_content_without_compacting(var); - let loc_expr = &*arena.alloc(Loc { - value: ptr_to_ast(env, field_ptr, field_layout, content), - region: Region::zero(), - }); - - let field_name = Loc { - value: &*arena.alloc_str(label.as_str()), - region: Region::zero(), - }; - let loc_field = Loc { - value: AssignedField::RequiredValue(field_name, &[], loc_expr), - region: Region::zero(), - }; - - output.push(loc_field); - - // Advance the field pointer to the next field. - field_ptr = - unsafe { field_ptr.offset(field_layout.stack_size(env.ptr_bytes) as isize) }; - } - - let output = output.into_bump_slice(); - - Expr::Record(Collection::with_items(output)) - } -} - -fn unpack_single_element_tag_union(subs: &Subs, tags: UnionTags) -> (&TagName, &[Variable]) { - let (tag_name_index, payload_vars_index) = tags.iter_all().next().unwrap(); - - let tag_name = &subs[tag_name_index]; - let subs_slice = subs[payload_vars_index]; - let payload_vars = subs.get_subs_slice(subs_slice); - - (tag_name, payload_vars) -} - -fn unpack_two_element_tag_union( - subs: &Subs, - tags: UnionTags, -) -> (&TagName, &[Variable], &TagName, &[Variable]) { - let mut it = tags.iter_all(); - let (tag_name_index, payload_vars_index) = it.next().unwrap(); - - let tag_name1 = &subs[tag_name_index]; - let subs_slice = subs[payload_vars_index]; - let payload_vars1 = subs.get_subs_slice(subs_slice); - - let (tag_name_index, payload_vars_index) = it.next().unwrap(); - - let tag_name2 = &subs[tag_name_index]; - let subs_slice = subs[payload_vars_index]; - let payload_vars2 = subs.get_subs_slice(subs_slice); - - (tag_name1, payload_vars1, tag_name2, payload_vars2) -} - -fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a> { - use Content::*; - - let arena = env.arena; - - match content { - Structure(flat_type) => { - match flat_type { - FlatType::Record(fields, _) => { - debug_assert_eq!(fields.len(), 1); - - let (label, field) = fields - .sorted_iterator(env.subs, Variable::EMPTY_RECORD) - .next() - .unwrap(); - - let loc_label = Loc { - value: &*arena.alloc_str(label.as_str()), - region: Region::zero(), - }; - - let assigned_field = { - // We may be multiple levels deep in nested tag unions - // and/or records (e.g. { a: { b: { c: True } } }), - // so we need to do this recursively on the field type. - let field_var = *field.as_inner(); - let field_content = env.subs.get_content_without_compacting(field_var); - let loc_expr = Loc { - value: bool_to_ast(env, value, field_content), - region: Region::zero(), - }; - - AssignedField::RequiredValue(loc_label, &[], arena.alloc(loc_expr)) - }; - - let loc_assigned_field = Loc { - value: assigned_field, - region: Region::zero(), - }; - - Expr::Record(Collection::with_items(arena.alloc([loc_assigned_field]))) - } - FlatType::TagUnion(tags, _) if tags.len() == 1 => { - let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); - - let loc_tag_expr = { - let tag_name = &tag_name.as_ident_str(env.interns, env.home); - let tag_expr = if tag_name.starts_with('@') { - Expr::PrivateTag(arena.alloc_str(tag_name)) - } else { - Expr::GlobalTag(arena.alloc_str(tag_name)) - }; - - &*arena.alloc(Loc { - value: tag_expr, - region: Region::zero(), - }) - }; - - let payload = { - // Since this has the layout of a number, there should be - // exactly one payload in this tag. - debug_assert_eq!(payload_vars.len(), 1); - - let var = *payload_vars.iter().next().unwrap(); - let content = env.subs.get_content_without_compacting(var); - - let loc_payload = &*arena.alloc(Loc { - value: bool_to_ast(env, value, content), - region: Region::zero(), - }); - - arena.alloc([loc_payload]) - }; - - Expr::Apply(loc_tag_expr, payload, CalledVia::Space) - } - FlatType::TagUnion(tags, _) if tags.len() == 2 => { - let (tag_name_1, payload_vars_1, tag_name_2, payload_vars_2) = - unpack_two_element_tag_union(env.subs, *tags); - - debug_assert!(payload_vars_1.is_empty()); - debug_assert!(payload_vars_2.is_empty()); - - let tag_name = if value { - max_by_key(tag_name_1, tag_name_2, |n| { - n.as_ident_str(env.interns, env.home) - }) - } else { - min_by_key(tag_name_1, tag_name_2, |n| { - n.as_ident_str(env.interns, env.home) - }) - }; - - tag_name_to_expr(env, tag_name) - } - other => { - unreachable!("Unexpected FlatType {:?} in bool_to_ast", other); - } - } - } - Alias(_, _, var) => { - let content = env.subs.get_content_without_compacting(*var); - - bool_to_ast(env, value, content) - } - other => { - unreachable!("Unexpected FlatType {:?} in bool_to_ast", other); - } - } -} - -fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a> { - use Content::*; - - let arena = env.arena; - - match content { - Structure(flat_type) => { - match flat_type { - FlatType::Record(fields, _) => { - debug_assert_eq!(fields.len(), 1); - - let (label, field) = fields - .sorted_iterator(env.subs, Variable::EMPTY_RECORD) - .next() - .unwrap(); - - let loc_label = Loc { - value: &*arena.alloc_str(label.as_str()), - region: Region::zero(), - }; - - let assigned_field = { - // We may be multiple levels deep in nested tag unions - // and/or records (e.g. { a: { b: { c: True } } }), - // so we need to do this recursively on the field type. - let field_var = *field.as_inner(); - let field_content = env.subs.get_content_without_compacting(field_var); - let loc_expr = Loc { - value: byte_to_ast(env, value, field_content), - region: Region::zero(), - }; - - AssignedField::RequiredValue(loc_label, &[], arena.alloc(loc_expr)) - }; - - let loc_assigned_field = Loc { - value: assigned_field, - region: Region::zero(), - }; - - Expr::Record(Collection::with_items(arena.alloc([loc_assigned_field]))) - } - FlatType::TagUnion(tags, _) if tags.len() == 1 => { - let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); - - let loc_tag_expr = { - let tag_name = &tag_name.as_ident_str(env.interns, env.home); - let tag_expr = if tag_name.starts_with('@') { - Expr::PrivateTag(arena.alloc_str(tag_name)) - } else { - Expr::GlobalTag(arena.alloc_str(tag_name)) - }; - - &*arena.alloc(Loc { - value: tag_expr, - region: Region::zero(), - }) - }; - - let payload = { - // Since this has the layout of a number, there should be - // exactly one payload in this tag. - debug_assert_eq!(payload_vars.len(), 1); - - let var = *payload_vars.iter().next().unwrap(); - let content = env.subs.get_content_without_compacting(var); - - let loc_payload = &*arena.alloc(Loc { - value: byte_to_ast(env, value, content), - region: Region::zero(), - }); - - arena.alloc([loc_payload]) - }; - - Expr::Apply(loc_tag_expr, payload, CalledVia::Space) - } - FlatType::TagUnion(tags, _) => { - // anything with fewer tags is not a byte - debug_assert!(tags.len() > 2); - - let tags_vec: std::vec::Vec<(TagName, std::vec::Vec)> = tags - .unsorted_iterator(env.subs, Variable::EMPTY_TAG_UNION) - .map(|(a, b)| (a.clone(), b.to_vec())) - .collect(); - - let union_variant = - union_sorted_tags_help(env.arena, tags_vec, None, env.subs, env.ptr_bytes); - - match union_variant { - UnionVariant::ByteUnion(tagnames) => { - let tag_name = &tagnames[value as usize]; - let tag_expr = tag_name_to_expr(env, tag_name); - let loc_tag_expr = Loc::at_zero(tag_expr); - Expr::Apply(env.arena.alloc(loc_tag_expr), &[], CalledVia::Space) - } - _ => unreachable!("invalid union variant for a Byte!"), - } - } - other => { - unreachable!("Unexpected FlatType {:?} in bool_to_ast", other); - } - } - } - Alias(_, _, var) => { - let content = env.subs.get_content_without_compacting(*var); - - byte_to_ast(env, value, content) - } - other => { - unreachable!("Unexpected FlatType {:?} in bool_to_ast", other); - } - } -} - -fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> Expr<'a> { - use Content::*; - - let arena = env.arena; - - match content { - Structure(flat_type) => { - match flat_type { - FlatType::Apply(Symbol::NUM_NUM, _) => num_expr, - FlatType::Record(fields, _) => { - // This was a single-field record that got unwrapped at runtime. - // Even if it was an i64 at runtime, we still need to report - // it as a record with the correct field name! - // Its type signature will tell us that. - debug_assert_eq!(fields.len(), 1); - - let (label, field) = fields - .sorted_iterator(env.subs, Variable::EMPTY_RECORD) - .next() - .unwrap(); - - let loc_label = Loc { - value: &*arena.alloc_str(label.as_str()), - region: Region::zero(), - }; - - let assigned_field = { - // We may be multiple levels deep in nested tag unions - // and/or records (e.g. { a: { b: { c: 5 } } }), - // so we need to do this recursively on the field type. - let field_var = *field.as_inner(); - let field_content = env.subs.get_content_without_compacting(field_var); - let loc_expr = Loc { - value: num_to_ast(env, num_expr, field_content), - region: Region::zero(), - }; - - AssignedField::RequiredValue(loc_label, &[], arena.alloc(loc_expr)) - }; - let loc_assigned_field = Loc { - value: assigned_field, - region: Region::zero(), - }; - - Expr::Record(Collection::with_items(arena.alloc([loc_assigned_field]))) - } - FlatType::TagUnion(tags, _) => { - // This was a single-tag union that got unwrapped at runtime. - debug_assert_eq!(tags.len(), 1); - - let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); - - // If this tag union represents a number, skip right to - // returning tis as an Expr::Num - if let TagName::Private(Symbol::NUM_AT_NUM) = &tag_name { - return num_expr; - } - - let loc_tag_expr = { - let tag_name = &tag_name.as_ident_str(env.interns, env.home); - let tag_expr = if tag_name.starts_with('@') { - Expr::PrivateTag(arena.alloc_str(tag_name)) - } else { - Expr::GlobalTag(arena.alloc_str(tag_name)) - }; - - &*arena.alloc(Loc { - value: tag_expr, - region: Region::zero(), - }) - }; - - let payload = { - // Since this has the layout of a number, there should be - // exactly one payload in this tag. - debug_assert_eq!(payload_vars.len(), 1); - - let var = *payload_vars.iter().next().unwrap(); - let content = env.subs.get_content_without_compacting(var); - - let loc_payload = &*arena.alloc(Loc { - value: num_to_ast(env, num_expr, content), - region: Region::zero(), - }); - - arena.alloc([loc_payload]) - }; - - Expr::Apply(loc_tag_expr, payload, CalledVia::Space) - } - other => { - panic!("Unexpected FlatType {:?} in num_to_ast", other); - } - } - } - Alias(_, _, var) => { - let content = env.subs.get_content_without_compacting(*var); - - num_to_ast(env, num_expr, content) - } - other => { - panic!("Unexpected FlatType {:?} in num_to_ast", other); - } - } -} - -/// This is centralized in case we want to format it differently later, -/// e.g. adding underscores for large numbers -fn number_literal_to_ast(arena: &Bump, num: T) -> Expr<'_> { - Expr::Num(arena.alloc(format!("{}", num))) -} - -#[cfg(target_endian = "little")] -#[cfg(target_pointer_width = "64")] -/// TODO implement this for 32-bit and big-endian targets. NOTE: As of this writing, -/// we don't have big-endian small strings implemented yet! -fn str_to_ast<'a>(arena: &'a Bump, string: &'a str) -> Expr<'a> { - let bytes: [u8; 16] = unsafe { std::mem::transmute::<&'a str, [u8; 16]>(string) }; - let is_small = (bytes[15] & 0b1000_0000) != 0; - - if is_small { - let len = (bytes[15] & 0b0111_1111) as usize; - let mut string = bumpalo::collections::String::with_capacity_in(len, arena); - - for byte in bytes.iter().take(len) { - string.push(*byte as char); - } - - str_slice_to_ast(arena, arena.alloc(string)) - } else { - // Roc string literals are stored inside the constant section of the program - // That means this memory is gone when the jit function is done - // (as opposed to heap memory, which we can leak and then still use after) - // therefore we must make an owned copy of the string here - let string = bumpalo::collections::String::from_str_in(string, arena).into_bump_str(); - str_slice_to_ast(arena, string) - } -} - -fn str_slice_to_ast<'a>(_arena: &'a Bump, string: &'a str) -> Expr<'a> { - if string.contains('\n') { - todo!( - "this string contains newlines, so render it as a multiline string: {:?}", - Expr::Str(StrLiteral::PlainLine(string)) - ); - } else { - Expr::Str(StrLiteral::PlainLine(string)) - } -} - -// TODO this is currently nighly-only: use the implementation in std once it's stabilized -pub fn max_by std::cmp::Ordering>(v1: T, v2: T, compare: F) -> T { - use std::cmp::Ordering; - - match compare(&v1, &v2) { - Ordering::Less | Ordering::Equal => v2, - Ordering::Greater => v1, - } -} - -pub fn min_by std::cmp::Ordering>(v1: T, v2: T, compare: F) -> T { - use std::cmp::Ordering; - - match compare(&v1, &v2) { - Ordering::Less | Ordering::Equal => v1, - Ordering::Greater => v2, - } -} - -pub fn max_by_key K, K: Ord>(v1: T, v2: T, mut f: F) -> T { - max_by(v1, v2, |v1, v2| f(v1).cmp(&f(v2))) -} - -pub fn min_by_key K, K: Ord>(v1: T, v2: T, mut f: F) -> T { - min_by(v1, v2, |v1, v2| f(v1).cmp(&f(v2))) -} diff --git a/cli/src/repl/gen.rs b/cli/src/repl/gen.rs deleted file mode 100644 index 83593bc999..0000000000 --- a/cli/src/repl/gen.rs +++ /dev/null @@ -1,273 +0,0 @@ -use crate::repl::eval; -use bumpalo::Bump; -use inkwell::context::Context; -use inkwell::module::Linkage; -use roc_build::link::module_to_dylib; -use roc_build::program::FunctionIterator; -use roc_can::builtins::builtin_defs_map; -use roc_collections::all::{MutMap, MutSet}; -use roc_fmt::annotation::Formattable; -use roc_fmt::annotation::{Newlines, Parens}; -use roc_gen_llvm::llvm::externs::add_default_roc_externs; -use roc_load::file::LoadingProblem; -use roc_mono::ir::OptLevel; -use roc_parse::parser::SyntaxError; -use roc_types::pretty_print::{content_to_string, name_all_type_vars}; -use std::path::{Path, PathBuf}; -use std::str::from_utf8_unchecked; -use target_lexicon::Triple; - -pub enum ReplOutput { - Problems(Vec), - NoProblems { expr: String, expr_type: String }, -} - -pub fn gen_and_eval<'a>( - src: &[u8], - target: Triple, - opt_level: OptLevel, -) -> Result> { - use roc_reporting::report::{ - can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE, - }; - - let arena = Bump::new(); - - // SAFETY: we've already verified that this is valid UTF-8 during parsing. - let src_str: &str = unsafe { from_utf8_unchecked(src) }; - - let stdlib = roc_builtins::std::standard_stdlib(); - let filename = PathBuf::from("REPL.roc"); - let src_dir = Path::new("fake/test/path"); - - let module_src = promote_expr_to_module(src_str); - - let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; - - let exposed_types = MutMap::default(); - let loaded = roc_load::file::load_and_monomorphize_from_str( - &arena, - filename, - &module_src, - &stdlib, - src_dir, - exposed_types, - ptr_bytes, - builtin_defs_map, - ); - - let mut loaded = match loaded { - Ok(v) => v, - Err(LoadingProblem::FormattedReport(report)) => { - return Ok(ReplOutput::Problems(vec![report])); - } - Err(e) => { - panic!("error while loading module: {:?}", e) - } - }; - - use roc_load::file::MonomorphizedModule; - let MonomorphizedModule { - procedures, - entry_point, - interns, - exposed_to_host, - mut subs, - module_id: home, - sources, - .. - } = loaded; - - let mut lines = Vec::new(); - - for (home, (module_path, src)) in sources { - let can_problems = loaded.can_problems.remove(&home).unwrap_or_default(); - let type_problems = loaded.type_problems.remove(&home).unwrap_or_default(); - let mono_problems = loaded.mono_problems.remove(&home).unwrap_or_default(); - - let error_count = can_problems.len() + type_problems.len() + mono_problems.len(); - - if error_count == 0 { - continue; - } - - let src_lines: Vec<&str> = src.split('\n').collect(); - let palette = DEFAULT_PALETTE; - - // Report parsing and canonicalization problems - let alloc = RocDocAllocator::new(&src_lines, home, &interns); - - for problem in can_problems.into_iter() { - let report = can_problem(&alloc, module_path.clone(), problem); - let mut buf = String::new(); - - report.render_color_terminal(&mut buf, &alloc, &palette); - - lines.push(buf); - } - - for problem in type_problems { - if let Some(report) = type_problem(&alloc, module_path.clone(), problem) { - let mut buf = String::new(); - - report.render_color_terminal(&mut buf, &alloc, &palette); - - lines.push(buf); - } - } - - for problem in mono_problems { - let report = mono_problem(&alloc, module_path.clone(), problem); - let mut buf = String::new(); - - report.render_color_terminal(&mut buf, &alloc, &palette); - - lines.push(buf); - } - } - - if !lines.is_empty() { - Ok(ReplOutput::Problems(lines)) - } else { - let context = Context::create(); - let builder = context.create_builder(); - let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; - let module = arena.alloc(roc_gen_llvm::llvm::build::module_from_builtins( - &target, &context, "", - )); - - // mark our zig-defined builtins as internal - for function in FunctionIterator::from_module(module) { - let name = function.get_name().to_str().unwrap(); - if name.starts_with("roc_builtins") { - function.set_linkage(Linkage::Internal); - } - } - - debug_assert_eq!(exposed_to_host.len(), 1); - let (main_fn_symbol, main_fn_var) = exposed_to_host.iter().next().unwrap(); - let main_fn_symbol = *main_fn_symbol; - let main_fn_var = *main_fn_var; - - // pretty-print the expr type string for later. - name_all_type_vars(main_fn_var, &mut subs); - let content = subs.get_content_without_compacting(main_fn_var); - let expr_type_str = content_to_string(content, &subs, home, &interns); - - let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) { - Some(layout) => *layout, - None => { - return Ok(ReplOutput::NoProblems { - expr: "".to_string(), - expr_type: expr_type_str, - }); - } - }; - - let module = arena.alloc(module); - let (module_pass, function_pass) = - roc_gen_llvm::llvm::build::construct_optimization_passes(module, opt_level); - - let (dibuilder, compile_unit) = roc_gen_llvm::llvm::build::Env::new_debug_info(module); - - // Compile and add all the Procs before adding main - let env = roc_gen_llvm::llvm::build::Env { - arena: &arena, - builder: &builder, - dibuilder: &dibuilder, - compile_unit: &compile_unit, - context: &context, - interns, - module, - ptr_bytes, - is_gen_test: true, // so roc_panic is generated - // important! we don't want any procedures to get the C calling convention - exposed_to_host: MutSet::default(), - }; - - // Add roc_alloc, roc_realloc, and roc_dealloc, since the repl has no - // platform to provide them. - add_default_roc_externs(&env); - - let (main_fn_name, main_fn) = roc_gen_llvm::llvm::build::build_procedures_return_main( - &env, - opt_level, - procedures, - entry_point, - ); - - env.dibuilder.finalize(); - - // we don't use the debug info, and it causes weird errors. - module.strip_debug_info(); - - // Uncomment this to see the module's un-optimized LLVM instruction output: - // env.module.print_to_stderr(); - - if main_fn.verify(true) { - function_pass.run_on(&main_fn); - } else { - panic!("Main function {} failed LLVM verification in build. Uncomment things nearby to see more details.", main_fn_name); - } - - module_pass.run_on(env.module); - - // Uncomment this to see the module's optimized LLVM instruction output: - // env.module.print_to_stderr(); - - // Verify the module - if let Err(errors) = env.module.verify() { - panic!( - "Errors defining module:\n{}\n\nUncomment things nearby to see more details.", - errors.to_string() - ); - } - - let lib = module_to_dylib(env.module, &target, opt_level) - .expect("Error loading compiled dylib for test"); - let res_answer = unsafe { - eval::jit_to_ast( - &arena, - lib, - main_fn_name, - main_fn_layout, - content, - &env.interns, - home, - &subs, - ptr_bytes, - ) - }; - let mut expr = roc_fmt::Buf::new_in(&arena); - - use eval::ToAstProblem::*; - match res_answer { - Ok(answer) => { - answer.format_with_options(&mut expr, Parens::NotNeeded, Newlines::Yes, 0); - } - Err(FunctionLayout) => { - expr.indent(0); - expr.push_str(""); - } - } - - Ok(ReplOutput::NoProblems { - expr: expr.into_bump_str().to_string(), - expr_type: expr_type_str, - }) - } -} - -fn promote_expr_to_module(src: &str) -> String { - let mut buffer = - String::from("app \"app\" provides [ replOutput ] to \"./platform\"\n\nreplOutput =\n"); - - for line in src.lines() { - // indent the body! - buffer.push_str(" "); - buffer.push_str(line); - buffer.push('\n'); - } - - buffer -} diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index c765fb8cb3..a4a105349b 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -6,12 +6,16 @@ extern crate roc_collections; extern crate roc_load; extern crate roc_module; +#[macro_use] +extern crate indoc; + #[cfg(test)] mod cli_run { use cli_utils::helpers::{ - example_file, examples_dir, extract_valgrind_errors, fixture_file, run_cmd, run_roc, - run_with_valgrind, ValgrindError, ValgrindErrorXWhat, + example_file, examples_dir, extract_valgrind_errors, fixture_file, known_bad_file, run_cmd, + run_roc, run_with_valgrind, ValgrindError, ValgrindErrorXWhat, }; + use roc_test_utils::assert_multiline_str_eq; use serial_test::serial; use std::path::{Path, PathBuf}; @@ -44,6 +48,27 @@ mod cli_run { use_valgrind: bool, } + fn strip_colors(str: &str) -> String { + use roc_reporting::report::*; + str.replace(RED_CODE, "") + .replace(WHITE_CODE, "") + .replace(BLUE_CODE, "") + .replace(YELLOW_CODE, "") + .replace(GREEN_CODE, "") + .replace(CYAN_CODE, "") + .replace(MAGENTA_CODE, "") + .replace(RESET_CODE, "") + .replace(BOLD_CODE, "") + .replace(UNDERLINE_CODE, "") + } + + fn check_compile_error(file: &Path, flags: &[&str], expected: &str) { + let compile_out = run_roc(&[&["check", file.to_str().unwrap()], &flags[..]].concat()); + let err = compile_out.stdout.trim(); + let err = strip_colors(&err); + assert_multiline_str_eq!(err, expected.into()); + } + fn check_output_with_stdin( file: &Path, stdin: &[&str], @@ -62,7 +87,7 @@ mod cli_run { let compile_out = run_roc(&[&["build", file.to_str().unwrap()], &all_flags[..]].concat()); if !compile_out.stderr.is_empty() { - panic!("{}", compile_out.stderr); + panic!("roc build had stderr: {}", compile_out.stderr); } assert!(compile_out.status.success(), "bad status {:?}", compile_out); @@ -351,11 +376,19 @@ mod cli_run { // use_valgrind: true, // }, cli:"cli" => Example { - filename: "Echo.roc", - executable_filename: "echo", + filename: "form.roc", + executable_filename: "form", stdin: &["Giovanni\n", "Giorgio\n"], input_file: None, - expected_ending: "Hi, Giovanni Giorgio!\n", + expected_ending: "Hi, Giovanni Giorgio! 👋\n", + use_valgrind: true, + }, + tui:"tui" => Example { + filename: "Main.roc", + executable_filename: "tui", + stdin: &["foo\n"], // NOTE: adding more lines leads to memory leaks + input_file: None, + expected_ending: "Hello Worldfoo!\n", use_valgrind: true, }, // custom_malloc:"custom-malloc" => Example { @@ -388,9 +421,11 @@ mod cli_run { macro_rules! benchmarks { ($($test_name:ident => $benchmark:expr,)+) => { + $( #[test] #[cfg_attr(not(debug_assertions), serial(benchmark))] + #[cfg(all(not(feature = "wasm32-cli-run"), not(feature = "i386-cli-run")))] fn $test_name() { let benchmark = $benchmark; let file_name = examples_dir("benchmarks").join(benchmark.filename); @@ -601,6 +636,14 @@ mod cli_run { expected_ending: "", use_valgrind: true, }, + issue2279 => Example { + filename: "Issue2279.roc", + executable_filename: "issue2279", + stdin: &[], + input_file: None, + expected_ending: "Hello, world!\n", + use_valgrind: true, + }, quicksort_app => Example { filename: "QuicksortApp.roc", executable_filename: "quicksortapp", @@ -732,6 +775,94 @@ mod cli_run { true, ); } + + #[test] + fn known_type_error() { + check_compile_error( + &known_bad_file("TypeError.roc"), + &[], + indoc!( + r#" + ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + + I cannot find a `d` value + + 10│ _ <- await (line d) + ^ + + Did you mean one of these? + + U8 + Ok + I8 + F64 + + ────────────────────────────────────────────────────────────────────────────────"# + ), + ); + } + + #[test] + fn exposed_not_defined() { + check_compile_error( + &known_bad_file("ExposedNotDefined.roc"), + &[], + indoc!( + r#" + ── MISSING DEFINITION ────────────────────────────────────────────────────────── + + bar is listed as exposed, but it isn't defined in this module. + + You can fix this by adding a definition for bar, or by removing it + from exposes. + + ────────────────────────────────────────────────────────────────────────────────"# + ), + ); + } + + #[test] + fn unused_import() { + check_compile_error( + &known_bad_file("UnusedImport.roc"), + &[], + indoc!( + r#" + ── UNUSED IMPORT ─────────────────────────────────────────────────────────────── + + Nothing from Symbol is used in this module. + + 3│ imports [ Symbol.{ Ident } ] + ^^^^^^^^^^^^^^^^ + + Since Symbol isn't used, you don't need to import it. + + ────────────────────────────────────────────────────────────────────────────────"# + ), + ); + } + + #[test] + fn unknown_generates_with() { + check_compile_error( + &known_bad_file("UnknownGeneratesWith.roc"), + &[], + indoc!( + r#" + ── UNKNOWN GENERATES FUNCTION ────────────────────────────────────────────────── + + I don't know how to generate the foobar function. + + 4│ generates Effect with [ after, map, always, foobar ] + ^^^^^^ + + Only specific functions like `after` and `map` can be generated.Learn + more about hosted modules at TODO. + + ────────────────────────────────────────────────────────────────────────────────"# + ), + ); + } } #[allow(dead_code)] diff --git a/cli/tests/fixtures/multi-dep-str/platform/Package-Config.roc b/cli/tests/fixtures/multi-dep-str/platform/Package-Config.roc index ac9d12d39f..eb69cce73a 100644 --- a/cli/tests/fixtures/multi-dep-str/platform/Package-Config.roc +++ b/cli/tests/fixtures/multi-dep-str/platform/Package-Config.roc @@ -4,7 +4,6 @@ platform "examples/multi-module" packages {} imports [] provides [ mainForHost ] - effects fx.Effect {} mainForHost : Str mainForHost = main diff --git a/cli/tests/fixtures/multi-dep-thunk/platform/Package-Config.roc b/cli/tests/fixtures/multi-dep-thunk/platform/Package-Config.roc index 274980c20f..68c2ca0962 100644 --- a/cli/tests/fixtures/multi-dep-thunk/platform/Package-Config.roc +++ b/cli/tests/fixtures/multi-dep-thunk/platform/Package-Config.roc @@ -4,7 +4,6 @@ platform "examples/multi-dep-thunk" packages {} imports [] provides [ mainForHost ] - effects fx.Effect {} mainForHost : Str mainForHost = main diff --git a/cli/tests/known_bad/ExposedNotDefined.roc b/cli/tests/known_bad/ExposedNotDefined.roc new file mode 100644 index 0000000000..b94dc7de59 --- /dev/null +++ b/cli/tests/known_bad/ExposedNotDefined.roc @@ -0,0 +1,3 @@ +interface Foo + exposes [ bar ] + imports [] diff --git a/cli/tests/known_bad/Symbol.roc b/cli/tests/known_bad/Symbol.roc new file mode 100644 index 0000000000..73e6cce822 --- /dev/null +++ b/cli/tests/known_bad/Symbol.roc @@ -0,0 +1,6 @@ +interface Symbol + exposes [ Ident ] + imports [] + +# NOTE: this module is fine, but used by UnusedImport.roc to uselessly import +Ident : Str diff --git a/cli/tests/known_bad/TypeError.roc b/cli/tests/known_bad/TypeError.roc new file mode 100644 index 0000000000..9a36916ff4 --- /dev/null +++ b/cli/tests/known_bad/TypeError.roc @@ -0,0 +1,11 @@ +app "type-error" + packages { pf: "platform" } + imports [ pf.Stdout.{ line }, pf.Task.{ await } ] + provides [ main ] to pf + +main = + _ <- await (line "a") + _ <- await (line "b") + _ <- await (line "c") + _ <- await (line d) + line "e" diff --git a/cli/tests/known_bad/UnknownGeneratesWith.roc b/cli/tests/known_bad/UnknownGeneratesWith.roc new file mode 100644 index 0000000000..828c6d1a2c --- /dev/null +++ b/cli/tests/known_bad/UnknownGeneratesWith.roc @@ -0,0 +1,4 @@ +hosted UnknownGeneratesWith + exposes [ Effect, after, map, always ] + imports [] + generates Effect with [ after, map, always, foobar ] diff --git a/cli/tests/known_bad/UnusedImport.roc b/cli/tests/known_bad/UnusedImport.roc new file mode 100644 index 0000000000..1968d0fc76 --- /dev/null +++ b/cli/tests/known_bad/UnusedImport.roc @@ -0,0 +1,7 @@ +interface UnusedImport + exposes [ plainText, emText ] + imports [ Symbol.{ Ident } ] + +plainText = \str -> PlainText str + +emText = \str -> EmText str diff --git a/cli/tests/known_bad/platform b/cli/tests/known_bad/platform new file mode 120000 index 0000000000..79724c8631 --- /dev/null +++ b/cli/tests/known_bad/platform @@ -0,0 +1 @@ +../../../examples/cli/platform \ No newline at end of file diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs deleted file mode 100644 index 63c0ff54a4..0000000000 --- a/cli/tests/repl_eval.rs +++ /dev/null @@ -1,596 +0,0 @@ -#[macro_use] -extern crate pretty_assertions; - -#[macro_use] -extern crate indoc; - -#[cfg(test)] -mod repl_eval { - use cli_utils::helpers; - - const ERROR_MESSAGE_START: char = '─'; - - fn expect_success(input: &str, expected: &str) { - let out = helpers::repl_eval(input); - - assert_eq!(&out.stderr, ""); - assert_eq!(&out.stdout, expected); - assert!(out.status.success()); - } - - fn expect_failure(input: &str, expected: &str) { - let out = helpers::repl_eval(input); - - // there may be some other stuff printed (e.g. unification errors) - // so skip till the header of the first error - match out.stdout.find(ERROR_MESSAGE_START) { - Some(index) => { - assert_eq!(&out.stderr, ""); - assert_eq!(&out.stdout[index..], expected); - assert!(out.status.success()); - } - None => { - assert_eq!(&out.stderr, ""); - assert!(out.status.success()); - panic!( - "I expected a failure, but there is no error message in stdout:\n\n{}", - &out.stdout - ); - } - } - } - - #[test] - fn literal_0() { - expect_success("0", "0 : Num *"); - } - - #[test] - fn literal_42() { - expect_success("42", "42 : Num *"); - } - - #[test] - fn literal_0x0() { - expect_success("0x0", "0 : Int *"); - } - - #[test] - fn literal_0x42() { - expect_success("0x42", "66 : Int *"); - } - - #[test] - fn literal_0point0() { - expect_success("0.0", "0 : Float *"); - } - - #[test] - fn literal_4point2() { - expect_success("4.2", "4.2 : Float *"); - } - - #[test] - fn num_addition() { - expect_success("1 + 2", "3 : Num *"); - } - - #[test] - fn int_addition() { - expect_success("0x1 + 2", "3 : I64"); - } - - #[test] - fn float_addition() { - expect_success("1.1 + 2", "3.1 : F64"); - } - - #[test] - fn num_rem() { - expect_success("299 % 10", "Ok 9 : Result (Int *) [ DivByZero ]*"); - } - - #[test] - fn num_floor_division_success() { - expect_success("Num.divFloor 4 3", "Ok 1 : Result (Int *) [ DivByZero ]*"); - } - - #[test] - fn num_floor_division_divby_zero() { - expect_success( - "Num.divFloor 4 0", - "Err DivByZero : Result (Int *) [ DivByZero ]*", - ); - } - - #[test] - fn num_ceil_division_success() { - expect_success("Num.divCeil 4 3", "Ok 2 : Result (Int *) [ DivByZero ]*") - } - - #[test] - fn bool_in_record() { - expect_success("{ x: 1 == 1 }", "{ x: True } : { x : Bool }"); - expect_success( - "{ z: { y: { x: 1 == 1 } } }", - "{ z: { y: { x: True } } } : { z : { y : { x : Bool } } }", - ); - expect_success("{ x: 1 != 1 }", "{ x: False } : { x : Bool }"); - expect_success( - "{ x: 1 == 1, y: 1 != 1 }", - "{ x: True, y: False } : { x : Bool, y : Bool }", - ); - } - - #[test] - fn bool_basic_equality() { - expect_success("1 == 1", "True : Bool"); - expect_success("1 != 1", "False : Bool"); - } - - #[test] - fn arbitrary_tag_unions() { - expect_success("if 1 == 1 then Red else Green", "Red : [ Green, Red ]*"); - expect_success("if 1 != 1 then Red else Green", "Green : [ Green, Red ]*"); - } - - #[test] - fn tag_without_arguments() { - expect_success("True", "True : [ True ]*"); - expect_success("False", "False : [ False ]*"); - } - - #[test] - fn byte_tag_union() { - expect_success( - "if 1 == 1 then Red else if 1 == 1 then Green else Blue", - "Red : [ Blue, Green, Red ]*", - ); - - expect_success( - "{ y: { x: if 1 == 1 then Red else if 1 == 1 then Green else Blue } }", - "{ y: { x: Red } } : { y : { x : [ Blue, Green, Red ]* } }", - ); - } - - #[test] - fn tag_in_record() { - expect_success( - "{ x: Foo 1 2 3, y : 4 }", - "{ x: Foo 1 2 3, y: 4 } : { x : [ Foo (Num *) (Num *) (Num *) ]*, y : Num * }", - ); - expect_success( - "{ x: Foo 1 2 3 }", - "{ x: Foo 1 2 3 } : { x : [ Foo (Num *) (Num *) (Num *) ]* }", - ); - expect_success("{ x: Unit }", "{ x: Unit } : { x : [ Unit ]* }"); - } - - #[test] - fn single_element_tag_union() { - expect_success("True 1", "True 1 : [ True (Num *) ]*"); - expect_success("Foo 1 3.14", "Foo 1 3.14 : [ Foo (Num *) (Float *) ]*"); - } - - #[test] - fn newtype_of_unit() { - expect_success("Foo Bar", "Foo Bar : [ Foo [ Bar ]* ]*"); - } - - #[test] - fn tag_with_arguments() { - expect_success("True 1", "True 1 : [ True (Num *) ]*"); - - expect_success( - "if 1 == 1 then True 3 else False 3.14", - "True 3 : [ False (Float *), True (Num *) ]*", - ) - } - - #[test] - fn literal_empty_str() { - expect_success("\"\"", "\"\" : Str"); - } - - #[test] - fn literal_ascii_str() { - expect_success("\"Hello, World!\"", "\"Hello, World!\" : Str"); - } - - #[test] - fn literal_utf8_str() { - expect_success("\"👩‍👩‍👦‍👦\"", "\"👩‍👩‍👦‍👦\" : Str"); - } - - #[test] - fn str_concat() { - expect_success( - "Str.concat \"Hello, \" \"World!\"", - "\"Hello, World!\" : Str", - ); - } - - #[test] - fn str_count_graphemes() { - expect_success("Str.countGraphemes \"å🤔\"", "2 : Nat"); - } - - #[test] - fn literal_empty_list() { - expect_success("[]", "[] : List *"); - } - - #[test] - fn literal_empty_list_empty_record() { - expect_success("[ {} ]", "[ {} ] : List {}"); - } - - #[test] - fn literal_num_list() { - expect_success("[ 1, 2, 3 ]", "[ 1, 2, 3 ] : List (Num *)"); - } - - #[test] - fn literal_int_list() { - expect_success("[ 0x1, 0x2, 0x3 ]", "[ 1, 2, 3 ] : List (Int *)"); - } - - #[test] - fn literal_float_list() { - expect_success("[ 1.1, 2.2, 3.3 ]", "[ 1.1, 2.2, 3.3 ] : List (Float *)"); - } - - #[test] - fn literal_string_list() { - expect_success(r#"[ "a", "b", "cd" ]"#, r#"[ "a", "b", "cd" ] : List Str"#); - } - - #[test] - fn nested_string_list() { - expect_success( - r#"[ [ [ "a", "b", "cd" ], [ "y", "z" ] ], [ [] ], [] ]"#, - r#"[ [ [ "a", "b", "cd" ], [ "y", "z" ] ], [ [] ], [] ] : List (List (List Str))"#, - ); - } - - #[test] - fn nested_num_list() { - expect_success( - r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ]"#, - r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List (Num *)))"#, - ); - } - - #[test] - fn nested_int_list() { - expect_success( - r#"[ [ [ 4, 3, 2 ], [ 1, 0x0 ] ], [ [] ], [] ]"#, - r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List I64))"#, - ); - } - - #[test] - fn nested_float_list() { - expect_success( - r#"[ [ [ 4, 3, 2 ], [ 1, 0.0 ] ], [ [] ], [] ]"#, - r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List F64))"#, - ); - } - - #[test] - fn num_bitwise_and() { - expect_success("Num.bitwiseAnd 20 20", "20 : Int *"); - - expect_success("Num.bitwiseAnd 25 10", "8 : Int *"); - - expect_success("Num.bitwiseAnd 200 0", "0 : Int *") - } - - #[test] - fn num_bitwise_xor() { - expect_success("Num.bitwiseXor 20 20", "0 : Int *"); - - expect_success("Num.bitwiseXor 15 14", "1 : Int *"); - - expect_success("Num.bitwiseXor 7 15", "8 : Int *"); - - expect_success("Num.bitwiseXor 200 0", "200 : Int *") - } - - #[test] - fn num_add_wrap() { - expect_success("Num.addWrap Num.maxInt 1", "-9223372036854775808 : Int *"); - } - - #[test] - fn num_sub_wrap() { - expect_success("Num.subWrap Num.minInt 1", "9223372036854775807 : Int *"); - } - - #[test] - fn num_mul_wrap() { - expect_success("Num.mulWrap Num.maxInt 2", "-2 : Int *"); - } - - #[test] - fn num_add_checked() { - expect_success("Num.addChecked 1 1", "Ok 2 : Result (Num *) [ Overflow ]*"); - expect_success( - "Num.addChecked Num.maxInt 1", - "Err Overflow : Result I64 [ Overflow ]*", - ); - } - - #[test] - fn num_sub_checked() { - expect_success("Num.subChecked 1 1", "Ok 0 : Result (Num *) [ Overflow ]*"); - expect_success( - "Num.subChecked Num.minInt 1", - "Err Overflow : Result I64 [ Overflow ]*", - ); - } - - #[test] - fn num_mul_checked() { - expect_success( - "Num.mulChecked 20 2", - "Ok 40 : Result (Num *) [ Overflow ]*", - ); - expect_success( - "Num.mulChecked Num.maxInt 2", - "Err Overflow : Result I64 [ Overflow ]*", - ); - } - - #[test] - fn list_concat() { - expect_success( - "List.concat [ 1.1, 2.2 ] [ 3.3, 4.4, 5.5 ]", - "[ 1.1, 2.2, 3.3, 4.4, 5.5 ] : List (Float *)", - ); - } - - #[test] - fn list_contains() { - expect_success("List.contains [] 0", "False : Bool"); - expect_success("List.contains [ 1, 2, 3 ] 2", "True : Bool"); - expect_success("List.contains [ 1, 2, 3 ] 4", "False : Bool"); - } - - #[test] - fn list_sum() { - expect_success("List.sum []", "0 : Num *"); - expect_success("List.sum [ 1, 2, 3 ]", "6 : Num *"); - expect_success("List.sum [ 1.1, 2.2, 3.3 ]", "6.6 : F64"); - } - - #[test] - fn list_first() { - expect_success( - "List.first [ 12, 9, 6, 3 ]", - "Ok 12 : Result (Num *) [ ListWasEmpty ]*", - ); - expect_success( - "List.first []", - "Err ListWasEmpty : Result * [ ListWasEmpty ]*", - ); - } - - #[test] - fn list_last() { - expect_success( - "List.last [ 12, 9, 6, 3 ]", - "Ok 3 : Result (Num *) [ ListWasEmpty ]*", - ); - - expect_success( - "List.last []", - "Err ListWasEmpty : Result * [ ListWasEmpty ]*", - ); - } - - #[test] - fn empty_record() { - expect_success("{}", "{} : {}"); - } - - #[test] - fn basic_1_field_i64_record() { - // Even though this gets unwrapped at runtime, the repl should still - // report it as a record - expect_success("{ foo: 42 }", "{ foo: 42 } : { foo : Num * }"); - } - - #[test] - fn basic_1_field_f64_record() { - // Even though this gets unwrapped at runtime, the repl should still - // report it as a record - expect_success("{ foo: 4.2 }", "{ foo: 4.2 } : { foo : Float * }"); - } - - #[test] - fn nested_1_field_i64_record() { - // Even though this gets unwrapped at runtime, the repl should still - // report it as a record - expect_success( - "{ foo: { bar: { baz: 42 } } }", - "{ foo: { bar: { baz: 42 } } } : { foo : { bar : { baz : Num * } } }", - ); - } - - #[test] - fn nested_1_field_f64_record() { - // Even though this gets unwrapped at runtime, the repl should still - // report it as a record - expect_success( - "{ foo: { bar: { baz: 4.2 } } }", - "{ foo: { bar: { baz: 4.2 } } } : { foo : { bar : { baz : Float * } } }", - ); - } - - #[test] - fn basic_2_field_i64_record() { - expect_success( - "{ foo: 0x4, bar: 0x2 }", - "{ bar: 2, foo: 4 } : { bar : Int *, foo : Int * }", - ); - } - - #[test] - fn basic_2_field_f64_record() { - expect_success( - "{ foo: 4.1, bar: 2.3 }", - "{ bar: 2.3, foo: 4.1 } : { bar : Float *, foo : Float * }", - ); - } - - #[test] - fn basic_2_field_mixed_record() { - expect_success( - "{ foo: 4.1, bar: 2 }", - "{ bar: 2, foo: 4.1 } : { bar : Num *, foo : Float * }", - ); - } - - #[test] - fn basic_3_field_record() { - expect_success( - "{ foo: 4.1, bar: 2, baz: 0x5 }", - "{ bar: 2, baz: 5, foo: 4.1 } : { bar : Num *, baz : Int *, foo : Float * }", - ); - } - - #[test] - fn list_of_1_field_records() { - // Even though these get unwrapped at runtime, the repl should still - // report them as records - expect_success("[ { foo: 42 } ]", "[ { foo: 42 } ] : List { foo : Num * }"); - } - - #[test] - fn list_of_2_field_records() { - expect_success( - "[ { foo: 4.1, bar: 2 } ]", - "[ { bar: 2, foo: 4.1 } ] : List { bar : Num *, foo : Float * }", - ); - } - - #[test] - fn three_element_record() { - // if this tests turns out to fail on 32-bit platforms, look at jit_to_ast_help - expect_success( - "{ a: 1, b: 2, c: 3 }", - "{ a: 1, b: 2, c: 3 } : { a : Num *, b : Num *, c : Num * }", - ); - } - - #[test] - fn four_element_record() { - // if this tests turns out to fail on 32-bit platforms, look at jit_to_ast_help - expect_success( - "{ a: 1, b: 2, c: 3, d: 4 }", - "{ a: 1, b: 2, c: 3, d: 4 } : { a : Num *, b : Num *, c : Num *, d : Num * }", - ); - } - - // #[test] - // fn multiline_string() { - // // If a string contains newlines, format it as a multiline string in the output - // expect_success(r#""\n\nhi!\n\n""#, "\"\"\"\n\nhi!\n\n\"\"\""); - // } - - #[test] - fn list_of_3_field_records() { - expect_success( - "[ { foo: 4.1, bar: 2, baz: 0x3 } ]", - "[ { bar: 2, baz: 3, foo: 4.1 } ] : List { bar : Num *, baz : Int *, foo : Float * }", - ); - } - - #[test] - fn identity_lambda() { - expect_success("\\x -> x", " : a -> a"); - } - - #[test] - fn sum_lambda() { - expect_success("\\x, y -> x + y", " : Num a, Num a -> Num a"); - } - - #[test] - fn stdlib_function() { - expect_success("Num.abs", " : Num a -> Num a"); - } - - #[test] - fn too_few_args() { - expect_failure( - "Num.add 2", - indoc!( - r#" - ── TOO FEW ARGS ──────────────────────────────────────────────────────────────── - - The add function expects 2 arguments, but it got only 1: - - 4│ Num.add 2 - ^^^^^^^ - - Roc does not allow functions to be partially applied. Use a closure to - make partial application explicit. - "# - ), - ); - } - - #[test] - fn type_problem() { - expect_failure( - "1 + \"\"", - indoc!( - r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── - - The 2nd argument to add is not what I expect: - - 4│ 1 + "" - ^^ - - This argument is a string of type: - - Str - - But add needs the 2nd argument to be: - - Num a - "# - ), - ); - } - - #[test] - fn issue_2149() { - expect_success(r#"Str.toI8 "127""#, "Ok 127 : Result I8 [ InvalidNumStr ]*"); - expect_success( - r#"Str.toI8 "128""#, - "Err InvalidNumStr : Result I8 [ InvalidNumStr ]*", - ); - expect_success( - r#"Str.toI16 "32767""#, - "Ok 32767 : Result I16 [ InvalidNumStr ]*", - ); - expect_success( - r#"Str.toI16 "32768""#, - "Err InvalidNumStr : Result I16 [ InvalidNumStr ]*", - ); - } - - // #[test] - // fn parse_problem() { - // // can't find something that won't parse currently - // } - // - // #[test] - // fn mono_problem() { - // // can't produce a mono error (non-exhaustive pattern) yet - // } -} diff --git a/cli_utils/Cargo.lock b/cli_utils/Cargo.lock index 852c98d3fb..f6cf4cf5a9 100644 --- a/cli_utils/Cargo.lock +++ b/cli_utils/Cargo.lock @@ -386,6 +386,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "clipboard-win" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8340083d28acb43451166543b98c838299b7e0863621be53a338adceea0ed" +dependencies = [ + "error-code", + "str-buf", + "winapi", +] + [[package]] name = "cocoa" version = "0.24.0" @@ -480,7 +491,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4423d79fed83ebd9ab81ec21fa97144300a961782158287dc9bf7eddac37ff0b" dependencies = [ - "clipboard-win", + "clipboard-win 3.1.1", "objc", "objc-foundation", "objc_id", @@ -808,9 +819,9 @@ dependencies = [ [[package]] name = "dirs-next" -version = "1.0.2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf36e65a80337bea855cd4ef9b8401ffce06a7baedf2e85ec467b1ac3f6e82b6" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" dependencies = [ "cfg-if 1.0.0", "dirs-sys-next", @@ -898,6 +909,12 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + [[package]] name = "env_logger" version = "0.9.0" @@ -911,12 +928,33 @@ dependencies = [ "termcolor", ] +[[package]] +name = "error-code" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5115567ac25674e0043e472be13d14e537f37ea8aa4bdc4aef0c89add1db1ff" +dependencies = [ + "libc", + "str-buf", +] + [[package]] name = "fake-simd" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +[[package]] +name = "fd-lock" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a16910e685088843d53132b04e0f10a571fdb193224fc589685b3ba1ce4cb03d" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "windows-sys", +] + [[package]] name = "find-crate" version = "0.6.3" @@ -1691,16 +1729,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c44922cb3dbb1c70b5e5f443d63b64363a898564d739ba5198e3a9138442868d" [[package]] -name = "nix" -version = "0.17.0" +name = "nibble_vec" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" dependencies = [ - "bitflags", - "cc", - "cfg-if 0.1.10", - "libc", - "void", + "smallvec", ] [[package]] @@ -1740,6 +1774,19 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nix" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", + "memoffset", +] + [[package]] name = "nom" version = "7.1.0" @@ -2246,6 +2293,16 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + [[package]] name = "rand" version = "0.8.4" @@ -2418,12 +2475,13 @@ dependencies = [ "roc_builtins", "roc_can", "roc_collections", + "roc_error_macros", "roc_load", "roc_module", "roc_parse", "roc_problem", "roc_region", - "roc_reporting", + "roc_target", "roc_types", "roc_unify", "snafu", @@ -2453,6 +2511,7 @@ dependencies = [ "roc_reporting", "roc_solve", "roc_std", + "roc_target", "roc_types", "roc_unify", "serde_json", @@ -2467,6 +2526,7 @@ dependencies = [ "roc_collections", "roc_module", "roc_region", + "roc_target", "roc_types", ] @@ -2492,31 +2552,24 @@ dependencies = [ "bumpalo", "clap 3.0.0-beta.5", "const_format", - "inkwell 0.1.0", - "libloading 0.7.1", "mimalloc", "roc_build", "roc_builtins", "roc_can", "roc_collections", - "roc_constrain", "roc_docs", "roc_editor", + "roc_error_macros", "roc_fmt", - "roc_gen_llvm", "roc_linker", "roc_load", "roc_module", "roc_mono", "roc_parse", - "roc_problem", "roc_region", + "roc_repl_cli", "roc_reporting", - "roc_solve", - "roc_types", - "roc_unify", - "rustyline", - "rustyline-derive", + "roc_target", "target-lexicon", "tempfile", ] @@ -2574,6 +2627,7 @@ dependencies = [ "roc_module", "roc_parse", "roc_region", + "roc_target", "roc_types", "snafu", ] @@ -2623,6 +2677,10 @@ dependencies = [ "winit", ] +[[package]] +name = "roc_error_macros" +version = "0.1.0" + [[package]] name = "roc_fmt" version = "0.1.0" @@ -2643,12 +2701,13 @@ dependencies = [ "packed_struct", "roc_builtins", "roc_collections", + "roc_error_macros", "roc_module", "roc_mono", "roc_problem", "roc_region", - "roc_reporting", "roc_solve", + "roc_target", "roc_types", "roc_unify", "target-lexicon", @@ -2663,10 +2722,11 @@ dependencies = [ "morphic_lib", "roc_builtins", "roc_collections", + "roc_error_macros", "roc_module", "roc_mono", - "roc_reporting", "roc_std", + "roc_target", "target-lexicon", ] @@ -2677,10 +2737,11 @@ dependencies = [ "bumpalo", "roc_builtins", "roc_collections", + "roc_error_macros", "roc_module", "roc_mono", - "roc_reporting", "roc_std", + "roc_target", ] [[package]] @@ -2725,6 +2786,7 @@ dependencies = [ "roc_region", "roc_reporting", "roc_solve", + "roc_target", "roc_types", "roc_unify", "ven_pretty", @@ -2758,6 +2820,7 @@ dependencies = [ "roc_region", "roc_solve", "roc_std", + "roc_target", "roc_types", "roc_unify", "static_assertions", @@ -2789,6 +2852,50 @@ dependencies = [ [[package]] name = "roc_region" version = "0.1.0" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "roc_repl_cli" +version = "0.1.0" +dependencies = [ + "bumpalo", + "const_format", + "inkwell 0.1.0", + "libloading 0.7.1", + "roc_build", + "roc_collections", + "roc_gen_llvm", + "roc_load", + "roc_mono", + "roc_parse", + "roc_repl_eval", + "roc_target", + "roc_types", + "rustyline", + "rustyline-derive", + "target-lexicon", +] + +[[package]] +name = "roc_repl_eval" +version = "0.1.0" +dependencies = [ + "bumpalo", + "roc_builtins", + "roc_can", + "roc_collections", + "roc_fmt", + "roc_load", + "roc_module", + "roc_mono", + "roc_parse", + "roc_region", + "roc_reporting", + "roc_target", + "roc_types", +] [[package]] name = "roc_reporting" @@ -2826,6 +2933,13 @@ dependencies = [ name = "roc_std" version = "0.1.0" +[[package]] +name = "roc_target" +version = "0.1.0" +dependencies = [ + "target-lexicon", +] + [[package]] name = "roc_types" version = "0.1.0" @@ -2887,16 +3001,21 @@ dependencies = [ [[package]] name = "rustyline" -version = "6.2.0" -source = "git+https://github.com/rtfeldman/rustyline?tag=prompt-fix#a6b8a20d2bf5c3793d7367848be2f4afec2f0d99" +version = "9.1.1" +source = "git+https://github.com/rtfeldman/rustyline?tag=v9.1.1#7053ae0fe0ee710d38ed5845dd979113382994dc" dependencies = [ - "cfg-if 0.1.10", + "bitflags", + "cfg-if 1.0.0", + "clipboard-win 4.2.2", "dirs-next", + "fd-lock", "libc", "log", "memchr", - "nix 0.17.0", + "nix 0.23.1", + "radix_trie", "scopeguard", + "smallvec", "unicode-segmentation", "unicode-width", "utf8parse", @@ -2905,8 +3024,8 @@ dependencies = [ [[package]] name = "rustyline-derive" -version = "0.3.1" -source = "git+https://github.com/rtfeldman/rustyline?tag=prompt-fix#a6b8a20d2bf5c3793d7367848be2f4afec2f0d99" +version = "0.6.0" +source = "git+https://github.com/rtfeldman/rustyline?tag=v9.1.1#7053ae0fe0ee710d38ed5845dd979113382994dc" dependencies = [ "quote", "syn", @@ -3175,6 +3294,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "str-buf" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d44a3643b4ff9caf57abcee9c2c621d6c03d9135e0d8b589bd9afb5992cb176a" + [[package]] name = "strip-ansi-escapes" version = "0.1.1" @@ -3418,12 +3543,6 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" -[[package]] -name = "void" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" - [[package]] name = "vte" version = "0.10.1" @@ -3816,6 +3935,49 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82ca39602d5cbfa692c4b67e3bcbb2751477355141c1ed434c94da4186836ff6" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52695a41e536859d5308cc613b4a022261a274390b25bd29dfff4bf08505f3c2" + +[[package]] +name = "windows_i686_gnu" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f54725ac23affef038fecb177de6c9bf065787c2f432f79e3c373da92f3e1d8a" + +[[package]] +name = "windows_i686_msvc" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d5158a43cc43623c0729d1ad6647e62fa384a3d135fd15108d37c683461f64" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc31f409f565611535130cfe7ee8e6655d3fa99c1c61013981e491921b5ce954" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f2b8c7cbd3bfdddd9ab98769f9746a7fad1bca236554cd032b78d768bc0e89f" + [[package]] name = "winit" version = "0.25.0" diff --git a/cli_utils/src/helpers.rs b/cli_utils/src/helpers.rs index 11a6a404f3..e83556176e 100644 --- a/cli_utils/src/helpers.rs +++ b/cli_utils/src/helpers.rs @@ -4,7 +4,6 @@ extern crate roc_load; extern crate roc_module; extern crate tempfile; -use roc_cli::repl::{INSTRUCTIONS, WELCOME_MESSAGE}; use serde::Deserialize; use serde_xml_rs::from_str; use std::env; @@ -32,7 +31,8 @@ pub fn path_to_roc_binary() -> PathBuf { .or_else(|| { env::current_exe().ok().map(|mut path| { path.pop(); - if path.ends_with("deps") { path.pop(); + if path.ends_with("deps") { + path.pop(); } path }) @@ -294,86 +294,14 @@ pub fn fixture_file(dir_name: &str, file_name: &str) -> PathBuf { } #[allow(dead_code)] -pub fn repl_eval(input: &str) -> Out { - let mut cmd = Command::new(path_to_roc_binary()); +pub fn known_bad_file(file_name: &str) -> PathBuf { + let mut path = root_dir(); - cmd.arg("repl"); + // Descend into cli/tests/known_bad/{file_name} + path.push("cli"); + path.push("tests"); + path.push("known_bad"); + path.push(file_name); - let mut child = cmd - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() - .expect("failed to execute compiled `roc` binary in CLI test"); - - { - let stdin = child.stdin.as_mut().expect("Failed to open stdin"); - - // Send the input expression - stdin - .write_all(input.as_bytes()) - .expect("Failed to write input to stdin"); - - // Evaluate the expression - stdin - .write_all(b"\n") - .expect("Failed to write newline to stdin"); - - // Gracefully exit the repl - stdin - .write_all(b":exit\n") - .expect("Failed to write :exit to stdin"); - } - - let output = child - .wait_with_output() - .expect("Error waiting for REPL child process to exit."); - - // Remove the initial instructions from the output. - - let expected_instructions = format!("{}{}", WELCOME_MESSAGE, INSTRUCTIONS); - let stdout = String::from_utf8(output.stdout).unwrap(); - - assert!( - stdout.starts_with(&expected_instructions), - "Unexpected repl output: {}", - stdout - ); - - let (_, answer) = stdout.split_at(expected_instructions.len()); - let answer = if answer.is_empty() { - // The repl crashed before completing the evaluation. - // This is most likely due to a segfault. - if output.status.to_string() == "signal: 11" { - panic!( - "repl segfaulted during the test. Stderr was {:?}", - String::from_utf8(output.stderr).unwrap() - ); - } else { - panic!("repl exited unexpectedly before finishing evaluation. Exit status was {:?} and stderr was {:?}", output.status, String::from_utf8(output.stderr).unwrap()); - } - } else { - let expected_after_answer = "\n".to_string(); - - assert!( - answer.ends_with(&expected_after_answer), - "Unexpected repl output after answer: {}", - answer - ); - - // Use [1..] to trim the leading '\n' - // and (len - 1) to trim the trailing '\n' - let (answer, _) = answer[1..].split_at(answer.len() - expected_after_answer.len() - 1); - - // Remove ANSI escape codes from the answer - for example: - // - // Before: "42 \u{1b}[35m:\u{1b}[0m Num *" - // After: "42 : Num *" - strip_ansi_escapes::strip(answer).unwrap() - }; - - Out { - stdout: String::from_utf8(answer).unwrap(), - stderr: String::from_utf8(output.stderr).unwrap(), - status: output.status, - } + path } diff --git a/compiler/README.md b/compiler/README.md index 77e55b5762..e57984562d 100644 --- a/compiler/README.md +++ b/compiler/README.md @@ -147,7 +147,7 @@ The compiler contains a lot of code! If you're new to the project it can be hard After you get into the details, you'll discover that some parts of the compiler have more than one entry point. And things can be interwoven together in subtle and complex ways, for reasons to do with performance, edge case handling, etc. But if this is "day one" for you, and you're just trying to get familiar with things, this should be "good enough". -The compiler is invoked from the CLI via `build_file` in cli/src/build.rs +The compiler is invoked from the CLI via `build_file` in cli/src/build.rs | Phase | Entry point / main functions | | ------------------------------------- | ------------------------------------------------ | @@ -164,3 +164,24 @@ The compiler is invoked from the CLI via `build_file` in cli/src/build.rs | Code gen (unoptimized but fast, Wasm) | gen_wasm/src/lib.rs: build_module | For a more detailed understanding of the compilation phases, see the `Phase`, `BuildTask`, and `Msg` enums in `load/src/file.rs`. + +## Debugging intermediate representations + +### The mono IR + +If you observe a miscomplication, you may first want to check the generated mono +IR for your code - maybe there was a problem during specialization or layout +generation. One way to do this is to add a test to `test_mono/src/tests.rs` +and run the tests with `cargo test -p test_mono`; this will write the mono +IR to a file. + +You may also want to set some or all of the following environment variables: + +- `PRINT_IR_AFTER_SPECIALIZATION=1` prints the mono IR after function + specialization to stdout +- `PRINT_IR_AFTER_RESET_REUSE=1` prints the mono IR after insertion of + reset/reuse isntructions to stdout +- `PRINT_IR_AFTER_REFCOUNT=1` prints the mono IR after insertion of reference + counting instructions to stdout +- `PRETTY_PRINT_IR_SYMBOLS=1` instructs the pretty printer to dump all the + information it knows about the mono IR whenever it is printed diff --git a/compiler/build/Cargo.toml b/compiler/build/Cargo.toml index 80aaa3dd00..ab61cf2637 100644 --- a/compiler/build/Cargo.toml +++ b/compiler/build/Cargo.toml @@ -19,6 +19,7 @@ roc_unify = { path = "../unify" } roc_solve = { path = "../solve" } roc_mono = { path = "../mono" } roc_load = { path = "../load" } +roc_target = { path = "../roc_target" } roc_gen_llvm = { path = "../gen_llvm", optional = true } roc_gen_wasm = { path = "../gen_wasm", optional = true } roc_gen_dev = { path = "../gen_dev", default-features = false } diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index d1cb5b617d..b3d5e52062 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -753,49 +753,52 @@ fn link_linux( // NOTE: order of arguments to `ld` matters here! // The `-l` flags should go after the `.o` arguments - Ok(( - Command::new("ld") - // Don't allow LD_ env vars to affect this - .env_clear() - .env("PATH", &env_path) - // Keep NIX_ env vars - .envs( - env::vars() - .filter(|&(ref k, _)| k.starts_with("NIX_")) - .collect::>(), - ) - .args(&[ - "--gc-sections", - "--eh-frame-hdr", - "-A", - arch_str(target), - "-pie", - libcrt_path.join("crti.o").to_str().unwrap(), - libcrt_path.join("crtn.o").to_str().unwrap(), - ]) - .args(&base_args) - .args(&["-dynamic-linker", ld_linux]) - .args(input_paths) - // ld.lld requires this argument, and does not accept --arch - // .args(&["-L/usr/lib/x86_64-linux-gnu"]) - .args(&[ - // Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496365925 - // for discussion and further references - "-lc", - "-lm", - "-lpthread", - "-ldl", - "-lrt", - "-lutil", - "-lc_nonshared", - libgcc_path.to_str().unwrap(), - // Output - "-o", - output_path.as_path().to_str().unwrap(), // app (or app.so or app.dylib etc.) - ]) - .spawn()?, - output_path, - )) + + let mut command = Command::new("ld"); + + command + // Don't allow LD_ env vars to affect this + .env_clear() + .env("PATH", &env_path) + // Keep NIX_ env vars + .envs( + env::vars() + .filter(|&(ref k, _)| k.starts_with("NIX_")) + .collect::>(), + ) + .args(&[ + "--gc-sections", + "--eh-frame-hdr", + "-A", + arch_str(target), + "-pie", + libcrt_path.join("crti.o").to_str().unwrap(), + libcrt_path.join("crtn.o").to_str().unwrap(), + ]) + .args(&base_args) + .args(&["-dynamic-linker", ld_linux]) + .args(input_paths) + // ld.lld requires this argument, and does not accept --arch + // .args(&["-L/usr/lib/x86_64-linux-gnu"]) + .args(&[ + // Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496365925 + // for discussion and further references + "-lc", + "-lm", + "-lpthread", + "-ldl", + "-lrt", + "-lutil", + "-lc_nonshared", + libgcc_path.to_str().unwrap(), + // Output + "-o", + output_path.as_path().to_str().unwrap(), // app (or app.so or app.dylib etc.) + ]); + + let output = command.spawn()?; + + Ok((output, output_path)) } fn link_macos( @@ -853,11 +856,29 @@ fn link_macos( "-lSystem", "-lresolv", "-lpthread", + // This `-F PATH` flag is needed for `-framework` flags to work + "-F", + "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/", + // These frameworks are needed for GUI examples to work + "-framework", + "AudioUnit", + "-framework", + "Cocoa", + "-framework", + "CoreAudio", + "-framework", + "CoreVideo", + "-framework", + "IOKit", + "-framework", + "Metal", + "-framework", + "QuartzCore", // "-lrt", // TODO shouldn't we need this? // "-lc_nonshared", // TODO shouldn't we need this? // "-lgcc", // TODO will eventually need compiler_rt from gcc or something - see https://github.com/rtfeldman/roc/pull/554#discussion_r496370840 - // "-framework", // Uncomment this line & the following ro run the `rand` crate in examples/cli - // "Security", + "-framework", + "Security", // Output "-o", output_path.to_str().unwrap(), // app diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index 8bcbb60893..252a75c050 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -5,6 +5,7 @@ pub use roc_gen_llvm::llvm::build::FunctionIterator; use roc_load::file::{LoadedModule, MonomorphizedModule}; 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; @@ -30,7 +31,6 @@ const LLVM_VERSION: &str = "12"; pub fn report_problems_monomorphized(loaded: &mut MonomorphizedModule) -> usize { report_problems_help( loaded.total_problems(), - &loaded.header_sources, &loaded.sources, &loaded.interns, &mut loaded.can_problems, @@ -42,7 +42,6 @@ pub fn report_problems_monomorphized(loaded: &mut MonomorphizedModule) -> usize pub fn report_problems_typechecked(loaded: &mut LoadedModule) -> usize { report_problems_help( loaded.total_problems(), - &loaded.header_sources, &loaded.sources, &loaded.interns, &mut loaded.can_problems, @@ -53,7 +52,6 @@ pub fn report_problems_typechecked(loaded: &mut LoadedModule) -> usize { fn report_problems_help( total_problems: usize, - header_sources: &MutMap)>, sources: &MutMap)>, interns: &Interns, can_problems: &mut MutMap>, @@ -74,12 +72,9 @@ fn report_problems_help( for (home, (module_path, src)) in sources.iter() { let mut src_lines: Vec<&str> = Vec::new(); - if let Some((_, header_src)) = header_sources.get(home) { - src_lines.extend(header_src.split('\n')); - src_lines.extend(src.split('\n').skip(1)); - } else { - src_lines.extend(src.split('\n')); - } + src_lines.extend(src.split('\n')); + + let lines = LineInfo::new(&src_lines.join("\n")); // Report parsing and canonicalization problems let alloc = RocDocAllocator::new(&src_lines, *home, interns); @@ -87,7 +82,7 @@ fn report_problems_help( let problems = can_problems.remove(home).unwrap_or_default(); for problem in problems.into_iter() { - let report = can_problem(&alloc, module_path.clone(), problem); + let report = can_problem(&alloc, &lines, module_path.clone(), problem); let severity = report.severity; let mut buf = String::new(); @@ -106,7 +101,7 @@ fn report_problems_help( let problems = type_problems.remove(home).unwrap_or_default(); for problem in problems { - if let Some(report) = type_problem(&alloc, module_path.clone(), problem) { + if let Some(report) = type_problem(&alloc, &lines, module_path.clone(), problem) { let severity = report.severity; let mut buf = String::new(); @@ -126,7 +121,7 @@ fn report_problems_help( let problems = mono_problems.remove(home).unwrap_or_default(); for problem in problems { - let report = mono_problem(&alloc, module_path.clone(), problem); + let report = mono_problem(&alloc, &lines, module_path.clone(), problem); let severity = report.severity; let mut buf = String::new(); @@ -240,7 +235,7 @@ pub fn gen_from_mono_module_llvm( let code_gen_start = SystemTime::now(); // Generate the binary - let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; + let target_info = roc_target::TargetInfo::from(target); let context = Context::create(); let module = arena.alloc(module_from_builtins(target, &context, "app")); @@ -291,11 +286,11 @@ pub fn gen_from_mono_module_llvm( context: &context, interns: loaded.interns, module, - ptr_bytes, + target_info, // in gen_tests, the compiler provides roc_panic // and sets up the setjump/longjump exception handling is_gen_test: false, - exposed_to_host: loaded.exposed_to_host.keys().copied().collect(), + exposed_to_host: loaded.exposed_to_host.values.keys().copied().collect(), }; roc_gen_llvm::llvm::build::build_procedures( @@ -500,6 +495,7 @@ fn gen_from_mono_module_dev_wasm32( let exposed_to_host = loaded .exposed_to_host + .values .keys() .copied() .collect::>(); @@ -510,7 +506,18 @@ fn gen_from_mono_module_dev_wasm32( exposed_to_host, }; - let bytes = roc_gen_wasm::build_module(&env, &mut interns, procedures).unwrap(); + let platform_and_builtins_object_file_bytes: &[u8] = if true { + todo!("The WebAssembly dev backend is a work in progress. Coming soon!") + } else { + &[] // This `if` gets rid of "unreachable code" warnings. When we're ready to use it, we'll notice! + }; + + let bytes = roc_gen_wasm::build_module( + &env, + &mut interns, + platform_and_builtins_object_file_bytes, + procedures, + ); std::fs::write(&app_o_file, &bytes).expect("failed to write object to file"); @@ -537,7 +544,7 @@ fn gen_from_mono_module_dev_assembly( let env = roc_gen_dev::Env { arena, module_id, - exposed_to_host: exposed_to_host.keys().copied().collect(), + exposed_to_host: exposed_to_host.values.keys().copied().collect(), lazy_literals, generate_allocators, }; diff --git a/compiler/builtins/Cargo.toml b/compiler/builtins/Cargo.toml index 9321839c93..91879456d7 100644 --- a/compiler/builtins/Cargo.toml +++ b/compiler/builtins/Cargo.toml @@ -10,3 +10,4 @@ roc_collections = { path = "../collections" } roc_region = { path = "../region" } roc_module = { path = "../module" } roc_types = { path = "../types" } +roc_target = { path = "../roc_target" } diff --git a/compiler/builtins/README.md b/compiler/builtins/README.md index a83e924cf7..ceaf3007e3 100644 --- a/compiler/builtins/README.md +++ b/compiler/builtins/README.md @@ -4,7 +4,7 @@ Builtins are the functions and modules that are implicitly imported into every m ### module/src/symbol.rs -Towards the bottom of `symbol.rs` there is a `define_builtins!` macro being used that takes many modules and function names. The first level (`List`, `Int` ..) is the module name, and the second level is the function or value name (`reverse`, `mod` ..). If you wanted to add a `Int` function called `addTwo` go to `2 Int: "Int" => {` and inside that case add to the bottom `38 INT_ADD_TWO: "addTwo"` (assuming there are 37 existing ones). +Towards the bottom of `symbol.rs` there is a `define_builtins!` macro being used that takes many modules and function names. The first level (`List`, `Int` ..) is the module name, and the second level is the function or value name (`reverse`, `mod` ..). If you wanted to add a `Int` function called `addTwo` go to `2 Int: "Int" => {` and inside that case add to the bottom `38 INT_ADD_TWO: "addTwo"` (assuming there are 37 existing ones). Some of these have `#` inside their name (`first#list`, `#lt` ..). This is a trick we are doing to hide implementation details from Roc programmers. To a Roc programmer, a name with `#` in it is invalid, because `#` means everything after it is parsed to a comment. We are constructing these functions manually, so we are circumventing the parsing step and dont have such restrictions. We get to make functions and values with `#` which as a consequence are not accessible to Roc programmers. Roc programmers simply cannot reference them. @@ -80,7 +80,7 @@ fn atan() { ``` But replace `Num.atan` and the type signature with the new builtin. -### gen/test/*.rs +### test_gen/test/*.rs In this directory, there are a couple files like `gen_num.rs`, `gen_str.rs`, etc. For the `Str` module builtins, put the test in `gen_str.rs`, etc. Find the one for the new builtin, and add a test like: ``` #[test] diff --git a/compiler/builtins/bitcode/README.md b/compiler/builtins/bitcode/README.md index 1d9fbb225b..e7a03e341f 100644 --- a/compiler/builtins/bitcode/README.md +++ b/compiler/builtins/bitcode/README.md @@ -27,7 +27,7 @@ There will be two directories like `roc_builtins-[some random characters]`, look `out` directory as a child. > 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 +> 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` ## Calling bitcode functions diff --git a/compiler/builtins/bitcode/build.zig b/compiler/builtins/bitcode/build.zig index 3e2ca38c25..e7db5bf3a5 100644 --- a/compiler/builtins/bitcode/build.zig +++ b/compiler/builtins/bitcode/build.zig @@ -26,7 +26,7 @@ pub fn build(b: *Builder) void { .default_target = CrossTarget{ .cpu_model = .baseline, // TODO allow for native target for maximum speed - } + }, }); const i386_target = makeI386Target(); const wasm32_target = makeWasm32Target(); diff --git a/compiler/builtins/bitcode/src/dict.zig b/compiler/builtins/bitcode/src/dict.zig index 3812fda619..d45008fbd2 100644 --- a/compiler/builtins/bitcode/src/dict.zig +++ b/compiler/builtins/bitcode/src/dict.zig @@ -750,7 +750,8 @@ pub fn dictWalk( const alignment_u32 = alignment.toU32(); // allocate space to write the result of the stepper into // experimentally aliasing the accum and output pointers is not a good idea - const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment_u32); + // TODO handle alloc failing! + const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment_u32) orelse unreachable; var b1 = output orelse unreachable; var b2 = bytes_ptr; diff --git a/compiler/builtins/bitcode/src/expect.zig b/compiler/builtins/bitcode/src/expect.zig new file mode 100644 index 0000000000..3734b578ce --- /dev/null +++ b/compiler/builtins/bitcode/src/expect.zig @@ -0,0 +1,162 @@ +const std = @import("std"); +const utils = @import("utils.zig"); +const CSlice = utils.CSlice; +const always_inline = std.builtin.CallOptions.Modifier.always_inline; + +const Failure = struct { + start_line: u32, + end_line: u32, + start_col: u16, + end_col: u16, +}; + +// BEGIN FAILURES GLOBALS /////////////////// +var failures_mutex = std.Thread.Mutex{}; +var failures: [*]Failure = undefined; +var failure_length: usize = 0; +var failure_capacity: usize = 0; +// END FAILURES GLOBALS ///////////////////// + +pub fn expectFailed( + start_line: u32, + end_line: u32, + start_col: u16, + end_col: u16, +) void { + const new_failure = Failure{ .start_line = start_line, .end_line = end_line, .start_col = start_col, .end_col = end_col }; + + // Lock the failures mutex before reading from any of the failures globals, + // and then release the lock once we're done modifying things. + + // TODO FOR ZIG 0.9: this API changed in https://github.com/ziglang/zig/commit/008b0ec5e58fc7e31f3b989868a7d1ea4df3f41d + // to this: https://github.com/ziglang/zig/blob/c710d5eefe3f83226f1651947239730e77af43cb/lib/std/Thread/Mutex.zig + // + // ...so just use these two lines of code instead of the non-commented-out ones to make this work in Zig 0.9: + // + // failures_mutex.lock(); + // defer failures_mutex.release(); + // + // 👆 👆 👆 IF UPGRADING TO ZIG 0.9, LOOK HERE! 👆 👆 👆 + const held = failures_mutex.acquire(); + defer held.release(); + + // If we don't have enough capacity to add a failure, allocate a new failures pointer. + if (failure_length >= failure_capacity) { + if (failure_capacity > 0) { + // We already had previous failures allocated, so try to realloc in order + // to grow the size in-place without having to memcpy bytes over. + const old_pointer = failures; + const old_bytes = failure_capacity * @sizeOf(Failure); + + failure_capacity *= 2; + + const new_bytes = failure_capacity * @sizeOf(Failure); + const failures_u8 = @ptrCast([*]u8, @alignCast(@alignOf(Failure), failures)); + const raw_pointer = utils.realloc(failures_u8, new_bytes, old_bytes, @alignOf(Failure)); + + failures = @ptrCast([*]Failure, @alignCast(@alignOf(Failure), raw_pointer)); + + // If realloc wasn't able to expand in-place (that is, it returned a different pointer), + // then copy the data into the new pointer and dealloc the old one. + if (failures != old_pointer) { + const old_pointer_u8 = @ptrCast([*]u8, old_pointer); + utils.memcpy(@ptrCast([*]u8, failures), old_pointer_u8, old_bytes); + utils.dealloc(old_pointer_u8, @alignOf(Failure)); + } + } else { + // We've never had any failures before, so allocate the failures for the first time. + failure_capacity = 10; + + const raw_pointer = utils.alloc(failure_capacity * @sizeOf(Failure), @alignOf(Failure)); + + failures = @ptrCast([*]Failure, @alignCast(@alignOf(Failure), raw_pointer)); + } + } + + failures[failure_length] = new_failure; + failure_length += 1; +} + +pub fn expectFailedC( + start_line: u32, + end_line: u32, + start_col: u16, + end_col: u16, +) callconv(.C) void { + return @call(.{ .modifier = always_inline }, expectFailed, .{ start_line, end_line, start_col, end_col }); +} + +pub fn getExpectFailures() []Failure { + // TODO FOR ZIG 0.9: this API changed in https://github.com/ziglang/zig/commit/008b0ec5e58fc7e31f3b989868a7d1ea4df3f41d + // to this: https://github.com/ziglang/zig/blob/c710d5eefe3f83226f1651947239730e77af43cb/lib/std/Thread/Mutex.zig + // + // ...so just use these two lines of code instead of the non-commented-out ones to make this work in Zig 0.9: + // + // failures_mutex.lock(); + // defer failures_mutex.release(); + // + // 👆 👆 👆 IF UPGRADING TO ZIG 0.9, LOOK HERE! 👆 👆 👆 + const held = failures_mutex.acquire(); + defer held.release(); + + if (failure_length > 0) { + // defensively clone failures, in case someone modifies the originals after the mutex has been released. + const num_bytes = failure_length * @sizeOf(Failure); + // TODO handle the possibility of alloc failing + const raw_clones = utils.alloc(num_bytes, @alignOf(Failure)) orelse unreachable; + + utils.memcpy(raw_clones, @ptrCast([*]u8, failures), num_bytes); + + const clones = @ptrCast([*]Failure, @alignCast(@alignOf(Failure), raw_clones)); + + return clones[0..failure_length]; + } else { + return failures[0..0]; + } +} + +pub fn getExpectFailuresC() callconv(.C) CSlice { + var bytes = @ptrCast(*c_void, failures); + + return .{ .pointer = bytes, .len = failure_length }; +} + +pub fn deinitFailures() void { + // TODO FOR ZIG 0.9: this API changed in https://github.com/ziglang/zig/commit/008b0ec5e58fc7e31f3b989868a7d1ea4df3f41d + // to this: https://github.com/ziglang/zig/blob/c710d5eefe3f83226f1651947239730e77af43cb/lib/std/Thread/Mutex.zig + // + // ...so just use these two lines of code instead of the non-commented-out ones to make this work in Zig 0.9: + // + // failures_mutex.lock(); + // defer failures_mutex.release(); + // + // 👆 👆 👆 IF UPGRADING TO ZIG 0.9, LOOK HERE! 👆 👆 👆 + const held = failures_mutex.acquire(); + defer held.release(); + + utils.dealloc(@ptrCast([*]u8, failures), @alignOf(Failure)); + failure_length = 0; +} + +pub fn deinitFailuresC() callconv(.C) void { + return @call(.{ .modifier = always_inline }, deinitFailures, .{}); +} + +test "expectFailure does something" { + defer deinitFailures(); + + var fails = getExpectFailures(); + try std.testing.expectEqual(fails.len, 0); + + expectFailed(1, 2, 3, 4); + + fails = getExpectFailures(); + try std.testing.expectEqual(fails.len, 1); + utils.dealloc(@ptrCast([*]u8, fails.ptr), @alignOf([*]Failure)); + + const what_it_should_look_like = Failure{ .start_line = 1, .end_line = 2, .start_col = 3, .end_col = 4 }; + + fails = getExpectFailures(); + try std.testing.expectEqual(fails[0], what_it_should_look_like); + utils.dealloc(@ptrCast([*]u8, fails.ptr), @alignOf([*]Failure)); +} diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index 62d567bbcd..e94941f4ed 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -550,7 +550,8 @@ pub fn listKeepResult( var output = RocList.allocate(alignment, list.len(), list.len() * after_width); const target_ptr = output.bytes orelse unreachable; - var temporary = @ptrCast([*]u8, utils.alloc(result_width, alignment)); + // TODO handle alloc failing! + var temporary = utils.alloc(result_width, alignment) orelse unreachable; if (data_is_owned) { inc_n_data(data, size); @@ -614,7 +615,8 @@ pub fn listWalk( inc_n_data(data, list.len()); } - const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment); + // TODO handle alloc failing! + const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment) orelse unreachable; var b1 = output orelse unreachable; var b2 = bytes_ptr; @@ -660,7 +662,8 @@ pub fn listWalkBackwards( inc_n_data(data, list.len()); } - const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment); + // TODO handle alloc failing! + const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment) orelse unreachable; var b1 = output orelse unreachable; var b2 = bytes_ptr; @@ -708,7 +711,8 @@ pub fn listWalkUntil( return; } - const bytes_ptr: [*]u8 = utils.alloc(continue_stop_width, alignment); + // TODO handle alloc failing! + const bytes_ptr: [*]u8 = utils.alloc(continue_stop_width, alignment) orelse unreachable; // NOTE: assumes data bytes are the first bytes in a tag @memcpy(bytes_ptr, accum orelse unreachable, accum_width); diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index c925054a75..ce1cc10757 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -1,6 +1,7 @@ const std = @import("std"); const math = std.math; const utils = @import("utils.zig"); +const expect = @import("expect.zig"); const ROC_BUILTINS = "roc_builtins"; const NUM = "num"; @@ -141,12 +142,14 @@ comptime { } // Utils - comptime { exportUtilsFn(utils.test_panic, "test_panic"); exportUtilsFn(utils.increfC, "incref"); exportUtilsFn(utils.decrefC, "decref"); exportUtilsFn(utils.decrefCheckNullC, "decref_check_null"); + exportExpectFn(expect.expectFailedC, "expect_failed"); + exportExpectFn(expect.getExpectFailuresC, "get_expect_failures"); + exportExpectFn(expect.deinitFailuresC, "deinit_failures"); @export(utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak }); } @@ -175,6 +178,10 @@ fn exportUtilsFn(comptime func: anytype, comptime func_name: []const u8) void { exportBuiltinFn(func, "utils." ++ func_name); } +fn exportExpectFn(comptime func: anytype, comptime func_name: []const u8) void { + exportBuiltinFn(func, "expect." ++ func_name); +} + // Custom panic function, as builtin Zig version errors during LLVM verification pub fn panic(message: []const u8, stacktrace: ?*std.builtin.StackTrace) noreturn { const builtin = @import("builtin"); diff --git a/compiler/builtins/bitcode/src/utils.zig b/compiler/builtins/bitcode/src/utils.zig index 3655d5cfdf..5b14e3623f 100644 --- a/compiler/builtins/bitcode/src/utils.zig +++ b/compiler/builtins/bitcode/src/utils.zig @@ -18,14 +18,18 @@ extern fn roc_dealloc(c_ptr: *anyopaque, alignment: u32) callconv(.C) void; // Signals to the host that the program has panicked extern fn roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void; +// should work just like libc memcpy (we can't assume libc is present) +extern fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void; + comptime { const builtin = @import("builtin"); - // During tetsts, use the testing allocators to satisfy these functions. + // During tests, use the testing allocators to satisfy these functions. if (builtin.is_test) { @export(testing_roc_alloc, .{ .name = "roc_alloc", .linkage = .Strong }); @export(testing_roc_realloc, .{ .name = "roc_realloc", .linkage = .Strong }); @export(testing_roc_dealloc, .{ .name = "roc_dealloc", .linkage = .Strong }); @export(testing_roc_panic, .{ .name = "roc_panic", .linkage = .Strong }); + @export(testing_roc_memcpy, .{ .name = "roc_memcpy", .linkage = .Strong }); } } @@ -53,8 +57,16 @@ fn testing_roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void { @panic("Roc panicked"); } -pub fn alloc(size: usize, alignment: u32) [*]u8 { - return @ptrCast([*]u8, @call(.{ .modifier = always_inline }, roc_alloc, .{ size, alignment })); +fn testing_roc_memcpy(dest: *c_void, src: *c_void, bytes: usize) callconv(.C) ?*c_void { + const zig_dest = @ptrCast([*]u8, dest); + const zig_src = @ptrCast([*]u8, src); + + @memcpy(zig_dest, zig_src, bytes); + return dest; +} + +pub fn alloc(size: usize, alignment: u32) ?[*]u8 { + return @ptrCast(?[*]u8, @call(.{ .modifier = always_inline }, roc_alloc, .{ size, alignment })); } pub fn realloc(c_ptr: [*]u8, new_size: usize, old_size: usize, alignment: u32) [*]u8 { @@ -70,6 +82,10 @@ pub fn panic(c_ptr: *anyopaque, alignment: u32) callconv(.C) void { return @call(.{ .modifier = always_inline }, roc_panic, .{ c_ptr, alignment }); } +pub fn memcpy(dst: [*]u8, src: [*]u8, size: usize) void { + @call(.{ .modifier = always_inline }, roc_memcpy, .{ dst, src, size }); +} + // indirection because otherwise zig creates an alias to the panic function which our LLVM code // does not know how to deal with pub fn test_panic(c_ptr: *anyopaque, alignment: u32) callconv(.C) void { @@ -173,7 +189,8 @@ pub fn allocateWithRefcount( switch (alignment) { 16 => { - var new_bytes: [*]align(16) u8 = @alignCast(16, alloc(length, alignment)); + // TODO handle alloc failing! + var new_bytes: [*]align(16) u8 = @alignCast(16, alloc(length, alignment) orelse unreachable); var as_usize_array = @ptrCast([*]usize, new_bytes); as_usize_array[0] = 0; @@ -185,7 +202,8 @@ pub fn allocateWithRefcount( return first_slot; }, 8 => { - var raw = alloc(length, alignment); + // TODO handle alloc failing! + var raw = alloc(length, alignment) orelse unreachable; var new_bytes: [*]align(8) u8 = @alignCast(8, raw); var as_isize_array = @ptrCast([*]isize, new_bytes); @@ -197,7 +215,8 @@ pub fn allocateWithRefcount( return first_slot; }, 4 => { - var raw = alloc(length, alignment); + // TODO handle alloc failing! + var raw = alloc(length, alignment) orelse unreachable; var new_bytes: [*]align(@alignOf(isize)) u8 = @alignCast(@alignOf(isize), raw); var as_isize_array = @ptrCast([*]isize, new_bytes); @@ -217,6 +236,11 @@ pub fn allocateWithRefcount( } } +pub const CSlice = extern struct { + pointer: *c_void, + len: usize, +}; + pub fn unsafeReallocate( source_ptr: [*]u8, alignment: u32, diff --git a/compiler/builtins/docs/Bool.roc b/compiler/builtins/docs/Bool.roc index d4a3b673fe..29d8dbbc90 100644 --- a/compiler/builtins/docs/Bool.roc +++ b/compiler/builtins/docs/Bool.roc @@ -44,9 +44,9 @@ and : Bool, Bool -> Bool ## `a || b` is shorthand for `Bool.or a b`. ## ## >>> True || True -# +## ## >>> True || False -# +## ## >>> False || True ## ## >>> False || False diff --git a/compiler/builtins/docs/List.roc b/compiler/builtins/docs/List.roc index 09700da429..9d6a58bdbe 100644 --- a/compiler/builtins/docs/List.roc +++ b/compiler/builtins/docs/List.roc @@ -32,6 +32,8 @@ interface List set, single, sortWith, + split, + sublist, sum, swap, walk, @@ -205,7 +207,7 @@ empty : List * ## Returns a list with the given length, where every element is the given value. ## ## -repeat : elem, Nat -> List elem +repeat : Nat, elem -> List elem ## Returns a list of all the integers between one and another, ## including both of the given numbers. @@ -277,7 +279,7 @@ map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e ## This works like [List.map], except it also passes the index ## of the element to the conversion function. -mapWithIndex : List before, (before, Nat -> after) -> List after +mapWithIndex : List before, (Nat, before -> after) -> List after ## This works like [List.map], except at any time you can return `Err` to ## cancel the entire operation immediately, and return that #Err. diff --git a/compiler/builtins/docs/Num.roc b/compiler/builtins/docs/Num.roc index 4c012e99b3..c4672f0c7f 100644 --- a/compiler/builtins/docs/Num.roc +++ b/compiler/builtins/docs/Num.roc @@ -62,10 +62,25 @@ interface Num isZero, log, maxFloat, + maxI8, + maxU8, + maxI16, + maxU16, + maxI32, + maxU32, + maxI64, + maxU64, maxI128, - maxInt, minFloat, - minInt, + minI8, + minU8, + minI16, + minU16, + minI32, + minU32, + minI64, + minU64, + minI128, modInt, modFloat, mul, @@ -102,7 +117,7 @@ interface Num ## ## The number 1.5 technically has the type `Num (Fraction *)`, so when you pass ## two of them to [Num.add], the answer you get is `3.0 : Num (Fraction *)`. -# +## ## Similarly, the number 0x1 (that is, the integer 1 in hexadecimal notation) ## technically has the type `Num (Integer *)`, so when you pass two of them to ## [Num.add], the answer you get is `2 : Num (Integer *)`. @@ -359,47 +374,47 @@ Nat : Int [ @Natural ] ## ## | Range | Type | Size | ## |--------------------------------------------------------|-------|----------| -## | ` -128` | #I8 | 1 Byte | +## | ` -128` | [I8] | 1 Byte | ## | ` 127` | | | ## |--------------------------------------------------------|-------|----------| -## | ` 0` | #U8 | 1 Byte | +## | ` 0` | [U8] | 1 Byte | ## | ` 255` | | | ## |--------------------------------------------------------|-------|----------| -## | ` -32_768` | #I16 | 2 Bytes | +## | ` -32_768` | [I16] | 2 Bytes | ## | ` 32_767` | | | ## |--------------------------------------------------------|-------|----------| -## | ` 0` | #U16 | 2 Bytes | +## | ` 0` | [U16] | 2 Bytes | ## | ` 65_535` | | | ## |--------------------------------------------------------|-------|----------| -## | ` -2_147_483_648` | #I32 | 4 Bytes | +## | ` -2_147_483_648` | [I32] | 4 Bytes | ## | ` 2_147_483_647` | | | ## |--------------------------------------------------------|-------|----------| -## | ` 0` | #U32 | 4 Bytes | +## | ` 0` | [U32] | 4 Bytes | ## | ` (over 4 billion) 4_294_967_295` | | | ## |--------------------------------------------------------|-------|----------| -## | ` -9_223_372_036_854_775_808` | #I64 | 8 Bytes | +## | ` -9_223_372_036_854_775_808` | [I64] | 8 Bytes | ## | ` 9_223_372_036_854_775_807` | | | ## |--------------------------------------------------------|-------|----------| -## | ` 0` | #U64 | 8 Bytes | +## | ` 0` | [U64] | 8 Bytes | ## | ` (over 18 quintillion) 18_446_744_073_709_551_615` | | | ## |--------------------------------------------------------|-------|----------| -## | `-170_141_183_460_469_231_731_687_303_715_884_105_728` | #I128 | 16 Bytes | +## | `-170_141_183_460_469_231_731_687_303_715_884_105_728` | [I128]| 16 Bytes | ## | ` 170_141_183_460_469_231_731_687_303_715_884_105_727` | | | ## |--------------------------------------------------------|-------|----------| -## | ` (over 340 undecillion) 0` | #U128 | 16 Bytes | +## | ` (over 340 undecillion) 0` | [U128]| 16 Bytes | ## | ` 340_282_366_920_938_463_463_374_607_431_768_211_455` | | | ## -## Roc also has one variable-size integer type: #Nat. The size of #Nat is equal +## Roc also has one variable-size integer type: [Nat]. The size of [Nat] is equal ## to the size of a memory address, which varies by system. For example, when -## compiling for a 64-bit system, #Nat is the same as #U64. When compiling for a -## 32-bit system, it's the same as #U32. +## compiling for a 64-bit system, [Nat] is the same as [U64]. When compiling for a +## 32-bit system, it's the same as [U32]. ## -## A common use for #Nat is to store the length ("len" for short) of a -## collection like #List, #Set, or #Map. 64-bit systems can represent longer +## A common use for [Nat] is to store the length ("len" for short) of a +## collection like a [List]. 64-bit systems can represent longer ## lists in memory than 32-bit systems can, which is why the length of a list -## is represented as a #Nat in Roc. +## is represented as a [Nat] in Roc. ## -## If any operation would result in an #Int that is either too big +## If any operation would result in an [Int] that is either too big ## or too small to fit in that range (e.g. calling `Int.maxI32 + 1`), ## then the operation will *overflow*. When an overflow occurs, the program will crash. ## @@ -501,7 +516,16 @@ add : Num a, Num a -> Num a ## ## This is the same as [Num.add] except if the operation overflows, instead of ## panicking or returning ∞ or -∞, it will return `Err Overflow`. -addCheckOverflow : Num a, Num a -> Result (Num a) [ Overflow ]* +addChecked : Num a, Num a -> Result (Num a) [ Overflow ]* + +## Add two numbers, clamping on the maximum representable number rather than +## overflowing. +## +## This is the same as [Num.add] except for the saturating behavior if the +## addition is to overflow. +## For example, if `x : U8` is 200 and `y : U8` is 100, `addSaturated x y` will +## yield 255, the maximum value of a `U8`. +addSaturated : Num a, Num a -> Num a ## Subtract two numbers of the same type. ## @@ -528,7 +552,16 @@ sub : Num a, Num a -> Num a ## ## This is the same as [Num.sub] except if the operation overflows, instead of ## panicking or returning ∞ or -∞, it will return `Err Overflow`. -subCheckOverflow : Num a, Num a -> Result (Num a) [ Overflow ]* +subChecked : Num a, Num a -> Result (Num a) [ Overflow ]* + +## Subtract two numbers, clamping on the minimum representable number rather +## than overflowing. +## +## This is the same as [Num.sub] except for the saturating behavior if the +## subtraction is to overflow. +## For example, if `x : U8` is 10 and `y : U8` is 20, `subSaturated x y` will +## yield 0, the minimum value of a `U8`. +subSaturated : Num a, Num a -> Num a ## Multiply two numbers of the same type. ## @@ -749,6 +782,15 @@ not : Int a -> Int a ## Limits +## The lowest number that can be stored in a #Nat without underflowing its +## available memory and crashing. +## +## For reference, this is the number zero, because #Nat is +## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), +## and zero is the lowest unsigned number. +## Unsigned numbers cannot be negative. +minNat : Nat + ## The highest number that can be stored in a #Nat without overflowing its ## available memory and crashing. ## @@ -757,83 +799,192 @@ not : Int a -> Int a ## 32-bit system, this will be equal to #Num.maxU32. maxNat : Nat -## The number zero. +## The lowest number that can be stored in an #I8 without underflowing its +## available memory and crashing. ## -## #Num.minNat is the lowest number that can be stored in a #Nat, which is zero -## because #Nat is [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), -## and zero is the lowest unsigned number. Unsigned numbers cannot be negative. -minNat : Nat +## For reference, this number is `-128`. +## +## Note that the positive version of this number is larger than #Int.maxI8, +## which means if you call #Num.abs on #Int.minI8, it will overflow and crash! +minI8 : I8 + +## The highest number that can be stored in an #I8 without overflowing its +## available memory and crashing. +## +## For reference, this number is `127`. +## +## Note that this is smaller than the positive version of #Int.minI8, +## which means if you call #Num.abs on #Int.minI8, it will overflow and crash! +maxI8 : I8 + +## The lowest number that can be stored in a #U8 without underflowing its +## available memory and crashing. +## +## For reference, this number is zero, because #U8 is +## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), +## and zero is the lowest unsigned number. +## Unsigned numbers cannot be negative. +minU8 : U8 + +## The highest number that can be stored in a #U8 without overflowing its +## available memory and crashing. +## +## For reference, this number is `255`. +maxU8 : U8 + +## The lowest number that can be stored in an #I16 without underflowing its +## available memory and crashing. +## +## For reference, this number is `-32_768`. +## +## Note that the positive version of this number is larger than #Int.maxI16, +## which means if you call #Num.abs on #Int.minI16, it will overflow and crash! +minI16 : I16 + +## The highest number that can be stored in an #I16 without overflowing its +## available memory and crashing. +## +## For reference, this number is `32_767`. +## +## Note that this is smaller than the positive version of #Int.minI16, +## which means if you call #Num.abs on #Int.minI16, it will overflow and crash! +maxI16 : I16 + +## The lowest number that can be stored in a #U16 without underflowing its +## available memory and crashing. +## +## For reference, this number is zero, because #U16 is +## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), +## and zero is the lowest unsigned number. +## Unsigned numbers cannot be negative. +minU16 : U16 + +## The highest number that can be stored in a #U16 without overflowing its +## available memory and crashing. +## +## For reference, this number is `65_535`. +maxU16 : U16 + +## The lowest number that can be stored in an #I32 without underflowing its +## available memory and crashing. +## +## For reference, this number is `-2_147_483_648`. +## +## Note that the positive version of this number is larger than #Int.maxI32, +## which means if you call #Num.abs on #Int.minI32, it will overflow and crash! +minI32 : I32 ## The highest number that can be stored in an #I32 without overflowing its ## available memory and crashing. ## -## Note that this is smaller than the positive version of #Int.minI32 +## For reference, this number is `2_147_483_647`, +## which is over 2 million. +## +## Note that this is smaller than the positive version of #Int.minI32, ## which means if you call #Num.abs on #Int.minI32, it will overflow and crash! maxI32 : I32 -## The min number that can be stored in an #I32 without overflowing its +## The lowest number that can be stored in a #U32 without underflowing its ## available memory and crashing. ## -## Note that the positive version of this number is this is larger than -## #Int.maxI32, which means if you call #Num.abs on #Int.minI32, it will overflow and crash! -minI32 : I32 - -## The highest number that can be stored in a #U64 without overflowing its -## available memory and crashing. -## -## For reference, that number is `18_446_744_073_709_551_615`, which is over 18 quintillion. -maxU64 : U64 - -## The number zero. -## -## #Num.minU64 is the lowest number that can be stored in a #U64, which is zero -## because #U64 is [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), -## and zero is the lowest unsigned number. Unsigned numbers cannot be negative. -minU64 : U64 +## For reference, this number is zero, because #U32 is +## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), +## and zero is the lowest unsigned number. +## Unsigned numbers cannot be negative. +minU32 : U32 ## The highest number that can be stored in a #U32 without overflowing its ## available memory and crashing. ## -## For reference, that number is `4_294_967_295`, which is over 4 million. +## For reference, this number is `4_294_967_295`, +## which is over 4 million. maxU32 : U32 -## The number zero. +## The min number that can be stored in an #I64 without underflowing its +## available memory and crashing. ## -## #Num.minU32 is the lowest number that can be stored in a #U32, which is zero -## because #U32 is [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), +## For reference, this number is `-`. +## +## Note that the positive version of this number is larger than #Int.maxI64, +## which means if you call #Num.abs on #Int.minI64, it will overflow and crash! +minI64 : I64 + +## The highest number that can be stored in an #I64 without overflowing its +## available memory and crashing. +## +## For reference, this number is ``, +## which is over 2 million. +## +## Note that this is smaller than the positive version of #Int.minI64, +## which means if you call #Num.abs on #Int.minI64, it will overflow and crash! +maxI64 : I64 + +## The lowest number that can be stored in a #U64 without underflowing its +## available memory and crashing. +## +## For reference, this number is zero because #U64 is +## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), ## and zero is the lowest unsigned number. Unsigned numbers cannot be negative. -minU32 : U32 +minU64 : U64 -## The highest supported #F64 value you can have, which is approximately 1.8 × 10^308. +## The highest number that can be stored in a #U64 without overflowing its +## available memory and crashing. ## -## If you go higher than this, your running Roc code will crash - so be careful not to! -maxF64 : F64 +## For reference, this number is `18_446_744_073_709_551_615`, +## which is over 18 quintillion. +maxU64 : U64 -## The lowest supported #F64 value you can have, which is approximately -1.8 × 10^308. +## The lowest number that can be stored in an #I128 without underflowing its +## available memory and crashing. ## -## If you go lower than this, your running Roc code will crash - so be careful not to! -minF64 : F64 +## For reference, this number is `-170_141_183_460_469_231_731_687_303_715_884_105_728`. +## +## Note that the positive version of this number is larger than #Int.maxI128, +## which means if you call #Num.abs on #Int.minI128, it will overflow and crash! +minI128 : I128 -## The highest supported #F32 value you can have, which is approximately 1.8 × 10^308. +## The highest number that can be stored in an #I128 without overflowing its +## available memory and crashing. ## -## If you go higher than this, your running Roc code will crash - so be careful not to! -maxF32 : F32 +## For reference, this number is `170_141_183_460_469_231_731_687_303_715_884_105_727`, +## which is over 2 million. +## +## Note that this is smaller than the positive version of #Int.minI128, +## which means if you call #Num.abs on #Int.minI128, it will overflow and crash! +maxI128 : I128 ## The lowest supported #F32 value you can have, which is approximately -1.8 × 10^308. ## ## If you go lower than this, your running Roc code will crash - so be careful not to! minF32 : F32 -## The highest supported #Dec value you can have, which is precisely 170_141_183_460_469_231_731.687303715884105727. +## The highest supported #F32 value you can have, which is approximately 1.8 × 10^308. ## ## If you go higher than this, your running Roc code will crash - so be careful not to! -maxDec : Dec +maxF32 : F32 -## The lowest supported #Dec value you can have, which is precisely -170_141_183_460_469_231_731.687303715884105728. +## The lowest supported #F64 value you can have, which is approximately -1.8 × 10^308. +## +## If you go lower than this, your running Roc code will crash - so be careful not to! +minF64 : F64 + +## The highest supported #F64 value you can have, which is approximately 1.8 × 10^308. +## +## If you go higher than this, your running Roc code will crash - so be careful not to! +maxF64 : F64 + +## The lowest supported #Dec value you can have, +## which is precisely -170_141_183_460_469_231_731.687303715884105728. ## ## If you go lower than this, your running Roc code will crash - so be careful not to! minDec : Dec +## The highest supported #Dec value you can have, +## which is precisely 170_141_183_460_469_231_731.687303715884105727. +## +## If you go higher than this, your running Roc code will crash - so be careful not to! +maxDec : Dec + ## Constants ## An approximation of e, specifically 2.718281828459045. @@ -992,7 +1143,7 @@ shr : Int a, Int a -> Int a ## [Arithmetic bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Arithmetic_shift) right. ## -## This is called `shlWrap` because any bits shifted +## This is called `shrWrap` because any bits shifted ## off the end of the number will be wrapped around to ## the beginning. (In contrast, [shr] replaces discarded bits with zeroes.) shrWrap : Int a, Int a -> Int a diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index 6746068a66..c52c30cf09 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -1,4 +1,5 @@ use roc_module::symbol::Symbol; +use roc_target::TargetInfo; use std::ops::Index; pub const BUILTINS_HOST_OBJ_PATH: &str = env!( @@ -46,14 +47,21 @@ impl FloatWidth { } } - pub const fn alignment_bytes(&self) -> u32 { + pub const fn alignment_bytes(&self, target_info: TargetInfo) -> u32 { + use roc_target::Architecture; use std::mem::align_of; use FloatWidth::*; // TODO actually alignment is architecture-specific match self { F32 => align_of::() as u32, - F64 => align_of::() as u32, + F64 => match target_info.architecture { + Architecture::X86_64 + | Architecture::Aarch64 + | Architecture::Arm + | Architecture::Wasm32 => 8, + Architecture::X86_32 => 4, + }, F128 => align_of::() as u32, } } @@ -106,16 +114,22 @@ impl IntWidth { } } - pub const fn alignment_bytes(&self) -> u32 { + pub const fn alignment_bytes(&self, target_info: TargetInfo) -> u32 { + use roc_target::Architecture; use std::mem::align_of; use IntWidth::*; - // TODO actually alignment is architecture-specific match self { U8 | I8 => align_of::() as u32, U16 | I16 => align_of::() as u32, U32 | I32 => align_of::() as u32, - U64 | I64 => align_of::() as u32, + U64 | I64 => match target_info.architecture { + Architecture::X86_64 + | Architecture::Aarch64 + | Architecture::Arm + | Architecture::Wasm32 => 8, + Architecture::X86_32 => 4, + }, U128 | I128 => align_of::() as u32, } } @@ -201,22 +215,28 @@ macro_rules! float_intrinsic { #[macro_export] macro_rules! int_intrinsic { - ($name:literal) => {{ + ($signed_name:literal, $unsigned_name:literal) => {{ let mut output = IntrinsicName::default(); - output.options[4] = concat!($name, ".i8"); - output.options[5] = concat!($name, ".i16"); - output.options[6] = concat!($name, ".i32"); - output.options[7] = concat!($name, ".i64"); - output.options[8] = concat!($name, ".i128"); - 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"); + // The indeces align with the `Index` impl for `IntrinsicName`. + output.options[4] = concat!($unsigned_name, ".i8"); + output.options[5] = concat!($unsigned_name, ".i16"); + output.options[6] = concat!($unsigned_name, ".i32"); + output.options[7] = concat!($unsigned_name, ".i64"); + output.options[8] = concat!($unsigned_name, ".i128"); + + output.options[9] = concat!($signed_name, ".i8"); + output.options[10] = concat!($signed_name, ".i16"); + output.options[11] = concat!($signed_name, ".i32"); + output.options[12] = concat!($signed_name, ".i64"); + output.options[13] = concat!($signed_name, ".i128"); output }}; + + ($name:literal) => { + int_intrinsic!($name, $name) + }; } pub const NUM_ASIN: IntrinsicName = float_intrinsic!("roc_builtins.num.asin"); @@ -316,3 +336,6 @@ pub const UTILS_TEST_PANIC: &str = "roc_builtins.utils.test_panic"; pub const UTILS_INCREF: &str = "roc_builtins.utils.incref"; pub const UTILS_DECREF: &str = "roc_builtins.utils.decref"; 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"; diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 4a7b422402..15276e9e8b 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -141,6 +141,13 @@ pub fn types() -> MutMap { Box::new(int_type(flex(TVAR1))), ); + // addSaturated : Num a, Num a -> Num a + add_top_level_function_type!( + Symbol::NUM_ADD_SATURATED, + vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))], + Box::new(int_type(flex(TVAR1))), + ); + // sub or (-) : Num a, Num a -> Num a add_top_level_function_type!( Symbol::NUM_SUB, @@ -162,6 +169,13 @@ pub fn types() -> MutMap { Box::new(result_type(num_type(flex(TVAR1)), overflow())), ); + // subSaturated : Num a, Num a -> Num a + add_top_level_function_type!( + Symbol::NUM_SUB_SATURATED, + vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))], + Box::new(int_type(flex(TVAR1))), + ); + // mul or (*) : Num a, Num a -> Num a add_top_level_function_type!( Symbol::NUM_MUL, @@ -288,12 +302,6 @@ pub fn types() -> MutMap { Box::new(bool_type()) ); - // maxInt : Int range - add_type!(Symbol::NUM_MAX_INT, int_type(flex(TVAR1))); - - // minInt : Int range - add_type!(Symbol::NUM_MIN_INT, int_type(flex(TVAR1))); - let div_by_zero = SolvedType::TagUnion( vec![(TagName::Global("DivByZero".into()), vec![])], Box::new(SolvedType::Wildcard), @@ -383,6 +391,57 @@ pub fn types() -> MutMap { Box::new(bool_type()), ); + // minI8 : I8 + add_type!(Symbol::NUM_MIN_I8, i8_type()); + + // maxI8 : I8 + add_type!(Symbol::NUM_MAX_I8, i8_type()); + + // minU8 : U8 + add_type!(Symbol::NUM_MIN_U8, u8_type()); + + // maxU8 : U8 + add_type!(Symbol::NUM_MAX_U8, u8_type()); + + // minI16 : I16 + add_type!(Symbol::NUM_MIN_I16, i16_type()); + + // maxI16 : I16 + add_type!(Symbol::NUM_MAX_I16, i16_type()); + + // minU16 : U16 + add_type!(Symbol::NUM_MIN_U16, u16_type()); + + // maxU16 : U16 + add_type!(Symbol::NUM_MAX_U16, u16_type()); + + // minI32 : I32 + add_type!(Symbol::NUM_MIN_I32, i32_type()); + + // maxI32 : I32 + add_type!(Symbol::NUM_MAX_I32, i32_type()); + + // minU32 : U32 + add_type!(Symbol::NUM_MIN_U32, u32_type()); + + // maxU32 : U32 + add_type!(Symbol::NUM_MAX_U32, u32_type()); + + // minI64 : I64 + add_type!(Symbol::NUM_MIN_I64, i64_type()); + + // maxI64 : I64 + add_type!(Symbol::NUM_MAX_I64, i64_type()); + + // minU64 : U64 + add_type!(Symbol::NUM_MIN_U64, u64_type()); + + // maxU64 : U64 + add_type!(Symbol::NUM_MAX_U64, u64_type()); + + // minI128 : I128 + add_type!(Symbol::NUM_MIN_I128, i128_type()); + // maxI128 : I128 add_type!(Symbol::NUM_MAX_I128, i128_type()); @@ -1267,6 +1326,20 @@ pub fn types() -> MutMap { Box::new(list_type(flex(TVAR1))), ); + // sortAsc : List (Num a) -> List (Num a) + add_top_level_function_type!( + Symbol::LIST_SORT_ASC, + vec![list_type(num_type(flex(TVAR1)))], + Box::new(list_type(num_type(flex(TVAR1)))) + ); + + // sortDesc : List (Num a) -> List (Num a) + add_top_level_function_type!( + Symbol::LIST_SORT_DESC, + vec![list_type(num_type(flex(TVAR1)))], + Box::new(list_type(num_type(flex(TVAR1)))) + ); + // find : List elem, (elem -> Bool) -> Result elem [ NotFound ]* { let not_found = SolvedType::TagUnion( diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index 2867a23f03..5cd1b22164 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -357,7 +357,7 @@ fn can_annotation_help( actual: Box::new(actual), } } - None => Type::Apply(symbol, args), + None => Type::Apply(symbol, args, region), } } BoundVariable(v) => { @@ -377,7 +377,8 @@ fn can_annotation_help( As( loc_inner, _spaces, - AliasHeader { + alias_header + @ AliasHeader { name, vars: loc_vars, }, @@ -390,7 +391,7 @@ fn can_annotation_help( ) { Ok(symbol) => symbol, - Err((original_region, shadow)) => { + Err((original_region, shadow, _new_symbol)) => { let problem = Problem::Shadowed(original_region, shadow.clone()); env.problem(roc_problem::can::Problem::ShadowingInAnnotation { @@ -439,20 +440,43 @@ fn can_annotation_help( } } + let alias_args = vars.iter().map(|(_, v)| v.clone()).collect::>(); + let alias_actual = if let Type::TagUnion(tags, ext) = inner_type { let rec_var = var_store.fresh(); let mut new_tags = Vec::with_capacity(tags.len()); + let mut is_nested_datatype = false; for (tag_name, args) in tags { let mut new_args = Vec::with_capacity(args.len()); for arg in args { let mut new_arg = arg.clone(); - new_arg.substitute_alias(symbol, &Type::Variable(rec_var)); + let substitution_result = + new_arg.substitute_alias(symbol, &alias_args, &Type::Variable(rec_var)); + + if let Err(differing_recursion_region) = substitution_result { + env.problems + .push(roc_problem::can::Problem::NestedDatatype { + alias: symbol, + def_region: alias_header.region(), + differing_recursion_region, + }); + is_nested_datatype = true; + } + + // Either way, add the argument; not doing so would only result in more + // confusing error messages later on. new_args.push(new_arg); } new_tags.push((tag_name.clone(), new_args)); } - Type::RecursiveTagUnion(rec_var, new_tags, ext) + if is_nested_datatype { + // We don't have a way to represent nested data types; hence, we don't actually + // use the recursion var in them, and should avoid marking them as such. + Type::TagUnion(new_tags, ext) + } else { + Type::RecursiveTagUnion(rec_var, new_tags, ext) + } } else { inner_type }; diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 235f8ba9c2..3a8d1ba9e4 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -1,6 +1,7 @@ use crate::def::Def; -use crate::expr::{self, ClosureData, Expr::*}; +use crate::expr::{self, ClosureData, Expr::*, IntValue}; use crate::expr::{Expr, Field, Recursive}; +use crate::num::{FloatBound, IntBound, IntWidth, NumericBound}; use crate::pattern::Pattern; use roc_collections::all::SendMap; use roc_module::called_via::CalledVia; @@ -44,6 +45,23 @@ macro_rules! macro_magic { /// delegates to the compiler-internal List.getUnsafe function to do the actual /// lookup (if the bounds check passed). That internal function is hardcoded in code gen, /// which works fine because it doesn't involve any open tag unions. + +/// Does a builtin depend on any other builtins? +/// +/// NOTE: you are supposed to give all symbols that are relied on, +/// even those that are relied on transitively! +pub fn builtin_dependencies(symbol: Symbol) -> &'static [Symbol] { + match symbol { + Symbol::LIST_SORT_ASC => &[Symbol::LIST_SORT_WITH, Symbol::NUM_COMPARE], + Symbol::LIST_SORT_DESC => &[Symbol::LIST_SORT_WITH], + Symbol::LIST_PRODUCT => &[Symbol::LIST_WALK, Symbol::NUM_MUL], + Symbol::LIST_SUM => &[Symbol::LIST_WALK, Symbol::NUM_ADD], + Symbol::LIST_JOIN_MAP => &[Symbol::LIST_WALK, Symbol::LIST_CONCAT], + _ => &[], + } +} + +/// Implementation for a builtin pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option { debug_assert!(symbol.is_builtin()); @@ -125,6 +143,8 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option LIST_WALK_BACKWARDS => list_walk_backwards, LIST_WALK_UNTIL => list_walk_until, LIST_SORT_WITH => list_sort_with, + LIST_SORT_ASC => list_sort_asc, + LIST_SORT_DESC => list_sort_desc, LIST_ANY => list_any, LIST_ALL => list_all, LIST_FIND => list_find, @@ -156,9 +176,11 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option NUM_ADD => num_add, NUM_ADD_CHECKED => num_add_checked, NUM_ADD_WRAP => num_add_wrap, + NUM_ADD_SATURATED => num_add_saturated, NUM_SUB => num_sub, NUM_SUB_WRAP => num_sub_wrap, NUM_SUB_CHECKED => num_sub_checked, + NUM_SUB_SATURATED => num_sub_saturated, NUM_MUL => num_mul, NUM_MUL_WRAP => num_mul_wrap, NUM_MUL_CHECKED => num_mul_checked, @@ -195,8 +217,6 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option NUM_ASIN => num_asin, NUM_BYTES_TO_U16 => num_bytes_to_u16, NUM_BYTES_TO_U32 => num_bytes_to_u32, - NUM_MAX_INT => num_max_int, - NUM_MIN_INT => num_min_int, NUM_BITWISE_AND => num_bitwise_and, NUM_BITWISE_XOR => num_bitwise_xor, NUM_BITWISE_OR => num_bitwise_or, @@ -204,6 +224,23 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option NUM_SHIFT_RIGHT => num_shift_right_by, NUM_SHIFT_RIGHT_ZERO_FILL => num_shift_right_zf_by, NUM_INT_CAST=> num_int_cast, + NUM_MIN_I8=> num_min_i8, + NUM_MAX_I8=> num_max_i8, + NUM_MIN_U8=> num_min_u8, + NUM_MAX_U8=> num_max_u8, + NUM_MIN_I16=> num_min_i16, + NUM_MAX_I16=> num_max_i16, + NUM_MIN_U16=> num_min_u16, + NUM_MAX_U16=> num_max_u16, + NUM_MIN_I32=> num_min_i32, + NUM_MAX_I32=> num_max_i32, + NUM_MIN_U32=> num_min_u32, + NUM_MAX_U32=> num_max_u32, + NUM_MIN_I64=> num_min_i64, + NUM_MAX_I64=> num_max_i64, + NUM_MIN_U64=> num_min_u64, + NUM_MAX_U64=> num_max_u64, + NUM_MIN_I128=> num_min_i128, NUM_MAX_I128=> num_max_i128, NUM_TO_STR => num_to_str, RESULT_MAP => result_map, @@ -353,36 +390,6 @@ fn lowlevel_5(symbol: Symbol, op: LowLevel, var_store: &mut VarStore) -> Def { ) } -/// Num.maxInt : Int -fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def { - let int_var = var_store.fresh(); - let int_precision_var = var_store.fresh(); - let body = int(int_var, int_precision_var, i64::MAX.into()); - - Def { - annotation: None, - expr_var: int_var, - loc_expr: Loc::at_zero(body), - loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)), - pattern_vars: SendMap::default(), - } -} - -/// Num.minInt : Int -fn num_min_int(symbol: Symbol, var_store: &mut VarStore) -> Def { - let int_var = var_store.fresh(); - let int_precision_var = var_store.fresh(); - let body = int(int_var, int_precision_var, i64::MIN.into()); - - Def { - annotation: None, - expr_var: int_var, - loc_expr: Loc::at_zero(body), - loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)), - pattern_vars: SendMap::default(), - } -} - // Num.toStr : Num a -> Str fn num_to_str(symbol: Symbol, var_store: &mut VarStore) -> Def { let num_var = var_store.fresh(); @@ -641,6 +648,11 @@ fn num_add_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { num_overflow_checked(symbol, var_store, LowLevel::NumAddChecked) } +/// Num.addSaturated : Int a, Int a -> Int a +fn num_add_saturated(symbol: Symbol, var_store: &mut VarStore) -> Def { + num_binop(symbol, var_store, LowLevel::NumAddSaturated) +} + /// Num.sub : Num a, Num a -> Num a fn num_sub(symbol: Symbol, var_store: &mut VarStore) -> Def { num_binop(symbol, var_store, LowLevel::NumSub) @@ -656,6 +668,11 @@ fn num_sub_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { num_overflow_checked(symbol, var_store, LowLevel::NumSubChecked) } +/// Num.subSaturated : Int a, Int a -> Int a +fn num_sub_saturated(symbol: Symbol, var_store: &mut VarStore) -> Def { + num_binop(symbol, var_store, LowLevel::NumSubSaturated) +} + /// Num.mul : Num a, Num a -> Num a fn num_mul(symbol: Symbol, var_store: &mut VarStore) -> Def { num_binop(symbol, var_store, LowLevel::NumMul) @@ -777,7 +794,7 @@ fn num_is_zero(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::Eq, args: vec![ (arg_var, Var(Symbol::ARG_1)), - (arg_var, num(unbound_zero_var, 0)), + (arg_var, num(unbound_zero_var, 0, num_no_bound())), ], ret_var: bool_var, }; @@ -800,7 +817,7 @@ fn num_is_negative(symbol: Symbol, var_store: &mut VarStore) -> Def { let body = RunLowLevel { op: LowLevel::NumGt, args: vec![ - (arg_var, num(unbound_zero_var, 0)), + (arg_var, num(unbound_zero_var, 0, num_no_bound())), (arg_var, Var(Symbol::ARG_1)), ], ret_var: bool_var, @@ -825,7 +842,7 @@ fn num_is_positive(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::NumGt, args: vec![ (arg_var, Var(Symbol::ARG_1)), - (arg_var, num(unbound_zero_var, 0)), + (arg_var, num(unbound_zero_var, 0, num_no_bound())), ], ret_var: bool_var, }; @@ -848,14 +865,17 @@ fn num_is_odd(symbol: Symbol, var_store: &mut VarStore) -> Def { let body = RunLowLevel { op: LowLevel::Eq, args: vec![ - (arg_var, int(var_store.fresh(), var_store.fresh(), 1)), + ( + arg_var, + int::(var_store.fresh(), var_store.fresh(), 1, int_no_bound()), + ), ( arg_var, RunLowLevel { op: LowLevel::NumRemUnchecked, args: vec![ (arg_var, Var(Symbol::ARG_1)), - (arg_var, num(unbound_two_var, 2)), + (arg_var, num(unbound_two_var, 2, num_no_bound())), ], ret_var: arg_var, }, @@ -882,14 +902,14 @@ fn num_is_even(symbol: Symbol, var_store: &mut VarStore) -> Def { let body = RunLowLevel { op: LowLevel::Eq, args: vec![ - (arg_var, num(arg_num_var, 0)), + (arg_var, num(arg_num_var, 0, num_no_bound())), ( arg_var, RunLowLevel { op: LowLevel::NumRemUnchecked, args: vec![ (arg_var, Var(Symbol::ARG_1)), - (arg_var, num(arg_num_var, 2)), + (arg_var, num(arg_num_var, 2, num_no_bound())), ], ret_var: arg_var, }, @@ -943,7 +963,10 @@ fn num_sqrt(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::NumGte, args: vec![ (float_var, Var(Symbol::ARG_1)), - (float_var, float(unbound_zero_var, precision_var, 0.0)), + ( + float_var, + float(unbound_zero_var, precision_var, 0.0, float_no_bound()), + ), ], ret_var: bool_var, }), @@ -989,7 +1012,10 @@ fn num_log(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::NumGt, args: vec![ (float_var, Var(Symbol::ARG_1)), - (float_var, float(unbound_zero_var, precision_var, 0.0)), + ( + float_var, + float(unbound_zero_var, precision_var, 0.0, float_no_bound()), + ), ], ret_var: bool_var, }), @@ -1225,31 +1251,104 @@ fn num_int_cast(symbol: Symbol, var_store: &mut VarStore) -> Def { lowlevel_1(symbol, LowLevel::NumIntCast, var_store) } +/// Num.minI8: I8 +fn num_min_i8(symbol: Symbol, var_store: &mut VarStore) -> Def { + int_min_or_max::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(symbol, var_store, u64::MAX, IntBound::Exact(IntWidth::U64)) +} + +/// Num.minI128: I128 +fn num_min_i128(symbol: Symbol, var_store: &mut VarStore) -> Def { + int_min_or_max::( + symbol, + var_store, + i128::MIN, + IntBound::Exact(IntWidth::I128), + ) +} + /// Num.maxI128: I128 fn num_max_i128(symbol: Symbol, var_store: &mut VarStore) -> Def { - let int_var = var_store.fresh(); - let int_precision_var = var_store.fresh(); - let body = int(int_var, int_precision_var, i128::MAX); - - let std = roc_builtins::std::types(); - let solved = std.get(&symbol).unwrap(); - let mut free_vars = roc_types::solved_types::FreeVars::default(); - let signature = roc_types::solved_types::to_type(&solved.0, &mut free_vars, var_store); - - let annotation = crate::def::Annotation { - signature, - introduced_variables: Default::default(), - region: Region::zero(), - aliases: Default::default(), - }; - - Def { - annotation: Some(annotation), - expr_var: int_var, - loc_expr: Loc::at_zero(body), - loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)), - pattern_vars: SendMap::default(), - } + int_min_or_max::( + symbol, + var_store, + i128::MAX, + IntBound::Exact(IntWidth::I128), + ) } /// List.isEmpty : List * -> Bool @@ -1262,7 +1361,7 @@ fn list_is_empty(symbol: Symbol, var_store: &mut VarStore) -> Def { let body = RunLowLevel { op: LowLevel::Eq, args: vec![ - (len_var, num(unbound_zero_var, 0)), + (len_var, num(unbound_zero_var, 0, num_no_bound())), ( len_var, RunLowLevel { @@ -1374,7 +1473,15 @@ fn str_to_num(symbol: Symbol, var_store: &mut VarStore) -> Def { loc_expr: Box::new(no_region(Var(Symbol::ARG_2))), }, ), - (errorcode_var, int(errorcode_var, Variable::UNSIGNED8, 0)), + ( + errorcode_var, + int::( + errorcode_var, + Variable::UNSIGNED8, + 0, + IntBound::Exact(IntWidth::U8), + ), + ), ], ret_var: bool_var, }), @@ -2116,7 +2223,12 @@ fn list_swap(symbol: Symbol, var_store: &mut VarStore) -> Def { fn list_take_first(symbol: Symbol, var_store: &mut VarStore) -> Def { let list_var = var_store.fresh(); let len_var = var_store.fresh(); - let zero = int(len_var, Variable::NATURAL, 0); + let zero = int::( + len_var, + Variable::NATURAL, + 0, + IntBound::Exact(IntWidth::Nat), + ); let body = RunLowLevel { op: LowLevel::ListSublist, @@ -2142,7 +2254,12 @@ fn list_take_last(symbol: Symbol, var_store: &mut VarStore) -> Def { let list_var = var_store.fresh(); let len_var = var_store.fresh(); - let zero = int(len_var, Variable::NATURAL, 0); + let zero = int::( + len_var, + Variable::NATURAL, + 0, + IntBound::Exact(IntWidth::Nat), + ); let bool_var = var_store.fresh(); let get_list_len = RunLowLevel { @@ -2252,7 +2369,12 @@ fn list_intersperse(symbol: Symbol, var_store: &mut VarStore) -> Def { let clos_elem_sym = Symbol::ARG_4; let int_var = var_store.fresh(); - let zero = int(int_var, Variable::NATURAL, 0); + let zero = int::( + int_var, + Variable::NATURAL, + 0, + IntBound::Exact(IntWidth::Nat), + ); // \acc, elem -> acc |> List.append sep |> List.append elem let clos = Closure(ClosureData { @@ -2332,7 +2454,12 @@ fn list_split(symbol: Symbol, var_store: &mut VarStore) -> Def { let clos_ret_var = var_store.fresh(); let ret_var = var_store.fresh(); - let zero = int(index_var, Variable::NATURAL, 0); + let zero = int::( + index_var, + Variable::NATURAL, + 0, + IntBound::Exact(IntWidth::Nat), + ); let clos = Closure(ClosureData { function_type: clos_fun_var, @@ -2494,7 +2621,10 @@ fn list_drop_first(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::ListDropAt, args: vec![ (list_var, Var(Symbol::ARG_1)), - (index_var, int(num_var, num_precision_var, 0)), + ( + index_var, + int::(num_var, num_precision_var, 0, int_no_bound()), + ), ], ret_var: list_var, }; @@ -2591,7 +2721,10 @@ fn list_drop_last(symbol: Symbol, var_store: &mut VarStore) -> Def { ret_var: len_var, }, ), - (arg_var, int(num_var, num_precision_var, 1)), + ( + arg_var, + int::(num_var, num_precision_var, 1, int_no_bound()), + ), ], ret_var: len_var, }, @@ -2789,7 +2922,10 @@ fn list_min(symbol: Symbol, var_store: &mut VarStore) -> Def { RunLowLevel { op: LowLevel::NotEq, args: vec![ - (len_var, int(num_var, num_precision_var, 0)), + ( + len_var, + int::(num_var, num_precision_var, 0, int_no_bound()), + ), ( len_var, RunLowLevel { @@ -2820,7 +2956,15 @@ fn list_min(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::ListGetUnsafe, args: vec![ (list_var, Var(Symbol::ARG_1)), - (arg_var, int(num_var, num_precision_var, 0)), + ( + arg_var, + int::( + num_var, + num_precision_var, + 0, + int_no_bound(), + ), + ), ], ret_var: list_elem_var, }, @@ -2919,7 +3063,10 @@ fn list_max(symbol: Symbol, var_store: &mut VarStore) -> Def { RunLowLevel { op: LowLevel::NotEq, args: vec![ - (len_var, int(num_var, num_precision_var, 0)), + ( + len_var, + int::(num_var, num_precision_var, 0, int_no_bound()), + ), ( len_var, RunLowLevel { @@ -2950,7 +3097,15 @@ fn list_max(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::ListGetUnsafe, args: vec![ (list_var, Var(Symbol::ARG_1)), - (arg_var, int(num_var, num_precision_var, 0)), + ( + arg_var, + int::( + num_var, + num_precision_var, + 0, + int_no_bound(), + ), + ), ], ret_var: list_elem_var, }, @@ -3033,15 +3188,25 @@ fn list_sum(symbol: Symbol, var_store: &mut VarStore) -> Def { let list_var = var_store.fresh(); let closure_var = var_store.fresh(); - let body = RunLowLevel { - op: LowLevel::ListWalk, - args: vec![ - (list_var, Var(Symbol::ARG_1)), - (num_var, num(var_store.fresh(), 0)), - (closure_var, list_sum_add(num_var, var_store)), - ], + let function = ( + var_store.fresh(), + Loc::at_zero(Expr::Var(Symbol::LIST_WALK)), + var_store.fresh(), ret_var, - }; + ); + + let body = Expr::Call( + Box::new(function), + vec![ + (list_var, Loc::at_zero(Var(Symbol::ARG_1))), + ( + num_var, + Loc::at_zero(num(var_store.fresh(), 0, num_no_bound())), + ), + (closure_var, Loc::at_zero(Var(Symbol::NUM_ADD))), + ], + CalledVia::Space, + ); defn( symbol, @@ -3052,60 +3217,37 @@ fn list_sum(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } -fn list_sum_add(num_var: Variable, var_store: &mut VarStore) -> Expr { - let body = RunLowLevel { - op: LowLevel::NumAdd, - args: vec![(num_var, Var(Symbol::ARG_3)), (num_var, Var(Symbol::ARG_4))], - ret_var: num_var, - }; - - defn_help( - Symbol::LIST_SUM_ADD, - vec![(num_var, Symbol::ARG_3), (num_var, Symbol::ARG_4)], - var_store, - body, - num_var, - ) -} - /// List.product : List (Num a) -> Num a fn list_product(symbol: Symbol, var_store: &mut VarStore) -> Def { let num_var = var_store.fresh(); - let ret_var = num_var; let list_var = var_store.fresh(); let closure_var = var_store.fresh(); - let body = RunLowLevel { - op: LowLevel::ListWalk, - args: vec![ - (list_var, Var(Symbol::ARG_1)), - (num_var, num(var_store.fresh(), 1)), - (closure_var, list_product_mul(num_var, var_store)), + let function = ( + var_store.fresh(), + Loc::at_zero(Expr::Var(Symbol::LIST_WALK)), + var_store.fresh(), + num_var, + ); + + let body = Expr::Call( + Box::new(function), + vec![ + (list_var, Loc::at_zero(Var(Symbol::ARG_1))), + ( + num_var, + Loc::at_zero(num(var_store.fresh(), 1, num_no_bound())), + ), + (closure_var, Loc::at_zero(Var(Symbol::NUM_MUL))), ], - ret_var, - }; + CalledVia::Space, + ); defn( symbol, vec![(list_var, Symbol::ARG_1)], var_store, body, - ret_var, - ) -} - -fn list_product_mul(num_var: Variable, var_store: &mut VarStore) -> Expr { - let body = RunLowLevel { - op: LowLevel::NumMul, - args: vec![(num_var, Var(Symbol::ARG_3)), (num_var, Var(Symbol::ARG_4))], - ret_var: num_var, - }; - - defn_help( - Symbol::LIST_PRODUCT_MUL, - vec![(num_var, Symbol::ARG_3), (num_var, Symbol::ARG_4)], - var_store, - body, num_var, ) } @@ -3183,6 +3325,91 @@ fn list_sort_with(symbol: Symbol, var_store: &mut VarStore) -> Def { lowlevel_2(symbol, LowLevel::ListSortWith, var_store) } +/// List.sortAsc : List (Num a) -> List (Num a) +fn list_sort_asc(symbol: Symbol, var_store: &mut VarStore) -> Def { + let list_var = var_store.fresh(); + let closure_var = var_store.fresh(); + let ret_var = list_var; + + let function = ( + var_store.fresh(), + Loc::at_zero(Expr::Var(Symbol::LIST_SORT_WITH)), + var_store.fresh(), + ret_var, + ); + + let body = Expr::Call( + Box::new(function), + vec![ + (list_var, Loc::at_zero(Var(Symbol::ARG_1))), + (closure_var, Loc::at_zero(Var(Symbol::NUM_COMPARE))), + ], + CalledVia::Space, + ); + + defn( + symbol, + vec![(list_var, Symbol::ARG_1)], + var_store, + body, + ret_var, + ) +} + +/// List.sortDesc : List (Num a) -> List (Num a) +fn list_sort_desc(symbol: Symbol, var_store: &mut VarStore) -> Def { + let list_var = var_store.fresh(); + let num_var = var_store.fresh(); + let closure_var = var_store.fresh(); + let compare_ret_var = var_store.fresh(); + let ret_var = list_var; + + let closure = Closure(ClosureData { + function_type: closure_var, + closure_type: var_store.fresh(), + closure_ext_var: var_store.fresh(), + return_type: compare_ret_var, + name: Symbol::LIST_SORT_DESC_COMPARE, + recursive: Recursive::NotRecursive, + captured_symbols: vec![], + arguments: vec![ + (num_var, no_region(Pattern::Identifier(Symbol::ARG_2))), + (num_var, no_region(Pattern::Identifier(Symbol::ARG_3))), + ], + loc_body: { + Box::new(no_region(RunLowLevel { + op: LowLevel::NumCompare, + args: vec![(num_var, Var(Symbol::ARG_3)), (num_var, Var(Symbol::ARG_2))], + ret_var: compare_ret_var, + })) + }, + }); + + let function = ( + var_store.fresh(), + Loc::at_zero(Expr::Var(Symbol::LIST_SORT_WITH)), + var_store.fresh(), + ret_var, + ); + + let body = Expr::Call( + Box::new(function), + vec![ + (list_var, Loc::at_zero(Var(Symbol::ARG_1))), + (closure_var, Loc::at_zero(closure)), + ], + CalledVia::Space, + ); + + defn( + symbol, + vec![(list_var, Symbol::ARG_1)], + var_store, + body, + ret_var, + ) +} + /// List.any: List elem, (elem -> Bool) -> Bool fn list_any(symbol: Symbol, var_store: &mut VarStore) -> Def { lowlevel_2(symbol, LowLevel::ListAny, var_store) @@ -3664,7 +3891,7 @@ fn num_rem(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::NotEq, args: vec![ (num_var, Var(Symbol::ARG_2)), - (num_var, num(unbound_zero_var, 0)), + (num_var, num(unbound_zero_var, 0, num_no_bound())), ], ret_var: bool_var, }, @@ -3767,7 +3994,10 @@ fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::NotEq, args: vec![ (num_var, Var(Symbol::ARG_2)), - (num_var, float(unbound_zero_var, precision_var, 0.0)), + ( + num_var, + float(unbound_zero_var, precision_var, 0.0, float_no_bound()), + ), ], ret_var: bool_var, }, @@ -3832,7 +4062,12 @@ fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def { (num_var, Var(Symbol::ARG_2)), ( num_var, - int(unbound_zero_var, unbound_zero_precision_var, 0), + int::( + unbound_zero_var, + unbound_zero_precision_var, + 0, + int_no_bound(), + ), ), ], ret_var: bool_var, @@ -3898,7 +4133,12 @@ fn num_div_ceil(symbol: Symbol, var_store: &mut VarStore) -> Def { (num_var, Var(Symbol::ARG_2)), ( num_var, - int(unbound_zero_var, unbound_zero_precision_var, 0), + int::( + unbound_zero_var, + unbound_zero_precision_var, + 0, + int_no_bound(), + ), ), ], ret_var: bool_var, @@ -3968,7 +4208,10 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def { RunLowLevel { op: LowLevel::NotEq, args: vec![ - (len_var, int(zero_var, zero_precision_var, 0)), + ( + len_var, + int::(zero_var, zero_precision_var, 0, int_no_bound()), + ), ( len_var, RunLowLevel { @@ -3992,7 +4235,10 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::ListGetUnsafe, args: vec![ (list_var, Var(Symbol::ARG_1)), - (len_var, int(zero_var, zero_precision_var, 0)), + ( + len_var, + int::(zero_var, zero_precision_var, 0, int_no_bound()), + ), ], ret_var: list_elem_var, }, @@ -4049,7 +4295,10 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def { RunLowLevel { op: LowLevel::NotEq, args: vec![ - (len_var, int(num_var, num_precision_var, 0)), + ( + len_var, + int::(num_var, num_precision_var, 0, int_no_bound()), + ), ( len_var, RunLowLevel { @@ -4088,7 +4337,15 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def { ret_var: len_var, }, ), - (arg_var, int(num_var, num_precision_var, 1)), + ( + arg_var, + int::( + num_var, + num_precision_var, + 1, + int_no_bound(), + ), + ), ], ret_var: len_var, }, @@ -4716,7 +4973,10 @@ fn num_bytes_to(symbol: Symbol, var_store: &mut VarStore, offset: i64, low_level add_var, RunLowLevel { ret_var: cast_var, - args: vec![(cast_var, num(var_store.fresh(), offset))], + args: vec![( + cast_var, + num(var_store.fresh(), offset, num_no_bound()), + )], op: LowLevel::NumIntCast, }, ), @@ -4804,16 +5064,80 @@ fn defn_help( } #[inline(always)] -fn int(num_var: Variable, precision_var: Variable, i: i128) -> Expr { - Int(num_var, precision_var, i.to_string().into_boxed_str(), i) +fn int_min_or_max(symbol: Symbol, var_store: &mut VarStore, i: I128, bound: IntBound) -> Def +where + I128: Into, +{ + let int_var = var_store.fresh(); + let int_precision_var = var_store.fresh(); + let body = int::(int_var, int_precision_var, i, bound); + + let std = roc_builtins::std::types(); + let solved = std.get(&symbol).unwrap(); + let mut free_vars = roc_types::solved_types::FreeVars::default(); + let signature = roc_types::solved_types::to_type(&solved.0, &mut free_vars, var_store); + + let annotation = crate::def::Annotation { + signature, + introduced_variables: Default::default(), + region: Region::zero(), + aliases: Default::default(), + }; + + Def { + annotation: Some(annotation), + expr_var: int_var, + loc_expr: Loc::at_zero(body), + loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)), + pattern_vars: SendMap::default(), + } +} + +fn num_no_bound() -> NumericBound { + NumericBound::None +} + +fn int_no_bound() -> IntBound { + IntBound::None +} + +fn float_no_bound() -> FloatBound { + FloatBound::None } #[inline(always)] -fn float(num_var: Variable, precision_var: Variable, f: f64) -> Expr { - Float(num_var, precision_var, f.to_string().into_boxed_str(), f) +fn int(num_var: Variable, precision_var: Variable, i: I128, bound: IntBound) -> Expr +where + I128: Into, +{ + let ii = i.into(); + Int( + num_var, + precision_var, + ii.to_string().into_boxed_str(), + IntValue::I128(ii), + bound, + ) } #[inline(always)] -fn num(num_var: Variable, i: i64) -> Expr { - Num(num_var, i.to_string().into_boxed_str(), i) +fn float(num_var: Variable, precision_var: Variable, f: f64, bound: FloatBound) -> Expr { + Float( + num_var, + precision_var, + f.to_string().into_boxed_str(), + f, + bound, + ) +} + +#[inline(always)] +fn num>(num_var: Variable, i: I, bound: NumericBound) -> Expr { + let i = i.into(); + Num( + num_var, + i.to_string().into_boxed_str(), + IntValue::I128(i), + bound, + ) } diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 1ed85980e1..2e046dbb07 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -277,7 +277,7 @@ pub fn canonicalize_defs<'a>( let mut can_vars: Vec> = Vec::with_capacity(vars.len()); let mut is_phantom = false; - for loc_lowercase in vars { + for loc_lowercase in vars.iter() { if let Some(var) = can_ann .introduced_variables .var_by_name(&loc_lowercase.value) @@ -303,23 +303,42 @@ pub fn canonicalize_defs<'a>( continue; } + let mut is_nested_datatype = false; if can_ann.typ.contains_symbol(symbol) { - make_tag_union_recursive( + let alias_args = can_vars + .iter() + .map(|l| (l.value.0.clone(), Type::Variable(l.value.1))) + .collect::>(); + let alias_region = + Region::across_all([name.region].iter().chain(vars.iter().map(|l| &l.region))); + + let made_recursive = make_tag_union_recursive( env, - symbol, + Loc::at(alias_region, (symbol, &alias_args)), 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, ); + + is_nested_datatype = made_recursive.is_err(); } - scope.add_alias(symbol, ann.region, can_vars.clone(), can_ann.typ.clone()); + if is_nested_datatype { + // Bail out + continue; + } + + scope.add_alias(symbol, name.region, can_vars.clone(), can_ann.typ.clone()); let alias = scope.lookup_alias(symbol).expect("alias is added to scope"); 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); // Now that we have the scope completely assembled, and shadowing resolved, @@ -805,7 +824,7 @@ fn pattern_to_vars_by_symbol( ) { use Pattern::*; match pattern { - Identifier(symbol) => { + Identifier(symbol) | Shadowed(_, _, symbol) => { vars_by_symbol.insert(*symbol, expr_var); } @@ -821,15 +840,13 @@ fn pattern_to_vars_by_symbol( } } - NumLiteral(_, _, _) - | IntLiteral(_, _, _) - | FloatLiteral(_, _, _) + NumLiteral(..) + | IntLiteral(..) + | FloatLiteral(..) | StrLiteral(_) | Underscore | MalformedPattern(_, _) | UnsupportedPattern(_) => {} - - Shadowed(_, _) => {} } } @@ -880,7 +897,7 @@ fn canonicalize_pending_def<'a>( Pattern::Identifier(symbol) => RuntimeError::NoImplementationNamed { def_symbol: *symbol, }, - Pattern::Shadowed(region, loc_ident) => RuntimeError::Shadowing { + Pattern::Shadowed(region, loc_ident, _new_symbol) => RuntimeError::Shadowing { original_region: *region, shadow: loc_ident.clone(), }, @@ -962,66 +979,7 @@ fn canonicalize_pending_def<'a>( } } - Alias { - name, ann, vars, .. - } => { - let symbol = name.value; - let can_ann = canonicalize_annotation(env, scope, &ann.value, ann.region, var_store); - - // Record all the annotation's references in output.references.lookups - - for symbol in can_ann.references { - output.references.lookups.insert(symbol); - output.references.referenced_aliases.insert(symbol); - } - - let mut can_vars: Vec> = Vec::with_capacity(vars.len()); - - for loc_lowercase in vars { - if let Some(var) = can_ann - .introduced_variables - .var_by_name(&loc_lowercase.value) - { - // This is a valid lowercase rigid var for the alias. - can_vars.push(Loc { - value: (loc_lowercase.value.clone(), *var), - region: loc_lowercase.region, - }); - } else { - env.problems.push(Problem::PhantomTypeArgument { - alias: symbol, - variable_region: loc_lowercase.region, - variable_name: loc_lowercase.value.clone(), - }); - } - } - - scope.add_alias(symbol, name.region, can_vars.clone(), can_ann.typ.clone()); - - if can_ann.typ.contains_symbol(symbol) { - // the alias is recursive. If it's a tag union, we attempt to fix this - if let Type::TagUnion(tags, ext) = can_ann.typ { - // re-canonicalize the alias with the alias already in scope - let rec_var = var_store.fresh(); - let mut rec_type_union = Type::RecursiveTagUnion(rec_var, tags, ext); - rec_type_union.substitute_alias(symbol, &Type::Variable(rec_var)); - - scope.add_alias(symbol, name.region, can_vars, rec_type_union); - } else { - env.problems - .push(Problem::CyclicAlias(symbol, name.region, vec![])); - return output; - } - } - - let alias = scope.lookup_alias(symbol).expect("alias was not added"); - aliases.insert(symbol, alias.clone()); - - output - .introduced_variables - .union(&can_ann.introduced_variables); - } - + Alias { .. } => unreachable!("Aliases are handled in a separate pass"), InvalidAlias => { // invalid aliases (shadowed, incorrect patterns) get ignored } @@ -1557,7 +1515,7 @@ fn to_pending_def<'a>( )) } - Err((original_region, loc_shadowed_symbol)) => { + Err((original_region, loc_shadowed_symbol, _new_symbol)) => { env.problem(Problem::ShadowingInAnnotation { original_region, shadow: loc_shadowed_symbol, @@ -1681,9 +1639,16 @@ fn correct_mutual_recursive_type_alias<'a>( var_store, &mut ImSet::default(), ); - make_tag_union_recursive( + + let alias_args = &alias + .type_variables + .iter() + .map(|l| (l.value.0.clone(), Type::Variable(l.value.1))) + .collect::>(); + + let _made_recursive = make_tag_union_recursive( env, - *rec, + Loc::at(alias.header_region(), (*rec, alias_args)), alias.region, others, &mut alias.typ, @@ -1697,25 +1662,71 @@ fn correct_mutual_recursive_type_alias<'a>( } } +/// Attempt to make a tag union recursive at the position of `recursive_alias`; for example, +/// +/// ```roc +/// [ Cons a (ConsList a), Nil ] as ConsList a +/// ``` +/// +/// can be made recursive at the position "ConsList a" with a fresh recursive variable, say r1: +/// +/// ```roc +/// [ Cons a r1, Nil ] as r1 +/// ``` +/// +/// Returns `Err` if the tag union is recursive, but there is no structure-preserving recursion +/// variable for it. This can happen when the type is a nested datatype, for example in either of +/// +/// ```roc +/// Nested a : [ Chain a (Nested (List a)), Term ] +/// DuoList a b : [ Cons a (DuoList b a), Nil ] +/// ``` +/// +/// When `Err` is returned, a problem will be added to `env`. fn make_tag_union_recursive<'a>( env: &mut Env<'a>, - symbol: Symbol, + recursive_alias: Loc<(Symbol, &[(Lowercase, Type)])>, region: Region, others: Vec, typ: &mut Type, var_store: &mut VarStore, can_report_error: &mut bool, -) { +) -> Result<(), ()> { + let Loc { + value: (symbol, args), + region: alias_region, + } = recursive_alias; + let vars = args.iter().map(|(_, t)| t.clone()).collect::>(); match typ { Type::TagUnion(tags, ext) => { let rec_var = var_store.fresh(); - *typ = Type::RecursiveTagUnion(rec_var, tags.to_vec(), ext.clone()); - typ.substitute_alias(symbol, &Type::Variable(rec_var)); + let mut pending_typ = Type::RecursiveTagUnion(rec_var, tags.to_vec(), ext.clone()); + let substitution_result = + pending_typ.substitute_alias(symbol, &vars, &Type::Variable(rec_var)); + match substitution_result { + Ok(()) => { + // We can substitute the alias presence for the variable exactly. + *typ = pending_typ; + Ok(()) + } + Err(differing_recursion_region) => { + env.problems.push(Problem::NestedDatatype { + alias: symbol, + def_region: alias_region, + differing_recursion_region, + }); + Err(()) + } + } } - Type::RecursiveTagUnion(_, _, _) => {} - Type::Alias { actual, .. } => make_tag_union_recursive( + Type::RecursiveTagUnion(_, _, _) => Ok(()), + Type::Alias { + actual, + type_arguments, + .. + } => make_tag_union_recursive( env, - symbol, + Loc::at_zero((symbol, type_arguments)), region, others, actual, @@ -1733,6 +1744,7 @@ fn make_tag_union_recursive<'a>( let problem = Problem::CyclicAlias(symbol, region, others); env.problems.push(problem); } + Ok(()) } } } diff --git a/compiler/can/src/effect_module.rs b/compiler/can/src/effect_module.rs new file mode 100644 index 0000000000..3735c85239 --- /dev/null +++ b/compiler/can/src/effect_module.rs @@ -0,0 +1,1612 @@ +use crate::annotation::IntroducedVariables; +use crate::def::{Declaration, Def}; +use crate::env::Env; +use crate::expr::{ClosureData, Expr, Recursive}; +use crate::pattern::Pattern; +use crate::scope::Scope; +use roc_collections::all::{MutSet, SendMap}; +use roc_module::called_via::CalledVia; +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; + +#[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, +} + +/// 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, + declarations: &mut Vec, + generated_functions: HostedGeneratedFunctions, +) { + macro_rules! helper { + ($f:expr) => {{ + let (symbol, def) = $f( + env, + scope, + effect_symbol, + TagName::Private(effect_symbol), + var_store, + ); + + // make the outside world know this symbol exists + exposed_symbols.insert(symbol); + + 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 + // show up with their name. We have to register them like below to make the names show up in + // debug prints + if false { + env.home.register_debug_idents(&env.ident_ids); + } +} + +macro_rules! new_symbol { + ($scope:expr, $env:expr, $name:expr) => {{ + $scope + .introduce( + $name.into(), + &$env.exposed_ident_ids, + &mut $env.ident_ids, + Region::zero(), + ) + .unwrap() + }}; +} + +fn build_effect_always( + env: &mut Env, + scope: &mut Scope, + effect_symbol: Symbol, + effect_tag_name: TagName, + var_store: &mut VarStore, +) -> (Symbol, Def) { + // Effect.always = \value -> @Effect \{} -> value + + let value_symbol = { + scope + .introduce( + "effect_always_value".into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + Region::zero(), + ) + .unwrap() + }; + + let inner_closure_symbol = { + scope + .introduce( + "effect_always_inner".into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + Region::zero(), + ) + .unwrap() + }; + + let always_symbol = { + scope + .introduce( + "always".into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + Region::zero(), + ) + .unwrap() + }; + + // \{} -> value + let const_closure = { + let arguments = vec![( + var_store.fresh(), + Loc::at_zero(empty_record_pattern(var_store)), + )]; + + let body = Expr::Var(value_symbol); + + Expr::Closure(ClosureData { + function_type: var_store.fresh(), + closure_type: var_store.fresh(), + closure_ext_var: var_store.fresh(), + return_type: var_store.fresh(), + name: inner_closure_symbol, + captured_symbols: vec![(value_symbol, var_store.fresh())], + recursive: Recursive::NotRecursive, + arguments, + loc_body: Box::new(Loc::at_zero(body)), + }) + }; + + // \value -> @Effect \{} -> value + let (function_var, always_closure) = { + // `@Effect \{} -> value` + let body = Expr::Tag { + variant_var: var_store.fresh(), + ext_var: var_store.fresh(), + name: effect_tag_name.clone(), + arguments: vec![(var_store.fresh(), Loc::at_zero(const_closure))], + }; + + let arguments = vec![( + var_store.fresh(), + Loc::at_zero(Pattern::Identifier(value_symbol)), + )]; + + let function_var = var_store.fresh(); + let closure = Expr::Closure(ClosureData { + function_type: function_var, + closure_type: var_store.fresh(), + closure_ext_var: var_store.fresh(), + return_type: var_store.fresh(), + name: always_symbol, + captured_symbols: Vec::new(), + recursive: Recursive::NotRecursive, + arguments, + loc_body: Box::new(Loc::at_zero(body)), + }); + + (function_var, closure) + }; + + let mut introduced_variables = IntroducedVariables::default(); + + let signature = { + // Effect.always : a -> Effect a + let var_a = var_store.fresh(); + introduced_variables.insert_named("a".into(), var_a); + + let effect_a = build_effect_alias( + effect_symbol, + effect_tag_name, + "a", + var_a, + Type::Variable(var_a), + var_store, + &mut introduced_variables, + ); + + let closure_var = var_store.fresh(); + introduced_variables.insert_wildcard(closure_var); + + Type::Function( + vec![Type::Variable(var_a)], + Box::new(Type::Variable(closure_var)), + Box::new(effect_a), + ) + }; + + let def_annotation = crate::def::Annotation { + signature, + introduced_variables, + aliases: SendMap::default(), + region: Region::zero(), + }; + + let pattern = Pattern::Identifier(always_symbol); + let mut pattern_vars = SendMap::default(); + pattern_vars.insert(always_symbol, function_var); + let def = Def { + loc_pattern: Loc::at_zero(pattern), + loc_expr: Loc::at_zero(always_closure), + expr_var: function_var, + pattern_vars, + annotation: Some(def_annotation), + }; + + (always_symbol, def) +} + +fn build_effect_map( + env: &mut Env, + scope: &mut Scope, + effect_symbol: Symbol, + effect_tag_name: TagName, + var_store: &mut VarStore, +) -> (Symbol, Def) { + // Effect.map = \@Effect thunk, mapper -> @Effect \{} -> mapper (thunk {}) + + let thunk_symbol = { + scope + .introduce( + "effect_map_thunk".into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + Region::zero(), + ) + .unwrap() + }; + + let mapper_symbol = { + scope + .introduce( + "effect_map_mapper".into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + Region::zero(), + ) + .unwrap() + }; + + let map_symbol = { + scope + .introduce( + "map".into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + Region::zero(), + ) + .unwrap() + }; + + // `thunk {}` + let force_thunk_call = { + let boxed = ( + var_store.fresh(), + Loc::at_zero(Expr::Var(thunk_symbol)), + var_store.fresh(), + var_store.fresh(), + ); + + let arguments = vec![(var_store.fresh(), Loc::at_zero(Expr::EmptyRecord))]; + Expr::Call(Box::new(boxed), arguments, CalledVia::Space) + }; + + // `toEffect (thunk {})` + let mapper_call = { + let boxed = ( + var_store.fresh(), + Loc::at_zero(Expr::Var(mapper_symbol)), + var_store.fresh(), + var_store.fresh(), + ); + + let arguments = vec![(var_store.fresh(), Loc::at_zero(force_thunk_call))]; + Expr::Call(Box::new(boxed), arguments, CalledVia::Space) + }; + + let inner_closure_symbol = { + scope + .introduce( + "effect_map_inner".into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + Region::zero(), + ) + .unwrap() + }; + + // \{} -> mapper (thunk {}) + let inner_closure = { + let arguments = vec![( + var_store.fresh(), + Loc::at_zero(empty_record_pattern(var_store)), + )]; + + Expr::Closure(ClosureData { + function_type: var_store.fresh(), + closure_type: var_store.fresh(), + closure_ext_var: var_store.fresh(), + return_type: var_store.fresh(), + name: inner_closure_symbol, + captured_symbols: vec![ + (thunk_symbol, var_store.fresh()), + (mapper_symbol, var_store.fresh()), + ], + recursive: Recursive::NotRecursive, + arguments, + loc_body: Box::new(Loc::at_zero(mapper_call)), + }) + }; + + let arguments = vec![ + ( + var_store.fresh(), + Loc::at_zero(Pattern::AppliedTag { + whole_var: var_store.fresh(), + ext_var: var_store.fresh(), + tag_name: effect_tag_name.clone(), + arguments: vec![( + var_store.fresh(), + Loc::at_zero(Pattern::Identifier(thunk_symbol)), + )], + }), + ), + ( + var_store.fresh(), + Loc::at_zero(Pattern::Identifier(mapper_symbol)), + ), + ]; + + // `@Effect \{} -> (mapper (thunk {}))` + let body = Expr::Tag { + variant_var: var_store.fresh(), + ext_var: var_store.fresh(), + name: effect_tag_name.clone(), + arguments: vec![(var_store.fresh(), Loc::at_zero(inner_closure))], + }; + + let function_var = var_store.fresh(); + let map_closure = Expr::Closure(ClosureData { + function_type: function_var, + closure_type: var_store.fresh(), + closure_ext_var: var_store.fresh(), + return_type: var_store.fresh(), + name: map_symbol, + captured_symbols: Vec::new(), + recursive: Recursive::NotRecursive, + arguments, + loc_body: Box::new(Loc::at_zero(body)), + }); + + let mut introduced_variables = IntroducedVariables::default(); + + let signature = { + // Effect.map : Effect a, (a -> b) -> Effect b + let var_a = var_store.fresh(); + let var_b = var_store.fresh(); + + introduced_variables.insert_named("a".into(), var_a); + introduced_variables.insert_named("b".into(), var_b); + + let effect_a = build_effect_alias( + effect_symbol, + effect_tag_name.clone(), + "a", + var_a, + Type::Variable(var_a), + var_store, + &mut introduced_variables, + ); + + let effect_b = build_effect_alias( + effect_symbol, + effect_tag_name, + "b", + var_b, + Type::Variable(var_b), + var_store, + &mut introduced_variables, + ); + + let closure_var = var_store.fresh(); + introduced_variables.insert_wildcard(closure_var); + let a_to_b = { + Type::Function( + vec![Type::Variable(var_a)], + Box::new(Type::Variable(closure_var)), + Box::new(Type::Variable(var_b)), + ) + }; + + let closure_var = var_store.fresh(); + introduced_variables.insert_wildcard(closure_var); + Type::Function( + vec![effect_a, a_to_b], + Box::new(Type::Variable(closure_var)), + Box::new(effect_b), + ) + }; + + let def_annotation = crate::def::Annotation { + signature, + introduced_variables, + aliases: SendMap::default(), + region: Region::zero(), + }; + + let pattern = Pattern::Identifier(map_symbol); + let mut pattern_vars = SendMap::default(); + pattern_vars.insert(map_symbol, function_var); + let def = Def { + loc_pattern: Loc::at_zero(pattern), + loc_expr: Loc::at_zero(map_closure), + expr_var: function_var, + pattern_vars, + annotation: Some(def_annotation), + }; + + (map_symbol, def) +} + +fn build_effect_after( + env: &mut Env, + scope: &mut Scope, + effect_symbol: Symbol, + effect_tag_name: TagName, + var_store: &mut VarStore, +) -> (Symbol, Def) { + // Effect.after = \@Effect effect, toEffect -> toEffect (effect {}) + + let thunk_symbol = { + scope + .introduce( + "effect_after_thunk".into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + Region::zero(), + ) + .unwrap() + }; + + let to_effect_symbol = { + scope + .introduce( + "effect_after_toEffect".into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + Region::zero(), + ) + .unwrap() + }; + + let after_symbol = { + scope + .introduce( + "after".into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + Region::zero(), + ) + .unwrap() + }; + + // `thunk {}` + let force_thunk_call = { + let boxed = ( + var_store.fresh(), + Loc::at_zero(Expr::Var(thunk_symbol)), + var_store.fresh(), + var_store.fresh(), + ); + + let arguments = vec![(var_store.fresh(), Loc::at_zero(Expr::EmptyRecord))]; + Expr::Call(Box::new(boxed), arguments, CalledVia::Space) + }; + + // `toEffect (thunk {})` + let to_effect_call = { + let boxed = ( + var_store.fresh(), + Loc::at_zero(Expr::Var(to_effect_symbol)), + var_store.fresh(), + var_store.fresh(), + ); + + let arguments = vec![(var_store.fresh(), Loc::at_zero(force_thunk_call))]; + Expr::Call(Box::new(boxed), arguments, CalledVia::Space) + }; + + let arguments = vec![ + ( + var_store.fresh(), + Loc::at_zero(Pattern::AppliedTag { + whole_var: var_store.fresh(), + ext_var: var_store.fresh(), + tag_name: effect_tag_name.clone(), + arguments: vec![( + var_store.fresh(), + Loc::at_zero(Pattern::Identifier(thunk_symbol)), + )], + }), + ), + ( + var_store.fresh(), + Loc::at_zero(Pattern::Identifier(to_effect_symbol)), + ), + ]; + + let function_var = var_store.fresh(); + let after_closure = Expr::Closure(ClosureData { + function_type: function_var, + closure_type: var_store.fresh(), + closure_ext_var: var_store.fresh(), + return_type: var_store.fresh(), + name: after_symbol, + captured_symbols: Vec::new(), + recursive: Recursive::NotRecursive, + arguments, + loc_body: Box::new(Loc::at_zero(to_effect_call)), + }); + + let mut introduced_variables = IntroducedVariables::default(); + + let signature = { + let var_a = var_store.fresh(); + let var_b = var_store.fresh(); + + introduced_variables.insert_named("a".into(), var_a); + introduced_variables.insert_named("b".into(), var_b); + + let effect_a = build_effect_alias( + effect_symbol, + effect_tag_name.clone(), + "a", + var_a, + Type::Variable(var_a), + var_store, + &mut introduced_variables, + ); + + let effect_b = build_effect_alias( + effect_symbol, + effect_tag_name, + "b", + var_b, + Type::Variable(var_b), + var_store, + &mut introduced_variables, + ); + + let closure_var = var_store.fresh(); + introduced_variables.insert_wildcard(closure_var); + let a_to_effect_b = Type::Function( + vec![Type::Variable(var_a)], + Box::new(Type::Variable(closure_var)), + Box::new(effect_b.clone()), + ); + + let closure_var = var_store.fresh(); + introduced_variables.insert_wildcard(closure_var); + Type::Function( + vec![effect_a, a_to_effect_b], + Box::new(Type::Variable(closure_var)), + Box::new(effect_b), + ) + }; + + let def_annotation = crate::def::Annotation { + signature, + introduced_variables, + aliases: SendMap::default(), + region: Region::zero(), + }; + + let pattern = Pattern::Identifier(after_symbol); + let mut pattern_vars = SendMap::default(); + pattern_vars.insert(after_symbol, function_var); + let def = Def { + loc_pattern: Loc::at_zero(pattern), + loc_expr: Loc::at_zero(after_closure), + expr_var: function_var, + pattern_vars, + annotation: Some(def_annotation), + }; + + (after_symbol, def) +} + +/// turn `value` into `@Effect \{} -> value` +fn wrap_in_effect_thunk( + body: Expr, + effect_tag_name: TagName, + closure_name: Symbol, + captured_symbols: Vec, + var_store: &mut VarStore, +) -> Expr { + let captured_symbols: Vec<_> = captured_symbols + .into_iter() + .map(|x| (x, var_store.fresh())) + .collect(); + + // \{} -> body + let const_closure = { + let arguments = vec![( + var_store.fresh(), + Loc::at_zero(empty_record_pattern(var_store)), + )]; + + Expr::Closure(ClosureData { + function_type: var_store.fresh(), + closure_type: var_store.fresh(), + closure_ext_var: var_store.fresh(), + return_type: var_store.fresh(), + name: closure_name, + // captured_symbols: vec![(value_symbol, var_store.fresh())], + captured_symbols, + recursive: Recursive::NotRecursive, + arguments, + loc_body: Box::new(Loc::at_zero(body)), + }) + }; + + // `@Effect \{} -> value` + Expr::Tag { + variant_var: var_store.fresh(), + ext_var: var_store.fresh(), + name: effect_tag_name, + arguments: vec![(var_store.fresh(), Loc::at_zero(const_closure))], + } +} + +/// given `effect : Effect a`, unwrap the thunk and force it, giving a value of type `a` +fn force_effect( + effect: Expr, + effect_tag_name: TagName, + thunk_symbol: Symbol, + var_store: &mut VarStore, +) -> Expr { + let whole_var = var_store.fresh(); + let ext_var = var_store.fresh(); + + let thunk_var = var_store.fresh(); + + let pattern = Pattern::AppliedTag { + ext_var, + whole_var, + tag_name: effect_tag_name, + arguments: vec![(thunk_var, Loc::at_zero(Pattern::Identifier(thunk_symbol)))], + }; + + let pattern_vars = SendMap::default(); + // pattern_vars.insert(thunk_symbol, thunk_var); + + let def = Def { + loc_pattern: Loc::at_zero(pattern), + loc_expr: Loc::at_zero(effect), + expr_var: var_store.fresh(), + pattern_vars, + annotation: None, + }; + + let ret_var = var_store.fresh(); + + let force_thunk_call = { + let boxed = ( + var_store.fresh(), + Loc::at_zero(Expr::Var(thunk_symbol)), + var_store.fresh(), + ret_var, + ); + + let arguments = vec![(var_store.fresh(), Loc::at_zero(Expr::EmptyRecord))]; + let call = Expr::Call(Box::new(boxed), arguments, CalledVia::Space); + + Loc::at_zero(call) + }; + + Expr::LetNonRec(Box::new(def), Box::new(force_thunk_call), var_store.fresh()) +} + +fn build_effect_forever( + env: &mut Env, + scope: &mut Scope, + effect_symbol: Symbol, + effect_tag_name: TagName, + var_store: &mut VarStore, +) -> (Symbol, Def) { + // morally + // + // Effect.forever = \effect -> Effect.after effect (\_ -> Effect.forever effect) + // + // Here we inline the `Effect.after`, and get + // + // Effect.forever : Effect a -> Effect b + // Effect.forever = \effect -> + // @Effect \{} -> + // @Effect thunk1 = effect + // _ = thunk1 {} + // @Effect thunk2 = Effect.forever effect + // thunk2 {} + // + // We then rely on our defunctionalization to turn this into a tail-recursive loop. + // First the `@Effect` wrapper melts away + // + // Effect.forever : ({} -> a) -> ({} -> b) + // Effect.forever = \effect -> + // \{} -> + // thunk1 = effect + // _ = thunk1 {} + // thunk2 = Effect.forever effect + // thunk2 {} + // + // Then we defunctionalize + // + // foreverInner = \{}, { effect } -> + // thunk1 = effect + // _ = thunk1 {} + // thunk2 = Effect.forever effect + // thunk2 {} + // + // Effect.forever : [ C foreverInner { effect : T } ] + // Effect.forever = \effect -> + // C { effect } + // + // And we have to adjust the call + // + // foreverInner = \{}, { effect } -> + // thunk1 = effect + // _ = thunk1 {} + // thunk2 = Effect.forever effect + // when thunk2 is + // C env -> foreverInner {} env.effect + // + // Making `foreverInner` perfectly tail-call optimizable + + let forever_symbol = { + scope + .introduce( + "forever".into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + Region::zero(), + ) + .unwrap() + }; + + let effect = { + scope + .introduce( + "effect".into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + Region::zero(), + ) + .unwrap() + }; + + let body = build_effect_forever_body( + env, + scope, + effect_tag_name.clone(), + forever_symbol, + effect, + var_store, + ); + + let arguments = vec![(var_store.fresh(), Loc::at_zero(Pattern::Identifier(effect)))]; + + let function_var = var_store.fresh(); + let after_closure = Expr::Closure(ClosureData { + function_type: var_store.fresh(), + closure_type: var_store.fresh(), + closure_ext_var: var_store.fresh(), + return_type: var_store.fresh(), + name: forever_symbol, + captured_symbols: Vec::new(), + recursive: Recursive::Recursive, + arguments, + loc_body: Box::new(Loc::at_zero(body)), + }); + + let mut introduced_variables = IntroducedVariables::default(); + + let signature = { + let var_a = var_store.fresh(); + let var_b = var_store.fresh(); + + introduced_variables.insert_named("a".into(), var_a); + introduced_variables.insert_named("b".into(), var_b); + + let effect_a = build_effect_alias( + effect_symbol, + effect_tag_name.clone(), + "a", + var_a, + Type::Variable(var_a), + var_store, + &mut introduced_variables, + ); + + let effect_b = build_effect_alias( + effect_symbol, + effect_tag_name, + "b", + var_b, + Type::Variable(var_b), + var_store, + &mut introduced_variables, + ); + + let closure_var = var_store.fresh(); + introduced_variables.insert_wildcard(closure_var); + + Type::Function( + vec![effect_a], + Box::new(Type::Variable(closure_var)), + Box::new(effect_b), + ) + }; + + let def_annotation = crate::def::Annotation { + signature, + introduced_variables, + aliases: SendMap::default(), + region: Region::zero(), + }; + + let pattern = Pattern::Identifier(forever_symbol); + let mut pattern_vars = SendMap::default(); + pattern_vars.insert(forever_symbol, function_var); + let def = Def { + loc_pattern: Loc::at_zero(pattern), + loc_expr: Loc::at_zero(after_closure), + expr_var: function_var, + pattern_vars, + annotation: Some(def_annotation), + }; + + (forever_symbol, def) +} + +fn build_effect_forever_body( + env: &mut Env, + scope: &mut Scope, + effect_tag_name: TagName, + forever_symbol: Symbol, + effect: Symbol, + var_store: &mut VarStore, +) -> Expr { + let closure_name = { + scope + .introduce( + "forever_inner".into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + Region::zero(), + ) + .unwrap() + }; + + let inner_body = build_effect_forever_inner_body( + env, + scope, + effect_tag_name.clone(), + forever_symbol, + effect, + var_store, + ); + + let captured_symbols = vec![effect]; + wrap_in_effect_thunk( + inner_body, + effect_tag_name, + closure_name, + captured_symbols, + var_store, + ) +} + +fn build_effect_forever_inner_body( + env: &mut Env, + scope: &mut Scope, + effect_tag_name: TagName, + forever_symbol: Symbol, + effect: Symbol, + var_store: &mut VarStore, +) -> Expr { + let thunk1_symbol = { + scope + .introduce( + "thunk1".into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + Region::zero(), + ) + .unwrap() + }; + + let thunk2_symbol = { + scope + .introduce( + "thunk2".into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + Region::zero(), + ) + .unwrap() + }; + + // Effect thunk1 = effect + let thunk_from_effect = { + let whole_var = var_store.fresh(); + let ext_var = var_store.fresh(); + + let thunk_var = var_store.fresh(); + + let pattern = Pattern::AppliedTag { + ext_var, + whole_var, + tag_name: effect_tag_name.clone(), + arguments: vec![(thunk_var, Loc::at_zero(Pattern::Identifier(thunk1_symbol)))], + }; + + let pattern_vars = SendMap::default(); + + Def { + loc_pattern: Loc::at_zero(pattern), + loc_expr: Loc::at_zero(Expr::Var(effect)), + expr_var: var_store.fresh(), + pattern_vars, + annotation: None, + } + }; + + // thunk1 {} + let force_thunk_call = { + let ret_var = var_store.fresh(); + let boxed = ( + var_store.fresh(), + Loc::at_zero(Expr::Var(thunk1_symbol)), + var_store.fresh(), + ret_var, + ); + + let arguments = vec![(var_store.fresh(), Loc::at_zero(Expr::EmptyRecord))]; + let call = Expr::Call(Box::new(boxed), arguments, CalledVia::Space); + + Loc::at_zero(call) + }; + + // _ = thunk1 {} + let force_thunk1 = Def { + loc_pattern: Loc::at_zero(Pattern::Underscore), + loc_expr: force_thunk_call, + expr_var: var_store.fresh(), + pattern_vars: Default::default(), + annotation: None, + }; + + // recursive call `forever effect` + let forever_effect = { + let boxed = ( + var_store.fresh(), + Loc::at_zero(Expr::Var(forever_symbol)), + var_store.fresh(), + var_store.fresh(), + ); + + let arguments = vec![(var_store.fresh(), Loc::at_zero(Expr::Var(effect)))]; + Expr::Call(Box::new(boxed), arguments, CalledVia::Space) + }; + + // ``` + // Effect thunk2 = forever effect + // thunk2 {} + // ``` + let force_thunk2 = Loc::at_zero(force_effect( + forever_effect, + effect_tag_name, + thunk2_symbol, + var_store, + )); + + Expr::LetNonRec( + Box::new(thunk_from_effect), + Box::new(Loc::at_zero(Expr::LetNonRec( + Box::new(force_thunk1), + Box::new(force_thunk2), + var_store.fresh(), + ))), + var_store.fresh(), + ) +} + +fn build_effect_loop( + env: &mut Env, + scope: &mut Scope, + effect_symbol: Symbol, + effect_tag_name: TagName, + var_store: &mut VarStore, +) -> (Symbol, Def) { + let loop_symbol = new_symbol!(scope, env, "loop"); + let state_symbol = new_symbol!(scope, env, "state"); + let step_symbol = new_symbol!(scope, env, "step"); + + let body = build_effect_loop_body( + env, + scope, + effect_tag_name.clone(), + loop_symbol, + state_symbol, + step_symbol, + var_store, + ); + + let arguments = vec![ + ( + var_store.fresh(), + Loc::at_zero(Pattern::Identifier(state_symbol)), + ), + ( + var_store.fresh(), + Loc::at_zero(Pattern::Identifier(step_symbol)), + ), + ]; + + let function_var = var_store.fresh(); + let after_closure = Expr::Closure(ClosureData { + function_type: var_store.fresh(), + closure_type: var_store.fresh(), + closure_ext_var: var_store.fresh(), + return_type: var_store.fresh(), + name: loop_symbol, + captured_symbols: Vec::new(), + recursive: Recursive::Recursive, + arguments, + loc_body: Box::new(Loc::at_zero(body)), + }); + + let mut introduced_variables = IntroducedVariables::default(); + + let signature = { + let var_a = var_store.fresh(); + let var_b = var_store.fresh(); + + introduced_variables.insert_named("a".into(), var_a); + introduced_variables.insert_named("b".into(), var_b); + + let effect_b = build_effect_alias( + effect_symbol, + effect_tag_name.clone(), + "b", + var_b, + Type::Variable(var_b), + var_store, + &mut introduced_variables, + ); + + let state_type = { + let step_tag_name = TagName::Global("Step".into()); + let done_tag_name = TagName::Global("Done".into()); + + Type::TagUnion( + vec![ + (step_tag_name, vec![Type::Variable(var_a)]), + (done_tag_name, vec![Type::Variable(var_b)]), + ], + Box::new(Type::EmptyTagUnion), + ) + }; + + let effect_state_type = { + let closure_var = var_store.fresh(); + introduced_variables.insert_wildcard(closure_var); + + let actual = { + Type::TagUnion( + vec![( + effect_tag_name, + vec![Type::Function( + vec![Type::EmptyRec], + Box::new(Type::Variable(closure_var)), + Box::new(state_type.clone()), + )], + )], + Box::new(Type::EmptyTagUnion), + ) + }; + + Type::Alias { + symbol: effect_symbol, + type_arguments: vec![("a".into(), state_type)], + lambda_set_variables: vec![roc_types::types::LambdaSet(Type::Variable( + closure_var, + ))], + actual: Box::new(actual), + } + }; + + let closure_var = var_store.fresh(); + introduced_variables.insert_wildcard(closure_var); + + let step_type = Type::Function( + vec![Type::Variable(var_a)], + Box::new(Type::Variable(closure_var)), + Box::new(effect_state_type), + ); + + let closure_var = var_store.fresh(); + introduced_variables.insert_wildcard(closure_var); + + Type::Function( + vec![Type::Variable(var_a), step_type], + Box::new(Type::Variable(closure_var)), + Box::new(effect_b), + ) + }; + + let def_annotation = crate::def::Annotation { + signature, + introduced_variables, + aliases: SendMap::default(), + region: Region::zero(), + }; + + let pattern = Pattern::Identifier(loop_symbol); + let mut pattern_vars = SendMap::default(); + pattern_vars.insert(loop_symbol, function_var); + let def = Def { + loc_pattern: Loc::at_zero(pattern), + loc_expr: Loc::at_zero(after_closure), + expr_var: function_var, + pattern_vars, + annotation: Some(def_annotation), + }; + + (loop_symbol, def) +} + +fn build_effect_loop_body( + env: &mut Env, + scope: &mut Scope, + effect_tag_name: TagName, + loop_symbol: Symbol, + state_symbol: Symbol, + step_symbol: Symbol, + var_store: &mut VarStore, +) -> Expr { + let closure_name = { + scope + .introduce( + "loop_inner".into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + Region::zero(), + ) + .unwrap() + }; + + let inner_body = build_effect_loop_inner_body( + env, + scope, + effect_tag_name.clone(), + loop_symbol, + state_symbol, + step_symbol, + var_store, + ); + + let captured_symbols = vec![state_symbol, step_symbol]; + wrap_in_effect_thunk( + inner_body, + effect_tag_name, + closure_name, + captured_symbols, + var_store, + ) +} + +fn applied_tag_pattern( + tag_name: TagName, + argument_symbols: &[Symbol], + var_store: &mut VarStore, +) -> Pattern { + let arguments = argument_symbols + .iter() + .map(|s| { + let pattern = Pattern::Identifier(*s); + + (var_store.fresh(), Loc::at_zero(pattern)) + }) + .collect(); + + Pattern::AppliedTag { + ext_var: var_store.fresh(), + whole_var: var_store.fresh(), + tag_name, + arguments, + } +} + +fn build_effect_loop_inner_body( + env: &mut Env, + scope: &mut Scope, + effect_tag_name: TagName, + loop_symbol: Symbol, + state_symbol: Symbol, + step_symbol: Symbol, + var_store: &mut VarStore, +) -> Expr { + let thunk1_symbol = new_symbol!(scope, env, "thunk3"); + let thunk2_symbol = new_symbol!(scope, env, "thunk4"); + + let new_state_symbol = new_symbol!(scope, env, "newState"); + let done_symbol = new_symbol!(scope, env, "done"); + + // Effect thunk1 = step state + let thunk_from_effect = { + let whole_var = var_store.fresh(); + let ext_var = var_store.fresh(); + + let thunk_var = var_store.fresh(); + + let pattern = Pattern::AppliedTag { + ext_var, + whole_var, + tag_name: effect_tag_name.clone(), + arguments: vec![(thunk_var, Loc::at_zero(Pattern::Identifier(thunk1_symbol)))], + }; + + let pattern_vars = SendMap::default(); + + // `step state` + let rhs = { + let boxed = ( + var_store.fresh(), + Loc::at_zero(Expr::Var(step_symbol)), + var_store.fresh(), + var_store.fresh(), + ); + + let arguments = vec![(var_store.fresh(), Loc::at_zero(Expr::Var(state_symbol)))]; + Expr::Call(Box::new(boxed), arguments, CalledVia::Space) + }; + + Def { + loc_pattern: Loc::at_zero(pattern), + loc_expr: Loc::at_zero(rhs), + expr_var: var_store.fresh(), + pattern_vars, + annotation: None, + } + }; + + // thunk1 {} + let force_thunk_call = { + let ret_var = var_store.fresh(); + let boxed = ( + var_store.fresh(), + Loc::at_zero(Expr::Var(thunk1_symbol)), + var_store.fresh(), + ret_var, + ); + + let arguments = vec![(var_store.fresh(), Loc::at_zero(Expr::EmptyRecord))]; + let call = Expr::Call(Box::new(boxed), arguments, CalledVia::Space); + + Loc::at_zero(call) + }; + + // recursive call `loop newState step` + let loop_new_state_step = { + let boxed = ( + var_store.fresh(), + Loc::at_zero(Expr::Var(loop_symbol)), + var_store.fresh(), + var_store.fresh(), + ); + + let arguments = vec![ + (var_store.fresh(), Loc::at_zero(Expr::Var(new_state_symbol))), + (var_store.fresh(), Loc::at_zero(Expr::Var(step_symbol))), + ]; + Expr::Call(Box::new(boxed), arguments, CalledVia::Space) + }; + + // ``` + // Effect thunk2 = loop effect + // thunk2 {} + // ``` + let force_thunk2 = force_effect( + loop_new_state_step, + effect_tag_name, + thunk2_symbol, + var_store, + ); + + let step_branch = { + let step_tag_name = TagName::Global("Step".into()); + + let step_pattern = applied_tag_pattern(step_tag_name, &[new_state_symbol], var_store); + + crate::expr::WhenBranch { + patterns: vec![Loc::at_zero(step_pattern)], + value: Loc::at_zero(force_thunk2), + guard: None, + } + }; + + let done_branch = { + let done_tag_name = TagName::Global("Done".into()); + let done_pattern = applied_tag_pattern(done_tag_name, &[done_symbol], var_store); + + crate::expr::WhenBranch { + patterns: vec![Loc::at_zero(done_pattern)], + value: Loc::at_zero(Expr::Var(done_symbol)), + guard: None, + } + }; + + let branches = vec![step_branch, done_branch]; + + let match_on_force_thunk1 = Expr::When { + cond_var: var_store.fresh(), + expr_var: var_store.fresh(), + region: Region::zero(), + loc_cond: Box::new(force_thunk_call), + branches, + }; + + Expr::LetNonRec( + Box::new(thunk_from_effect), + Box::new(Loc::at_zero(match_on_force_thunk1)), + var_store.fresh(), + ) +} + +pub fn build_host_exposed_def( + env: &mut Env, + scope: &mut Scope, + symbol: Symbol, + ident: &str, + effect_tag_name: TagName, + var_store: &mut VarStore, + annotation: crate::annotation::Annotation, +) -> Def { + let expr_var = var_store.fresh(); + let pattern = Pattern::Identifier(symbol); + let mut pattern_vars = SendMap::default(); + pattern_vars.insert(symbol, expr_var); + + let mut arguments: Vec<(Variable, Loc)> = Vec::new(); + let mut linked_symbol_arguments: Vec<(Variable, Expr)> = Vec::new(); + let mut captured_symbols: Vec<(Symbol, Variable)> = Vec::new(); + + let def_body = { + match annotation.typ.shallow_dealias() { + Type::Function(args, _, _) => { + for i in 0..args.len() { + let name = format!("closure_arg_{}_{}", ident, i); + + let arg_symbol = { + let ident = name.clone().into(); + scope + .introduce( + ident, + &env.exposed_ident_ids, + &mut env.ident_ids, + Region::zero(), + ) + .unwrap() + }; + + let arg_var = var_store.fresh(); + + arguments.push((arg_var, Loc::at_zero(Pattern::Identifier(arg_symbol)))); + + captured_symbols.push((arg_symbol, arg_var)); + linked_symbol_arguments.push((arg_var, Expr::Var(arg_symbol))); + } + + let foreign_symbol_name = format!("roc_fx_{}", ident); + let low_level_call = Expr::ForeignCall { + foreign_symbol: foreign_symbol_name.into(), + args: linked_symbol_arguments, + ret_var: var_store.fresh(), + }; + + let effect_closure_symbol = { + let name = format!("effect_closure_{}", ident); + + let ident = name.into(); + scope + .introduce( + ident, + &env.exposed_ident_ids, + &mut env.ident_ids, + Region::zero(), + ) + .unwrap() + }; + + let effect_closure = Expr::Closure(ClosureData { + function_type: var_store.fresh(), + closure_type: var_store.fresh(), + closure_ext_var: var_store.fresh(), + return_type: var_store.fresh(), + name: effect_closure_symbol, + captured_symbols, + recursive: Recursive::NotRecursive, + arguments: vec![( + var_store.fresh(), + Loc::at_zero(empty_record_pattern(var_store)), + )], + loc_body: Box::new(Loc::at_zero(low_level_call)), + }); + + let body = Expr::Tag { + variant_var: var_store.fresh(), + ext_var: var_store.fresh(), + name: effect_tag_name, + arguments: vec![(var_store.fresh(), Loc::at_zero(effect_closure))], + }; + + Expr::Closure(ClosureData { + function_type: var_store.fresh(), + closure_type: var_store.fresh(), + closure_ext_var: var_store.fresh(), + return_type: var_store.fresh(), + name: symbol, + captured_symbols: std::vec::Vec::new(), + recursive: Recursive::NotRecursive, + arguments, + loc_body: Box::new(Loc::at_zero(body)), + }) + } + _ => { + // not a function + + let foreign_symbol_name = format!("roc_fx_{}", ident); + let low_level_call = Expr::ForeignCall { + foreign_symbol: foreign_symbol_name.into(), + args: linked_symbol_arguments, + ret_var: var_store.fresh(), + }; + + let effect_closure_symbol = { + let name = format!("effect_closure_{}", ident); + + let ident = name.into(); + scope + .introduce( + ident, + &env.exposed_ident_ids, + &mut env.ident_ids, + Region::zero(), + ) + .unwrap() + }; + + let empty_record_pattern = Pattern::RecordDestructure { + whole_var: var_store.fresh(), + ext_var: var_store.fresh(), + destructs: vec![], + }; + + let effect_closure = Expr::Closure(ClosureData { + function_type: var_store.fresh(), + closure_type: var_store.fresh(), + closure_ext_var: var_store.fresh(), + return_type: var_store.fresh(), + name: effect_closure_symbol, + captured_symbols, + recursive: Recursive::NotRecursive, + arguments: vec![(var_store.fresh(), Loc::at_zero(empty_record_pattern))], + loc_body: Box::new(Loc::at_zero(low_level_call)), + }); + + Expr::Tag { + variant_var: var_store.fresh(), + ext_var: var_store.fresh(), + name: effect_tag_name, + arguments: vec![(var_store.fresh(), Loc::at_zero(effect_closure))], + } + } + } + }; + + let def_annotation = crate::def::Annotation { + signature: annotation.typ, + introduced_variables: annotation.introduced_variables, + aliases: annotation.aliases, + region: Region::zero(), + }; + + Def { + loc_pattern: Loc::at_zero(pattern), + loc_expr: Loc::at_zero(def_body), + expr_var, + pattern_vars, + annotation: Some(def_annotation), + } +} + +fn build_effect_alias( + effect_symbol: Symbol, + effect_tag_name: TagName, + a_name: &str, + a_var: Variable, + a_type: Type, + var_store: &mut VarStore, + introduced_variables: &mut IntroducedVariables, +) -> Type { + let closure_var = var_store.fresh(); + introduced_variables.insert_wildcard(closure_var); + + let actual = { + Type::TagUnion( + vec![( + effect_tag_name, + vec![Type::Function( + vec![Type::EmptyRec], + Box::new(Type::Variable(closure_var)), + Box::new(a_type), + )], + )], + Box::new(Type::EmptyTagUnion), + ) + }; + + Type::Alias { + symbol: effect_symbol, + 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), + } +} + +pub fn build_effect_actual( + effect_tag_name: TagName, + a_type: Type, + var_store: &mut VarStore, +) -> Type { + let closure_var = var_store.fresh(); + + Type::TagUnion( + vec![( + effect_tag_name, + vec![Type::Function( + vec![Type::EmptyRec], + Box::new(Type::Variable(closure_var)), + Box::new(a_type), + )], + )], + Box::new(Type::EmptyTagUnion), + ) +} + +#[inline(always)] +fn empty_record_pattern(var_store: &mut VarStore) -> Pattern { + Pattern::RecordDestructure { + whole_var: var_store.fresh(), + ext_var: var_store.fresh(), + destructs: vec![], + } +} diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index baa558c303..86579c248f 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -3,8 +3,8 @@ use crate::builtins::builtin_defs_map; use crate::def::{can_defs_with_return, Def}; use crate::env::Env; use crate::num::{ - finish_parsing_base, finish_parsing_float, finish_parsing_int, float_expr_from_result, - int_expr_from_result, num_expr_from_result, + finish_parsing_base, finish_parsing_float, finish_parsing_num, float_expr_from_result, + int_expr_from_result, num_expr_from_result, FloatBound, IntBound, NumericBound, }; use crate::pattern::{canonicalize_pattern, Pattern}; use crate::procedure::References; @@ -20,7 +20,7 @@ use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError}; use roc_region::all::{Loc, Region}; use roc_types::subs::{VarStore, Variable}; use roc_types::types::Alias; -use std::fmt::Debug; +use std::fmt::{Debug, Display}; use std::{char, u32}; #[derive(Clone, Default, Debug, PartialEq)] @@ -46,17 +46,32 @@ impl Output { } } +#[derive(Clone, Debug, PartialEq)] +pub enum IntValue { + I128(i128), + U128(u128), +} + +impl Display for IntValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + IntValue::I128(n) => Display::fmt(&n, f), + IntValue::U128(n) => Display::fmt(&n, f), + } + } +} + #[derive(Clone, Debug, PartialEq)] pub enum Expr { // Literals // 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, i64), + Num(Variable, Box, IntValue, NumericBound), // Int and Float store a variable to generate better error messages - Int(Variable, Variable, Box, i128), - Float(Variable, Variable, Box, f64), + Int(Variable, Variable, Box, IntValue, IntBound), + Float(Variable, Variable, Box, f64, FloatBound), Str(Box), List { elem_var: Variable, @@ -208,20 +223,20 @@ pub fn canonicalize_expr<'a>( use Expr::*; let (expr, output) = match expr { - ast::Expr::Num(str) => { + &ast::Expr::Num(str) => { let answer = num_expr_from_result( var_store, - finish_parsing_int(*str).map(|int| (*str, int)), + finish_parsing_num(str).map(|result| (str, result)), region, env, ); (answer, Output::default()) } - ast::Expr::Float(str) => { + &ast::Expr::Float(str) => { let answer = float_expr_from_result( var_store, - finish_parsing_float(str).map(|f| (*str, f)), + finish_parsing_float(str).map(|(f, bound)| (str, f, bound)), region, env, ); @@ -758,13 +773,13 @@ pub fn canonicalize_expr<'a>( let region1 = Region::new( *binop1_position, - binop1_position.bump_column(binop1.width()), + binop1_position.bump_column(binop1.width() as u32), ); let loc_binop1 = Loc::at(region1, *binop1); let region2 = Region::new( *binop2_position, - binop2_position.bump_column(binop2.width()), + binop2_position.bump_column(binop2.width() as u32), ); let loc_binop2 = Loc::at(region2, *binop2); @@ -790,21 +805,21 @@ pub fn canonicalize_expr<'a>( (RuntimeError(problem), Output::default()) } - ast::Expr::NonBase10Int { + &ast::Expr::NonBase10Int { string, base, is_negative, } => { // the minus sign is added before parsing, to get correct overflow/underflow behavior - let answer = match finish_parsing_base(string, *base, *is_negative) { - Ok(int) => { + let answer = match finish_parsing_base(string, base, is_negative) { + Ok((int, bound)) => { // Done in this kinda round about way with intermediate variables // to keep borrowed values around and make this compile let int_string = int.to_string(); let int_str = int_string.as_str(); - int_expr_from_result(var_store, Ok((int_str, int as i128)), region, *base, env) + int_expr_from_result(var_store, Ok((int_str, int, bound)), region, base, env) } - Err(e) => int_expr_from_result(var_store, Err(e), region, *base, env), + Err(e) => int_expr_from_result(var_store, Err(e), region, base, env), }; (answer, Output::default()) @@ -1226,9 +1241,9 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) -> match 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 - other @ Num(_, _, _) - | other @ Int(_, _, _, _) - | other @ Float(_, _, _, _) + other @ Num(..) + | other @ Int(..) + | other @ Float(..) | other @ Str { .. } | other @ RuntimeError(_) | other @ EmptyRecord @@ -1680,22 +1695,22 @@ fn desugar_str_segments(var_store: &mut VarStore, segments: Vec) -> let mut iter = segments.into_iter().rev(); let mut loc_expr = match iter.next() { - Some(Plaintext(string)) => Loc::new(0, 0, 0, 0, Expr::Str(string)), + Some(Plaintext(string)) => Loc::at(Region::zero(), Expr::Str(string)), Some(Interpolation(loc_expr)) => loc_expr, None => { // No segments? Empty string! - Loc::new(0, 0, 0, 0, Expr::Str("".into())) + Loc::at(Region::zero(), Expr::Str("".into())) } }; for seg in iter { let loc_new_expr = match seg { - Plaintext(string) => Loc::new(0, 0, 0, 0, Expr::Str(string)), + Plaintext(string) => Loc::at(Region::zero(), Expr::Str(string)), Interpolation(loc_interpolated_expr) => loc_interpolated_expr, }; - let fn_expr = Loc::new(0, 0, 0, 0, Expr::Var(Symbol::STR_CONCAT)); + let fn_expr = Loc::at(Region::zero(), Expr::Var(Symbol::STR_CONCAT)); let expr = Expr::Call( Box::new(( var_store.fresh(), @@ -1710,7 +1725,7 @@ fn desugar_str_segments(var_store: &mut VarStore, segments: Vec) -> CalledVia::StringInterpolation, ); - loc_expr = Loc::new(0, 0, 0, 0, expr); + loc_expr = Loc::at(Region::zero(), expr); } loc_expr.value diff --git a/compiler/can/src/lib.rs b/compiler/can/src/lib.rs index 7230ab1a36..fd813a423f 100644 --- a/compiler/can/src/lib.rs +++ b/compiler/can/src/lib.rs @@ -5,6 +5,7 @@ pub mod annotation; pub mod builtins; pub mod constraint; pub mod def; +pub mod effect_module; pub mod env; pub mod expected; pub mod expr; diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index 48837c4d3f..8fe6ff5cef 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -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; @@ -6,15 +7,16 @@ use crate::pattern::Pattern; use crate::scope::Scope; use bumpalo::Bump; use roc_collections::all::{MutMap, MutSet, SendMap}; -use roc_module::ident::Ident; use roc_module::ident::Lowercase; +use roc_module::ident::{Ident, TagName}; use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; use roc_parse::ast; +use roc_parse::header::HeaderFor; use roc_parse::pattern::PatternType; use roc_problem::can::{Problem, RuntimeError}; use roc_region::all::{Loc, Region}; use roc_types::subs::{VarStore, Variable}; -use roc_types::types::Alias; +use roc_types::types::{Alias, Type}; #[derive(Debug)] pub struct Module { @@ -39,11 +41,36 @@ pub struct ModuleOutput { pub scope: Scope, } +fn validate_generate_with<'a>( + generate_with: &'a [Loc>], +) -> (HostedGeneratedFunctions, Vec>) { + 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>( arena: &Bump, loc_defs: &'a [Loc>], + header_for: &roc_parse::header::HeaderFor, home: ModuleId, module_ids: &ModuleIds, exposed_ident_ids: IdentIds, @@ -59,12 +86,66 @@ where { 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); } + struct Hosted { + effect_symbol: Symbol, + generated_functions: HostedGeneratedFunctions, + } + + let hosted_info = if let HeaderFor::Hosted { + generates, + generates_with, + } = header_for + { + let name: &str = generates.into(); + let (generated_functions, unknown_generated) = validate_generate_with(generates_with); + + for unknown in unknown_generated { + env.problem(Problem::UnknownGeneratesWith(unknown)); + } + + let effect_symbol = scope + .introduce( + name.into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + Region::zero(), + ) + .unwrap(); + + let effect_tag_name = TagName::Private(effect_symbol); + + { + let a_var = var_store.fresh(); + + let actual = crate::effect_module::build_effect_actual( + effect_tag_name, + Type::Variable(a_var), + var_store, + ); + + scope.add_alias( + effect_symbol, + Region::zero(), + vec![Loc::at_zero(("a".into(), a_var))], + actual, + ); + } + + Some(Hosted { + effect_symbol, + generated_functions, + }) + } else { + None + }; + // Desugar operators (convert them to Apply calls, taking into account // operator precedence and associativity rules), before doing other canonicalization. // @@ -82,7 +163,6 @@ where })); } - let mut env = Env::new(home, dep_idents, module_ids, exposed_ident_ids); let mut lookups = Vec::with_capacity(num_deps); let mut rigid_variables = MutMap::default(); @@ -124,7 +204,11 @@ where // This is a type alias // the symbol should already be added to the scope when this module is canonicalized - debug_assert!(scope.contains_alias(symbol)); + debug_assert!( + scope.contains_alias(symbol), + "apparently, {:?} is not actually a type alias", + symbol + ); // but now we know this symbol by a different identifier, so we still need to add it to // the scope @@ -139,7 +223,7 @@ where } } - let (defs, scope, output, symbols_introduced) = canonicalize_defs( + let (defs, mut scope, output, symbols_introduced) = canonicalize_defs( &mut env, Output::default(), var_store, @@ -181,6 +265,17 @@ where references.insert(*symbol); } + // add any builtins used by other builtins + let transitive_builtins: Vec = references + .iter() + .filter(|s| s.is_builtin()) + .map(|s| crate::builtins::builtin_dependencies(*s)) + .flatten() + .copied() + .collect(); + + references.extend(transitive_builtins); + // NOTE previously we inserted builtin defs into the list of defs here // this is now done later, in file.rs. @@ -193,7 +288,26 @@ where (Ok(mut declarations), output) => { use crate::def::Declaration::*; - for decl in declarations.iter() { + if let Some(Hosted { + effect_symbol, + generated_functions, + }) = hosted_info + { + let mut exposed_symbols = MutSet::default(); + + // NOTE this currently builds all functions, not just the ones that the user requested + crate::effect_module::build_effect_builtins( + &mut env, + &mut scope, + effect_symbol, + var_store, + &mut exposed_symbols, + &mut declarations, + generated_functions, + ); + } + + for decl in declarations.iter_mut() { match decl { Declare(def) => { for (symbol, _) in def.pattern_vars.iter() { @@ -206,6 +320,59 @@ where exposed_but_not_defined.remove(symbol); } } + + // Temporary hack: we don't know exactly what symbols are hosted symbols, + // and which are meant to be normal definitions without a body. So for now + // we just assume they are hosted functions (meant to be provided by the platform) + if let Some(Hosted { effect_symbol, .. }) = hosted_info { + macro_rules! make_hosted_def { + () => { + let symbol = def.pattern_vars.iter().next().unwrap().0; + let ident_id = symbol.ident_id(); + let ident = + env.ident_ids.get_name(ident_id).unwrap().to_string(); + let def_annotation = def.annotation.clone().unwrap(); + let annotation = crate::annotation::Annotation { + typ: def_annotation.signature, + introduced_variables: def_annotation.introduced_variables, + references: Default::default(), + aliases: Default::default(), + }; + + let hosted_def = crate::effect_module::build_host_exposed_def( + &mut env, + &mut scope, + *symbol, + &ident, + TagName::Private(effect_symbol), + var_store, + annotation, + ); + + *def = hosted_def; + }; + } + + match &def.loc_expr.value { + Expr::RuntimeError(RuntimeError::NoImplementationNamed { + .. + }) => { + make_hosted_def!(); + } + Expr::Closure(closure_data) + if matches!( + closure_data.loc_body.value, + Expr::RuntimeError( + RuntimeError::NoImplementationNamed { .. } + ) + ) => + { + make_hosted_def!(); + } + + _ => {} + } + } } DeclareRec(defs) => { for def in defs { @@ -238,6 +405,18 @@ where let mut aliases = MutMap::default(); + if let Some(Hosted { effect_symbol, .. }) = hosted_info { + // Remove this from exposed_symbols, + // so that at the end of the process, + // we can see if there were any + // exposed symbols which did not have + // corresponding defs. + exposed_but_not_defined.remove(&effect_symbol); + + let hosted_alias = scope.lookup_alias(effect_symbol).unwrap().clone(); + aliases.insert(effect_symbol, hosted_alias); + } + for (symbol, alias) in output.aliases { // Remove this from exposed_symbols, // so that at the end of the process, @@ -263,8 +442,8 @@ where let runtime_error = RuntimeError::ExposedButNotDefined(symbol); let def = Def { - loc_pattern: Loc::new(0, 0, 0, 0, Pattern::Identifier(symbol)), - loc_expr: Loc::new(0, 0, 0, 0, Expr::RuntimeError(runtime_error)), + loc_pattern: Loc::at(Region::zero(), Pattern::Identifier(symbol)), + loc_expr: Loc::at(Region::zero(), Expr::RuntimeError(runtime_error)), expr_var: var_store.fresh(), pattern_vars, annotation: None, @@ -382,12 +561,12 @@ fn fix_values_captured_in_closure_pattern( } } Identifier(_) - | NumLiteral(_, _, _) - | IntLiteral(_, _, _) - | FloatLiteral(_, _, _) + | NumLiteral(..) + | IntLiteral(..) + | FloatLiteral(..) | StrLiteral(_) | Underscore - | Shadowed(_, _) + | Shadowed(..) | MalformedPattern(_, _) | UnsupportedPattern(_) => (), } @@ -438,9 +617,9 @@ fn fix_values_captured_in_closure_expr( fix_values_captured_in_closure_expr(&mut loc_body.value, no_capture_symbols); } - Num(_, _, _) - | Int(_, _, _, _) - | Float(_, _, _, _) + Num(..) + | Int(..) + | Float(..) | Str(_) | Var(_) | EmptyRecord diff --git a/compiler/can/src/num.rs b/compiler/can/src/num.rs index aff1231def..33c6d9bb6a 100644 --- a/compiler/can/src/num.rs +++ b/compiler/can/src/num.rs @@ -1,5 +1,5 @@ use crate::env::Env; -use crate::expr::Expr; +use crate::expr::{Expr, IntValue}; use roc_parse::ast::Base; use roc_problem::can::Problem; use roc_problem::can::RuntimeError::*; @@ -7,21 +7,33 @@ use roc_problem::can::{FloatErrorKind, IntErrorKind}; use roc_region::all::Region; use roc_types::subs::VarStore; use std::i64; - -// TODO use rust's integer parsing again -// -// We're waiting for libcore here, see https://github.com/rust-lang/rust/issues/22639 -// There is a nightly API for exposing the parse error. +use std::str; #[inline(always)] pub fn num_expr_from_result( var_store: &mut VarStore, - result: Result<(&str, i64), (&str, IntErrorKind)>, + result: Result<(&str, ParsedNumResult), (&str, IntErrorKind)>, region: Region, env: &mut Env, ) -> Expr { match result { - Ok((str, num)) => Expr::Num(var_store.fresh(), (*str).into(), num), + Ok((str, ParsedNumResult::UnknownNum(num, bound))) => { + Expr::Num(var_store.fresh(), (*str).into(), num, bound) + } + Ok((str, ParsedNumResult::Int(num, bound))) => Expr::Int( + var_store.fresh(), + var_store.fresh(), + (*str).into(), + num, + bound, + ), + Ok((str, ParsedNumResult::Float(num, bound))) => Expr::Float( + var_store.fresh(), + var_store.fresh(), + (*str).into(), + num, + bound, + ), Err((raw, error)) => { // (Num *) compiles to Int if it doesn't // get specialized to something else first, @@ -38,14 +50,20 @@ pub fn num_expr_from_result( #[inline(always)] pub fn int_expr_from_result( var_store: &mut VarStore, - result: Result<(&str, i128), (&str, IntErrorKind)>, + result: Result<(&str, IntValue, IntBound), (&str, IntErrorKind)>, region: Region, base: Base, env: &mut Env, ) -> Expr { // Int stores a variable to generate better error messages match result { - Ok((str, int)) => Expr::Int(var_store.fresh(), var_store.fresh(), (*str).into(), int), + Ok((str, int, bound)) => Expr::Int( + var_store.fresh(), + var_store.fresh(), + (*str).into(), + int, + bound, + ), Err((raw, error)) => { let runtime_error = InvalidInt(error, base, region, raw.into()); @@ -59,13 +77,19 @@ pub fn int_expr_from_result( #[inline(always)] pub fn float_expr_from_result( var_store: &mut VarStore, - result: Result<(&str, f64), (&str, FloatErrorKind)>, + result: Result<(&str, f64, FloatBound), (&str, FloatErrorKind)>, region: Region, env: &mut Env, ) -> Expr { // Float stores a variable to generate better error messages match result { - Ok((str, float)) => Expr::Float(var_store.fresh(), var_store.fresh(), (*str).into(), float), + Ok((str, float, bound)) => Expr::Float( + var_store.fresh(), + var_store.fresh(), + (*str).into(), + float, + bound, + ), Err((raw, error)) => { let runtime_error = InvalidFloat(error, region, raw.into()); @@ -76,11 +100,17 @@ pub fn float_expr_from_result( } } +pub enum ParsedNumResult { + Int(IntValue, IntBound), + Float(f64, FloatBound), + UnknownNum(IntValue, NumericBound), +} + #[inline(always)] -pub fn finish_parsing_int(raw: &str) -> Result { +pub fn finish_parsing_num(raw: &str) -> Result { // Ignore underscores. let radix = 10; - from_str_radix::(raw.replace("_", "").as_str(), radix).map_err(|e| (raw, e.kind)) + from_str_radix(raw.replace("_", "").as_str(), radix).map_err(|e| (raw, e)) } #[inline(always)] @@ -88,7 +118,7 @@ pub fn finish_parsing_base( raw: &str, base: Base, is_negative: bool, -) -> Result { +) -> Result<(IntValue, IntBound), (&str, IntErrorKind)> { let radix = match base { Base::Hex => 16, Base::Decimal => 10, @@ -98,18 +128,34 @@ pub fn finish_parsing_base( // Ignore underscores, insert - when negative to get correct underflow/overflow behavior (if is_negative { - from_str_radix::(format!("-{}", raw.replace("_", "")).as_str(), radix) + from_str_radix(format!("-{}", raw.replace("_", "")).as_str(), radix) } else { - from_str_radix::(raw.replace("_", "").as_str(), radix) + from_str_radix(raw.replace("_", "").as_str(), radix) }) - .map_err(|e| (raw, e.kind)) + .and_then(|parsed| match parsed { + ParsedNumResult::Float(..) => Err(IntErrorKind::FloatSuffix), + ParsedNumResult::Int(val, bound) => Ok((val, bound)), + ParsedNumResult::UnknownNum(val, NumericBound::None) => Ok((val, IntBound::None)), + ParsedNumResult::UnknownNum(val, NumericBound::AtLeastIntOrFloat { sign, width }) => { + Ok((val, IntBound::AtLeast { sign, width })) + } + }) + .map_err(|e| (raw, e)) } #[inline(always)] -pub fn finish_parsing_float(raw: &str) -> Result { +pub fn finish_parsing_float(raw: &str) -> Result<(f64, FloatBound), (&str, FloatErrorKind)> { + let (opt_bound, raw_without_suffix) = parse_literal_suffix(raw); + + let bound = match opt_bound { + None => FloatBound::None, + Some(ParsedWidth::Float(fw)) => FloatBound::Exact(fw), + Some(ParsedWidth::Int(_)) => return Err((raw, FloatErrorKind::IntSuffix)), + }; + // Ignore underscores. - match raw.replace("_", "").parse::() { - Ok(float) if float.is_finite() => Ok(float), + match raw_without_suffix.replace("_", "").parse::() { + Ok(float) if float.is_finite() => Ok((float, bound)), Ok(float) => { if float.is_sign_positive() { Err((raw, FloatErrorKind::PositiveInfinity)) @@ -121,6 +167,41 @@ pub fn finish_parsing_float(raw: &str) -> Result { } } +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum ParsedWidth { + Int(IntWidth), + Float(FloatWidth), +} + +fn parse_literal_suffix(num_str: &str) -> (Option, &str) { + macro_rules! parse_num_suffix { + ($($suffix:expr, $width:expr)*) => {$( + if num_str.ends_with($suffix) { + return (Some($width), num_str.get(0..num_str.len() - $suffix.len()).unwrap()); + } + )*} + } + + parse_num_suffix! { + "u8", ParsedWidth::Int(IntWidth::U8) + "u16", ParsedWidth::Int(IntWidth::U16) + "u32", ParsedWidth::Int(IntWidth::U32) + "u64", ParsedWidth::Int(IntWidth::U64) + "u128", ParsedWidth::Int(IntWidth::U128) + "i8", ParsedWidth::Int(IntWidth::I8) + "i16", ParsedWidth::Int(IntWidth::I16) + "i32", ParsedWidth::Int(IntWidth::I32) + "i64", ParsedWidth::Int(IntWidth::I64) + "i128", ParsedWidth::Int(IntWidth::I128) + "nat", ParsedWidth::Int(IntWidth::Nat) + "dec", ParsedWidth::Float(FloatWidth::Dec) + "f32", ParsedWidth::Float(FloatWidth::F32) + "f64", ParsedWidth::Float(FloatWidth::F64) + } + + (None, num_str) +} + /// Integer parsing code taken from the rust libcore, /// pulled in so we can give custom error messages /// @@ -129,44 +210,8 @@ pub fn finish_parsing_float(raw: &str) -> Result { /// the LEGAL_DETAILS file in the root directory of this distribution. /// /// Thanks to the Rust project and its contributors! -trait FromStrRadixHelper: PartialOrd + Copy { - fn min_value() -> Self; - fn max_value() -> Self; - fn from_u32(u: u32) -> Self; - fn checked_mul(&self, other: u32) -> Option; - fn checked_sub(&self, other: u32) -> Option; - fn checked_add(&self, other: u32) -> Option; -} - -macro_rules! doit { - ($($t:ty)*) => ($(impl FromStrRadixHelper for $t { - #[inline] - fn min_value() -> Self { Self::min_value() } - #[inline] - fn max_value() -> Self { Self::max_value() } - #[inline] - fn from_u32(u: u32) -> Self { u as Self } - #[inline] - fn checked_mul(&self, other: u32) -> Option { - Self::checked_mul(*self, other as Self) - } - #[inline] - fn checked_sub(&self, other: u32) -> Option { - Self::checked_sub(*self, other as Self) - } - #[inline] - fn checked_add(&self, other: u32) -> Option { - Self::checked_add(*self, other as Self) - } - })*) -} -// We only need the i64 implementation, but libcore defines -// doit! { i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize } -doit! { i64 } - -fn from_str_radix(src: &str, radix: u32) -> Result { +fn from_str_radix(src: &str, radix: u32) -> Result { use self::IntErrorKind::*; - use self::ParseIntError as PIE; assert!( (2..=36).contains(&radix), @@ -174,86 +219,294 @@ fn from_str_radix(src: &str, radix: u32) -> Result T::min_value(); - - // all valid digits are ascii, so we will just iterate over the utf8 bytes - // and cast them to chars. .to_digit() will safely return None for anything - // other than a valid ascii digit for the given radix, including the first-byte - // of multi-byte sequences - let src = src.as_bytes(); - - let (is_positive, digits) = match src[0] { - b'+' => (true, &src[1..]), - b'-' if is_signed_ty => (false, &src[1..]), - _ => (true, src), + use std::num::IntErrorKind as StdIEK; + let result = match i128::from_str_radix(src, radix) { + Ok(result) => IntValue::I128(result), + Err(pie) => match pie.kind() { + StdIEK::Empty => return Err(IntErrorKind::Empty), + StdIEK::InvalidDigit => return Err(IntErrorKind::InvalidDigit), + StdIEK::NegOverflow => return Err(IntErrorKind::Underflow), + StdIEK::PosOverflow => { + // try a u128 + match u128::from_str_radix(src, radix) { + Ok(result) => IntValue::U128(result), + Err(pie) => match pie.kind() { + StdIEK::InvalidDigit => return Err(IntErrorKind::InvalidDigit), + StdIEK::PosOverflow => return Err(IntErrorKind::Overflow), + StdIEK::Empty | StdIEK::Zero | StdIEK::NegOverflow => unreachable!(), + _ => unreachable!("I thought all possibilities were exhausted, but std::num added a new one") + }, + } + } + StdIEK::Zero => unreachable!("Parsed a i128"), + _ => unreachable!( + "I thought all possibilities were exhausted, but std::num added a new one" + ), + }, }; - if digits.is_empty() { - return Err(PIE { kind: Empty }); - } + let (lower_bound, is_negative) = match result { + IntValue::I128(num) => (lower_bound_of_int(num), num < 0), + IntValue::U128(_) => (IntWidth::U128, false), + }; - let mut result = T::from_u32(0); - if is_positive { - // The number is positive - for &c in digits { - let x = match (c as char).to_digit(radix) { - Some(x) => x, - None => return Err(PIE { kind: InvalidDigit }), - }; - result = match result.checked_mul(radix) { - Some(result) => result, - None => return Err(PIE { kind: Overflow }), - }; - result = match result.checked_add(x) { - Some(result) => result, - None => return Err(PIE { kind: Overflow }), + match opt_exact_bound { + 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(ParsedWidth::Float(fw)) => { + // For now, assume floats can represent all integers + // TODO: this is somewhat incorrect, revisit + Ok(ParsedNumResult::Float( + match result { + IntValue::I128(n) => n as f64, + IntValue::U128(n) => n as f64, + }, + FloatBound::Exact(fw), + )) + } + Some(ParsedWidth::Int(exact_width)) => { + // We need to check if the exact bound >= lower bound. + if exact_width.is_superset(&lower_bound, is_negative) { + // Great! Use the exact bound. + Ok(ParsedNumResult::Int(result, IntBound::Exact(exact_width))) + } else { + // This is something like 200i8; the lower bound is u8, which holds strictly more + // ints on the positive side than i8 does. Report an error depending on which side + // of the integers we checked. + let err = if is_negative { + UnderflowsSuffix { + suffix_type: exact_width.type_str(), + min_value: exact_width.min_value(), + } + } else { + OverflowsSuffix { + suffix_type: exact_width.type_str(), + max_value: exact_width.max_value(), + } + }; + Err(err) + } + } + } +} + +fn lower_bound_of_int(result: i128) -> IntWidth { + use IntWidth::*; + if result >= 0 { + // Positive + let result = result as u128; + if result > U64.max_value() { + I128 + } else if result > I64.max_value() { + U64 + } else if result > U32.max_value() { + I64 + } else if result > I32.max_value() { + U32 + } else if result > U16.max_value() { + I32 + } else if result > I16.max_value() { + U16 + } else if result > U8.max_value() { + I16 + } else if result > I8.max_value() { + U8 + } else { + I8 } } else { - // The number is negative - for &c in digits { - let x = match (c as char).to_digit(radix) { - Some(x) => x, - None => return Err(PIE { kind: InvalidDigit }), - }; - result = match result.checked_mul(radix) { - Some(result) => result, - None => return Err(PIE { kind: Underflow }), - }; - result = match result.checked_sub(x) { - Some(result) => result, - None => return Err(PIE { kind: Underflow }), - }; + // Negative + if result < I64.min_value() { + I128 + } else if result < I32.min_value() { + I64 + } else if result < I16.min_value() { + I32 + } else if result < I8.min_value() { + I16 + } else { + I8 } } - Ok(result) } -/// An error which can be returned when parsing an integer. -/// -/// This error is used as the error type for the `from_str_radix()` functions -/// on the primitive integer types, such as [`i8::from_str_radix`]. -/// -/// # Potential causes -/// -/// Among other causes, `ParseIntError` can be thrown because of leading or trailing whitespace -/// in the string e.g., when it is obtained from the standard input. -/// Using the [`str.trim()`] method ensures that no whitespace remains before parsing. -/// -/// [`str.trim()`]: ../../std/primitive.str.html#method.trim -/// [`i8::from_str_radix`]: ../../std/primitive.i8.html#method.from_str_radix -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ParseIntError { - kind: IntErrorKind, +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum IntSign { + Unsigned, + Signed, } -impl ParseIntError { - /// Outputs the detailed cause of parsing an integer failing. - pub fn kind(&self) -> &IntErrorKind { - &self.kind +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum IntWidth { + U8, + U16, + U32, + U64, + U128, + I8, + I16, + I32, + I64, + I128, + Nat, +} + +impl IntWidth { + /// Returns the `IntSign` and bit width of a variant. + fn sign_and_width(&self) -> (IntSign, u32) { + use IntSign::*; + use IntWidth::*; + match self { + U8 => (Unsigned, 8), + U16 => (Unsigned, 16), + U32 => (Unsigned, 32), + U64 => (Unsigned, 64), + U128 => (Unsigned, 128), + I8 => (Signed, 8), + I16 => (Signed, 16), + I32 => (Signed, 32), + I64 => (Signed, 64), + I128 => (Signed, 128), + // TODO: this is platform specific! + Nat => (Unsigned, 64), + } + } + + fn type_str(&self) -> &'static str { + use IntWidth::*; + match self { + U8 => "U8", + U16 => "U16", + U32 => "U32", + U64 => "U64", + U128 => "U128", + I8 => "I8", + I16 => "I16", + I32 => "I32", + I64 => "I64", + I128 => "I128", + Nat => "Nat", + } + } + + fn max_value(&self) -> u128 { + use IntWidth::*; + match self { + U8 => u8::MAX as u128, + U16 => u16::MAX as u128, + U32 => u32::MAX as u128, + U64 => u64::MAX as u128, + U128 => u128::MAX, + I8 => i8::MAX as u128, + I16 => i16::MAX as u128, + I32 => i32::MAX as u128, + I64 => i64::MAX as u128, + I128 => i128::MAX as u128, + // TODO: this is platform specific! + Nat => u64::MAX as u128, + } + } + + fn min_value(&self) -> i128 { + use IntWidth::*; + match self { + U8 | U16 | U32 | U64 | U128 | Nat => 0, + I8 => i8::MIN as i128, + I16 => i16::MIN as i128, + I32 => i32::MIN as i128, + I64 => i64::MIN as i128, + I128 => i128::MIN, + } + } + + /// Checks if `self` represents superset of integers that `lower_bound` represents, on a particular + /// side of the integers relative to 0. + /// + /// If `is_negative` is true, the negative side is checked; otherwise the positive side is checked. + pub fn is_superset(&self, lower_bound: &Self, is_negative: bool) -> bool { + use IntSign::*; + + if is_negative { + match (self.sign_and_width(), lower_bound.sign_and_width()) { + ((Signed, us), (Signed, lower_bound)) => us >= lower_bound, + // Unsigned ints can never represent negative numbers; signed (non-zero width) + // ints always can. + ((Unsigned, _), (Signed, _)) => false, + ((Signed, _), (Unsigned, _)) => true, + // Trivially true; both can only express 0. + ((Unsigned, _), (Unsigned, _)) => true, + } + } else { + match (self.sign_and_width(), lower_bound.sign_and_width()) { + ((Signed, us), (Signed, lower_bound)) + | ((Unsigned, us), (Unsigned, lower_bound)) => us >= lower_bound, + + // Unsigned ints with the same bit width as their unsigned counterparts can always + // express 2x more integers on the positive side as unsigned ints. + ((Unsigned, us), (Signed, lower_bound)) => us >= lower_bound, + + // ...but that means signed int widths can represent less than their unsigned + // counterparts, so the below is true iff the bit width is strictly greater. E.g. + // i16 is a superset of u8, but i16 is not a superset of u16. + ((Signed, us), (Unsigned, lower_bound)) => us > lower_bound, + } + } } } + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum FloatWidth { + Dec, + F32, + F64, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum SignDemand { + /// Can be signed or unsigned. + NoDemand, + /// Must be signed. + Signed, +} + +/// Describes a bound on the width of an integer. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum IntBound { + /// There is no bound on the width. + None, + /// Must have an exact width. + Exact(IntWidth), + /// Must have a certain sign and a minimum width. + AtLeast { sign: SignDemand, width: IntWidth }, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum FloatBound { + None, + Exact(FloatWidth), +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum NumericBound { + None, + /// Must be an integer of a certain size, or any float. + AtLeastIntOrFloat { + sign: SignDemand, + width: IntWidth, + }, +} diff --git a/compiler/can/src/operator.rs b/compiler/can/src/operator.rs index babfd0622c..04255f9175 100644 --- a/compiler/can/src/operator.rs +++ b/compiler/can/src/operator.rs @@ -121,8 +121,8 @@ pub fn desugar_def<'a>(arena: &'a Bump, def: &'a Def<'a>) -> Def<'a> { /// then replace the BinOp nodes with Apply nodes. Also drop SpaceBefore and SpaceAfter nodes. pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc>) -> &'a Loc> { match &loc_expr.value { - Float(_) - | Num(_) + Float(..) + | Num(..) | NonBase10Int { .. } | Str(_) | AccessorFunction(_) diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index 66132dbafb..41aa49ca9a 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -1,6 +1,9 @@ use crate::env::Env; -use crate::expr::{canonicalize_expr, unescape_char, Expr, Output}; -use crate::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; +use crate::expr::{canonicalize_expr, unescape_char, Expr, IntValue, Output}; +use crate::num::{ + finish_parsing_base, finish_parsing_float, finish_parsing_num, FloatBound, IntBound, + NumericBound, ParsedNumResult, +}; use crate::scope::Scope; use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::symbol::Symbol; @@ -9,6 +12,7 @@ use roc_parse::pattern::PatternType; use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError}; use roc_region::all::{Loc, Region}; use roc_types::subs::{VarStore, Variable}; + /// A pattern, including possible problems (e.g. shadowing) so that /// codegen can generate a runtime error if this pattern is reached. #[derive(Clone, Debug, PartialEq)] @@ -25,14 +29,14 @@ pub enum Pattern { ext_var: Variable, destructs: Vec>, }, - IntLiteral(Variable, Box, i64), - NumLiteral(Variable, Box, i64), - FloatLiteral(Variable, Box, f64), + NumLiteral(Variable, Box, IntValue, NumericBound), + IntLiteral(Variable, Variable, Box, IntValue, IntBound), + FloatLiteral(Variable, Variable, Box, f64, FloatBound), StrLiteral(Box), Underscore, // Runtime Exceptions - Shadowed(Region, Loc), + Shadowed(Region, Loc, Symbol), // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments! UnsupportedPattern(Region), // parse error patterns @@ -65,7 +69,7 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec) { use Pattern::*; match pattern { - Identifier(symbol) => { + Identifier(symbol) | Shadowed(_, _, symbol) => { symbols.push(*symbol); } @@ -85,15 +89,13 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec) { } } - NumLiteral(_, _, _) - | IntLiteral(_, _, _) - | FloatLiteral(_, _, _) + NumLiteral(..) + | IntLiteral(..) + | FloatLiteral(..) | StrLiteral(_) | Underscore | MalformedPattern(_, _) | UnsupportedPattern(_) => {} - - Shadowed(_, _) => {} } } @@ -121,13 +123,14 @@ pub fn canonicalize_pattern<'a>( Pattern::Identifier(symbol) } - Err((original_region, shadow)) => { + Err((original_region, shadow, new_symbol)) => { env.problem(Problem::RuntimeError(RuntimeError::Shadowing { original_region, shadow: shadow.clone(), })); + output.references.bound_symbols.insert(new_symbol); - Pattern::Shadowed(original_region, shadow) + Pattern::Shadowed(original_region, shadow, new_symbol) } }, GlobalTag(name) => { @@ -185,13 +188,19 @@ pub fn canonicalize_pattern<'a>( } } - FloatLiteral(str) => match pattern_type { + &FloatLiteral(str) => match pattern_type { WhenBranch => match finish_parsing_float(str) { Err(_error) => { let problem = MalformedPatternProblem::MalformedFloat; malformed_pattern(env, problem, region) } - Ok(float) => Pattern::FloatLiteral(var_store.fresh(), (*str).into(), float), + Ok((float, bound)) => Pattern::FloatLiteral( + var_store.fresh(), + var_store.fresh(), + (str).into(), + float, + bound, + ), }, ptype => unsupported_pattern(env, ptype, region), }, @@ -201,32 +210,58 @@ pub fn canonicalize_pattern<'a>( TopLevelDef | DefExpr => bad_underscore(env, region), }, - NumLiteral(str) => match pattern_type { - WhenBranch => match finish_parsing_int(str) { + &NumLiteral(str) => match pattern_type { + WhenBranch => match finish_parsing_num(str) { Err(_error) => { let problem = MalformedPatternProblem::MalformedInt; malformed_pattern(env, problem, region) } - Ok(int) => Pattern::NumLiteral(var_store.fresh(), (*str).into(), int), + Ok(ParsedNumResult::UnknownNum(int, bound)) => { + Pattern::NumLiteral(var_store.fresh(), (str).into(), int, bound) + } + Ok(ParsedNumResult::Int(int, bound)) => Pattern::IntLiteral( + var_store.fresh(), + var_store.fresh(), + (str).into(), + int, + bound, + ), + Ok(ParsedNumResult::Float(float, bound)) => Pattern::FloatLiteral( + var_store.fresh(), + var_store.fresh(), + (str).into(), + float, + bound, + ), }, ptype => unsupported_pattern(env, ptype, region), }, - NonBase10Literal { + &NonBase10Literal { string, base, is_negative, } => match pattern_type { - WhenBranch => match finish_parsing_base(string, *base, *is_negative) { + WhenBranch => match finish_parsing_base(string, base, is_negative) { Err(_error) => { - let problem = MalformedPatternProblem::MalformedBase(*base); + let problem = MalformedPatternProblem::MalformedBase(base); malformed_pattern(env, problem, region) } - Ok(int) => { - let sign_str = if *is_negative { "-" } else { "" }; + Ok((IntValue::U128(_), _)) if is_negative => { + // Can't negate a u128; that doesn't fit in any integer literal type we support. + let problem = MalformedPatternProblem::MalformedInt; + malformed_pattern(env, problem, region) + } + Ok((int, bound)) => { + let sign_str = if is_negative { "-" } else { "" }; let int_str = format!("{}{}", sign_str, int.to_string()).into_boxed_str(); - let i = if *is_negative { -int } else { int }; - Pattern::IntLiteral(var_store.fresh(), int_str, i) + let i = match int { + // Safety: this is fine because I128::MAX = |I128::MIN| - 1 + IntValue::I128(n) if is_negative => IntValue::I128(-n), + IntValue::I128(n) => IntValue::I128(n), + IntValue::U128(_) => unreachable!(), + }; + Pattern::IntLiteral(var_store.fresh(), var_store.fresh(), int_str, i, bound) } }, ptype => unsupported_pattern(env, ptype, region), @@ -268,7 +303,7 @@ pub fn canonicalize_pattern<'a>( }, }); } - Err((original_region, shadow)) => { + Err((original_region, shadow, new_symbol)) => { env.problem(Problem::RuntimeError(RuntimeError::Shadowing { original_region, shadow: shadow.clone(), @@ -278,7 +313,8 @@ pub fn canonicalize_pattern<'a>( // are, we're definitely shadowed and will // get a runtime exception as soon as we // encounter the first bad pattern. - opt_erroneous = Some(Pattern::Shadowed(original_region, shadow)); + opt_erroneous = + Some(Pattern::Shadowed(original_region, shadow, new_symbol)); } }; } @@ -339,7 +375,7 @@ pub fn canonicalize_pattern<'a>( }, }); } - Err((original_region, shadow)) => { + Err((original_region, shadow, new_symbol)) => { env.problem(Problem::RuntimeError(RuntimeError::Shadowing { original_region, shadow: shadow.clone(), @@ -349,7 +385,8 @@ pub fn canonicalize_pattern<'a>( // are, we're definitely shadowed and will // get a runtime exception as soon as we // encounter the first bad pattern. - opt_erroneous = Some(Pattern::Shadowed(original_region, shadow)); + opt_erroneous = + Some(Pattern::Shadowed(original_region, shadow, new_symbol)); } }; } @@ -452,7 +489,7 @@ fn add_bindings_from_patterns( use Pattern::*; match pattern { - Identifier(symbol) => { + Identifier(symbol) | Shadowed(_, _, symbol) => { answer.push((*symbol, *region)); } AppliedTag { @@ -472,12 +509,11 @@ fn add_bindings_from_patterns( answer.push((*symbol, *region)); } } - NumLiteral(_, _, _) - | IntLiteral(_, _, _) - | FloatLiteral(_, _, _) + NumLiteral(..) + | IntLiteral(..) + | FloatLiteral(..) | StrLiteral(_) | Underscore - | Shadowed(_, _) | MalformedPattern(_, _) | UnsupportedPattern(_) => (), } diff --git a/compiler/can/src/scope.rs b/compiler/can/src/scope.rs index eeab87438d..de7b233b08 100644 --- a/compiler/can/src/scope.rs +++ b/compiler/can/src/scope.rs @@ -110,15 +110,21 @@ impl Scope { exposed_ident_ids: &IdentIds, all_ident_ids: &mut IdentIds, region: Region, - ) -> Result)> { + ) -> Result, Symbol)> { match self.idents.get(&ident) { - Some((_, original_region)) => { + Some(&(_, original_region)) => { let shadow = Loc { - value: ident, + value: ident.clone(), region, }; - Err((*original_region, shadow)) + let ident_id = all_ident_ids.add(ident.clone()); + let symbol = Symbol::new(self.home, ident_id); + + self.symbols.insert(symbol, region); + self.idents.insert(ident, (symbol, region)); + + Err((original_region, shadow, symbol)) } None => { // If this IdentId was already added previously diff --git a/compiler/can/tests/test_can.rs b/compiler/can/tests/test_can.rs index 99a01d3169..3489b0677f 100644 --- a/compiler/can/tests/test_can.rs +++ b/compiler/can/tests/test_can.rs @@ -15,7 +15,7 @@ mod test_can { use crate::helpers::{can_expr_with, test_home, CanExprOut}; use bumpalo::Bump; use roc_can::expr::Expr::{self, *}; - use roc_can::expr::{ClosureData, Recursive}; + use roc_can::expr::{ClosureData, IntValue, Recursive}; use roc_problem::can::{CycleEntry, FloatErrorKind, IntErrorKind, Problem, RuntimeError}; use roc_region::all::{Position, Region}; use std::{f64, i64}; @@ -32,7 +32,7 @@ mod test_can { let actual_out = can_expr_with(&arena, test_home(), input); match actual_out.loc_expr.value { - Expr::Float(_, _, _, actual) => { + Expr::Float(_, _, _, actual, _) => { assert_eq!(expected, actual); } actual => { @@ -46,8 +46,8 @@ mod test_can { let actual_out = can_expr_with(&arena, test_home(), input); match actual_out.loc_expr.value { - Expr::Int(_, _, _, actual) => { - assert_eq!(expected, actual); + Expr::Int(_, _, _, actual, _) => { + assert_eq!(IntValue::I128(expected), actual); } actual => { panic!("Expected an Int *, but got: {:?}", actual); @@ -55,13 +55,13 @@ mod test_can { } } - fn assert_can_num(input: &str, expected: i64) { + fn assert_can_num(input: &str, expected: i128) { let arena = Bump::new(); let actual_out = can_expr_with(&arena, test_home(), input); match actual_out.loc_expr.value { - Expr::Num(_, _, actual) => { - assert_eq!(expected, actual); + Expr::Num(_, _, actual, _) => { + assert_eq!(IntValue::I128(expected), actual); } actual => { panic!("Expected a Num, but got: {:?}", actual); @@ -79,7 +79,7 @@ mod test_can { fn int_too_large() { use roc_parse::ast::Base; - let string = (i64::MAX as i128 + 1).to_string(); + let string = "340_282_366_920_938_463_463_374_607_431_768_211_456".to_string(); assert_can( &string.clone(), @@ -96,7 +96,7 @@ mod test_can { fn int_too_small() { use roc_parse::ast::Base; - let string = (i64::MIN as i128 - 1).to_string(); + let string = "-170_141_183_460_469_231_731_687_303_715_884_105_729".to_string(); assert_can( &string.clone(), @@ -186,12 +186,12 @@ mod test_can { #[test] fn num_max() { - assert_can_num(&(i64::MAX.to_string()), i64::MAX); + assert_can_num(&(i64::MAX.to_string()), i64::MAX.into()); } #[test] fn num_min() { - assert_can_num(&(i64::MIN.to_string()), i64::MIN); + assert_can_num(&(i64::MIN.to_string()), i64::MIN.into()); } #[test] @@ -368,9 +368,11 @@ mod test_can { let arena = Bump::new(); let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src); - assert_eq!(problems.len(), 1); + assert_eq!(problems.len(), 2); assert!(problems.iter().all(|problem| match problem { Problem::RuntimeError(RuntimeError::Shadowing { .. }) => true, + // Due to one of the shadows + Problem::UnusedDef(..) => true, _ => false, })); } @@ -389,9 +391,11 @@ mod test_can { let arena = Bump::new(); let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src); - assert_eq!(problems.len(), 1); + assert_eq!(problems.len(), 2); assert!(problems.iter().all(|problem| match problem { Problem::RuntimeError(RuntimeError::Shadowing { .. }) => true, + // Due to one of the shadows + Problem::UnusedDef(..) => true, _ => false, })); } @@ -410,10 +414,12 @@ mod test_can { let arena = Bump::new(); let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src); - assert_eq!(problems.len(), 1); + assert_eq!(problems.len(), 2); println!("{:#?}", problems); assert!(problems.iter().all(|problem| match problem { Problem::RuntimeError(RuntimeError::Shadowing { .. }) => true, + // Due to one of the shadows + Problem::UnusedDef(..) => true, _ => false, })); } @@ -524,7 +530,7 @@ mod test_can { fn annotation_followed_with_unrelated_affectation() { let src = indoc!( r#" - F : Str + F : Str x = 1 @@ -545,10 +551,10 @@ mod test_can { fn two_annotations_followed_with_unrelated_affectation() { let src = indoc!( r#" - G : Str + G : Str + + F : {} - F : {} - x = 1 x @@ -629,7 +635,7 @@ mod test_can { fn incorrect_optional_value() { let src = indoc!( r#" - { x ? 42 } + { x ? 42 } "# ); let arena = Bump::new(); @@ -943,14 +949,8 @@ mod test_can { let problem = Problem::RuntimeError(RuntimeError::CircularDef(vec![CycleEntry { symbol: interns.symbol(home, "x".into()), - symbol_region: Region::new( - Position { line: 0, column: 0 }, - Position { line: 0, column: 1 }, - ), - expr_region: Region::new( - Position { line: 0, column: 4 }, - Position { line: 0, column: 5 }, - ), + symbol_region: Region::new(Position::new(0), Position::new(1)), + expr_region: Region::new(Position::new(4), Position::new(5)), }])); assert_eq!(is_circular_def, true); @@ -980,36 +980,18 @@ mod test_can { let problem = Problem::RuntimeError(RuntimeError::CircularDef(vec![ CycleEntry { symbol: interns.symbol(home, "x".into()), - symbol_region: Region::new( - Position { line: 0, column: 0 }, - Position { line: 0, column: 1 }, - ), - expr_region: Region::new( - Position { line: 0, column: 4 }, - Position { line: 0, column: 5 }, - ), + symbol_region: Region::new(Position::new(0), Position::new(1)), + expr_region: Region::new(Position::new(4), Position::new(5)), }, CycleEntry { symbol: interns.symbol(home, "y".into()), - symbol_region: Region::new( - Position { line: 1, column: 0 }, - Position { line: 1, column: 1 }, - ), - expr_region: Region::new( - Position { line: 1, column: 4 }, - Position { line: 1, column: 5 }, - ), + symbol_region: Region::new(Position::new(6), Position::new(7)), + expr_region: Region::new(Position::new(10), Position::new(11)), }, CycleEntry { symbol: interns.symbol(home, "z".into()), - symbol_region: Region::new( - Position { line: 2, column: 0 }, - Position { line: 2, column: 1 }, - ), - expr_region: Region::new( - Position { line: 2, column: 4 }, - Position { line: 2, column: 5 }, - ), + symbol_region: Region::new(Position::new(12), Position::new(13)), + expr_region: Region::new(Position::new(16), Position::new(17)), }, ])); @@ -1028,7 +1010,7 @@ mod test_can { let src = indoc!( r#" x = Dict.empty - + Dict.len x "# ); diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index c47a1e5796..b5ac1cf14f 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -1,6 +1,7 @@ use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::LetConstraint; use roc_can::expected::Expected::{self, *}; +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; @@ -10,28 +11,65 @@ use roc_types::types::Category; use roc_types::types::Reason; use roc_types::types::Type::{self, *}; +#[must_use] +pub fn add_numeric_bound_constr( + constrs: &mut Vec, + num_type: Type, + bound: impl TypedNumericBound, + region: Region, + category: Category, +) -> Type { + let range = bound.bounded_range(); + + let total_num_type = num_type; + + match range.len() { + 0 => total_num_type, + 1 => { + let actual_type = Variable(range[0]); + constrs.push(Eq( + total_num_type.clone(), + Expected::ForReason(Reason::NumericLiteralSuffix, actual_type, region), + category, + region, + )); + total_num_type + } + _ => RangedNumber(Box::new(total_num_type), range), + } +} + #[inline(always)] pub fn int_literal( num_var: Variable, precision_var: Variable, expected: Expected, region: Region, + bound: IntBound, ) -> Constraint { - let num_type = Variable(num_var); let reason = Reason::IntLiteral; - exists( - vec![num_var], - And(vec![ - Eq( - num_type.clone(), - ForReason(reason, num_int(Type::Variable(precision_var)), region), - Category::Int, - region, - ), - Eq(num_type, expected, Category::Int, region), - ]), - ) + 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". + let num_type = add_numeric_bound_constr( + &mut constrs, + Variable(num_var), + bound, + region, + Category::Num, + ); + constrs.extend(vec![ + Eq( + num_type.clone(), + ForReason(reason, num_int(Type::Variable(precision_var)), region), + Category::Int, + region, + ), + Eq(num_type, expected, Category::Int, region), + ]); + + exists(vec![num_var], And(constrs)) } #[inline(always)] @@ -40,22 +78,46 @@ pub fn float_literal( precision_var: Variable, expected: Expected, region: Region, + bound: FloatBound, ) -> Constraint { - let num_type = Variable(num_var); let reason = Reason::FloatLiteral; - exists( - vec![num_var, precision_var], - And(vec![ - Eq( - num_type.clone(), - ForReason(reason, num_float(Type::Variable(precision_var)), region), - Category::Float, - region, - ), - Eq(num_type, expected, Category::Float, region), - ]), - ) + let mut constrs = Vec::with_capacity(3); + let num_type = add_numeric_bound_constr( + &mut constrs, + Variable(num_var), + bound, + region, + Category::Float, + ); + constrs.extend(vec![ + Eq( + num_type.clone(), + ForReason(reason, num_float(Type::Variable(precision_var)), region), + Category::Float, + region, + ), + Eq(num_type, expected, Category::Float, region), + ]); + + exists(vec![num_var, precision_var], And(constrs)) +} + +#[inline(always)] +pub fn num_literal( + num_var: Variable, + expected: Expected, + region: Region, + bound: NumericBound, +) -> Constraint { + let open_number_type = crate::builtins::num_num(Type::Variable(num_var)); + + let mut constrs = Vec::with_capacity(3); + let num_type = + add_numeric_bound_constr(&mut constrs, open_number_type, bound, region, Category::Num); + constrs.extend(vec![Eq(num_type, expected, Category::Num, region)]); + + exists(vec![num_var], And(constrs)) } #[inline(always)] @@ -71,7 +133,7 @@ pub fn exists(flex_vars: Vec, constraint: Constraint) -> Constraint { #[inline(always)] pub fn builtin_type(symbol: Symbol, args: Vec) -> Type { - Type::Apply(symbol, args) + Type::Apply(symbol, args, Region::zero()) } #[inline(always)] @@ -188,3 +250,85 @@ pub fn num_num(typ: Type) -> Type { Box::new(alias_content), ) } + +pub trait TypedNumericBound { + fn bounded_range(&self) -> Vec; +} + +impl TypedNumericBound for IntBound { + fn bounded_range(&self) -> Vec { + match self { + IntBound::None => vec![], + IntBound::Exact(w) => vec![match w { + IntWidth::U8 => Variable::U8, + IntWidth::U16 => Variable::U16, + IntWidth::U32 => Variable::U32, + IntWidth::U64 => Variable::U64, + IntWidth::U128 => Variable::U128, + IntWidth::I8 => Variable::I8, + IntWidth::I16 => Variable::I16, + IntWidth::I32 => Variable::I32, + IntWidth::I64 => Variable::I64, + IntWidth::I128 => Variable::I128, + IntWidth::Nat => Variable::NAT, + }], + IntBound::AtLeast { sign, width } => { + let whole_range: &[(IntWidth, Variable)] = match sign { + SignDemand::NoDemand => { + &[ + (IntWidth::I8, Variable::I8), + (IntWidth::U8, Variable::U8), + (IntWidth::I16, Variable::I16), + (IntWidth::U16, Variable::U16), + (IntWidth::I32, Variable::I32), + (IntWidth::U32, Variable::U32), + (IntWidth::I64, Variable::I64), + (IntWidth::Nat, Variable::NAT), // FIXME: Nat's order here depends on the platform! + (IntWidth::U64, Variable::U64), + (IntWidth::I128, Variable::I128), + (IntWidth::U128, Variable::U128), + ] + } + SignDemand::Signed => &[ + (IntWidth::I8, Variable::I8), + (IntWidth::I16, Variable::I16), + (IntWidth::I32, Variable::I32), + (IntWidth::I64, Variable::I64), + (IntWidth::I128, Variable::I128), + ], + }; + whole_range + .iter() + .skip_while(|(lower_bound, _)| *lower_bound != *width) + .map(|(_, var)| *var) + .collect() + } + } + } +} + +impl TypedNumericBound for FloatBound { + fn bounded_range(&self) -> Vec { + match self { + FloatBound::None => vec![], + FloatBound::Exact(w) => vec![match w { + FloatWidth::Dec => Variable::DEC, + FloatWidth::F32 => Variable::F32, + FloatWidth::F64 => Variable::F64, + }], + } + } +} + +impl TypedNumericBound for NumericBound { + fn bounded_range(&self) -> Vec { + match self { + NumericBound::None => vec![], + &NumericBound::AtLeastIntOrFloat { sign, width } => { + let mut range = IntBound::AtLeast { sign, width }.bounded_range(); + range.extend_from_slice(&[Variable::F32, Variable::F64, Variable::DEC]); + range + } + } + } +} diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 71c05c8281..5d7ec4e22d 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -1,4 +1,6 @@ -use crate::builtins::{empty_list_type, float_literal, int_literal, list_type, str_type}; +use crate::builtins::{ + empty_list_type, float_literal, int_literal, list_type, num_literal, str_type, +}; use crate::pattern::{constrain_pattern, PatternState}; use roc_can::annotation::IntroducedVariables; use roc_can::constraint::Constraint::{self, *}; @@ -77,7 +79,7 @@ fn constrain_untyped_args( loc_pattern.region, pattern_expected, &mut pattern_state, - false, + true, ); vars.push(*pattern_var); @@ -96,17 +98,11 @@ pub fn constrain_expr( expected: Expected, ) -> Constraint { match expr { - Int(var, precision, _, _) => int_literal(*var, *precision, expected, region), - Num(var, _, _) => exists( - vec![*var], - Eq( - crate::builtins::num_num(Type::Variable(*var)), - expected, - Category::Num, - region, - ), - ), - Float(var, precision, _, _) => float_literal(*var, *precision, expected, region), + &Int(var, precision, _, _, bound) => int_literal(var, precision, expected, region, bound), + &Num(var, _, _, bound) => num_literal(var, expected, region, bound), + &Float(var, precision, _, _, bound) => { + float_literal(var, precision, expected, region, bound) + } EmptyRecord => constrain_empty_record(region, expected), Expr::Record { record_var, fields } => { if fields.is_empty() { @@ -1144,7 +1140,7 @@ fn constrain_def_pattern(env: &Env, loc_pattern: &Loc, expr_type: Type) loc_pattern.region, pattern_expected, &mut state, - false, + true, ); state diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index 3844ad3f08..bf77a86f36 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -48,17 +48,16 @@ fn headers_from_annotation_help( headers: &mut SendMap>, ) -> bool { match pattern { - Identifier(symbol) => { + Identifier(symbol) | Shadowed(_, _, symbol) => { headers.insert(*symbol, annotation.clone()); true } Underscore - | Shadowed(_, _) | MalformedPattern(_, _) | UnsupportedPattern(_) - | NumLiteral(_, _, _) - | IntLiteral(_, _, _) - | FloatLiteral(_, _, _) + | NumLiteral(..) + | IntLiteral(..) + | FloatLiteral(..) | StrLiteral(_) => true, RecordDestructure { destructs, .. } => match annotation.value.shallow_dealias() { @@ -159,11 +158,11 @@ pub fn constrain_pattern( PresenceConstraint::IsOpen, )); } - Underscore | UnsupportedPattern(_) | MalformedPattern(_, _) | Shadowed(_, _) => { + Underscore | UnsupportedPattern(_) | MalformedPattern(_, _) => { // Neither the _ pattern nor erroneous ones add any constraints. } - Identifier(symbol) => { + Identifier(symbol) | Shadowed(_, _, symbol) => { if destruct_position { state.constraints.push(Constraint::Present( expected.get_type_ref().clone(), @@ -179,31 +178,83 @@ pub fn constrain_pattern( ); } - NumLiteral(var, _, _) => { - state.vars.push(*var); + &NumLiteral(var, _, _, bound) => { + state.vars.push(var); + + let num_type = builtins::num_num(Type::Variable(var)); + + let num_type = builtins::add_numeric_bound_constr( + &mut state.constraints, + num_type, + bound, + region, + Category::Num, + ); state.constraints.push(Constraint::Pattern( region, PatternCategory::Num, - builtins::num_num(Type::Variable(*var)), + num_type, expected, )); } - IntLiteral(precision_var, _, _) => { + &IntLiteral(num_var, precision_var, _, _, bound) => { + // First constraint on the free num var; this improves the resolved type quality in + // case the bound is an alias. + let num_type = builtins::add_numeric_bound_constr( + &mut state.constraints, + Type::Variable(num_var), + bound, + region, + Category::Int, + ); + + // Link the free num var with the int var and our expectation. + let int_type = builtins::num_int(Type::Variable(precision_var)); + + state.constraints.push(Constraint::Eq( + num_type, // TODO check me if something breaks! + Expected::NoExpectation(int_type), + Category::Int, + region, + )); + + // Also constrain the pattern against the num var, again to reuse aliases if they're present. state.constraints.push(Constraint::Pattern( region, PatternCategory::Int, - builtins::num_int(Type::Variable(*precision_var)), + Type::Variable(num_var), expected, )); } - FloatLiteral(precision_var, _, _) => { + &FloatLiteral(num_var, precision_var, _, _, bound) => { + // First constraint on the free num var; this improves the resolved type quality in + // case the bound is an alias. + let num_type = builtins::add_numeric_bound_constr( + &mut state.constraints, + Type::Variable(num_var), + bound, + region, + Category::Float, + ); + + // Link the free num var with the float var and our expectation. + let float_type = builtins::num_float(Type::Variable(precision_var)); + + state.constraints.push(Constraint::Eq( + num_type.clone(), // TODO check me if something breaks! + Expected::NoExpectation(float_type), + Category::Float, + region, + )); + + // Also constrain the pattern against the num var, again to reuse aliases if they're present. state.constraints.push(Constraint::Pattern( region, PatternCategory::Float, - builtins::num_float(Type::Variable(*precision_var)), + num_type, // TODO check me if something breaks! expected, )); } diff --git a/compiler/fmt/src/annotation.rs b/compiler/fmt/src/annotation.rs index 638f2946e6..98c15b292a 100644 --- a/compiler/fmt/src/annotation.rs +++ b/compiler/fmt/src/annotation.rs @@ -3,7 +3,8 @@ use crate::{ spaces::{fmt_comments_only, fmt_spaces, NewlineAt, INDENT}, Buf, }; -use roc_parse::ast::{AliasHeader, AssignedField, Expr, Tag, TypeAnnotation}; +use roc_parse::ast::{AliasHeader, AssignedField, Collection, Expr, Tag, TypeAnnotation}; +use roc_parse::ident::UppercaseIdent; use roc_region::all::Loc; /// Does an AST node need parens around it? @@ -36,8 +37,8 @@ pub enum Parens { /// newlines are taken into account. #[derive(PartialEq, Eq, Clone, Copy)] pub enum Newlines { - Yes, No, + Yes, } pub trait Formattable { @@ -82,6 +83,20 @@ where } } +impl<'a, T> Formattable for Collection<'a, T> +where + T: Formattable, +{ + fn is_multiline(&self) -> bool { + // if there are any comments, they must go on their own line + // because otherwise they'd comment out the closing delimiter + !self.final_comments().is_empty() || + // if any of the items in the collection are multiline, + // then the whole collection must be multiline + self.items.iter().any(Formattable::is_multiline) + } +} + /// A Located formattable value is also formattable impl Formattable for Loc where @@ -107,6 +122,22 @@ where } } +impl<'a> Formattable for UppercaseIdent<'a> { + fn is_multiline(&self) -> bool { + false + } + + fn format_with_options<'buf>( + &self, + buf: &mut Buf<'buf>, + _parens: Parens, + _newlines: Newlines, + _indent: u16, + ) { + buf.push_str((*self).into()) + } +} + impl<'a> Formattable for TypeAnnotation<'a> { fn is_multiline(&self) -> bool { use roc_parse::ast::TypeAnnotation::*; @@ -246,8 +277,9 @@ impl<'a> Formattable for TypeAnnotation<'a> { } As(lhs, _spaces, AliasHeader { name, vars }) => { - // TODO use spaces? - lhs.value.format(buf, indent); + // TODO use _spaces? + lhs.value + .format_with_options(buf, Parens::InFunctionType, Newlines::No, indent); buf.spaces(1); buf.push_str("as"); buf.spaces(1); diff --git a/compiler/fmt/src/collection.rs b/compiler/fmt/src/collection.rs index 3f63ed7641..5765f0f4d1 100644 --- a/compiler/fmt/src/collection.rs +++ b/compiler/fmt/src/collection.rs @@ -16,11 +16,7 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>( ) where >::Item: Formattable, { - buf.indent(indent); - let is_multiline = - items.iter().any(|item| item.is_multiline()) || !items.final_comments().is_empty(); - - if is_multiline { + if items.is_multiline() { let braces_indent = indent; let item_indent = braces_indent + INDENT; if newline == Newlines::Yes { @@ -52,9 +48,12 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>( item_indent, ); buf.newline(); + buf.indent(braces_indent); + buf.push(end); } else { // is_multiline == false // there is no comment to add + buf.indent(indent); buf.push(start); let mut iter = items.iter().peekable(); while let Some(item) = iter.next() { @@ -68,7 +67,7 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>( if !items.is_empty() { buf.spaces(1); } + + buf.push(end); } - buf.indent(indent); - buf.push(end); } diff --git a/compiler/fmt/src/def.rs b/compiler/fmt/src/def.rs index e3cae69f2a..8ea8ed9639 100644 --- a/compiler/fmt/src/def.rs +++ b/compiler/fmt/src/def.rs @@ -46,7 +46,9 @@ impl<'a> Formattable for Def<'a> { indent + INDENT, ); } else { - buf.push_str(" : "); + buf.spaces(1); + buf.push_str(":"); + buf.spaces(1); loc_annotation.format_with_options( buf, Parens::NotNeeded, diff --git a/compiler/fmt/src/expr.rs b/compiler/fmt/src/expr.rs index 2241c1b5f3..8487c19761 100644 --- a/compiler/fmt/src/expr.rs +++ b/compiler/fmt/src/expr.rs @@ -6,7 +6,7 @@ use crate::spaces::{fmt_comments_only, fmt_spaces, NewlineAt, INDENT}; use crate::Buf; use roc_module::called_via::{self, BinOp}; use roc_parse::ast::{ - AssignedField, Base, Collection, CommentOrNewline, Expr, Pattern, WhenBranch, + AssignedField, Base, Collection, CommentOrNewline, Expr, ExtractSpaces, Pattern, WhenBranch, }; use roc_parse::ast::{StrLiteral, StrSegment}; use roc_region::all::Loc; @@ -27,8 +27,8 @@ impl<'a> Formattable for Expr<'a> { } // These expressions never have newlines - Float(_) - | Num(_) + Float(..) + | Num(..) | NonBase10Int { .. } | Access(_, _) | AccessorFunction(_) @@ -196,17 +196,25 @@ impl<'a> Formattable for Expr<'a> { buf.push(')'); } } - Num(string) | Float(string) | GlobalTag(string) | PrivateTag(string) => { + &Num(string) => { + buf.indent(indent); + buf.push_str(string); + } + &Float(string) => { + buf.indent(indent); + buf.push_str(string); + } + GlobalTag(string) | PrivateTag(string) => { buf.indent(indent); buf.push_str(string) } - NonBase10Int { + &NonBase10Int { base, string, is_negative, } => { buf.indent(indent); - if *is_negative { + if is_negative { buf.push('-'); } @@ -514,11 +522,15 @@ fn fmt_when<'a, 'buf>( let patterns = &branch.patterns; let expr = &branch.value; let (first_pattern, rest) = patterns.split_first().unwrap(); - let is_multiline = match rest.last() { - None => false, - Some(last_pattern) => { - first_pattern.region.start().line != last_pattern.region.end().line - } + let is_multiline = if let Some((last_pattern, inner_patterns)) = rest.split_last() { + !first_pattern.value.extract_spaces().after.is_empty() + || !last_pattern.value.extract_spaces().before.is_empty() + || inner_patterns.iter().any(|p| { + let spaces = p.value.extract_spaces(); + !spaces.before.is_empty() || !spaces.after.is_empty() + }) + } else { + false }; fmt_pattern( diff --git a/compiler/fmt/src/lib.rs b/compiler/fmt/src/lib.rs index e7200d1413..1d99edbcd6 100644 --- a/compiler/fmt/src/lib.rs +++ b/compiler/fmt/src/lib.rs @@ -11,6 +11,7 @@ pub mod spaces; use bumpalo::{collections::String, Bump}; +#[derive(Debug)] pub struct Buf<'a> { text: String<'a>, spaces_to_flush: usize, diff --git a/compiler/fmt/src/module.rs b/compiler/fmt/src/module.rs index 42a5c303be..ba698dd2f8 100644 --- a/compiler/fmt/src/module.rs +++ b/compiler/fmt/src/module.rs @@ -5,9 +5,10 @@ use crate::spaces::{fmt_default_spaces, fmt_spaces, INDENT}; use crate::Buf; use roc_parse::ast::{Collection, Module, Spaced}; use roc_parse::header::{ - AppHeader, Effects, ExposedName, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry, - PackageName, PlatformHeader, PlatformRequires, PlatformRigid, To, TypedIdent, + AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry, + PackageName, PlatformHeader, PlatformRequires, To, TypedIdent, }; +use roc_parse::ident::UppercaseIdent; use roc_region::all::Loc; pub fn fmt_module<'a, 'buf>(buf: &mut Buf<'buf>, module: &'a Module<'a>) { @@ -21,6 +22,9 @@ pub fn fmt_module<'a, 'buf>(buf: &mut Buf<'buf>, module: &'a Module<'a>) { Module::Platform { header } => { fmt_platform_header(buf, header); } + Module::Hosted { header } => { + fmt_hosted_header(buf, header); + } } } @@ -49,6 +53,45 @@ pub fn fmt_interface_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a Interface fmt_imports(buf, header.imports, indent); } +pub fn fmt_hosted_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a HostedHeader<'a>) { + let indent = INDENT; + + buf.indent(0); + buf.push_str("hosted"); + + // module name + fmt_default_spaces(buf, header.after_hosted_keyword, indent); + buf.push_str(header.name.value.as_str()); + + // exposes + fmt_default_spaces(buf, header.before_exposes, indent); + buf.indent(indent); + buf.push_str("exposes"); + fmt_default_spaces(buf, header.after_exposes, indent); + fmt_exposes(buf, header.exposes, indent); + + // imports + fmt_default_spaces(buf, header.before_imports, indent); + buf.indent(indent); + buf.push_str("imports"); + fmt_default_spaces(buf, header.after_imports, indent); + fmt_imports(buf, header.imports, indent); + + // generates + fmt_default_spaces(buf, header.before_generates, indent); + buf.indent(indent); + buf.push_str("generates"); + fmt_default_spaces(buf, header.after_generates, indent); + buf.push_str(header.generates.into()); + + // with + fmt_default_spaces(buf, header.before_with, indent); + buf.indent(indent); + buf.push_str("with"); + fmt_default_spaces(buf, header.after_with, indent); + fmt_exposes(buf, header.generates_with, indent); +} + pub fn fmt_app_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a AppHeader<'a>) { let indent = INDENT; buf.indent(0); @@ -76,7 +119,7 @@ pub fn fmt_app_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a AppHeader<'a>) buf.indent(indent); buf.push_str("provides"); fmt_default_spaces(buf, header.after_provides, indent); - fmt_provides(buf, header.provides, indent); + fmt_provides(buf, header.provides, header.provides_types, indent); fmt_default_spaces(buf, header.before_to, indent); buf.indent(indent); buf.push_str("to"); @@ -126,9 +169,7 @@ pub fn fmt_platform_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a PlatformHe buf.indent(indent); buf.push_str("provides"); fmt_default_spaces(buf, header.after_provides, indent); - fmt_provides(buf, header.provides, indent); - - fmt_effects(buf, &header.effects, indent); + fmt_provides(buf, header.provides, None, indent); } fn fmt_requires<'a, 'buf>(buf: &mut Buf<'buf>, requires: &PlatformRequires<'a>, indent: u16) { @@ -140,22 +181,6 @@ fn fmt_requires<'a, 'buf>(buf: &mut Buf<'buf>, requires: &PlatformRequires<'a>, buf.push_str(" }"); } -fn fmt_effects<'a, 'buf>(buf: &mut Buf<'buf>, effects: &Effects<'a>, indent: u16) { - fmt_default_spaces(buf, effects.spaces_before_effects_keyword, indent); - buf.indent(indent); - buf.push_str("effects"); - fmt_default_spaces(buf, effects.spaces_after_effects_keyword, indent); - - buf.indent(indent); - buf.push_str(effects.effect_shortname); - buf.push('.'); - buf.push_str(effects.effect_type_name); - - fmt_default_spaces(buf, effects.spaces_after_type_name, indent); - - fmt_collection(buf, indent, '{', '}', effects.entries, Newlines::No) -} - impl<'a> Formattable for TypedIdent<'a> { fn is_multiline(&self) -> bool { false @@ -179,8 +204,14 @@ fn fmt_package_name<'buf>(buf: &mut Buf<'buf>, name: PackageName, _indent: u16) impl<'a, T: Formattable> Formattable for Spaced<'a, T> { fn is_multiline(&self) -> bool { - // TODO - false + use Spaced::*; + + match self { + Item(formattable) => formattable.is_multiline(), + SpaceBefore(formattable, spaces) | SpaceAfter(formattable, spaces) => { + !spaces.is_empty() || formattable.is_multiline() + } + } } fn format_with_options<'buf>( @@ -206,32 +237,25 @@ impl<'a, T: Formattable> Formattable for Spaced<'a, T> { } } -impl<'a> Formattable for PlatformRigid<'a> { - fn is_multiline(&self) -> bool { - false - } - - fn format<'buf>(&self, buf: &mut Buf<'buf>, _indent: u16) { - buf.push_str(self.rigid); - buf.push_str("=>"); - buf.push_str(self.alias); - } -} - fn fmt_imports<'a, 'buf>( buf: &mut Buf<'buf>, loc_entries: Collection<'a, Loc>>>, indent: u16, ) { - fmt_collection(buf, indent, '[', ']', loc_entries, Newlines::No) + fmt_collection(buf, indent + INDENT, '[', ']', loc_entries, Newlines::No) } fn fmt_provides<'a, 'buf>( buf: &mut Buf<'buf>, - loc_entries: Collection<'a, Loc>>>, + loc_exposed_names: Collection<'a, Loc>>>, + loc_provided_types: Option>>>>, indent: u16, ) { - fmt_collection(buf, indent, '[', ']', loc_entries, Newlines::No) + fmt_collection(buf, indent, '[', ']', loc_exposed_names, Newlines::No); + if let Some(loc_provided) = loc_provided_types { + fmt_default_spaces(buf, &[], indent); + fmt_collection(buf, indent + INDENT, '{', '}', loc_provided, Newlines::No); + } } fn fmt_to<'buf>(buf: &mut Buf<'buf>, to: To, indent: u16) { @@ -248,7 +272,7 @@ fn fmt_exposes<'buf, N: Formattable + Copy>( loc_entries: Collection<'_, Loc>>, indent: u16, ) { - fmt_collection(buf, indent, '[', ']', loc_entries, Newlines::No) + fmt_collection(buf, indent + INDENT, '[', ']', loc_entries, Newlines::No) } pub trait FormatName { @@ -282,7 +306,8 @@ impl<'a> Formattable for ExposedName<'a> { false } - fn format<'buf>(&self, buf: &mut Buf<'buf>, _indent: u16) { + fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) { + buf.indent(indent); buf.push_str(self.as_str()); } } @@ -330,6 +355,8 @@ fn fmt_packages_entry<'a, 'buf>(buf: &mut Buf<'buf>, entry: &PackageEntry<'a>, i fn fmt_imports_entry<'a, 'buf>(buf: &mut Buf<'buf>, entry: &ImportsEntry<'a>, indent: u16) { use roc_parse::header::ImportsEntry::*; + buf.indent(indent); + match entry { Module(module, loc_exposes_entries) => { buf.push_str(module.as_str()); diff --git a/compiler/fmt/src/pattern.rs b/compiler/fmt/src/pattern.rs index 2aac5b3e91..e09f9e19d7 100644 --- a/compiler/fmt/src/pattern.rs +++ b/compiler/fmt/src/pattern.rs @@ -31,9 +31,9 @@ impl<'a> Formattable for Pattern<'a> { | Pattern::GlobalTag(_) | Pattern::PrivateTag(_) | Pattern::Apply(_, _) - | Pattern::NumLiteral(_) + | Pattern::NumLiteral(..) | Pattern::NonBase10Literal { .. } - | Pattern::FloatLiteral(_) + | Pattern::FloatLiteral(..) | Pattern::StrLiteral(_) | Pattern::Underscore(_) | Pattern::Malformed(_) @@ -116,17 +116,17 @@ impl<'a> Formattable for Pattern<'a> { loc_pattern.format(buf, indent); } - NumLiteral(string) => { + &NumLiteral(string) => { buf.indent(indent); buf.push_str(string); } - NonBase10Literal { + &NonBase10Literal { base, string, is_negative, } => { buf.indent(indent); - if *is_negative { + if is_negative { buf.push('-'); } @@ -139,7 +139,7 @@ impl<'a> Formattable for Pattern<'a> { buf.push_str(string); } - FloatLiteral(string) => { + &FloatLiteral(string) => { buf.indent(indent); buf.push_str(string); } diff --git a/compiler/fmt/tests/test_fmt.rs b/compiler/fmt/tests/test_fmt.rs index 2b5561fdce..a49914abce 100644 --- a/compiler/fmt/tests/test_fmt.rs +++ b/compiler/fmt/tests/test_fmt.rs @@ -410,19 +410,19 @@ mod test_fmt { indoc!( r#" x = (5) - - + + y = ((10)) - + 42 "# ), indoc!( r#" x = 5 - + y = 10 - + 42 "# ), @@ -766,9 +766,9 @@ mod test_fmt { # comment 2 x: 42 - + # comment 3 - + # comment 4 }"# ), @@ -819,7 +819,7 @@ mod test_fmt { f: { y : Int *, x : Int * , } - + f"# ), indoc!( @@ -910,7 +910,7 @@ mod test_fmt { { # comment } - + f"# ), ); @@ -935,7 +935,7 @@ mod test_fmt { indoc!( r#" f : - { + { x: Int * # comment 1 , # comment 2 @@ -951,7 +951,7 @@ mod test_fmt { # comment 1 # comment 2 } - + f"# ), ); @@ -1007,7 +1007,7 @@ mod test_fmt { indoc!( r#" identity = \a - -> + -> a + b identity 4010 @@ -1387,7 +1387,7 @@ mod test_fmt { expr_formats_to( indoc!( r#" - { + { }"# ), "{}", @@ -2640,6 +2640,25 @@ mod test_fmt { )); } + #[test] + fn multi_line_interface() { + module_formats_same(indoc!( + r#" + interface Foo + exposes + [ + Stuff, + Things, + somethingElse, + ] + imports + [ + Blah, + Baz.{ stuff, things }, + ]"# + )); + } + #[test] fn single_line_app() { module_formats_same(indoc!( @@ -2652,20 +2671,47 @@ mod test_fmt { fn single_line_platform() { module_formats_same( "platform \"folkertdev/foo\" \ - requires { model=>Model, msg=>Msg } { main : Effect {} } \ + requires { Model, Msg } { main : Effect {} } \ exposes [] \ packages {} \ imports [ Task.{ Task } ] \ - provides [ mainForHost ] \ - effects fx.Effect \ - { \ - putLine : Str -> Effect {}, \ - putInt : I64 -> Effect {}, \ - getInt : Effect { value : I64, errorCode : [ A, B ], isError : Bool } \ - }", + provides [ mainForHost ]", ); } + #[test] + fn single_line_hosted() { + module_formats_same(indoc!( + r#" + hosted Foo exposes [] imports [] generates Bar with []"# + )); + } + + #[test] + fn multi_line_hosted() { + module_formats_same(indoc!( + r#" + hosted Foo + exposes + [ + Stuff, + Things, + somethingElse, + ] + imports + [ + Blah, + Baz.{ stuff, things }, + ] + generates Bar with + [ + map, + after, + loop, + ]"# + )); + } + /// Annotations and aliases #[test] @@ -2763,7 +2809,7 @@ mod test_fmt { # comment 2 # comment 3 ] - + b "# ), diff --git a/compiler/gen_dev/Cargo.toml b/compiler/gen_dev/Cargo.toml index 41fe784e7e..ca81c78e67 100644 --- a/compiler/gen_dev/Cargo.toml +++ b/compiler/gen_dev/Cargo.toml @@ -16,7 +16,8 @@ roc_builtins = { path = "../builtins" } roc_unify = { path = "../unify" } roc_solve = { path = "../solve" } roc_mono = { path = "../mono" } -roc_reporting = { path = "../../reporting" } +roc_target = { path = "../roc_target" } +roc_error_macros = { path = "../../error_macros" } bumpalo = { version = "3.8.0", features = ["collections"] } target-lexicon = "0.12.2" # TODO: Deal with the update of object to 0.27. diff --git a/compiler/gen_dev/src/generic64/aarch64.rs b/compiler/gen_dev/src/generic64/aarch64.rs index 3c493882dd..591c799052 100644 --- a/compiler/gen_dev/src/generic64/aarch64.rs +++ b/compiler/gen_dev/src/generic64/aarch64.rs @@ -3,9 +3,9 @@ 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; -use roc_reporting::internal_error; #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] #[allow(dead_code)] diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 1a3a549be0..f30e98edc3 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -2,17 +2,18 @@ use crate::{Backend, Env, Relocation}; use bumpalo::collections::Vec; use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_collections::all::{MutMap, MutSet}; +use roc_error_macros::internal_error; use roc_module::symbol::{Interns, Symbol}; use roc_mono::code_gen_help::CodeGenHelp; use roc_mono::ir::{BranchInfo, JoinPointId, Literal, Param, ProcLayout, SelfRecursive, Stmt}; use roc_mono::layout::{Builtin, Layout}; -use roc_reporting::internal_error; +use roc_target::TargetInfo; use std::marker::PhantomData; pub mod aarch64; pub mod x86_64; -const PTR_SIZE: u32 = 8; +const TARGET_INFO: TargetInfo = TargetInfo::default_x86_64(); pub trait CallConv { const BASE_PTR_REG: GeneralReg; @@ -308,7 +309,7 @@ pub fn new_backend_64bit< phantom_cc: PhantomData, env, interns, - helper_proc_gen: CodeGenHelp::new(env.arena, IntWidth::I64, env.module_id), + helper_proc_gen: CodeGenHelp::new(env.arena, TARGET_INFO, env.module_id), helper_proc_symbols: bumpalo::vec![in env.arena], proc_name: None, is_self_recursive: None, @@ -974,7 +975,7 @@ impl< } fn create_struct(&mut self, sym: &Symbol, layout: &Layout<'a>, fields: &'a [Symbol]) { - let struct_size = layout.stack_size(PTR_SIZE); + let struct_size = layout.stack_size(TARGET_INFO); if let Layout::Struct(field_layouts) = layout { if struct_size > 0 { @@ -991,7 +992,7 @@ impl< let mut current_offset = offset; for (field, field_layout) in fields.iter().zip(field_layouts.iter()) { self.copy_symbol_to_stack_offset(current_offset, field, field_layout); - let field_size = field_layout.stack_size(PTR_SIZE); + let field_size = field_layout.stack_size(TARGET_INFO); current_offset += field_size as i32; } } else { @@ -1029,14 +1030,14 @@ impl< if let Some(SymbolStorage::Base { offset, .. }) = self.symbol_storage_map.get(structure) { let mut data_offset = *offset; for i in 0..index { - let field_size = field_layouts[i as usize].stack_size(PTR_SIZE); + let field_size = field_layouts[i as usize].stack_size(TARGET_INFO); data_offset += field_size as i32; } self.symbol_storage_map.insert( *sym, SymbolStorage::Base { offset: data_offset, - size: field_layouts[index as usize].stack_size(PTR_SIZE), + size: field_layouts[index as usize].stack_size(TARGET_INFO), owned: false, }, ); @@ -1569,10 +1570,10 @@ impl< { debug_assert_eq!( *size, - layout.stack_size(PTR_SIZE), + layout.stack_size(TARGET_INFO), "expected struct to have same size as data being stored in it" ); - for i in 0..layout.stack_size(PTR_SIZE) as i32 { + for i in 0..layout.stack_size(TARGET_INFO) as i32 { ASM::mov_reg64_base32(&mut self.buf, tmp_reg, from_offset + i); ASM::mov_base32_reg64(&mut self.buf, to_offset + i, tmp_reg); } diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index 7060f78af0..359dd4670f 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -1,13 +1,13 @@ -use crate::generic64::{Assembler, CallConv, RegTrait, SymbolStorage, PTR_SIZE}; +use crate::generic64::{Assembler, CallConv, RegTrait, SymbolStorage, TARGET_INFO}; use crate::{ single_register_builtins, single_register_floats, single_register_integers, 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_reporting::internal_error; // 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. @@ -451,7 +451,7 @@ impl CallConv for 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. // details here: https://github.com/hjl-tools/x86-psABI/wiki/x86-64-psABI-1.0.pdf - ret_layout.stack_size(PTR_SIZE) > 16 + ret_layout.stack_size(TARGET_INFO) > 16 } } @@ -775,7 +775,7 @@ impl CallConv for 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 - ret_layout.stack_size(PTR_SIZE) > 8 + ret_layout.stack_size(TARGET_INFO) > 8 } } diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index b188f28b41..af382ee988 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -5,8 +5,9 @@ use bumpalo::{collections::Vec, Bump}; use roc_builtins::bitcode::{self, FloatWidth, IntWidth}; use roc_collections::all::{MutMap, MutSet}; +use roc_error_macros::internal_error; use roc_module::ident::{ModuleName, TagName}; -use roc_module::low_level::LowLevel; +use roc_module::low_level::{LowLevel, LowLevelWrapperType}; use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_mono::code_gen_help::CodeGenHelp; use roc_mono::ir::{ @@ -14,7 +15,6 @@ use roc_mono::ir::{ SelfRecursive, Stmt, }; use roc_mono::layout::{Builtin, Layout, LayoutId, LayoutIds}; -use roc_reporting::internal_error; mod generic64; mod object_builder; @@ -260,8 +260,9 @@ trait Backend<'a> { ret_layout, .. } => { - // If this function is just a lowlevel wrapper, then inline it - if let Some(lowlevel) = LowLevel::from_inlined_wrapper(*func_sym) { + if let LowLevelWrapperType::CanBeReplacedBy(lowlevel) = + LowLevelWrapperType::from_symbol(*func_sym) + { self.build_run_low_level( sym, &lowlevel, diff --git a/compiler/gen_dev/src/object_builder.rs b/compiler/gen_dev/src/object_builder.rs index ac8fa1d922..d8675c880d 100644 --- a/compiler/gen_dev/src/object_builder.rs +++ b/compiler/gen_dev/src/object_builder.rs @@ -8,11 +8,11 @@ use object::{ SymbolFlags, SymbolKind, SymbolScope, }; use roc_collections::all::MutMap; +use roc_error_macros::internal_error; use roc_module::symbol; use roc_module::symbol::Interns; use roc_mono::ir::{Proc, ProcLayout}; use roc_mono::layout::LayoutIds; -use roc_reporting::internal_error; use target_lexicon::{Architecture as TargetArch, BinaryFormat as TargetBF, Triple}; // This is used by some code below which is currently commented out. diff --git a/compiler/gen_llvm/Cargo.toml b/compiler/gen_llvm/Cargo.toml index 2c8fb91ab9..e87ed1c434 100644 --- a/compiler/gen_llvm/Cargo.toml +++ b/compiler/gen_llvm/Cargo.toml @@ -10,8 +10,9 @@ edition = "2018" roc_collections = { path = "../collections" } roc_module = { path = "../module" } roc_builtins = { path = "../builtins" } -roc_reporting = { path = "../../reporting" } +roc_error_macros = { path = "../../error_macros" } roc_mono = { path = "../mono" } +roc_target = { path = "../roc_target" } roc_std = { path = "../../roc_std" } morphic_lib = { path = "../../vendor/morphic_lib" } bumpalo = { version = "3.8.0", features = ["collections"] } diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index dbcd2f7790..9ca24e1739 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -45,7 +45,7 @@ use inkwell::types::{ use inkwell::values::BasicValueEnum::{self, *}; use inkwell::values::{ BasicMetadataValueEnum, BasicValue, CallSiteValue, CallableValue, FloatValue, FunctionValue, - InstructionOpcode, InstructionValue, IntValue, PointerValue, StructValue, + InstructionOpcode, InstructionValue, IntValue, PhiValue, PointerValue, StructValue, }; use inkwell::OptimizationLevel; use inkwell::{AddressSpace, IntPredicate}; @@ -55,6 +55,7 @@ use morphic_lib::{ use roc_builtins::bitcode::{self, FloatWidth, IntWidth, IntrinsicName}; use roc_builtins::{float_intrinsic, int_intrinsic}; use roc_collections::all::{ImMap, MutMap, MutSet}; +use roc_error_macros::internal_error; use roc_module::low_level::LowLevel; use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_mono::ir::{ @@ -62,7 +63,7 @@ use roc_mono::ir::{ ModifyRc, OptLevel, ProcLayout, }; use roc_mono::layout::{Builtin, LambdaSet, Layout, LayoutIds, TagIdIntType, UnionLayout}; -use roc_reporting::internal_error; +use roc_target::TargetInfo; use target_lexicon::{Architecture, OperatingSystem, Triple}; /// This is for Inkwell's FunctionValue::verify - we want to know the verification @@ -129,7 +130,7 @@ impl<'ctx> Iterator for FunctionIterator<'ctx> { pub struct Scope<'a, 'ctx> { symbols: ImMap, BasicValueEnum<'ctx>)>, pub top_level_thunks: ImMap, FunctionValue<'ctx>)>, - join_points: ImMap, &'a [PointerValue<'ctx>])>, + join_points: ImMap, &'a [PhiValue<'ctx>])>, } impl<'a, 'ctx> Scope<'a, 'ctx> { @@ -166,7 +167,7 @@ pub struct Env<'a, 'ctx, 'env> { pub compile_unit: &'env DICompileUnit<'ctx>, pub module: &'ctx Module<'ctx>, pub interns: Interns, - pub ptr_bytes: u32, + pub target_info: TargetInfo, pub is_gen_test: bool, pub exposed_to_host: MutSet, } @@ -195,15 +196,9 @@ impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> { pub fn ptr_int(&self) -> IntType<'ctx> { let ctx = self.context; - match self.ptr_bytes { - 1 => ctx.i8_type(), - 2 => ctx.i16_type(), - 4 => ctx.i32_type(), - 8 => ctx.i64_type(), - _ => panic!( - "Invalid target: Roc does't support compiling to {}-bit systems.", - self.ptr_bytes * 8 - ), + match self.target_info.ptr_width() { + roc_target::PtrWidth::Bytes4 => ctx.i32_type(), + roc_target::PtrWidth::Bytes8 => ctx.i64_type(), } } @@ -212,11 +207,11 @@ impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> { /// on 64-bit systems, this is i128 /// on 32-bit systems, this is i64 pub fn str_list_c_abi(&self) -> IntType<'ctx> { - crate::llvm::convert::str_list_int(self.context, self.ptr_bytes) + crate::llvm::convert::str_list_int(self.context, self.target_info) } pub fn small_str_bytes(&self) -> u32 { - self.ptr_bytes * 2 + self.target_info.ptr_width() as u32 * 2 } pub fn build_intrinsic_call( @@ -269,7 +264,7 @@ impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> { } pub fn alignment_intvalue(&self, element_layout: &Layout<'a>) -> BasicValueEnum<'ctx> { - let alignment = element_layout.alignment_bytes(self.ptr_bytes); + let alignment = element_layout.alignment_bytes(self.target_info); let alignment_iv = self.alignment_const(alignment); alignment_iv.into() @@ -317,12 +312,9 @@ impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> { ) -> CallSiteValue<'ctx> { let false_val = self.context.bool_type().const_int(0, false); - let intrinsic_name = match self.ptr_bytes { - 8 => LLVM_MEMSET_I64, - 4 => LLVM_MEMSET_I32, - other => { - unreachable!("Unsupported number of ptr_bytes {:?}", other); - } + let intrinsic_name = match self.target_info.ptr_width() { + roc_target::PtrWidth::Bytes8 => LLVM_MEMSET_I64, + roc_target::PtrWidth::Bytes4 => LLVM_MEMSET_I32, }; self.build_intrinsic_call( @@ -493,6 +485,12 @@ fn add_int_intrinsic<'ctx, F>( }; } + check!(IntWidth::U8, ctx.i8_type()); + check!(IntWidth::U16, ctx.i16_type()); + check!(IntWidth::U32, ctx.i32_type()); + check!(IntWidth::U64, ctx.i64_type()); + check!(IntWidth::U128, ctx.i128_type()); + check!(IntWidth::I8, ctx.i8_type()); check!(IntWidth::I16, ctx.i16_type()); check!(IntWidth::I32, ctx.i32_type()); @@ -562,23 +560,31 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) { }); add_float_intrinsic(ctx, module, &LLVM_FLOOR, |t| t.fn_type(&[t.into()], false)); - add_int_intrinsic(ctx, module, &LLVM_SADD_WITH_OVERFLOW, |t| { + add_int_intrinsic(ctx, module, &LLVM_ADD_WITH_OVERFLOW, |t| { let fields = [t.into(), i1_type.into()]; ctx.struct_type(&fields, false) .fn_type(&[t.into(), t.into()], false) }); - add_int_intrinsic(ctx, module, &LLVM_SSUB_WITH_OVERFLOW, |t| { + add_int_intrinsic(ctx, module, &LLVM_SUB_WITH_OVERFLOW, |t| { let fields = [t.into(), i1_type.into()]; ctx.struct_type(&fields, false) .fn_type(&[t.into(), t.into()], false) }); - add_int_intrinsic(ctx, module, &LLVM_SMUL_WITH_OVERFLOW, |t| { + add_int_intrinsic(ctx, module, &LLVM_MUL_WITH_OVERFLOW, |t| { let fields = [t.into(), i1_type.into()]; ctx.struct_type(&fields, false) .fn_type(&[t.into(), t.into()], false) }); + + add_int_intrinsic(ctx, module, &LLVM_ADD_SATURATED, |t| { + t.fn_type(&[t.into(), t.into()], false) + }); + + add_int_intrinsic(ctx, module, &LLVM_SUB_SATURATED, |t| { + t.fn_type(&[t.into(), t.into()], false) + }); } const LLVM_POW: IntrinsicName = float_intrinsic!("llvm.pow"); @@ -602,9 +608,15 @@ static LLVM_STACK_SAVE: &str = "llvm.stacksave"; static LLVM_SETJMP: &str = "llvm.eh.sjlj.setjmp"; pub static LLVM_LONGJMP: &str = "llvm.eh.sjlj.longjmp"; -const LLVM_SADD_WITH_OVERFLOW: IntrinsicName = int_intrinsic!("llvm.sadd.with.overflow"); -const LLVM_SSUB_WITH_OVERFLOW: IntrinsicName = int_intrinsic!("llvm.ssub.with.overflow"); -const LLVM_SMUL_WITH_OVERFLOW: IntrinsicName = int_intrinsic!("llvm.smul.with.overflow"); +const LLVM_ADD_WITH_OVERFLOW: IntrinsicName = + 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"); +const LLVM_MUL_WITH_OVERFLOW: IntrinsicName = + 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"); fn add_intrinsic<'ctx>( module: &Module<'ctx>, @@ -759,11 +771,13 @@ pub fn build_exp_literal<'a, 'ctx, 'env>( env.context.bool_type().const_int(*int as u64, false).into() } Layout::Builtin(Builtin::Int(int_width)) => { - int_with_precision(env, *int as i128, *int_width).into() + int_with_precision(env, *int, *int_width).into() } _ => panic!("Invalid layout for int literal = {:?}", layout), }, + U128(int) => const_u128(env, *int).into(), + Float(float) => match layout { Layout::Builtin(Builtin::Float(float_width)) => { float_with_precision(env, *float, *float_width) @@ -1394,6 +1408,18 @@ fn build_wrapped_tag<'a, 'ctx, 'env>( ); field_vals.push(ptr); + } else if matches!( + tag_field_layout, + Layout::Union(UnionLayout::NonRecursive(_)) + ) { + debug_assert!(val.is_pointer_value()); + + // We store non-recursive unions without any indirection. + let reified = env + .builder + .build_load(val.into_pointer_value(), "load_non_recursive"); + + field_vals.push(reified); } else { // this check fails for recursive tag unions, but can be helpful while debugging // debug_assert_eq!(tag_field_layout, val_layout); @@ -1406,7 +1432,7 @@ fn build_wrapped_tag<'a, 'ctx, 'env>( let raw_data_ptr = allocate_tag(env, parent, reuse_allocation, union_layout, tags); let struct_type = env.context.struct_type(&field_types, false); - if union_layout.stores_tag_id_as_data(env.ptr_bytes) { + if union_layout.stores_tag_id_as_data(env.target_info) { let tag_id_ptr = builder .build_struct_gep(raw_data_ptr, TAG_ID_INDEX, "tag_id_index") .unwrap(); @@ -1442,10 +1468,16 @@ fn build_wrapped_tag<'a, 'ctx, 'env>( pub fn tag_alloca<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, - type_: BasicTypeEnum<'ctx>, + basic_type: BasicTypeEnum<'ctx>, name: &str, ) -> PointerValue<'ctx> { - let result_alloca = env.builder.build_alloca(type_, name); + let parent = env + .builder + .get_insert_block() + .unwrap() + .get_parent() + .unwrap(); + let result_alloca = create_entry_block_alloca(env, parent, basic_type, name); // Initialize all memory of the alloca. This _should_ not be required, but currently // LLVM can access uninitialized memory after applying some optimizations. Hopefully @@ -1464,7 +1496,7 @@ pub fn tag_alloca<'a, 'ctx, 'env>( // After inlining, those checks are combined. That means that even if the tag is Err, // a check is done on the "string" to see if it is big or small, which will touch the // uninitialized memory. - let all_zeros = type_.const_zero(); + let all_zeros = basic_type.const_zero(); env.builder.build_store(result_alloca, all_zeros); result_alloca @@ -1486,7 +1518,7 @@ pub fn build_tag<'a, 'ctx, 'env>( UnionLayout::NonRecursive(tags) => { debug_assert!(union_size > 1); - let internal_type = block_of_memory_slices(env.context, tags, env.ptr_bytes); + let internal_type = block_of_memory_slices(env.context, tags, env.target_info); let tag_id_type = basic_type_from_layout(env, &tag_id_layout).into_int_type(); let wrapper_type = env @@ -1673,7 +1705,7 @@ pub fn build_tag<'a, 'ctx, 'env>( other_fields, } => { let tag_struct_type = - block_of_memory_slices(env.context, &[other_fields], env.ptr_bytes); + block_of_memory_slices(env.context, &[other_fields], env.target_info); if tag_id == *nullable_id as _ { let output_type = tag_struct_type.ptr_type(AddressSpace::Generic); @@ -1750,7 +1782,7 @@ fn tag_pointer_set_tag_id<'a, 'ctx, 'env>( pointer: PointerValue<'ctx>, ) -> PointerValue<'ctx> { // we only have 3 bits, so can encode only 0..7 (or on 32-bit targets, 2 bits to encode 0..3) - debug_assert!((tag_id as u32) < env.ptr_bytes); + debug_assert!((tag_id as u32) < env.target_info.ptr_width() as u32); let ptr_int = env.ptr_int(); @@ -1763,16 +1795,18 @@ fn tag_pointer_set_tag_id<'a, 'ctx, 'env>( .build_int_to_ptr(combined, pointer.get_type(), "to_ptr") } +pub fn tag_pointer_tag_id_bits_and_mask(target_info: TargetInfo) -> (u64, u64) { + match target_info.ptr_width() { + roc_target::PtrWidth::Bytes8 => (3, 0b0000_0111), + roc_target::PtrWidth::Bytes4 => (2, 0b0000_0011), + } +} + pub fn tag_pointer_read_tag_id<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, pointer: PointerValue<'ctx>, ) -> IntValue<'ctx> { - let mask: u64 = match env.ptr_bytes { - 8 => 0b0000_0111, - 4 => 0b0000_0011, - _ => unreachable!(), - }; - + let (_, mask) = tag_pointer_tag_id_bits_and_mask(env.target_info); let ptr_int = env.ptr_int(); let as_int = env.builder.build_ptr_to_int(pointer, ptr_int, "to_int"); @@ -1790,11 +1824,7 @@ pub fn tag_pointer_clear_tag_id<'a, 'ctx, 'env>( ) -> PointerValue<'ctx> { let ptr_int = env.ptr_int(); - let tag_id_bits_mask = match env.ptr_bytes { - 8 => 3, - 4 => 2, - _ => unreachable!(), - }; + let (tag_id_bits_mask, _) = tag_pointer_tag_id_bits_and_mask(env.target_info); let as_int = env.builder.build_ptr_to_int(pointer, ptr_int, "to_int"); @@ -1881,7 +1911,7 @@ pub fn get_tag_id<'a, 'ctx, 'env>( UnionLayout::Recursive(_) => { let argument_ptr = argument.into_pointer_value(); - if union_layout.stores_tag_id_as_data(env.ptr_bytes) { + if union_layout.stores_tag_id_as_data(env.target_info) { get_tag_id_wrapped(env, argument_ptr) } else { tag_pointer_read_tag_id(env, argument_ptr) @@ -1912,7 +1942,7 @@ pub fn get_tag_id<'a, 'ctx, 'env>( { env.builder.position_at_end(else_block); - let tag_id = if union_layout.stores_tag_id_as_data(env.ptr_bytes) { + let tag_id = if union_layout.stores_tag_id_as_data(env.target_info) { get_tag_id_wrapped(env, argument_ptr) } else { tag_pointer_read_tag_id(env, argument_ptr) @@ -2020,12 +2050,12 @@ fn lookup_at_index_ptr2<'a, 'ctx, 'env>( let result = if field_layout.is_passed_by_reference() { let field_type = basic_type_from_layout(env, &field_layout); - let align_bytes = field_layout.alignment_bytes(env.ptr_bytes); + let align_bytes = field_layout.alignment_bytes(env.target_info); let alloca = tag_alloca(env, field_type, "copied_tag"); if align_bytes > 0 { let size = env .ptr_int() - .const_int(field_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(field_layout.stack_size(env.target_info) as u64, false); env.builder .build_memcpy(alloca, align_bytes, elem_ptr, align_bytes, size) @@ -2058,8 +2088,8 @@ pub fn reserve_with_refcount<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>, ) -> PointerValue<'ctx> { - let stack_size = layout.stack_size(env.ptr_bytes); - let alignment_bytes = layout.alignment_bytes(env.ptr_bytes); + let stack_size = layout.stack_size(env.target_info); + let alignment_bytes = layout.alignment_bytes(env.target_info); let basic_type = basic_type_from_layout(env, layout); @@ -2071,9 +2101,9 @@ fn reserve_with_refcount_union_as_block_of_memory<'a, 'ctx, 'env>( union_layout: UnionLayout<'a>, fields: &[&[Layout<'a>]], ) -> PointerValue<'ctx> { - let ptr_bytes = env.ptr_bytes; + let ptr_bytes = env.target_info; - let block_type = block_of_memory_slices(env.context, fields, env.ptr_bytes); + let block_type = block_of_memory_slices(env.context, fields, env.target_info); let basic_type = if union_layout.stores_tag_id_as_data(ptr_bytes) { let tag_id_type = basic_type_from_layout(env, &union_layout.tag_id_layout()); @@ -2087,17 +2117,17 @@ fn reserve_with_refcount_union_as_block_of_memory<'a, 'ctx, 'env>( let mut stack_size = fields .iter() - .map(|tag| tag.iter().map(|l| l.stack_size(env.ptr_bytes)).sum()) + .map(|tag| tag.iter().map(|l| l.stack_size(env.target_info)).sum()) .max() .unwrap_or_default(); if union_layout.stores_tag_id_as_data(ptr_bytes) { - stack_size += union_layout.tag_id_layout().stack_size(env.ptr_bytes); + stack_size += union_layout.tag_id_layout().stack_size(env.target_info); } let alignment_bytes = fields .iter() - .map(|tag| tag.iter().map(|l| l.alignment_bytes(env.ptr_bytes))) + .map(|tag| tag.iter().map(|l| l.alignment_bytes(env.target_info))) .flatten() .max() .unwrap_or(0); @@ -2117,7 +2147,7 @@ fn reserve_with_refcount_help<'a, 'ctx, 'env>( let value_bytes_intvalue = len_type.const_int(stack_size as u64, false); - let rc1 = crate::llvm::refcounting::refcount_1(ctx, env.ptr_bytes); + let rc1 = crate::llvm::refcounting::refcount_1(ctx, env.target_info); allocate_with_refcount_help(env, basic_type, alignment_bytes, value_bytes_intvalue, rc1) } @@ -2145,8 +2175,9 @@ pub fn allocate_with_refcount_help<'a, 'ctx, 'env>( let builder = env.builder; let len_type = env.ptr_int(); + let ptr_width_u32 = env.target_info.ptr_width() as u32; - let extra_bytes = alignment_bytes.max(env.ptr_bytes); + let extra_bytes = alignment_bytes.max(ptr_width_u32); let ptr = { // number of bytes we will allocated @@ -2171,8 +2202,8 @@ pub fn allocate_with_refcount_help<'a, 'ctx, 'env>( .into_pointer_value(); let index = match extra_bytes { - n if n == env.ptr_bytes => 1, - n if n == 2 * env.ptr_bytes => 2, + n if n == ptr_width_u32 => 1, + n if n == 2 * ptr_width_u32 => 2, _ => unreachable!("invalid extra_bytes, {}", extra_bytes), }; @@ -2191,11 +2222,11 @@ pub fn allocate_with_refcount_help<'a, 'ctx, 'env>( }; let refcount_ptr = match extra_bytes { - n if n == env.ptr_bytes => { + n if n == ptr_width_u32 => { // the allocated pointer is the same as the refcounted pointer unsafe { PointerToRefcount::from_ptr(env, ptr) } } - n if n == 2 * env.ptr_bytes => { + n if n == 2 * ptr_width_u32 => { // the refcount is stored just before the start of the actual data // but in this case (because of alignment) not at the start of the allocated buffer PointerToRefcount::from_ptr_to_data(env, data_ptr) @@ -2246,14 +2277,15 @@ fn list_literal<'a, 'ctx, 'env>( // if element_type.is_int_type() { if false { let element_type = element_type.into_int_type(); - let element_width = element_layout.stack_size(env.ptr_bytes); + let element_width = element_layout.stack_size(env.target_info); let size = list_length * element_width as usize; let alignment = element_layout - .alignment_bytes(env.ptr_bytes) - .max(env.ptr_bytes); + .alignment_bytes(env.target_info) + .max(env.target_info.ptr_width() as u32); let mut is_all_constant = true; - let zero_elements = (env.ptr_bytes as f64 / element_width as f64).ceil() as usize; + let zero_elements = + (env.target_info.ptr_width() as u8 as f64 / element_width as f64).ceil() as usize; // runtime-evaluated elements let mut runtime_evaluated_elements = Vec::with_capacity_in(list_length, env.arena); @@ -2434,12 +2466,12 @@ pub fn store_roc_value<'a, 'ctx, 'env>( value: BasicValueEnum<'ctx>, ) { if layout.is_passed_by_reference() { - let align_bytes = layout.alignment_bytes(env.ptr_bytes); + let align_bytes = layout.alignment_bytes(env.target_info); if align_bytes > 0 { let size = env .ptr_int() - .const_int(layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(layout.stack_size(env.target_info) as u64, false); env.builder .build_memcpy( @@ -2535,7 +2567,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( let destination = out_parameter.into_pointer_value(); if layout.is_passed_by_reference() { - let align_bytes = layout.alignment_bytes(env.ptr_bytes); + let align_bytes = layout.alignment_bytes(env.target_info); if align_bytes > 0 { let value_ptr = value.into_pointer_value(); @@ -2548,7 +2580,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( } else { let size = env .ptr_int() - .const_int(layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(layout.stack_size(env.target_info) as u64, false); env.builder .build_memcpy( @@ -2617,21 +2649,30 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( let builder = env.builder; let context = env.context; - let mut joinpoint_args = Vec::with_capacity_in(parameters.len(), env.arena); - - for param in parameters.iter() { - let btype = basic_type_from_layout(env, ¶m.layout); - joinpoint_args.push(create_entry_block_alloca( - env, - parent, - btype, - "joinpointarg", - )); - } - // create new block let cont_block = context.append_basic_block(parent, "joinpointcont"); + let mut joinpoint_args = Vec::with_capacity_in(parameters.len(), env.arena); + { + let current = builder.get_insert_block().unwrap(); + builder.position_at_end(cont_block); + + for param in parameters.iter() { + let basic_type = basic_type_from_layout(env, ¶m.layout); + + let phi_type = if param.layout.is_passed_by_reference() { + basic_type.ptr_type(AddressSpace::Generic).into() + } else { + basic_type + }; + + let phi_node = env.builder.build_phi(phi_type, "joinpointarg"); + joinpoint_args.push(phi_node); + } + + builder.position_at_end(current); + } + // store this join point let joinpoint_args = joinpoint_args.into_bump_slice(); scope.join_points.insert(*id, (cont_block, joinpoint_args)); @@ -2651,12 +2692,9 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( // put the cont block at the back builder.position_at_end(cont_block); - for (ptr, param) in joinpoint_args.iter().zip(parameters.iter()) { - let value = if param.layout.is_passed_by_reference() { - (*ptr).into() - } else { - env.builder.build_load(*ptr, "load_jp_argument") - }; + // bind the values + for (phi_value, param) in joinpoint_args.iter().zip(parameters.iter()) { + let value = phi_value.as_basic_value(); scope.insert(param.symbol, (param.layout, value)); } @@ -2677,15 +2715,18 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( result } + Jump(join_point, arguments) => { let builder = env.builder; let context = env.context; - let (cont_block, argument_pointers) = scope.join_points.get(join_point).unwrap(); + let (cont_block, argument_phi_values) = scope.join_points.get(join_point).unwrap(); - for (pointer, argument) in argument_pointers.iter().zip(arguments.iter()) { - let (value, layout) = load_symbol_and_layout(scope, argument); + let current_block = builder.get_insert_block().unwrap(); - store_roc_value(env, *layout, *pointer, value); + for (phi_value, argument) in argument_phi_values.iter().zip(arguments.iter()) { + let (value, _) = load_symbol_and_layout(scope, argument); + + phi_value.add_incoming(&[(&value, current_block)]); } builder.build_unconditional_branch(*cont_block); @@ -2730,21 +2771,21 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( match layout { Layout::Builtin(Builtin::List(element_layout)) => { debug_assert!(value.is_struct_value()); - let alignment = element_layout.alignment_bytes(env.ptr_bytes); + let alignment = element_layout.alignment_bytes(env.target_info); build_list::decref(env, value.into_struct_value(), alignment); } Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => { debug_assert!(value.is_struct_value()); let alignment = key_layout - .alignment_bytes(env.ptr_bytes) - .max(value_layout.alignment_bytes(env.ptr_bytes)); + .alignment_bytes(env.target_info) + .max(value_layout.alignment_bytes(env.target_info)); build_dict::decref(env, value.into_struct_value(), alignment); } Layout::Builtin(Builtin::Set(key_layout)) => { debug_assert!(value.is_struct_value()); - let alignment = key_layout.alignment_bytes(env.ptr_bytes); + let alignment = key_layout.alignment_bytes(env.target_info); build_dict::decref(env, value.into_struct_value(), alignment); } @@ -3034,6 +3075,19 @@ fn const_i128<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, value: i128) -> IntValu .const_int_arbitrary_precision(&[a, b]) } +fn const_u128<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, value: u128) -> IntValue<'ctx> { + // truncate the lower 64 bits + let value = value as u128; + let a = value as u64; + + // get the upper 64 bits + let b = (value >> 64) as u64; + + env.context + .i128_type() + .const_int_arbitrary_precision(&[a, b]) +} + fn build_switch_ir<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_ids: &mut LayoutIds<'a>, @@ -3362,7 +3416,7 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>( builder.position_at_end(entry); - let wrapped_layout = roc_result_layout(env.arena, return_layout, env.ptr_bytes); + let wrapped_layout = roc_result_layout(env.arena, return_layout, env.target_info); call_roc_function(env, roc_function, &wrapped_layout, arguments_for_call) } else { call_roc_function(env, roc_function, &return_layout, arguments_for_call) @@ -3457,9 +3511,11 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>( let arguments_for_call = &arguments_for_call.into_bump_slice(); let call_result = { + let last_block = builder.get_insert_block().unwrap(); + let roc_wrapper_function = make_exception_catcher(env, roc_function, return_layout); - builder.position_at_end(entry); + builder.position_at_end(last_block); call_roc_function( env, @@ -3506,6 +3562,96 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>( c_function } +fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + roc_function: FunctionValue<'ctx>, + arguments: &[Layout<'a>], + return_layout: Layout<'a>, + c_function_name: &str, +) -> FunctionValue<'ctx> { + let it = arguments.iter().map(|l| basic_type_from_layout(env, l)); + let argument_types = Vec::from_iter_in(it, env.arena); + let return_type = basic_type_from_layout(env, &return_layout); + + let cc_return = to_cc_return(env, &return_layout); + let roc_return = RocReturn::from_layout(env, &return_layout); + + let c_function_type = cc_return.to_signature(env, return_type, argument_types.as_slice()); + + let c_function = add_func( + env.module, + c_function_name, + c_function_type, + Linkage::External, + C_CALL_CONV, + ); + + let subprogram = env.new_subprogram(c_function_name); + c_function.set_subprogram(subprogram); + + // STEP 2: build the exposed function's body + let builder = env.builder; + let context = env.context; + + let entry = context.append_basic_block(c_function, "entry"); + builder.position_at_end(entry); + + let params = c_function.get_params(); + + let param_types = Vec::from_iter_in(roc_function.get_type().get_param_types(), env.arena); + + 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 + (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_eq!(params.len(), param_types.len()); + + let it = params.iter().zip(param_types).map(|(arg, fastcc_type)| { + let arg_type = arg.get_type(); + if arg_type == *fastcc_type { + // the C and Fast calling conventions agree + *arg + } else { + complex_bitcast_check_size(env, *arg, *fastcc_type, "to_fastcc_type") + } + }); + + let arguments = Vec::from_iter_in(it, env.arena); + + let value = call_roc_function(env, roc_function, &return_layout, arguments.as_slice()); + + match cc_return { + CCReturn::Return => match roc_return { + RocReturn::Return => { + env.builder.build_return(Some(&value)); + } + RocReturn::ByPointer => { + let loaded = env + .builder + .build_load(value.into_pointer_value(), "load_result"); + env.builder.build_return(Some(&loaded)); + } + }, + CCReturn::ByPointer => { + let out_ptr = c_function.get_nth_param(0).unwrap().into_pointer_value(); + + env.builder.build_store(out_ptr, value); + env.builder.build_return(None); + } + CCReturn::Void => { + env.builder.build_return(None); + } + } + + c_function +} + fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, ident_string: &str, @@ -3534,122 +3680,14 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( &format!("{}_generic", c_function_name), ); - let wrapper_return_type = if env.is_gen_test { - roc_result_type(env, roc_function.get_type().get_return_type().unwrap()).into() - } else { - // roc_function.get_type().get_return_type().unwrap() - basic_type_from_layout(env, &return_layout) - }; - - let mut cc_argument_types = Vec::with_capacity_in(arguments.len(), env.arena); - for layout in arguments { - cc_argument_types.push(to_cc_type(env, layout)); - } - - // STEP 1: turn `f : a,b,c -> d` into `f : a,b,c, &d -> {}` if the C abi demands it - let mut argument_types = cc_argument_types; - let return_type = wrapper_return_type; - - let cc_return = to_cc_return(env, &return_layout); - - let c_function_type = match cc_return { - CCReturn::Void => env - .context - .void_type() - .fn_type(&function_arguments(env, &argument_types), false), - CCReturn::Return => return_type.fn_type(&function_arguments(env, &argument_types), false), - CCReturn::ByPointer => { - let output_type = return_type.ptr_type(AddressSpace::Generic); - argument_types.push(output_type.into()); - env.context - .void_type() - .fn_type(&function_arguments(env, &argument_types), false) - } - }; - - let c_function = add_func( - env.module, + let c_function = expose_function_to_host_help_c_abi_v2( + env, + roc_function, + arguments, + return_layout, c_function_name, - c_function_type, - Linkage::External, - C_CALL_CONV, ); - let subprogram = env.new_subprogram(c_function_name); - c_function.set_subprogram(subprogram); - - // STEP 2: build the exposed function's body - let builder = env.builder; - let context = env.context; - - let entry = context.append_basic_block(c_function, "entry"); - - builder.position_at_end(entry); - - debug_info_init!(env, c_function); - - // drop the final argument, which is the pointer we write the result into - let args_vector = c_function.get_params(); - let mut args = args_vector.as_slice(); - let args_length = args.len(); - - match cc_return { - CCReturn::Return => { - debug_assert_eq!(args.len(), roc_function.get_params().len()); - } - CCReturn::Void => { - debug_assert_eq!(args.len(), roc_function.get_params().len()); - } - CCReturn::ByPointer => match RocReturn::from_layout(env, &return_layout) { - RocReturn::ByPointer => { - debug_assert_eq!(args.len(), roc_function.get_params().len()); - } - RocReturn::Return => { - args = &args[..args.len() - 1]; - debug_assert_eq!(args.len(), roc_function.get_params().len()); - } - }, - } - - let mut arguments_for_call = Vec::with_capacity_in(args.len(), env.arena); - - let it = args.iter().zip(roc_function.get_type().get_param_types()); - for (arg, fastcc_type) in it { - let arg_type = arg.get_type(); - if arg_type == fastcc_type { - // the C and Fast calling conventions agree - arguments_for_call.push(*arg); - } else { - let cast = complex_bitcast_check_size(env, *arg, fastcc_type, "to_fastcc_type"); - arguments_for_call.push(cast); - } - } - - let arguments_for_call = arguments_for_call.into_bump_slice(); - - let call_result = call_roc_function(env, roc_function, &return_layout, arguments_for_call); - - match cc_return { - CCReturn::Void => { - // TODO return empty struct here? - builder.build_return(None); - } - CCReturn::Return => { - builder.build_return(Some(&call_result)); - } - CCReturn::ByPointer => { - let output_arg_index = args_length - 1; - - let output_arg = c_function - .get_nth_param(output_arg_index as u32) - .unwrap() - .into_pointer_value(); - - builder.build_store(output_arg, call_result); - builder.build_return(None); - } - } - // STEP 3: build a {} -> u64 function that gives the size of the return type let size_function_type = env.context.i64_type().fn_type(&[], false); let size_function_name: String = format!("roc__{}_size", ident_string); @@ -3665,20 +3703,30 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( let subprogram = env.new_subprogram(&size_function_name); size_function.set_subprogram(subprogram); - let entry = context.append_basic_block(size_function, "entry"); + let entry = env.context.append_basic_block(size_function, "entry"); - builder.position_at_end(entry); + env.builder.position_at_end(entry); debug_info_init!(env, size_function); + let return_type = if env.is_gen_test { + roc_result_type(env, roc_function.get_type().get_return_type().unwrap()).into() + } else { + // roc_function.get_type().get_return_type().unwrap() + basic_type_from_layout(env, &return_layout) + }; + let size: BasicValueEnum = return_type.size_of().unwrap().into(); - builder.build_return(Some(&size)); + env.builder.build_return(Some(&size)); c_function } pub fn get_sjlj_buffer<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValue<'ctx> { - let type_ = env.context.i8_type().array_type(5 * env.ptr_bytes); + let type_ = env + .context + .i8_type() + .array_type(5 * env.target_info.ptr_width() as u32); let global = match env.module.get_global("roc_sjlj_buffer") { Some(global) => global, @@ -3846,8 +3894,12 @@ fn make_exception_catcher<'a, 'ctx, 'env>( function_value } -fn roc_result_layout<'a>(arena: &'a Bump, return_layout: Layout<'a>, ptr_bytes: u32) -> Layout<'a> { - let elements = [Layout::u64(), Layout::usize(ptr_bytes), return_layout]; +fn roc_result_layout<'a>( + arena: &'a Bump, + return_layout: Layout<'a>, + target_info: TargetInfo, +) -> Layout<'a> { + let elements = [Layout::u64(), Layout::usize(target_info), return_layout]; Layout::Struct(arena.alloc(elements)) } @@ -4281,12 +4333,12 @@ pub fn build_closure_caller<'a, 'ctx, 'env>( let call_result = call_roc_function(env, evaluator, return_layout, &evaluator_arguments); if return_layout.is_passed_by_reference() { - let align_bytes = return_layout.alignment_bytes(env.ptr_bytes); + let align_bytes = return_layout.alignment_bytes(env.target_info); if align_bytes > 0 { let size = env .ptr_int() - .const_int(return_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(return_layout.stack_size(env.target_info) as u64, false); env.builder .build_memcpy( @@ -4972,7 +5024,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.ptr_bytes), **element_layout]; + let argument_layouts = &[Layout::usize(env.target_info), **element_layout]; let roc_function_call = roc_function_call( env, @@ -5250,11 +5302,6 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( } } -// TODO: Fix me! I should be different in tests vs. user code! -fn expect_failed() { - panic!("An expectation failed!"); -} - #[allow(clippy::too_many_arguments)] fn run_low_level<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, @@ -5265,7 +5312,6 @@ fn run_low_level<'a, 'ctx, 'env>( op: LowLevel, args: &[Symbol], update_mode: UpdateMode, - // expect_failed: *const (), ) -> BasicValueEnum<'ctx> { use LowLevel::*; @@ -5308,24 +5354,23 @@ fn run_low_level<'a, 'ctx, 'env>( let (string, _string_layout) = load_symbol_and_layout(scope, &args[0]); - if let Layout::Struct(struct_layout) = layout { - // match on the return layout to figure out which zig builtin we need - let intrinsic = match struct_layout[0] { - Layout::Builtin(Builtin::Int(int_width)) => &bitcode::STR_TO_INT[int_width], - Layout::Builtin(Builtin::Float(float_width)) => { - &bitcode::STR_TO_FLOAT[float_width] - } - Layout::Builtin(Builtin::Decimal) => bitcode::DEC_FROM_STR, - _ => unreachable!(), - }; + let number_layout = match layout { + Layout::Struct(fields) => fields[0], // TODO: why is it sometimes a struct? + _ => unreachable!(), + }; - let string = - complex_bitcast(env.builder, string, env.str_list_c_abi().into(), "to_utf8"); + // match on the return layout to figure out which zig builtin we need + let intrinsic = match number_layout { + Layout::Builtin(Builtin::Int(int_width)) => &bitcode::STR_TO_INT[int_width], + Layout::Builtin(Builtin::Float(float_width)) => &bitcode::STR_TO_FLOAT[float_width], + Layout::Builtin(Builtin::Decimal) => bitcode::DEC_FROM_STR, + _ => unreachable!(), + }; - call_bitcode_fn(env, &[string], intrinsic) - } else { - unreachable!() - } + let string = + complex_bitcast(env.builder, string, env.str_list_c_abi().into(), "to_utf8"); + + call_bitcode_fn(env, &[string], intrinsic) } StrFromInt => { // Str.fromInt : Int -> Str @@ -5794,8 +5839,9 @@ fn run_low_level<'a, 'ctx, 'env>( } NumAdd | NumSub | NumMul | NumLt | NumLte | NumGt | NumGte | NumRemUnchecked - | NumIsMultipleOf | NumAddWrap | NumAddChecked | NumDivUnchecked | NumDivCeilUnchecked - | NumPow | NumPowInt | NumSubWrap | NumSubChecked | NumMulWrap | NumMulChecked => { + | NumIsMultipleOf | NumAddWrap | NumAddChecked | NumAddSaturated | NumDivUnchecked + | NumDivCeilUnchecked | NumPow | NumPowInt | NumSubWrap | NumSubChecked + | NumSubSaturated | NumMulWrap | NumMulChecked => { debug_assert_eq!(args.len(), 2); let (lhs_arg, lhs_layout) = load_symbol_and_layout(scope, &args[0]); @@ -6027,31 +6073,37 @@ fn run_low_level<'a, 'ctx, 'env>( { bd.position_at_end(throw_block); - match env.ptr_bytes { - 8 => { - let fn_ptr_type = context - .void_type() - .fn_type(&[], false) - .ptr_type(AddressSpace::Generic); - let fn_addr = env - .ptr_int() - .const_int(expect_failed as *const () as u64, false); - let func: PointerValue<'ctx> = bd.build_int_to_ptr( - fn_addr, - fn_ptr_type, - "cast_expect_failed_addr_to_ptr", - ); + match env.target_info.ptr_width() { + roc_target::PtrWidth::Bytes8 => { + let func = env + .module + .get_function(bitcode::UTILS_EXPECT_FAILED) + .unwrap(); + // TODO get the actual line info instead of + // hardcoding as zero! let callable = CallableValue::try_from(func).unwrap(); + let start_line = context.i32_type().const_int(0, false); + let end_line = context.i32_type().const_int(0, false); + let start_col = context.i16_type().const_int(0, false); + let end_col = context.i16_type().const_int(0, false); - bd.build_call(callable, &[], "call_expect_failed"); + bd.build_call( + callable, + &[ + start_line.into(), + end_line.into(), + start_col.into(), + end_col.into(), + ], + "call_expect_failed", + ); bd.build_unconditional_branch(then_block); } - 4 => { + roc_target::PtrWidth::Bytes4 => { // temporary WASM implementation throw_exception(env, "An expectation failed!"); } - _ => unreachable!(), } } @@ -6133,6 +6185,7 @@ impl RocReturn { } } +#[derive(Debug)] enum CCReturn { /// Return as normal Return, @@ -6144,10 +6197,40 @@ enum CCReturn { Void, } +impl CCReturn { + fn to_signature<'a, 'ctx, 'env>( + &self, + env: &Env<'a, 'ctx, 'env>, + return_type: BasicTypeEnum<'ctx>, + argument_types: &[BasicTypeEnum<'ctx>], + ) -> FunctionType<'ctx> { + match self { + CCReturn::ByPointer => { + // turn the output type into a pointer type. Make it the first argument to the function + let output_type = return_type.ptr_type(AddressSpace::Generic); + let mut arguments: Vec<'_, BasicTypeEnum> = + bumpalo::vec![in env.arena; output_type.into()]; + arguments.extend(argument_types); + + let arguments = function_arguments(env, &arguments); + env.context.void_type().fn_type(&arguments, false) + } + CCReturn::Return => { + let arguments = function_arguments(env, argument_types); + return_type.fn_type(&arguments, false) + } + CCReturn::Void => { + let arguments = function_arguments(env, argument_types); + env.context.void_type().fn_type(&arguments, false) + } + } + } +} + /// 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 { - let return_size = layout.stack_size(env.ptr_bytes); - let pass_result_by_pointer = return_size > 2 * env.ptr_bytes; + let return_size = layout.stack_size(env.target_info); + let pass_result_by_pointer = return_size > 2 * env.target_info.ptr_width() as u32; if return_size == 0 { CCReturn::Void @@ -6200,6 +6283,7 @@ fn build_foreign_symbol<'a, 'ctx, 'env>( // - a FAST_CALL_CONV wrapper that we make here, e.g. `roc_fx_putLine_fastcc_wrapper` let return_type = basic_type_from_layout(env, ret_layout); + let roc_return = RocReturn::from_layout(env, ret_layout); let cc_return = to_cc_return(env, ret_layout); let mut cc_argument_types = @@ -6219,26 +6303,20 @@ fn build_foreign_symbol<'a, 'ctx, 'env>( arguments.push(value); } - let cc_type = match cc_return { - CCReturn::Void => env - .context - .void_type() - .fn_type(&function_arguments(env, &cc_argument_types), false), - CCReturn::ByPointer => { - cc_argument_types.push(return_type.ptr_type(AddressSpace::Generic).into()); - env.context - .void_type() - .fn_type(&function_arguments(env, &cc_argument_types), false) - } - CCReturn::Return => { - return_type.fn_type(&function_arguments(env, &cc_argument_types), false) - } - }; - + let cc_type = cc_return.to_signature(env, return_type, cc_argument_types.as_slice()); let cc_function = get_foreign_symbol(env, foreign.clone(), cc_type); - let fastcc_type = - return_type.fn_type(&function_arguments(env, &fastcc_argument_types), false); + let fastcc_type = match roc_return { + RocReturn::Return => { + return_type.fn_type(&function_arguments(env, &fastcc_argument_types), false) + } + RocReturn::ByPointer => { + fastcc_argument_types.push(return_type.ptr_type(AddressSpace::Generic).into()); + env.context + .void_type() + .fn_type(&function_arguments(env, &fastcc_argument_types), false) + } + }; let fastcc_function = add_func( env.module, @@ -6253,12 +6331,20 @@ fn build_foreign_symbol<'a, 'ctx, 'env>( let entry = context.append_basic_block(fastcc_function, "entry"); { builder.position_at_end(entry); - let return_pointer = env.builder.build_alloca(return_type, "return_value"); - let fastcc_parameters = fastcc_function.get_params(); + let mut fastcc_parameters = fastcc_function.get_params(); let mut cc_arguments = Vec::with_capacity_in(fastcc_parameters.len() + 1, env.arena); + let return_pointer = match roc_return { + RocReturn::Return => env.builder.build_alloca(return_type, "return_value"), + RocReturn::ByPointer => fastcc_parameters.pop().unwrap().into_pointer_value(), + }; + + if let CCReturn::ByPointer = cc_return { + cc_arguments.push(return_pointer.into()); + } + let it = fastcc_parameters.into_iter().zip(cc_argument_types.iter()); for (param, cc_type) in it { if param.get_type() == *cc_type { @@ -6270,21 +6356,28 @@ fn build_foreign_symbol<'a, 'ctx, 'env>( } } - if let CCReturn::ByPointer = cc_return { - cc_arguments.push(return_pointer.into()); - } - let call = env.builder.build_call(cc_function, &cc_arguments, "tmp"); call.set_call_convention(C_CALL_CONV); - let return_value = match cc_return { - CCReturn::Return => call.try_as_basic_value().left().unwrap(), + match roc_return { + RocReturn::Return => { + let return_value = match cc_return { + CCReturn::Return => call.try_as_basic_value().left().unwrap(), - CCReturn::ByPointer => env.builder.build_load(return_pointer, "read_result"), - CCReturn::Void => return_type.const_zero(), - }; + CCReturn::ByPointer => { + env.builder.build_load(return_pointer, "read_result") + } + CCReturn::Void => return_type.const_zero(), + }; - builder.build_return(Some(&return_value)); + builder.build_return(Some(&return_value)); + } + RocReturn::ByPointer => { + debug_assert!(matches!(cc_return, CCReturn::ByPointer)); + + builder.build_return(None); + } + } } builder.position_at_end(old); @@ -6354,7 +6447,7 @@ fn build_int_binop<'a, 'ctx, 'env>( NumAdd => { let result = env .call_intrinsic( - &LLVM_SADD_WITH_OVERFLOW[int_width], + &LLVM_ADD_WITH_OVERFLOW[int_width], &[lhs.into(), rhs.into()], ) .into_struct_value(); @@ -6363,13 +6456,16 @@ fn build_int_binop<'a, 'ctx, 'env>( } NumAddWrap => bd.build_int_add(lhs, rhs, "add_int_wrap").into(), NumAddChecked => env.call_intrinsic( - &LLVM_SADD_WITH_OVERFLOW[int_width], + &LLVM_ADD_WITH_OVERFLOW[int_width], &[lhs.into(), rhs.into()], ), + NumAddSaturated => { + env.call_intrinsic(&LLVM_ADD_SATURATED[int_width], &[lhs.into(), rhs.into()]) + } NumSub => { let result = env .call_intrinsic( - &LLVM_SSUB_WITH_OVERFLOW[int_width], + &LLVM_SUB_WITH_OVERFLOW[int_width], &[lhs.into(), rhs.into()], ) .into_struct_value(); @@ -6378,13 +6474,16 @@ fn build_int_binop<'a, 'ctx, 'env>( } NumSubWrap => bd.build_int_sub(lhs, rhs, "sub_int").into(), NumSubChecked => env.call_intrinsic( - &LLVM_SSUB_WITH_OVERFLOW[int_width], + &LLVM_SUB_WITH_OVERFLOW[int_width], &[lhs.into(), rhs.into()], ), + NumSubSaturated => { + env.call_intrinsic(&LLVM_SUB_SATURATED[int_width], &[lhs.into(), rhs.into()]) + } NumMul => { let result = env .call_intrinsic( - &LLVM_SMUL_WITH_OVERFLOW[int_width], + &LLVM_MUL_WITH_OVERFLOW[int_width], &[lhs.into(), rhs.into()], ) .into_struct_value(); @@ -6393,7 +6492,7 @@ fn build_int_binop<'a, 'ctx, 'env>( } NumMulWrap => bd.build_int_mul(lhs, rhs, "mul_int").into(), NumMulChecked => env.call_intrinsic( - &LLVM_SMUL_WITH_OVERFLOW[int_width], + &LLVM_MUL_WITH_OVERFLOW[int_width], &[lhs.into(), rhs.into()], ), NumGt => bd.build_int_compare(SGT, lhs, rhs, "int_gt").into(), @@ -7049,7 +7148,9 @@ fn define_global_str_literal_ptr<'a, 'ctx, 'env>( let ptr = unsafe { env.builder.build_in_bounds_gep( ptr, - &[env.ptr_int().const_int(env.ptr_bytes as u64, false)], + &[env + .ptr_int() + .const_int(env.target_info.ptr_width() as u64, false)], "get_rc_ptr", ) }; @@ -7079,11 +7180,11 @@ fn define_global_str_literal<'a, 'ctx, 'env>( Some(current) => current, None => { - let size = message.bytes().len() + env.ptr_bytes as usize; + let size = message.bytes().len() + env.target_info.ptr_width() as usize; let mut bytes = Vec::with_capacity_in(size, env.arena); // insert NULL bytes for the refcount - for _ in 0..env.ptr_bytes { + for _ in 0..env.target_info.ptr_width() as usize { bytes.push(env.context.i8_type().const_zero()); } @@ -7102,7 +7203,7 @@ fn define_global_str_literal<'a, 'ctx, 'env>( // strings are NULL-terminated, which means we can't store the refcount (which is 8 // NULL bytes) global.set_constant(true); - global.set_alignment(env.ptr_bytes); + global.set_alignment(env.target_info.ptr_width() as u32); global.set_unnamed_addr(true); global.set_linkage(inkwell::module::Linkage::Private); diff --git a/compiler/gen_llvm/src/llvm/build_dict.rs b/compiler/gen_llvm/src/llvm/build_dict.rs index 4feb77aa2d..37b81e9129 100644 --- a/compiler/gen_llvm/src/llvm/build_dict.rs +++ b/compiler/gen_llvm/src/llvm/build_dict.rs @@ -17,14 +17,15 @@ use inkwell::AddressSpace; use roc_builtins::bitcode; use roc_module::symbol::Symbol; use roc_mono::layout::{Builtin, Layout, LayoutIds}; +use roc_target::TargetInfo; #[repr(transparent)] struct Alignment(u8); impl Alignment { - fn from_key_value_layout(key: &Layout, value: &Layout, ptr_bytes: u32) -> Alignment { - let key_align = key.alignment_bytes(ptr_bytes); - let value_align = value.alignment_bytes(ptr_bytes); + fn from_key_value_layout(key: &Layout, value: &Layout, target_info: TargetInfo) -> Alignment { + let key_align = key.alignment_bytes(target_info); + let value_align = value.alignment_bytes(target_info); let mut bits = key_align.max(value_align) as u8; @@ -105,15 +106,15 @@ pub fn dict_insert<'a, 'ctx, 'env>( let key_width = env .ptr_int() - .const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(key_layout.stack_size(env.target_info) as u64, false); let value_width = env .ptr_int() - .const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(value_layout.stack_size(env.target_info) as u64, false); let result_ptr = builder.build_alloca(zig_dict_type(env), "result_ptr"); - let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); + let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info); let alignment_iv = alignment.as_int_value(env.context); let hash_fn = build_hash_wrapper(env, layout_ids, key_layout); @@ -162,15 +163,15 @@ pub fn dict_remove<'a, 'ctx, 'env>( let key_width = env .ptr_int() - .const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(key_layout.stack_size(env.target_info) as u64, false); let value_width = env .ptr_int() - .const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(value_layout.stack_size(env.target_info) as u64, false); let result_ptr = builder.build_alloca(zig_dict_type(env), "result_ptr"); - let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); + let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info); let alignment_iv = alignment.as_int_value(env.context); let hash_fn = build_hash_wrapper(env, layout_ids, key_layout); @@ -218,13 +219,13 @@ pub fn dict_contains<'a, 'ctx, 'env>( let key_width = env .ptr_int() - .const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(key_layout.stack_size(env.target_info) as u64, false); let value_width = env .ptr_int() - .const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(value_layout.stack_size(env.target_info) as u64, false); - let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); + let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info); let alignment_iv = alignment.as_int_value(env.context); let hash_fn = build_hash_wrapper(env, layout_ids, key_layout); @@ -264,13 +265,13 @@ pub fn dict_get<'a, 'ctx, 'env>( let key_width = env .ptr_int() - .const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(key_layout.stack_size(env.target_info) as u64, false); let value_width = env .ptr_int() - .const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(value_layout.stack_size(env.target_info) as u64, false); - let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); + let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info); let alignment_iv = alignment.as_int_value(env.context); let hash_fn = build_hash_wrapper(env, layout_ids, key_layout); @@ -366,13 +367,13 @@ pub fn dict_elements_rc<'a, 'ctx, 'env>( ) { let key_width = env .ptr_int() - .const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(key_layout.stack_size(env.target_info) as u64, false); let value_width = env .ptr_int() - .const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(value_layout.stack_size(env.target_info) as u64, false); - let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); + let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info); let alignment_iv = alignment.as_int_value(env.context); let (key_fn, value_fn) = match rc_operation { @@ -412,13 +413,13 @@ pub fn dict_keys<'a, 'ctx, 'env>( let key_width = env .ptr_int() - .const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(key_layout.stack_size(env.target_info) as u64, false); let value_width = env .ptr_int() - .const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(value_layout.stack_size(env.target_info) as u64, false); - let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); + let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info); let alignment_iv = alignment.as_int_value(env.context); let inc_key_fn = build_inc_wrapper(env, layout_ids, key_layout); @@ -454,19 +455,18 @@ fn pass_dict_c_abi<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, dict: BasicValueEnum<'ctx>, ) -> BasicValueEnum<'ctx> { - match env.ptr_bytes { - 4 => { + match env.target_info.ptr_width() { + roc_target::PtrWidth::Bytes4 => { let target_type = env.context.custom_width_int_type(96).into(); complex_bitcast(env.builder, dict, target_type, "to_i96") } - 8 => { + roc_target::PtrWidth::Bytes8 => { let dict_ptr = env.builder.build_alloca(zig_dict_type(env), "dict_ptr"); env.builder.build_store(dict_ptr, dict); dict_ptr.into() } - _ => unreachable!(), } } @@ -483,13 +483,13 @@ pub fn dict_union<'a, 'ctx, 'env>( let key_width = env .ptr_int() - .const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(key_layout.stack_size(env.target_info) as u64, false); let value_width = env .ptr_int() - .const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(value_layout.stack_size(env.target_info) as u64, false); - let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); + let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info); let alignment_iv = alignment.as_int_value(env.context); let hash_fn = build_hash_wrapper(env, layout_ids, key_layout); @@ -576,13 +576,13 @@ fn dict_intersect_or_difference<'a, 'ctx, 'env>( let key_width = env .ptr_int() - .const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(key_layout.stack_size(env.target_info) as u64, false); let value_width = env .ptr_int() - .const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(value_layout.stack_size(env.target_info) as u64, false); - let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); + let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info); let alignment_iv = alignment.as_int_value(env.context); let hash_fn = build_hash_wrapper(env, layout_ids, key_layout); @@ -631,7 +631,7 @@ pub fn dict_walk<'a, 'ctx, 'env>( let accum_ptr = builder.build_alloca(accum_bt, "accum_ptr"); env.builder.build_store(accum_ptr, accum); - let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); + let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info); let alignment_iv = alignment.as_int_value(env.context); let output_ptr = builder.build_alloca(accum_bt, "output_ptr"); @@ -671,13 +671,13 @@ pub fn dict_values<'a, 'ctx, 'env>( let key_width = env .ptr_int() - .const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(key_layout.stack_size(env.target_info) as u64, false); let value_width = env .ptr_int() - .const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(value_layout.stack_size(env.target_info) as u64, false); - let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); + let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info); let alignment_iv = alignment.as_int_value(env.context); let inc_value_fn = build_inc_wrapper(env, layout_ids, value_layout); @@ -729,14 +729,14 @@ pub fn set_from_list<'a, 'ctx, 'env>( let key_width = env .ptr_int() - .const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); + .const_int(key_layout.stack_size(env.target_info) as u64, false); let value_width = env.ptr_int().const_zero(); let result_alloca = builder.build_alloca(zig_dict_type(env), "result_alloca"); let alignment = - Alignment::from_key_value_layout(key_layout, &Layout::Struct(&[]), env.ptr_bytes); + Alignment::from_key_value_layout(key_layout, &Layout::Struct(&[]), env.target_info); let alignment_iv = alignment.as_int_value(env.context); let hash_fn = build_hash_wrapper(env, layout_ids, key_layout); diff --git a/compiler/gen_llvm/src/llvm/build_hash.rs b/compiler/gen_llvm/src/llvm/build_hash.rs index 8036871a74..0db5348d1d 100644 --- a/compiler/gen_llvm/src/llvm/build_hash.rs +++ b/compiler/gen_llvm/src/llvm/build_hash.rs @@ -120,7 +120,7 @@ fn hash_builtin<'a, 'ctx, 'env>( builtin: &Builtin<'a>, when_recursive: WhenRecursive<'a>, ) -> IntValue<'ctx> { - let ptr_bytes = env.ptr_bytes; + let ptr_bytes = env.target_info; match builtin { Builtin::Int(_) | Builtin::Float(_) | Builtin::Bool | Builtin::Decimal => { @@ -246,7 +246,7 @@ fn hash_struct<'a, 'ctx, 'env>( when_recursive: WhenRecursive<'a>, field_layouts: &[Layout<'a>], ) -> IntValue<'ctx> { - let ptr_bytes = env.ptr_bytes; + let ptr_bytes = env.target_info; let layout = Layout::Struct(field_layouts); @@ -423,7 +423,7 @@ fn hash_tag<'a, 'ctx, 'env>( env, seed, hash_bytes, - tag_id_layout.stack_size(env.ptr_bytes), + tag_id_layout.stack_size(env.target_info), ); // hash the tag data @@ -474,7 +474,7 @@ fn hash_tag<'a, 'ctx, 'env>( env, seed, hash_bytes, - tag_id_layout.stack_size(env.ptr_bytes), + tag_id_layout.stack_size(env.target_info), ); // hash the tag data @@ -574,7 +574,7 @@ fn hash_tag<'a, 'ctx, 'env>( env, seed, hash_bytes, - tag_id_layout.stack_size(env.ptr_bytes), + tag_id_layout.stack_size(env.target_info), ); // hash tag data diff --git a/compiler/gen_llvm/src/llvm/build_list.rs b/compiler/gen_llvm/src/llvm/build_list.rs index a1b7934c51..16ef7bdde8 100644 --- a/compiler/gen_llvm/src/llvm/build_list.rs +++ b/compiler/gen_llvm/src/llvm/build_list.rs @@ -87,7 +87,7 @@ pub fn layout_width<'a, 'ctx, 'env>( layout: &Layout<'a>, ) -> BasicValueEnum<'ctx> { env.ptr_int() - .const_int(layout.stack_size(env.ptr_bytes) as u64, false) + .const_int(layout.stack_size(env.target_info) as u64, false) .into() } @@ -1253,17 +1253,17 @@ pub fn allocate_list<'a, 'ctx, 'env>( let ctx = env.context; let len_type = env.ptr_int(); - let elem_bytes = elem_layout.stack_size(env.ptr_bytes) as u64; + let elem_bytes = elem_layout.stack_size(env.target_info) as u64; let bytes_per_element = len_type.const_int(elem_bytes, false); let number_of_data_bytes = builder.build_int_mul(bytes_per_element, number_of_elements, "data_length"); // the refcount of a new list is initially 1 // we assume that the list is indeed used (dead variables are eliminated) - let rc1 = crate::llvm::refcounting::refcount_1(ctx, env.ptr_bytes); + let rc1 = crate::llvm::refcounting::refcount_1(ctx, env.target_info); let basic_type = basic_type_from_layout(env, elem_layout); - let alignment_bytes = elem_layout.alignment_bytes(env.ptr_bytes); + let alignment_bytes = elem_layout.alignment_bytes(env.target_info); allocate_with_refcount_help(env, basic_type, alignment_bytes, number_of_data_bytes, rc1) } diff --git a/compiler/gen_llvm/src/llvm/build_str.rs b/compiler/gen_llvm/src/llvm/build_str.rs index 3c92e39cdb..4ce155aac6 100644 --- a/compiler/gen_llvm/src/llvm/build_str.rs +++ b/compiler/gen_llvm/src/llvm/build_str.rs @@ -10,6 +10,7 @@ use morphic_lib::UpdateMode; use roc_builtins::bitcode::{self, IntWidth}; use roc_module::symbol::Symbol; use roc_mono::layout::{Builtin, Layout}; +use roc_target::PtrWidth; use super::build::load_symbol; @@ -79,10 +80,9 @@ fn str_symbol_to_c_abi<'a, 'ctx, 'env>( ) -> IntValue<'ctx> { let string = load_symbol(scope, &symbol); - let target_type = match env.ptr_bytes { - 8 => env.context.i128_type().into(), - 4 => env.context.i64_type().into(), - _ => unreachable!(), + let target_type = match env.target_info.ptr_width() { + PtrWidth::Bytes8 => env.context.i128_type().into(), + PtrWidth::Bytes4 => env.context.i64_type().into(), }; complex_bitcast(env.builder, string, target_type, "str_to_c_abi").into_int_value() @@ -96,10 +96,9 @@ pub fn str_to_c_abi<'a, 'ctx, 'env>( env.builder.build_store(cell, value); - let target_type = match env.ptr_bytes { - 8 => env.context.i128_type(), - 4 => env.context.i64_type(), - _ => unreachable!(), + let target_type = match env.target_info.ptr_width() { + PtrWidth::Bytes8 => env.context.i128_type(), + PtrWidth::Bytes4 => env.context.i64_type(), }; let target_type_ptr = env @@ -310,20 +309,19 @@ fn decode_from_utf8_result<'a, 'ctx, 'env>( let builder = env.builder; let ctx = env.context; - let fields = match env.ptr_bytes { - 8 | 4 => [ + let fields = match env.target_info.ptr_width() { + PtrWidth::Bytes4 | PtrWidth::Bytes8 => [ env.ptr_int().into(), super::convert::zig_str_type(env).into(), env.context.bool_type().into(), ctx.i8_type().into(), ], - _ => unreachable!(), }; let record_type = env.context.struct_type(&fields, false); - match env.ptr_bytes { - 8 | 4 => { + match env.target_info.ptr_width() { + PtrWidth::Bytes4 | PtrWidth::Bytes8 => { let result_ptr_cast = env .builder .build_bitcast( @@ -337,7 +335,6 @@ fn decode_from_utf8_result<'a, 'ctx, 'env>( .build_load(result_ptr_cast, "load_utf8_validate_bytes_result") .into_struct_value() } - _ => unreachable!(), } } diff --git a/compiler/gen_llvm/src/llvm/convert.rs b/compiler/gen_llvm/src/llvm/convert.rs index 7f0faa2b96..c7d3142d40 100644 --- a/compiler/gen_llvm/src/llvm/convert.rs +++ b/compiler/gen_llvm/src/llvm/convert.rs @@ -4,6 +4,7 @@ use inkwell::types::{BasicType, BasicTypeEnum, FloatType, IntType, StructType}; use inkwell::AddressSpace; use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_mono::layout::{Builtin, Layout, UnionLayout}; +use roc_target::TargetInfo; fn basic_type_from_record<'a, 'ctx, 'env>( env: &crate::llvm::build::Env<'a, 'ctx, 'env>, @@ -36,7 +37,7 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>( match union_layout { NonRecursive(tags) => { - let data = block_of_memory_slices(env.context, tags, env.ptr_bytes); + let data = block_of_memory_slices(env.context, tags, env.target_info); env.context.struct_type(&[data, tag_id_type], false).into() } @@ -44,9 +45,9 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>( | NullableWrapped { other_tags: tags, .. } => { - let data = block_of_memory_slices(env.context, tags, env.ptr_bytes); + let data = block_of_memory_slices(env.context, tags, env.target_info); - if union_layout.stores_tag_id_as_data(env.ptr_bytes) { + if union_layout.stores_tag_id_as_data(env.target_info) { env.context .struct_type(&[data, tag_id_type], false) .ptr_type(AddressSpace::Generic) @@ -56,11 +57,12 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>( } } NullableUnwrapped { other_fields, .. } => { - let block = block_of_memory_slices(env.context, &[other_fields], env.ptr_bytes); + let block = + block_of_memory_slices(env.context, &[other_fields], env.target_info); block.ptr_type(AddressSpace::Generic).into() } NonNullableUnwrapped(fields) => { - let block = block_of_memory_slices(env.context, &[fields], env.ptr_bytes); + let block = block_of_memory_slices(env.context, &[fields], env.target_info); block.ptr_type(AddressSpace::Generic).into() } } @@ -95,7 +97,7 @@ pub fn basic_type_from_layout_1<'a, 'ctx, 'env>( match union_layout { NonRecursive(tags) => { - let data = block_of_memory_slices(env.context, tags, env.ptr_bytes); + let data = block_of_memory_slices(env.context, tags, env.target_info); let struct_type = env.context.struct_type(&[data, tag_id_type], false); struct_type.ptr_type(AddressSpace::Generic).into() @@ -104,9 +106,9 @@ pub fn basic_type_from_layout_1<'a, 'ctx, 'env>( | NullableWrapped { other_tags: tags, .. } => { - let data = block_of_memory_slices(env.context, tags, env.ptr_bytes); + let data = block_of_memory_slices(env.context, tags, env.target_info); - if union_layout.stores_tag_id_as_data(env.ptr_bytes) { + if union_layout.stores_tag_id_as_data(env.target_info) { env.context .struct_type(&[data, tag_id_type], false) .ptr_type(AddressSpace::Generic) @@ -116,11 +118,12 @@ pub fn basic_type_from_layout_1<'a, 'ctx, 'env>( } } NullableUnwrapped { other_fields, .. } => { - let block = block_of_memory_slices(env.context, &[other_fields], env.ptr_bytes); + let block = + block_of_memory_slices(env.context, &[other_fields], env.target_info); block.ptr_type(AddressSpace::Generic).into() } NonNullableUnwrapped(fields) => { - let block = block_of_memory_slices(env.context, &[fields], env.ptr_bytes); + let block = block_of_memory_slices(env.context, &[fields], env.target_info); block.ptr_type(AddressSpace::Generic).into() } } @@ -188,13 +191,13 @@ pub fn float_type_from_float_width<'a, 'ctx, 'env>( pub fn block_of_memory_slices<'ctx>( context: &'ctx Context, layouts: &[&[Layout<'_>]], - ptr_bytes: u32, + target_info: TargetInfo, ) -> BasicTypeEnum<'ctx> { let mut union_size = 0; for tag in layouts { let mut total = 0; for layout in tag.iter() { - total += layout.stack_size(ptr_bytes as u32); + total += layout.stack_size(target_info); } union_size = union_size.max(total); @@ -203,32 +206,16 @@ pub fn block_of_memory_slices<'ctx>( block_of_memory_help(context, union_size) } -pub fn union_data_is_struct<'a, 'ctx, 'env>( - env: &crate::llvm::build::Env<'a, 'ctx, 'env>, - layouts: &[Layout<'_>], -) -> StructType<'ctx> { - let data_type = basic_type_from_record(env, layouts); - union_data_is_struct_type(env.context, data_type.into_struct_type()) -} - -pub fn union_data_is_struct_type<'ctx>( - context: &'ctx Context, - struct_type: StructType<'ctx>, -) -> StructType<'ctx> { - let tag_id_type = context.i64_type(); - context.struct_type(&[struct_type.into(), tag_id_type.into()], false) -} - pub fn block_of_memory<'ctx>( context: &'ctx Context, layout: &Layout<'_>, - ptr_bytes: u32, + target_info: TargetInfo, ) -> BasicTypeEnum<'ctx> { // TODO make this dynamic - let mut union_size = layout.stack_size(ptr_bytes as u32); + let mut union_size = layout.stack_size(target_info); if let Layout::Union(UnionLayout::NonRecursive { .. }) = layout { - union_size -= ptr_bytes; + union_size -= target_info.ptr_width() as u32; } block_of_memory_help(context, union_size) @@ -267,16 +254,10 @@ fn block_of_memory_help(context: &Context, union_size: u32) -> BasicTypeEnum<'_> } /// The int type that the C ABI turns our RocList/RocStr into -pub fn str_list_int(ctx: &Context, ptr_bytes: u32) -> IntType<'_> { - match ptr_bytes { - 1 => ctx.i16_type(), - 2 => ctx.i32_type(), - 4 => ctx.i64_type(), - 8 => ctx.i128_type(), - _ => panic!( - "Invalid target: Roc does't support compiling to {}-bit systems.", - ptr_bytes * 8 - ), +pub fn str_list_int(ctx: &Context, target_info: TargetInfo) -> IntType<'_> { + match target_info.ptr_width() { + roc_target::PtrWidth::Bytes4 => ctx.i64_type(), + roc_target::PtrWidth::Bytes8 => ctx.i128_type(), } } diff --git a/compiler/gen_llvm/src/llvm/externs.rs b/compiler/gen_llvm/src/llvm/externs.rs index 75bc506cc2..889a0edd3b 100644 --- a/compiler/gen_llvm/src/llvm/externs.rs +++ b/compiler/gen_llvm/src/llvm/externs.rs @@ -42,6 +42,43 @@ pub fn add_default_roc_externs(env: &Env<'_, '_, '_>) { } } + // roc_memcpy + { + // The type of this function (but not the implementation) should have + // already been defined by the builtins, which rely on it. + let fn_val = module.get_function("roc_memcpy").unwrap(); + let mut params = fn_val.get_param_iter(); + let dest_arg = params.next().unwrap(); + let dest_alignment = 1; + let src_arg = params.next().unwrap(); + let src_alignment = 1; + let bytes_arg = params.next().unwrap(); + + debug_assert!(params.next().is_none()); + + // Add a basic block for the entry point + let entry = ctx.append_basic_block(fn_val, "entry"); + + builder.position_at_end(entry); + + // Call libc memcpy() + let _retval = builder + .build_memcpy( + dest_arg.into_pointer_value(), + dest_alignment, + src_arg.into_pointer_value(), + src_alignment, + bytes_arg.into_int_value(), + ) + .unwrap(); + + builder.build_return(None); + + if cfg!(debug_assertions) { + crate::llvm::build::verify_fn(fn_val); + } + } + // roc_realloc { let libc_realloc_val = { @@ -175,7 +212,9 @@ pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) { let buffer = crate::llvm::build::get_sjlj_buffer(env); // write our error message pointer - let index = env.ptr_int().const_int(3 * env.ptr_bytes as u64, false); + let index = env + .ptr_int() + .const_int(3 * env.target_info.ptr_width() as u64, false); let message_buffer_raw = unsafe { builder.build_gep(buffer, &[index], "raw_msg_buffer_ptr") }; let message_buffer = builder.build_bitcast( diff --git a/compiler/gen_llvm/src/llvm/refcounting.rs b/compiler/gen_llvm/src/llvm/refcounting.rs index b04acb06a9..2962361c29 100644 --- a/compiler/gen_llvm/src/llvm/refcounting.rs +++ b/compiler/gen_llvm/src/llvm/refcounting.rs @@ -18,21 +18,16 @@ use inkwell::{AddressSpace, IntPredicate}; use roc_module::symbol::Interns; use roc_module::symbol::Symbol; use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout}; +use roc_target::TargetInfo; /// "Infinite" reference count, for static values /// Ref counts are encoded as negative numbers where isize::MIN represents 1 pub const REFCOUNT_MAX: usize = 0_usize; -pub fn refcount_1(ctx: &Context, ptr_bytes: u32) -> IntValue<'_> { - match ptr_bytes { - 1 => ctx.i8_type().const_int(i8::MIN as u64, false), - 2 => ctx.i16_type().const_int(i16::MIN as u64, false), - 4 => ctx.i32_type().const_int(i32::MIN as u64, false), - 8 => ctx.i64_type().const_int(i64::MIN as u64, false), - _ => panic!( - "Invalid target: Roc does't support compiling to {}-bit systems.", - ptr_bytes * 8 - ), +pub fn refcount_1(ctx: &Context, target_info: TargetInfo) -> IntValue<'_> { + match target_info.ptr_width() { + roc_target::PtrWidth::Bytes4 => ctx.i32_type().const_int(i32::MIN as u64, false), + roc_target::PtrWidth::Bytes8 => ctx.i64_type().const_int(i64::MIN as u64, false), } } @@ -98,7 +93,7 @@ impl<'ctx> PointerToRefcount<'ctx> { pub fn is_1<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>) -> IntValue<'ctx> { let current = self.get_refcount(env); - let one = refcount_1(env.context, env.ptr_bytes); + let one = refcount_1(env.context, env.target_info); env.builder .build_int_compare(IntPredicate::EQ, current, one, "is_one") @@ -163,8 +158,8 @@ impl<'ctx> PointerToRefcount<'ctx> { pub fn decrement<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>) { let alignment = layout - .allocation_alignment_bytes(env.ptr_bytes) - .max(env.ptr_bytes); + .allocation_alignment_bytes(env.target_info) + .max(env.target_info.ptr_width() as u32); let context = env.context; let block = env.builder.get_insert_block().expect("to be in a function"); @@ -1192,7 +1187,7 @@ fn build_rec_union_help<'a, 'ctx, 'env>( debug_assert!(arg_val.is_pointer_value()); let current_tag_id = get_tag_id(env, fn_val, &union_layout, arg_val); - let value_ptr = if union_layout.stores_tag_id_in_pointer(env.ptr_bytes) { + let value_ptr = if union_layout.stores_tag_id_in_pointer(env.target_info) { tag_pointer_clear_tag_id(env, arg_val.into_pointer_value()) } else { arg_val.into_pointer_value() diff --git a/compiler/gen_llvm/src/run_roc.rs b/compiler/gen_llvm/src/run_roc.rs index 4428f68fbf..812fbdede6 100644 --- a/compiler/gen_llvm/src/run_roc.rs +++ b/compiler/gen_llvm/src/run_roc.rs @@ -38,10 +38,23 @@ macro_rules! run_jit_function { }}; ($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr, $errors:expr) => {{ + run_jit_function!($lib, $main_fn_name, $ty, $transform, $errors, &[]) + }}; + ($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr, $errors:expr, $expect_failures:expr) => {{ use inkwell::context::Context; + use roc_builtins::bitcode; use roc_gen_llvm::run_roc::RocCallResult; use std::mem::MaybeUninit; + #[derive(Debug, Copy, Clone)] + #[repr(C)] + struct Failure { + start_line: u32, + end_line: u32, + start_col: u16, + end_col: u16, + } + unsafe { let main: libloading::Symbol) -> ()> = $lib.get($main_fn_name.as_bytes()) @@ -49,11 +62,50 @@ macro_rules! run_jit_function { .ok_or(format!("Unable to JIT compile `{}`", $main_fn_name)) .expect("errored"); - let mut result = MaybeUninit::uninit(); + #[repr(C)] + struct Failures { + failures: *const Failure, + count: usize, + } - main(result.as_mut_ptr()); + impl Drop for Failures { + fn drop(&mut self) { + use std::alloc::{dealloc, Layout}; + use std::mem; - match result.assume_init().into() { + unsafe { + let layout = Layout::from_size_align_unchecked( + mem::size_of::(), + mem::align_of::(), + ); + + dealloc(self.failures as *mut u8, layout); + } + } + } + + let get_expect_failures: libloading::Symbol Failures> = $lib + .get(bitcode::UTILS_GET_EXPECT_FAILURES.as_bytes()) + .ok() + .ok_or(format!( + "Unable to JIT compile `{}`", + bitcode::UTILS_GET_EXPECT_FAILURES + )) + .expect("errored"); + let mut main_result = MaybeUninit::uninit(); + + main(main_result.as_mut_ptr()); + let failures = get_expect_failures(); + + if failures.count > 0 { + // TODO tell the user about the failures! + let failures = + unsafe { core::slice::from_raw_parts(failures.failures, failures.count) }; + + panic!("Failed with {} failures. Failures: ", failures.len()); + } + + match main_result.assume_init().into() { Ok(success) => { // only if there are no exceptions thrown, check for errors assert!($errors.is_empty(), "Encountered errors:\n{}", $errors); @@ -95,7 +147,7 @@ macro_rules! run_jit_function_dynamic_type { let flag = *result; if flag == 0 { - $transform(result.add(std::mem::size_of::>()) as *const u8) + $transform(result.add(std::mem::size_of::>()) as usize) } else { use std::ffi::CString; use std::os::raw::c_char; diff --git a/compiler/gen_wasm/Cargo.toml b/compiler/gen_wasm/Cargo.toml index c4e9386c69..b2223ee48a 100644 --- a/compiler/gen_wasm/Cargo.toml +++ b/compiler/gen_wasm/Cargo.toml @@ -11,5 +11,6 @@ roc_builtins = { path = "../builtins" } roc_collections = { path = "../collections" } roc_module = { path = "../module" } roc_mono = { path = "../mono" } +roc_target = { path = "../roc_target" } roc_std = { path = "../../roc_std" } -roc_reporting = { path = "../../reporting" } +roc_error_macros = { path = "../../error_macros" } diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 9d08da3b88..716758fc56 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -1,37 +1,32 @@ use bumpalo::{self, collections::Vec}; use code_builder::Align; -use roc_builtins::bitcode::{self, IntWidth}; +use roc_builtins::bitcode::IntWidth; use roc_collections::all::MutMap; use roc_module::ident::Ident; -use roc_module::low_level::LowLevel; +use roc_module::low_level::{LowLevel, LowLevelWrapperType}; use roc_module::symbol::{Interns, Symbol}; use roc_mono::code_gen_help::{CodeGenHelp, REFCOUNT_MAX}; use roc_mono::ir::{ - CallType, Expr, JoinPointId, ListLiteralElement, Literal, Proc, ProcLayout, Stmt, + BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, ModifyRc, Param, Proc, + ProcLayout, Stmt, }; +use roc_error_macros::internal_error; use roc_mono::layout::{Builtin, Layout, LayoutIds, TagIdIntType, UnionLayout}; -use roc_reporting::internal_error; -use crate::layout::{CallConv, ReturnMethod, StackMemoryFormat, WasmLayout}; -use crate::low_level::{dispatch_low_level, LowlevelBuildResult}; -use crate::storage::{StackMemoryLocation, Storage, StoredValue, StoredValueKind}; -use crate::wasm_module::linking::{ - DataSymbol, LinkingSection, RelocationSection, WasmObjectSymbol, WASM_SYM_BINDING_WEAK, - WASM_SYM_UNDEFINED, -}; -use crate::wasm_module::sections::{ - CodeSection, DataMode, DataSection, DataSegment, ExportSection, FunctionSection, GlobalSection, - Import, ImportDesc, ImportSection, MemorySection, TypeSection, WasmModule, -}; +use crate::layout::{CallConv, ReturnMethod, WasmLayout}; +use crate::low_level::LowLevelCall; +use crate::storage::{Storage, StoredValue, StoredValueKind}; +use crate::wasm_module::linking::{DataSymbol, LinkingSegment, WasmObjectSymbol}; +use crate::wasm_module::sections::{DataMode, DataSegment}; use crate::wasm_module::{ - code_builder, CodeBuilder, ConstExpr, Export, ExportType, Global, GlobalType, - LinkingSubSection, LocalId, Signature, SymInfo, ValueType, + code_builder, CodeBuilder, Export, ExportType, LocalId, Signature, SymInfo, ValueType, + WasmModule, }; use crate::{ - copy_memory, CopyMemoryConfig, Env, BUILTINS_IMPORT_MODULE_NAME, DEBUG_LOG_SETTINGS, - MEMORY_NAME, PTR_SIZE, PTR_TYPE, STACK_POINTER_GLOBAL_ID, STACK_POINTER_NAME, + copy_memory, round_up_to_alignment, CopyMemoryConfig, Env, DEBUG_LOG_SETTINGS, MEMORY_NAME, + PTR_SIZE, PTR_TYPE, STACK_POINTER_GLOBAL_ID, STACK_POINTER_NAME, TARGET_INFO, }; /// The memory address where the constants data will be loaded during module instantiation. @@ -39,102 +34,50 @@ use crate::{ /// Follow Emscripten's example by leaving 1kB unused (though 4 bytes would probably do!) const CONST_SEGMENT_BASE_ADDR: u32 = 1024; -/// Index of the data segment where we store constants -const CONST_SEGMENT_INDEX: usize = 0; - pub struct WasmBackend<'a> { - env: &'a Env<'a>, + pub env: &'a Env<'a>, interns: &'a mut Interns, // Module-level data module: WasmModule<'a>, layout_ids: LayoutIds<'a>, - constant_sym_index_map: MutMap<&'a str, usize>, - builtin_sym_index_map: MutMap<&'a str, usize>, + next_constant_addr: u32, + fn_index_offset: u32, + called_preload_fns: Vec<'a, u32>, proc_symbols: Vec<'a, (Symbol, u32)>, - linker_symbols: Vec<'a, SymInfo>, helper_proc_gen: CodeGenHelp<'a>, // Function-level data - code_builder: CodeBuilder<'a>, - storage: Storage<'a>, + pub code_builder: CodeBuilder<'a>, + pub storage: Storage<'a>, /// how many blocks deep are we (used for jumps) block_depth: u32, joinpoint_label_map: MutMap)>, - - debug_current_proc_index: usize, } impl<'a> WasmBackend<'a> { + #[allow(clippy::too_many_arguments)] pub fn new( env: &'a Env<'a>, interns: &'a mut Interns, layout_ids: LayoutIds<'a>, proc_symbols: Vec<'a, (Symbol, u32)>, - mut linker_symbols: Vec<'a, SymInfo>, - mut exports: Vec<'a, Export>, + mut module: WasmModule<'a>, + fn_index_offset: u32, helper_proc_gen: CodeGenHelp<'a>, ) -> Self { - const MEMORY_INIT_SIZE: u32 = 1024 * 1024; - let arena = env.arena; - let num_procs = proc_symbols.len(); - - exports.push(Export { - name: MEMORY_NAME.to_string(), + module.export.append(Export { + name: MEMORY_NAME.as_bytes(), ty: ExportType::Mem, index: 0, }); - - let stack_pointer = Global { - ty: GlobalType { - value_type: ValueType::I32, - is_mutable: true, - }, - init: ConstExpr::I32(MEMORY_INIT_SIZE as i32), - }; - - exports.push(Export { - name: STACK_POINTER_NAME.to_string(), + module.export.append(Export { + name: STACK_POINTER_NAME.as_bytes(), ty: ExportType::Global, index: STACK_POINTER_GLOBAL_ID, }); - linker_symbols.push(SymInfo::Global(WasmObjectSymbol::Defined { - flags: WASM_SYM_BINDING_WEAK, // TODO: this works but means external .o files decide how much stack we have! - index: STACK_POINTER_GLOBAL_ID, - name: STACK_POINTER_NAME.to_string(), - })); - - let const_segment = DataSegment { - mode: DataMode::Active { - offset: ConstExpr::I32(CONST_SEGMENT_BASE_ADDR as i32), - }, - init: Vec::with_capacity_in(64, arena), - }; - - let module = WasmModule { - types: TypeSection::new(arena, num_procs), - import: ImportSection::new(arena), - function: FunctionSection::new(arena, num_procs), - table: (), - memory: MemorySection::new(MEMORY_INIT_SIZE), - global: GlobalSection { - entries: bumpalo::vec![in arena; stack_pointer], - }, - export: ExportSection { entries: exports }, - start: (), - element: (), - code: CodeSection { - code_builders: Vec::with_capacity_in(num_procs, arena), - }, - data: DataSection { - segments: bumpalo::vec![in arena; const_segment], - }, - linking: LinkingSection::new(arena), - relocations: RelocationSection::new(arena, "reloc.CODE"), - }; - WasmBackend { env, interns, @@ -143,19 +86,17 @@ impl<'a> WasmBackend<'a> { module, layout_ids, - constant_sym_index_map: MutMap::default(), - builtin_sym_index_map: MutMap::default(), + next_constant_addr: CONST_SEGMENT_BASE_ADDR, + fn_index_offset, + called_preload_fns: Vec::with_capacity_in(2, env.arena), proc_symbols, - linker_symbols, helper_proc_gen, // Function-level data block_depth: 0, joinpoint_label_map: MutMap::default(), - code_builder: CodeBuilder::new(arena), - storage: Storage::new(arena), - - debug_current_proc_index: 0, + code_builder: CodeBuilder::new(env.arena), + storage: Storage::new(env.arena), } } @@ -166,7 +107,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 linker_sym_index = self.linker_symbols.len() as u32; + let linker_sym_index = self.module.linking.symbol_table.len() as u32; let name = self .layout_ids @@ -174,18 +115,16 @@ impl<'a> WasmBackend<'a> { .to_symbol_string(new_proc_sym, self.interns); self.proc_symbols.push((new_proc_sym, linker_sym_index)); - self.linker_symbols - .push(SymInfo::Function(WasmObjectSymbol::Defined { - flags: 0, - index: wasm_fn_index, - name, - })); + let linker_symbol = SymInfo::Function(WasmObjectSymbol::Defined { + flags: 0, + index: wasm_fn_index, + name, + }); + self.module.linking.symbol_table.push(linker_symbol); } - pub fn finalize_module(mut self) -> WasmModule<'a> { - let symbol_table = LinkingSubSection::SymbolTable(self.linker_symbols); - self.module.linking.subsections.push(symbol_table); - self.module + pub fn finalize(self) -> (WasmModule<'a>, Vec<'a, u32>) { + (self.module, self.called_preload_fns) } /// Register the debug names of Symbols in a global lookup table @@ -236,11 +175,9 @@ impl<'a> WasmBackend<'a> { println!("\ngenerating procedure {:?}\n", proc.name); } - self.debug_current_proc_index += 1; - self.start_proc(proc); - self.build_stmt(&proc.body, &proc.ret_layout); + self.stmt(&proc.body); self.finalize_proc(); self.reset(); @@ -304,6 +241,35 @@ impl<'a> WasmBackend<'a> { ***********************************************************/ + fn stmt(&mut self, stmt: &Stmt<'a>) { + match stmt { + Stmt::Let(_, _, _, _) => self.stmt_let(stmt), + + Stmt::Ret(sym) => self.stmt_ret(*sym), + + Stmt::Switch { + cond_symbol, + cond_layout, + branches, + default_branch, + ret_layout: _, + } => self.stmt_switch(*cond_symbol, cond_layout, branches, default_branch), + + Stmt::Join { + id, + parameters, + body, + remainder, + } => self.stmt_join(*id, parameters, body, remainder), + + Stmt::Jump(id, arguments) => self.stmt_jump(*id, arguments), + + Stmt::Refcounting(modify, following) => self.stmt_refcounting(modify, following), + + Stmt::RuntimeError(msg) => self.stmt_runtime_error(msg), + } + } + fn start_block(&mut self) { // Wasm blocks can have result types, but we don't use them. // You need the right type on the stack when you jump from an inner block to an outer one. @@ -323,7 +289,27 @@ impl<'a> WasmBackend<'a> { self.code_builder.end(); } - fn store_expr_value( + fn stmt_let(&mut self, stmt: &Stmt<'a>) { + let mut current_stmt = stmt; + while let Stmt::Let(sym, expr, layout, following) = current_stmt { + if DEBUG_LOG_SETTINGS.let_stmt_ir { + println!("let {:?} = {}", sym, expr.to_pretty(200)); // ignore `following`! Too confusing otherwise. + } + + let kind = match following { + Stmt::Ret(ret_sym) if *sym == *ret_sym => StoredValueKind::ReturnValue, + _ => StoredValueKind::Variable, + }; + + self.stmt_let_store_expr(*sym, layout, expr, kind); + + current_stmt = *following; + } + + self.stmt(current_stmt); + } + + fn stmt_let_store_expr( &mut self, sym: Symbol, layout: &Layout<'a>, @@ -332,7 +318,7 @@ impl<'a> WasmBackend<'a> { ) { let sym_storage = self.storage.allocate(*layout, sym, kind); - self.build_expr(&sym, expr, layout, &sym_storage); + self.expr(sym, expr, layout, &sym_storage); // If this value is stored in the VM stack, we need code_builder to track it // (since every instruction can change the VM stack) @@ -343,229 +329,207 @@ impl<'a> WasmBackend<'a> { } } - fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) { - match stmt { - Stmt::Let(_, _, _, _) => { - let mut current_stmt = stmt; - while let Stmt::Let(sym, expr, layout, following) = current_stmt { - if DEBUG_LOG_SETTINGS.let_stmt_ir { - println!("let {:?} = {}", sym, expr.to_pretty(200)); // ignore `following`! Too confusing otherwise. - } + fn stmt_ret(&mut self, sym: Symbol) { + use crate::storage::StoredValue::*; - let kind = match following { - Stmt::Ret(ret_sym) if *sym == *ret_sym => StoredValueKind::ReturnValue, - _ => StoredValueKind::Variable, - }; + let storage = self.storage.symbol_storage_map.get(&sym).unwrap(); - self.store_expr_value(*sym, layout, expr, kind); - - current_stmt = *following; - } - - self.build_stmt(current_stmt, ret_layout); - } - - Stmt::Ret(sym) => { - use crate::storage::StoredValue::*; - - let storage = self.storage.symbol_storage_map.get(sym).unwrap(); - - match storage { - StackMemory { - location, - size, - alignment_bytes, - .. - } => { - let (from_ptr, from_offset) = - location.local_and_offset(self.storage.stack_frame_pointer); - copy_memory( - &mut self.code_builder, - CopyMemoryConfig { - from_ptr, - from_offset, - to_ptr: LocalId(0), - to_offset: 0, - size: *size, - alignment_bytes: *alignment_bytes, - }, - ); - } - - _ => { - self.storage.load_symbols(&mut self.code_builder, &[*sym]); - - // If we have a return value, store it to the return variable - // This avoids complications with block result types when returning from nested blocks - if let Some(ret_var) = self.storage.return_var { - self.code_builder.set_local(ret_var); - } - } - } - // jump to the "stack frame pop" code at the end of the function - self.code_builder.br(self.block_depth - 1); - } - - Stmt::Switch { - cond_symbol, - cond_layout, - branches, - default_branch, - ret_layout: _, + match storage { + StackMemory { + location, + size, + alignment_bytes, + .. } => { - // NOTE currently implemented as a series of conditional jumps - // We may be able to improve this in the future with `Select` - // or `BrTable` - - // Ensure the condition value is not stored only in the VM stack - // Otherwise we can't reach it from inside the block - let cond_storage = self.storage.get(cond_symbol).to_owned(); - self.storage.ensure_value_has_local( + let (from_ptr, from_offset) = + location.local_and_offset(self.storage.stack_frame_pointer); + copy_memory( &mut self.code_builder, - *cond_symbol, - cond_storage, + CopyMemoryConfig { + from_ptr, + from_offset, + to_ptr: LocalId(0), + to_offset: 0, + size: *size, + alignment_bytes: *alignment_bytes, + }, ); - - // create a block for each branch except the default - for _ in 0..branches.len() { - self.start_block() - } - - let is_bool = matches!(cond_layout, Layout::Builtin(Builtin::Bool)); - let cond_type = WasmLayout::new(cond_layout).arg_types(CallConv::C)[0]; - - // then, we jump whenever the value under scrutiny is equal to the value of a branch - for (i, (value, _, _)) in branches.iter().enumerate() { - // put the cond_symbol on the top of the stack - self.storage - .load_symbols(&mut self.code_builder, &[*cond_symbol]); - - if is_bool { - // We already have a bool, don't need to compare against a const to get one - if *value == 0 { - self.code_builder.i32_eqz(); - } - } else { - match cond_type { - ValueType::I32 => { - self.code_builder.i32_const(*value as i32); - self.code_builder.i32_eq(); - } - ValueType::I64 => { - self.code_builder.i64_const(*value as i64); - self.code_builder.i64_eq(); - } - ValueType::F32 => { - self.code_builder.f32_const(f32::from_bits(*value as u32)); - self.code_builder.f32_eq(); - } - ValueType::F64 => { - self.code_builder.f64_const(f64::from_bits(*value as u64)); - self.code_builder.f64_eq(); - } - } - } - - // "break" out of `i` surrounding blocks - self.code_builder.br_if(i as u32); - } - - // if we never jumped because a value matched, we're in the default case - self.build_stmt(default_branch.1, ret_layout); - - // now put in the actual body of each branch in order - // (the first branch would have broken out of 1 block, - // hence we must generate its code first) - for (_, _, branch) in branches.iter() { - self.end_block(); - - self.build_stmt(branch, ret_layout); - } - } - Stmt::Join { - id, - parameters, - body, - remainder, - } => { - // make locals for join pointer parameters - let mut jp_param_storages = Vec::with_capacity_in(parameters.len(), self.env.arena); - for parameter in parameters.iter() { - let mut param_storage = self.storage.allocate( - parameter.layout, - parameter.symbol, - StoredValueKind::Variable, - ); - param_storage = self.storage.ensure_value_has_local( - &mut self.code_builder, - parameter.symbol, - param_storage, - ); - jp_param_storages.push(param_storage); - } - - self.start_block(); - - self.joinpoint_label_map - .insert(*id, (self.block_depth, jp_param_storages)); - - self.build_stmt(remainder, ret_layout); - - self.end_block(); - self.start_loop(); - - self.build_stmt(body, ret_layout); - - // ends the loop - self.end_block(); - } - Stmt::Jump(id, arguments) => { - let (target, param_storages) = self.joinpoint_label_map[id].clone(); - - for (arg_symbol, param_storage) in arguments.iter().zip(param_storages.iter()) { - let arg_storage = self.storage.get(arg_symbol).clone(); - self.storage.clone_value( - &mut self.code_builder, - param_storage, - &arg_storage, - *arg_symbol, - ); - } - - // jump - let levels = self.block_depth - target; - self.code_builder.br(levels); } - Stmt::Refcounting(modify, following) => { - let value = modify.get_symbol(); - let layout = self.storage.symbol_layouts[&value]; + _ => { + self.storage.load_symbols(&mut self.code_builder, &[sym]); - let ident_ids = self - .interns - .all_ident_ids - .get_mut(&self.env.module_id) - .unwrap(); - - let (rc_stmt, new_specializations) = self - .helper_proc_gen - .expand_refcount_stmt(ident_ids, layout, modify, *following); - - if false { - self.register_symbol_debug_names(); - println!("## rc_stmt:\n{}\n{:?}", rc_stmt.to_pretty(200), rc_stmt); + // If we have a return value, store it to the return variable + // This avoids complications with block result types when returning from nested blocks + if let Some(ret_var) = self.storage.return_var { + self.code_builder.set_local(ret_var); } - - // If any new specializations were created, register their symbol data - for spec in new_specializations.into_iter() { - self.register_helper_proc(spec); - } - - self.build_stmt(rc_stmt, ret_layout); } - - x => todo!("statement {:?}", x), } + // jump to the "stack frame pop" code at the end of the function + self.code_builder.br(self.block_depth - 1); + } + + fn stmt_switch( + &mut self, + cond_symbol: Symbol, + cond_layout: &Layout<'a>, + branches: &'a [(u64, BranchInfo<'a>, Stmt<'a>)], + default_branch: &(BranchInfo<'a>, &'a Stmt<'a>), + ) { + // NOTE currently implemented as a series of conditional jumps + // We may be able to improve this in the future with `Select` + // or `BrTable` + + // Ensure the condition value is not stored only in the VM stack + // Otherwise we can't reach it from inside the block + let cond_storage = self.storage.get(&cond_symbol).to_owned(); + self.storage + .ensure_value_has_local(&mut self.code_builder, cond_symbol, cond_storage); + + // create a block for each branch except the default + for _ in 0..branches.len() { + self.start_block() + } + + let is_bool = matches!(cond_layout, Layout::Builtin(Builtin::Bool)); + let cond_type = WasmLayout::new(cond_layout).arg_types(CallConv::C)[0]; + + // then, we jump whenever the value under scrutiny is equal to the value of a branch + for (i, (value, _, _)) in branches.iter().enumerate() { + // put the cond_symbol on the top of the stack + self.storage + .load_symbols(&mut self.code_builder, &[cond_symbol]); + + if is_bool { + // We already have a bool, don't need to compare against a const to get one + if *value == 0 { + self.code_builder.i32_eqz(); + } + } else { + match cond_type { + ValueType::I32 => { + self.code_builder.i32_const(*value as i32); + self.code_builder.i32_eq(); + } + ValueType::I64 => { + self.code_builder.i64_const(*value as i64); + self.code_builder.i64_eq(); + } + ValueType::F32 => { + self.code_builder.f32_const(f32::from_bits(*value as u32)); + self.code_builder.f32_eq(); + } + ValueType::F64 => { + self.code_builder.f64_const(f64::from_bits(*value as u64)); + self.code_builder.f64_eq(); + } + } + } + + // "break" out of `i` surrounding blocks + self.code_builder.br_if(i as u32); + } + + // if we never jumped because a value matched, we're in the default case + self.stmt(default_branch.1); + + // now put in the actual body of each branch in order + // (the first branch would have broken out of 1 block, + // hence we must generate its code first) + for (_, _, branch) in branches.iter() { + self.end_block(); + + self.stmt(branch); + } + } + + fn stmt_join( + &mut self, + id: JoinPointId, + parameters: &'a [Param<'a>], + body: &'a Stmt<'a>, + remainder: &'a Stmt<'a>, + ) { + // make locals for join pointer parameters + let mut jp_param_storages = Vec::with_capacity_in(parameters.len(), self.env.arena); + for parameter in parameters.iter() { + let mut param_storage = self.storage.allocate( + parameter.layout, + parameter.symbol, + StoredValueKind::Variable, + ); + param_storage = self.storage.ensure_value_has_local( + &mut self.code_builder, + parameter.symbol, + param_storage, + ); + jp_param_storages.push(param_storage); + } + + self.start_block(); + + self.joinpoint_label_map + .insert(id, (self.block_depth, jp_param_storages)); + + self.stmt(remainder); + + self.end_block(); + self.start_loop(); + + self.stmt(body); + + // ends the loop + self.end_block(); + } + + fn stmt_jump(&mut self, id: JoinPointId, arguments: &'a [Symbol]) { + let (target, param_storages) = self.joinpoint_label_map[&id].clone(); + + for (arg_symbol, param_storage) in arguments.iter().zip(param_storages.iter()) { + let arg_storage = self.storage.get(arg_symbol).clone(); + self.storage.clone_value( + &mut self.code_builder, + param_storage, + &arg_storage, + *arg_symbol, + ); + } + + // jump + let levels = self.block_depth - target; + self.code_builder.br(levels); + } + + fn stmt_refcounting(&mut self, modify: &ModifyRc, following: &'a Stmt<'a>) { + let value = modify.get_symbol(); + let layout = self.storage.symbol_layouts[&value]; + + let ident_ids = self + .interns + .all_ident_ids + .get_mut(&self.env.module_id) + .unwrap(); + + let (rc_stmt, new_specializations) = self + .helper_proc_gen + .expand_refcount_stmt(ident_ids, layout, modify, following); + + if false { + self.register_symbol_debug_names(); + println!("## rc_stmt:\n{}\n{:?}", rc_stmt.to_pretty(200), rc_stmt); + } + + // If any new specializations were created, register their symbol data + for spec in new_specializations.into_iter() { + self.register_helper_proc(spec); + } + + self.stmt(rc_stmt); + } + + fn stmt_runtime_error(&mut self, msg: &'a str) { + todo!("RuntimeError {:?}", msg) } /********************************************************** @@ -574,769 +538,63 @@ impl<'a> WasmBackend<'a> { ***********************************************************/ - fn build_expr( - &mut self, - sym: &Symbol, - expr: &Expr<'a>, - layout: &Layout<'a>, - storage: &StoredValue, - ) { - let wasm_layout = WasmLayout::new(layout); + fn expr(&mut self, sym: Symbol, expr: &Expr<'a>, layout: &Layout<'a>, storage: &StoredValue) { match expr { - Expr::Literal(lit) => self.load_literal(lit, storage, *sym, layout), + Expr::Literal(lit) => self.expr_literal(lit, storage, sym, layout), Expr::Call(roc_mono::ir::Call { call_type, arguments, - }) => match call_type { - CallType::ByName { name: func_sym, .. } => { - // If this function is just a lowlevel wrapper, then inline it - if let Some(lowlevel) = LowLevel::from_inlined_wrapper(*func_sym) { - return self.build_low_level( - lowlevel, - arguments, - *sym, - wasm_layout, - storage, - ); - } + }) => self.expr_call(call_type, arguments, sym, layout, storage), - let (param_types, ret_type) = self.storage.load_symbols_for_call( - self.env.arena, - &mut self.code_builder, - arguments, - *sym, - &wasm_layout, - CallConv::C, - ); - - for (func_index, (ir_sym, linker_sym_index)) in - self.proc_symbols.iter().enumerate() - { - if ir_sym == func_sym { - let num_wasm_args = param_types.len(); - let has_return_val = ret_type.is_some(); - self.code_builder.call( - func_index as u32, - *linker_sym_index, - num_wasm_args, - has_return_val, - ); - return; - } - } - - internal_error!( - "Could not find procedure {:?}\nKnown procedures: {:?}", - func_sym, - self.proc_symbols - ); - } - - CallType::LowLevel { op: lowlevel, .. } => { - self.build_low_level(*lowlevel, arguments, *sym, wasm_layout, storage) - } - - x => todo!("call type {:?}", x), - }, - - Expr::Struct(fields) => self.create_struct(sym, layout, fields), + Expr::Struct(fields) => self.expr_struct(sym, layout, storage, fields), Expr::StructAtIndex { index, field_layouts, structure, - } => { - self.storage.ensure_value_has_local( - &mut self.code_builder, - *sym, - storage.to_owned(), - ); - let (local_id, mut offset) = match self.storage.get(structure) { - StoredValue::StackMemory { location, .. } => { - location.local_and_offset(self.storage.stack_frame_pointer) - } + } => self.expr_struct_at_index(sym, storage, *index, field_layouts, *structure), - StoredValue::Local { - value_type, - local_id, - .. - } => { - debug_assert!(matches!(value_type, ValueType::I32)); - (*local_id, 0) - } + Expr::Array { elems, elem_layout } => self.expr_array(sym, storage, elem_layout, elems), - StoredValue::VirtualMachineStack { .. } => { - internal_error!("ensure_value_has_local didn't work") - } - }; - for field in field_layouts.iter().take(*index as usize) { - offset += field.stack_size(PTR_SIZE); - } - self.storage - .copy_value_from_memory(&mut self.code_builder, *sym, local_id, offset); - } - - Expr::Array { elems, elem_layout } => { - if let StoredValue::StackMemory { - location, - alignment_bytes, - .. - } = storage - { - let size = elem_layout.stack_size(PTR_SIZE) * (elems.len() as u32); - - // Allocate heap space and store its address in a local variable - let heap_local_id = self.storage.create_anonymous_local(PTR_TYPE); - self.allocate_with_refcount(Some(size), *alignment_bytes, 1); - self.code_builder.set_local(heap_local_id); - - let (stack_local_id, stack_offset) = - location.local_and_offset(self.storage.stack_frame_pointer); - - // elements pointer - self.code_builder.get_local(stack_local_id); - self.code_builder.get_local(heap_local_id); - self.code_builder.i32_store(Align::Bytes4, stack_offset); - - // length of the list - self.code_builder.get_local(stack_local_id); - self.code_builder.i32_const(elems.len() as i32); - self.code_builder.i32_store(Align::Bytes4, stack_offset + 4); - - let mut elem_offset = 0; - - for (i, elem) in elems.iter().enumerate() { - let elem_sym = match elem { - ListLiteralElement::Literal(lit) => { - // This has no Symbol but our storage methods expect one. - // Let's just pretend it was defined in a `Let`. - let debug_name = format!("{:?}_{}", sym, i); - let elem_sym = self.create_symbol(&debug_name); - let expr = Expr::Literal(*lit); - - self.store_expr_value( - elem_sym, - elem_layout, - &expr, - StoredValueKind::Variable, - ); - - elem_sym - } - - ListLiteralElement::Symbol(elem_sym) => *elem_sym, - }; - - elem_offset += self.storage.copy_value_to_memory( - &mut self.code_builder, - heap_local_id, - elem_offset, - elem_sym, - ); - } - } else { - internal_error!("Unexpected storage for Array {:?}: {:?}", sym, storage) - } - } - - Expr::EmptyArray => { - if let StoredValue::StackMemory { location, .. } = storage { - let (local_id, offset) = - location.local_and_offset(self.storage.stack_frame_pointer); - - // This is a minor cheat. - // What we want to write to stack memory is { elements: null, length: 0 } - // But instead of two 32-bit stores, we can do a single 64-bit store. - self.code_builder.get_local(local_id); - self.code_builder.i64_const(0); - self.code_builder.i64_store(Align::Bytes4, offset); - } else { - internal_error!("Unexpected storage for {:?}", sym) - } - } + Expr::EmptyArray => self.expr_empty_array(sym, storage), Expr::Tag { tag_layout: union_layout, tag_id, arguments, .. - } => self.build_tag(union_layout, *tag_id, arguments, *sym, storage), + } => self.expr_tag(union_layout, *tag_id, arguments, sym, storage), Expr::GetTagId { structure, union_layout, - } => self.build_get_tag_id(*structure, union_layout, *sym, storage), + } => self.expr_get_tag_id(*structure, union_layout, sym, storage), Expr::UnionAtIndex { structure, tag_id, union_layout, index, - } => self.build_union_at_index(*structure, *tag_id, union_layout, *index, *sym), + } => self.expr_union_at_index(*structure, *tag_id, union_layout, *index, sym), _ => todo!("Expression `{}`", expr.to_pretty(100)), } } - fn build_tag( - &mut self, - union_layout: &UnionLayout<'a>, - tag_id: TagIdIntType, - arguments: &'a [Symbol], - symbol: Symbol, - stored: &StoredValue, - ) { - if union_layout.tag_is_null(tag_id) { - self.code_builder.i32_const(0); - return; - } + /******************************************************************* + * Literals + *******************************************************************/ - let stores_tag_id_as_data = union_layout.stores_tag_id_as_data(PTR_SIZE); - let stores_tag_id_in_pointer = union_layout.stores_tag_id_in_pointer(PTR_SIZE); - let (data_size, data_alignment) = union_layout.data_size_and_alignment(PTR_SIZE); - - // We're going to use the pointer many times, so put it in a local variable - let stored_with_local = - self.storage - .ensure_value_has_local(&mut self.code_builder, symbol, stored.to_owned()); - - let (local_id, data_offset) = match stored_with_local { - StoredValue::StackMemory { location, .. } => { - location.local_and_offset(self.storage.stack_frame_pointer) - } - StoredValue::Local { local_id, .. } => { - // Tag is stored as a pointer to the heap. Call the allocator to get a memory address. - self.allocate_with_refcount(Some(data_size), data_alignment, 1); - self.code_builder.set_local(local_id); - (local_id, 0) - } - StoredValue::VirtualMachineStack { .. } => { - internal_error!("{:?} should have a local variable", symbol) - } - }; - - // Write the field values to memory - let mut field_offset = data_offset; - for field_symbol in arguments.iter() { - field_offset += self.storage.copy_value_to_memory( - &mut self.code_builder, - local_id, - field_offset, - *field_symbol, - ); - } - - // Store the tag ID (if any) - if stores_tag_id_as_data { - let id_offset = data_offset + data_size - data_alignment; - - let id_align = union_layout.tag_id_builtin().alignment_bytes(PTR_SIZE); - let id_align = Align::from(id_align); - - self.code_builder.get_local(local_id); - - match id_align { - Align::Bytes1 => { - self.code_builder.i32_const(tag_id as i32); - self.code_builder.i32_store8(id_align, id_offset); - } - Align::Bytes2 => { - self.code_builder.i32_const(tag_id as i32); - self.code_builder.i32_store16(id_align, id_offset); - } - Align::Bytes4 => { - self.code_builder.i32_const(tag_id as i32); - self.code_builder.i32_store(id_align, id_offset); - } - Align::Bytes8 => { - self.code_builder.i64_const(tag_id as i64); - self.code_builder.i64_store(id_align, id_offset); - } - } - } else if stores_tag_id_in_pointer { - self.code_builder.get_local(local_id); - self.code_builder.i32_const(tag_id as i32); - self.code_builder.i32_or(); - self.code_builder.set_local(local_id); - } - } - - fn build_get_tag_id( - &mut self, - structure: Symbol, - union_layout: &UnionLayout<'a>, - tag_id_symbol: Symbol, - stored_value: &StoredValue, - ) { - use UnionLayout::*; - - let block_result_id = match union_layout { - NonRecursive(_) => None, - Recursive(_) => None, - NonNullableUnwrapped(_) => { - self.code_builder.i32_const(0); - return; - } - NullableWrapped { nullable_id, .. } => { - let stored_with_local = self.storage.ensure_value_has_local( - &mut self.code_builder, - tag_id_symbol, - stored_value.to_owned(), - ); - let local_id = match stored_with_local { - StoredValue::Local { local_id, .. } => local_id, - _ => internal_error!("ensure_value_has_local didn't work"), - }; - - // load pointer - self.storage - .load_symbols(&mut self.code_builder, &[structure]); - - // null check - self.code_builder.i32_eqz(); - self.code_builder.if_(); - self.code_builder.i32_const(*nullable_id as i32); - self.code_builder.set_local(local_id); - self.code_builder.else_(); - Some(local_id) - } - NullableUnwrapped { nullable_id, .. } => { - self.code_builder.i32_const(!(*nullable_id) as i32); - self.code_builder.i32_const(*nullable_id as i32); - self.storage - .load_symbols(&mut self.code_builder, &[structure]); - self.code_builder.select(); - None - } - }; - - if union_layout.stores_tag_id_as_data(PTR_SIZE) { - let (data_size, data_alignment) = union_layout.data_size_and_alignment(PTR_SIZE); - let id_offset = data_size - data_alignment; - - let id_align = union_layout.tag_id_builtin().alignment_bytes(PTR_SIZE); - let id_align = Align::from(id_align); - - self.storage - .load_symbols(&mut self.code_builder, &[structure]); - - match union_layout.tag_id_builtin() { - Builtin::Bool | Builtin::Int(IntWidth::U8) => { - self.code_builder.i32_load8_u(id_align, id_offset) - } - Builtin::Int(IntWidth::U16) => self.code_builder.i32_load16_u(id_align, id_offset), - Builtin::Int(IntWidth::U32) => self.code_builder.i32_load(id_align, id_offset), - Builtin::Int(IntWidth::U64) => self.code_builder.i64_load(id_align, id_offset), - x => internal_error!("Unexpected layout for tag union id {:?}", x), - } - } else if union_layout.stores_tag_id_in_pointer(PTR_SIZE) { - self.storage - .load_symbols(&mut self.code_builder, &[structure]); - self.code_builder.i32_const(3); - self.code_builder.i32_and(); - } - - if let Some(local_id) = block_result_id { - self.code_builder.set_local(local_id); - self.code_builder.end(); - } - } - - fn build_union_at_index( - &mut self, - structure: Symbol, - tag_id: TagIdIntType, - union_layout: &UnionLayout<'a>, - index: u64, - symbol: Symbol, - ) { - use UnionLayout::*; - - debug_assert!(!union_layout.tag_is_null(tag_id)); - - let tag_index = tag_id as usize; - let field_layouts = match union_layout { - NonRecursive(tags) => tags[tag_index], - Recursive(tags) => tags[tag_index], - NonNullableUnwrapped(layouts) => *layouts, - NullableWrapped { - other_tags, - nullable_id, - } => { - let index = if tag_index > *nullable_id as usize { - tag_index - 1 - } else { - tag_index - }; - other_tags[index] - } - NullableUnwrapped { other_fields, .. } => *other_fields, - }; - - let field_offset: u32 = field_layouts - .iter() - .take(index as usize) - .map(|field_layout| field_layout.stack_size(PTR_SIZE)) - .sum(); - - // Get pointer and offset to the tag's data - let structure_storage = self.storage.get(&structure).to_owned(); - let stored_with_local = self.storage.ensure_value_has_local( - &mut self.code_builder, - structure, - structure_storage, - ); - let (tag_local_id, tag_offset) = match stored_with_local { - StoredValue::StackMemory { location, .. } => { - location.local_and_offset(self.storage.stack_frame_pointer) - } - StoredValue::Local { local_id, .. } => (local_id, 0), - StoredValue::VirtualMachineStack { .. } => { - internal_error!("{:?} should have a local variable", structure) - } - }; - - let stores_tag_id_in_pointer = union_layout.stores_tag_id_in_pointer(PTR_SIZE); - - let from_ptr = if stores_tag_id_in_pointer { - let ptr = self.storage.create_anonymous_local(ValueType::I32); - self.code_builder.get_local(tag_local_id); - self.code_builder.i32_const(-4); // 11111111...1100 - self.code_builder.i32_and(); - self.code_builder.set_local(ptr); - ptr - } else { - tag_local_id - }; - - let from_offset = tag_offset + field_offset; - self.storage - .copy_value_from_memory(&mut self.code_builder, symbol, from_ptr, from_offset); - } - - /// Allocate heap space and write an initial refcount - /// If the data size is known at compile time, pass it in comptime_data_size. - /// If size is only known at runtime, push *data* size to the VM stack first. - /// Leaves the *data* address on the VM stack - fn allocate_with_refcount( - &mut self, - comptime_data_size: Option, - alignment_bytes: u32, - initial_refcount: u32, - ) { - // Add extra bytes for the refcount - let extra_bytes = alignment_bytes.max(PTR_SIZE); - - if let Some(data_size) = comptime_data_size { - // Data size known at compile time and passed as an argument - self.code_builder - .i32_const((data_size + extra_bytes) as i32); - } else { - // Data size known only at runtime and is on top of VM stack - self.code_builder.i32_const(extra_bytes as i32); - self.code_builder.i32_add(); - } - - // Provide a constant for the alignment argument - self.code_builder.i32_const(alignment_bytes as i32); - - // Call the foreign function. (Zig and C calling conventions are the same for this signature) - let param_types = bumpalo::vec![in self.env.arena; ValueType::I32, ValueType::I32]; - let ret_type = Some(ValueType::I32); - self.call_zig_builtin("roc_alloc", param_types, ret_type); - - // Save the allocation address to a temporary local variable - let local_id = self.storage.create_anonymous_local(ValueType::I32); - self.code_builder.set_local(local_id); - - // Write the initial refcount - let refcount_offset = extra_bytes - PTR_SIZE; - let encoded_refcount = (initial_refcount as i32) - 1 + i32::MIN; - self.code_builder.get_local(local_id); - self.code_builder.i32_const(encoded_refcount); - self.code_builder.i32_store(Align::Bytes4, refcount_offset); - - // Put the data address on the VM stack - self.code_builder.get_local(local_id); - self.code_builder.i32_const(extra_bytes as i32); - self.code_builder.i32_add(); - } - - fn build_low_level( - &mut self, - lowlevel: LowLevel, - arguments: &'a [Symbol], - return_sym: Symbol, - return_layout: WasmLayout, - storage: &StoredValue, - ) { - use LowLevel::*; - - match lowlevel { - Eq | NotEq => { - self.build_eq_or_neq(lowlevel, arguments, return_sym, return_layout, storage) - } - PtrCast => { - // Don't want Zig calling convention when casting pointers. - self.storage.load_symbols(&mut self.code_builder, arguments); - } - Hash => todo!("Generic hash function generation"), - - // Almost all lowlevels take this branch, except for the special cases above - _ => { - // Load the arguments using Zig calling convention - let (param_types, ret_type) = self.storage.load_symbols_for_call( - self.env.arena, - &mut self.code_builder, - arguments, - return_sym, - &return_layout, - CallConv::Zig, - ); - - // Generate instructions OR decide which Zig function to call - let build_result = dispatch_low_level( - &mut self.code_builder, - &mut self.storage, - lowlevel, - arguments, - &return_layout, - ); - - // Handle the result - use LowlevelBuildResult::*; - match build_result { - Done => {} - BuiltinCall(name) => { - self.call_zig_builtin(name, param_types, ret_type); - } - NotImplemented => { - todo!("Low level operation {:?}", lowlevel) - } - } - } - } - } - - fn build_eq_or_neq( - &mut self, - lowlevel: LowLevel, - arguments: &'a [Symbol], - return_sym: Symbol, - return_layout: WasmLayout, - storage: &StoredValue, - ) { - let arg_layout = self.storage.symbol_layouts[&arguments[0]]; - let other_arg_layout = self.storage.symbol_layouts[&arguments[1]]; - debug_assert!( - arg_layout == other_arg_layout, - "Cannot do `==` comparison on different types" - ); - - match arg_layout { - Layout::Builtin( - Builtin::Int(_) | Builtin::Float(_) | Builtin::Bool | Builtin::Decimal, - ) => self.build_eq_or_neq_number(lowlevel, arguments, return_layout), - - Layout::Builtin(Builtin::Str) => { - let (param_types, ret_type) = self.storage.load_symbols_for_call( - self.env.arena, - &mut self.code_builder, - arguments, - return_sym, - &return_layout, - CallConv::Zig, - ); - self.call_zig_builtin(bitcode::STR_EQUAL, param_types, ret_type); - if matches!(lowlevel, LowLevel::NotEq) { - self.code_builder.i32_eqz(); - } - } - - // 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() => { - self.code_builder - .i32_const(if lowlevel == LowLevel::Eq { 1 } else { 0 }); - } - - // Void is always equal to void. This is the type for the contents of the empty list in `[] == []` - // This code will never execute, but we need a true or false value to type-check - Layout::Union(UnionLayout::NonRecursive(tags)) if tags.is_empty() => { - self.code_builder - .i32_const(if lowlevel == LowLevel::Eq { 1 } else { 0 }); - } - - Layout::Builtin(Builtin::Dict(_, _) | Builtin::Set(_) | Builtin::List(_)) - | Layout::Struct(_) - | Layout::Union(_) - | Layout::LambdaSet(_) => { - self.build_eq_specialized(&arg_layout, arguments, return_sym, storage); - if matches!(lowlevel, LowLevel::NotEq) { - self.code_builder.i32_eqz(); - } - } - - Layout::RecursivePointer => { - internal_error!( - "Tried to apply `==` to RecursivePointer values {:?}", - arguments, - ) - } - } - } - - fn build_eq_or_neq_number( - &mut self, - lowlevel: LowLevel, - arguments: &'a [Symbol], - return_layout: WasmLayout, - ) { - use StoredValue::*; - match self.storage.get(&arguments[0]).to_owned() { - VirtualMachineStack { value_type, .. } | Local { value_type, .. } => { - self.storage.load_symbols(&mut self.code_builder, arguments); - match lowlevel { - LowLevel::Eq => match value_type { - ValueType::I32 => self.code_builder.i32_eq(), - ValueType::I64 => self.code_builder.i64_eq(), - ValueType::F32 => self.code_builder.f32_eq(), - ValueType::F64 => self.code_builder.f64_eq(), - }, - LowLevel::NotEq => match value_type { - ValueType::I32 => self.code_builder.i32_ne(), - ValueType::I64 => self.code_builder.i64_ne(), - ValueType::F32 => self.code_builder.f32_ne(), - ValueType::F64 => self.code_builder.f64_ne(), - }, - _ => internal_error!("Low-level op {:?} handled in the wrong place", lowlevel), - } - } - StackMemory { - format, - location: location0, - .. - } => { - if let StackMemory { - location: location1, - .. - } = self.storage.get(&arguments[1]).to_owned() - { - self.build_eq_num128(format, [location0, location1], arguments, return_layout); - if matches!(lowlevel, LowLevel::NotEq) { - self.code_builder.i32_eqz(); - } - } - } - } - } - - fn build_eq_num128( - &mut self, - format: StackMemoryFormat, - locations: [StackMemoryLocation; 2], - arguments: &'a [Symbol], - return_layout: WasmLayout, - ) { - match format { - StackMemoryFormat::Decimal => { - // Both args are finite - let first = [arguments[0]]; - let second = [arguments[1]]; - dispatch_low_level( - &mut self.code_builder, - &mut self.storage, - LowLevel::NumIsFinite, - &first, - &return_layout, - ); - dispatch_low_level( - &mut self.code_builder, - &mut self.storage, - LowLevel::NumIsFinite, - &second, - &return_layout, - ); - self.code_builder.i32_and(); - - // AND they have the same bytes - self.build_eq_num128_bytes(locations); - self.code_builder.i32_and(); - } - - StackMemoryFormat::Int128 => self.build_eq_num128_bytes(locations), - - StackMemoryFormat::Float128 => todo!("equality for f128"), - - StackMemoryFormat::DataStructure => { - internal_error!("Data structure equality is handled elsewhere") - } - } - } - - /// Check that two 128-bit numbers contain the same bytes - fn build_eq_num128_bytes(&mut self, locations: [StackMemoryLocation; 2]) { - let (local0, offset0) = locations[0].local_and_offset(self.storage.stack_frame_pointer); - let (local1, offset1) = locations[1].local_and_offset(self.storage.stack_frame_pointer); - - self.code_builder.get_local(local0); - self.code_builder.i64_load(Align::Bytes8, offset0); - self.code_builder.get_local(local1); - self.code_builder.i64_load(Align::Bytes8, offset1); - self.code_builder.i64_eq(); - - self.code_builder.get_local(local0); - self.code_builder.i64_load(Align::Bytes8, offset0 + 8); - self.code_builder.get_local(local1); - self.code_builder.i64_load(Align::Bytes8, offset1 + 8); - self.code_builder.i64_eq(); - - self.code_builder.i32_and(); - } - - /// Call a helper procedure that implements `==` for a specific data structure - fn build_eq_specialized( - &mut self, - arg_layout: &Layout<'a>, - arguments: &'a [Symbol], - return_sym: Symbol, - storage: &StoredValue, - ) { - let ident_ids = self - .interns - .all_ident_ids - .get_mut(&self.env.module_id) - .unwrap(); - - // Get an IR expression for the call to the specialized procedure - let (specialized_call_expr, new_specializations) = self - .helper_proc_gen - .call_specialized_equals(ident_ids, arg_layout, arguments); - - // If any new specializations were created, register their symbol data - for spec in new_specializations.into_iter() { - self.register_helper_proc(spec); - } - - // Generate Wasm code for the IR call expression - let bool_layout = Layout::Builtin(Builtin::Bool); - self.build_expr( - &return_sym, - self.env.arena.alloc(specialized_call_expr), - &bool_layout, - storage, - ); - } - - fn load_literal( + fn expr_literal( &mut self, lit: &Literal<'a>, storage: &StoredValue, sym: Symbol, layout: &Layout<'a>, ) { - let not_supported_error = || todo!("Literal value {:?}", lit); + let invalid_error = + || internal_error!("Literal value {:?} has invalid storage {:?}", lit, storage); match storage { StoredValue::VirtualMachineStack { value_type, .. } => { @@ -1347,7 +605,7 @@ impl<'a> WasmBackend<'a> { (Literal::Int(x), ValueType::I32) => self.code_builder.i32_const(*x as i32), (Literal::Bool(x), ValueType::I32) => self.code_builder.i32_const(*x as i32), (Literal::Byte(x), ValueType::I32) => self.code_builder.i32_const(*x as i32), - _ => not_supported_error(), + _ => invalid_error(), }; } @@ -1398,7 +656,7 @@ impl<'a> WasmBackend<'a> { self.code_builder.i64_store(Align::Bytes4, offset); } else { let (linker_sym_index, elements_addr) = - self.lookup_string_constant(string, sym, layout); + self.expr_literal_big_str(string, sym, layout); self.code_builder.get_local(local_id); self.code_builder @@ -1410,84 +668,226 @@ impl<'a> WasmBackend<'a> { self.code_builder.i32_store(Align::Bytes4, offset + 4); }; } - _ => not_supported_error(), + _ => invalid_error(), } } - _ => not_supported_error(), + _ => invalid_error(), }; } - /// Look up a string constant in our internal data structures + /// Create a string constant in the module data section /// Return the data we need for code gen: linker symbol index and memory address - fn lookup_string_constant( + fn expr_literal_big_str( &mut self, string: &'a str, sym: Symbol, layout: &Layout<'a>, ) -> (u32, u32) { - match self.constant_sym_index_map.get(string) { - Some(linker_sym_index) => { - // We've seen this string before. The linker metadata has a reference - // to its offset in the constants data segment. - let syminfo = &self.linker_symbols[*linker_sym_index]; - match syminfo { - SymInfo::Data(DataSymbol::Defined { segment_offset, .. }) => { - let elements_addr = *segment_offset + CONST_SEGMENT_BASE_ADDR; - (*linker_sym_index as u32, elements_addr) - } - _ => internal_error!( - "Compiler bug: Invalid linker symbol info for string {:?}:\n{:?}", - string, - syminfo - ), - } + // Place the segment at a 4-byte aligned offset + let segment_addr = round_up_to_alignment!(self.next_constant_addr, PTR_SIZE); + let elements_addr = segment_addr + PTR_SIZE; + let length_with_refcount = 4 + string.len(); + self.next_constant_addr = segment_addr + length_with_refcount as u32; + + let mut segment = DataSegment { + mode: DataMode::active_at(segment_addr), + init: Vec::with_capacity_in(length_with_refcount, self.env.arena), + }; + + // Prefix the string bytes with "infinite" refcount + let refcount_max_bytes: [u8; 4] = (REFCOUNT_MAX as i32).to_le_bytes(); + segment.init.extend_from_slice(&refcount_max_bytes); + segment.init.extend_from_slice(string.as_bytes()); + + let segment_index = self.module.data.append_segment(segment); + + // Generate linker symbol + let name = self + .layout_ids + .get(sym, layout) + .to_symbol_string(sym, self.interns); + + let linker_symbol = SymInfo::Data(DataSymbol::Defined { + flags: 0, + name: name.clone(), + segment_index, + segment_offset: 4, + size: string.len() as u32, + }); + + // Ensure the linker keeps the segment aligned when relocating it + self.module.linking.segment_info.push(LinkingSegment { + name, + alignment: Align::Bytes4, + flags: 0, + }); + + let linker_sym_index = self.module.linking.symbol_table.len(); + self.module.linking.symbol_table.push(linker_symbol); + + (linker_sym_index as u32, elements_addr) + } + + /******************************************************************* + * Call expressions + *******************************************************************/ + + fn expr_call( + &mut self, + call_type: &CallType<'a>, + arguments: &'a [Symbol], + ret_sym: Symbol, + ret_layout: &Layout<'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::LowLevel { op: lowlevel, .. } => { + self.expr_call_low_level(*lowlevel, arguments, ret_sym, ret_layout, ret_storage) } - None => { - let const_segment_bytes = &mut self.module.data.segments[CONST_SEGMENT_INDEX].init; - - // Store the string in the data section - // Prefix it with a special refcount value (treated as "infinity") - // The string's `elements` field points at the data after the refcount - let refcount_max_bytes: [u8; 4] = (REFCOUNT_MAX as i32).to_le_bytes(); - const_segment_bytes.extend_from_slice(&refcount_max_bytes); - let elements_offset = const_segment_bytes.len() as u32; - let elements_addr = elements_offset + CONST_SEGMENT_BASE_ADDR; - const_segment_bytes.extend_from_slice(string.as_bytes()); - - // Generate linker info - // Just pick the symbol name from the first usage - let name = self - .layout_ids - .get(sym, layout) - .to_symbol_string(sym, self.interns); - let linker_symbol = SymInfo::Data(DataSymbol::Defined { - flags: 0, - name, - segment_index: CONST_SEGMENT_INDEX as u32, - segment_offset: elements_offset, - size: string.len() as u32, - }); - - let linker_sym_index = self.linker_symbols.len(); - self.constant_sym_index_map.insert(string, linker_sym_index); - self.linker_symbols.push(linker_symbol); - - (linker_sym_index as u32, elements_addr) - } + x => todo!("call type {:?}", x), } } - fn create_struct(&mut self, sym: &Symbol, layout: &Layout<'a>, fields: &'a [Symbol]) { - // TODO: we just calculated storage and now we're getting it out of a map - // Not passing it as an argument because I'm trying to match Backend method signatures - let storage = self.storage.get(sym).to_owned(); + fn expr_call_by_name( + &mut self, + func_sym: Symbol, + arguments: &'a [Symbol], + ret_sym: Symbol, + ret_layout: &Layout<'a>, + ret_storage: &StoredValue, + ) { + let wasm_layout = WasmLayout::new(ret_layout); + // If this function is just a lowlevel wrapper, then inline it + if let LowLevelWrapperType::CanBeReplacedBy(lowlevel) = + LowLevelWrapperType::from_symbol(func_sym) + { + return self.expr_call_low_level(lowlevel, arguments, ret_sym, ret_layout, ret_storage); + } + + let (param_types, ret_type) = self.storage.load_symbols_for_call( + self.env.arena, + &mut self.code_builder, + arguments, + ret_sym, + &wasm_layout, + CallConv::C, + ); + + for (roc_proc_index, (ir_sym, linker_sym_index)) in self.proc_symbols.iter().enumerate() { + 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( + wasm_fn_index, + *linker_sym_index, + num_wasm_args, + has_return_val, + ); + return; + } + } + + internal_error!( + "Could not find procedure {:?}\nKnown procedures: {:?}", + func_sym, + self.proc_symbols + ); + } + + fn expr_call_low_level( + &mut self, + lowlevel: LowLevel, + arguments: &'a [Symbol], + ret_symbol: Symbol, + ret_layout: &Layout<'a>, + ret_storage: &StoredValue, + ) { + let low_level_call = LowLevelCall { + lowlevel, + arguments, + ret_symbol, + ret_layout: ret_layout.to_owned(), + ret_storage: ret_storage.to_owned(), + }; + low_level_call.generate(self); + } + + /// Generate a call instruction to a Zig builtin function. + /// And if we haven't seen it before, add an Import and linker data for it. + /// Zig calls use LLVM's "fast" calling convention rather than our usual C ABI. + pub fn call_zig_builtin_after_loading_args( + &mut self, + name: &'a str, + num_wasm_args: usize, + has_return_val: bool, + ) { + let fn_index = self.module.names.functions[name.as_bytes()]; + self.called_preload_fns.push(fn_index); + let linker_symbol_index = u32::MAX; + + self.code_builder + .call(fn_index, linker_symbol_index, num_wasm_args, has_return_val); + } + + /// Call a helper procedure that implements `==` for a data structure (not numbers or Str) + /// If this is the first call for this Layout, it will generate the IR for the procedure. + /// Call stack is expr_call_low_level -> LowLevelCall::generate -> call_eq_specialized + /// It's a bit circuitous, but the alternative is to give low_level.rs `pub` access to + /// interns, helper_proc_gen, and expr(). That just seemed all wrong. + pub fn call_eq_specialized( + &mut self, + arguments: &'a [Symbol], + arg_layout: &Layout<'a>, + ret_symbol: Symbol, + ret_storage: &StoredValue, + ) { + let ident_ids = self + .interns + .all_ident_ids + .get_mut(&self.env.module_id) + .unwrap(); + + // Get an IR expression for the call to the specialized procedure + let (specialized_call_expr, new_specializations) = self + .helper_proc_gen + .call_specialized_equals(ident_ids, arg_layout, arguments); + + // If any new specializations were created, register their symbol data + for spec in new_specializations.into_iter() { + self.register_helper_proc(spec); + } + + // Generate Wasm code for the IR call expression + self.expr( + ret_symbol, + self.env.arena.alloc(specialized_call_expr), + &Layout::Builtin(Builtin::Bool), + ret_storage, + ); + } + + /******************************************************************* + * Structs + *******************************************************************/ + + fn expr_struct( + &mut self, + sym: Symbol, + layout: &Layout<'a>, + storage: &StoredValue, + fields: &'a [Symbol], + ) { if matches!(layout, Layout::Struct(_)) { match storage { StoredValue::StackMemory { location, size, .. } => { - if size > 0 { + if *size > 0 { let (local_id, struct_offset) = location.local_and_offset(self.storage.stack_frame_pointer); let mut field_offset = struct_offset; @@ -1510,77 +910,415 @@ impl<'a> WasmBackend<'a> { // Struct expression but not Struct layout => single element. Copy it. let field_storage = self.storage.get(&fields[0]).to_owned(); self.storage - .clone_value(&mut self.code_builder, &storage, &field_storage, fields[0]); + .clone_value(&mut self.code_builder, storage, &field_storage, fields[0]); } } - /// Generate a call instruction to a Zig builtin function. - /// And if we haven't seen it before, add an Import and linker data for it. - /// Zig calls use LLVM's "fast" calling convention rather than our usual C ABI. - fn call_zig_builtin( + fn expr_struct_at_index( &mut self, - name: &'a str, - param_types: Vec<'a, ValueType>, - ret_type: Option, + sym: Symbol, + storage: &StoredValue, + index: u64, + field_layouts: &'a [Layout<'a>], + structure: Symbol, ) { - let num_wasm_args = param_types.len(); - let has_return_val = ret_type.is_some(); + self.storage + .ensure_value_has_local(&mut self.code_builder, sym, storage.to_owned()); + let (local_id, mut offset) = match self.storage.get(&structure) { + StoredValue::StackMemory { location, .. } => { + location.local_and_offset(self.storage.stack_frame_pointer) + } - let (fn_index, linker_symbol_index) = match self.builtin_sym_index_map.get(name) { - Some(sym_idx) => match &self.linker_symbols[*sym_idx] { - SymInfo::Function(WasmObjectSymbol::Imported { index, .. }) => { - (*index, *sym_idx as u32) - } - x => internal_error!("Invalid linker symbol for builtin {}: {:?}", name, x), - }, + StoredValue::Local { + value_type, + local_id, + .. + } => { + debug_assert!(matches!(value_type, ValueType::I32)); + (*local_id, 0) + } - None => { - // Wasm function signature - let signature = Signature { - param_types, - ret_type, + StoredValue::VirtualMachineStack { .. } => { + internal_error!("ensure_value_has_local didn't work") + } + }; + for field in field_layouts.iter().take(index as usize) { + offset += field.stack_size(TARGET_INFO); + } + self.storage + .copy_value_from_memory(&mut self.code_builder, sym, local_id, offset); + } + + /******************************************************************* + * Heap allocation + *******************************************************************/ + + /// Allocate heap space and write an initial refcount + /// If the data size is known at compile time, pass it in comptime_data_size. + /// If size is only known at runtime, push *data* size to the VM stack first. + /// Leaves the *data* address on the VM stack + fn allocate_with_refcount( + &mut self, + comptime_data_size: Option, + alignment_bytes: u32, + initial_refcount: u32, + ) { + // Add extra bytes for the refcount + let extra_bytes = alignment_bytes.max(PTR_SIZE); + + if let Some(data_size) = comptime_data_size { + // Data size known at compile time and passed as an argument + self.code_builder + .i32_const((data_size + extra_bytes) as i32); + } else { + // Data size known only at runtime and is on top of VM stack + self.code_builder.i32_const(extra_bytes as i32); + self.code_builder.i32_add(); + } + + // Provide a constant for the alignment argument + self.code_builder.i32_const(alignment_bytes as i32); + + // Call the foreign function. (Zig and C calling conventions are the same for this signature) + self.call_zig_builtin_after_loading_args("roc_alloc", 2, true); + + // Save the allocation address to a temporary local variable + let local_id = self.storage.create_anonymous_local(ValueType::I32); + self.code_builder.tee_local(local_id); + + // Write the initial refcount + let refcount_offset = extra_bytes - PTR_SIZE; + let encoded_refcount = (initial_refcount as i32) - 1 + i32::MIN; + self.code_builder.i32_const(encoded_refcount); + self.code_builder.i32_store(Align::Bytes4, refcount_offset); + + // Put the data address on the VM stack + self.code_builder.get_local(local_id); + self.code_builder.i32_const(extra_bytes as i32); + self.code_builder.i32_add(); + } + + /******************************************************************* + * Arrays + *******************************************************************/ + + fn expr_array( + &mut self, + sym: Symbol, + storage: &StoredValue, + elem_layout: &Layout<'a>, + elems: &'a [ListLiteralElement<'a>], + ) { + if let StoredValue::StackMemory { location, .. } = storage { + let size = elem_layout.stack_size(TARGET_INFO) * (elems.len() as u32); + + // Allocate heap space and store its address in a local variable + let heap_local_id = self.storage.create_anonymous_local(PTR_TYPE); + let heap_alignment = elem_layout.alignment_bytes(TARGET_INFO); + self.allocate_with_refcount(Some(size), heap_alignment, 1); + self.code_builder.set_local(heap_local_id); + + let (stack_local_id, stack_offset) = + location.local_and_offset(self.storage.stack_frame_pointer); + + // elements pointer + self.code_builder.get_local(stack_local_id); + self.code_builder.get_local(heap_local_id); + self.code_builder.i32_store(Align::Bytes4, stack_offset); + + // length of the list + self.code_builder.get_local(stack_local_id); + self.code_builder.i32_const(elems.len() as i32); + self.code_builder.i32_store(Align::Bytes4, stack_offset + 4); + + let mut elem_offset = 0; + + for (i, elem) in elems.iter().enumerate() { + let elem_sym = match elem { + ListLiteralElement::Literal(lit) => { + // This has no Symbol but our storage methods expect one. + // Let's just pretend it was defined in a `Let`. + let debug_name = format!("{:?}_{}", sym, i); + let elem_sym = self.create_symbol(&debug_name); + let expr = Expr::Literal(*lit); + + self.stmt_let_store_expr( + elem_sym, + elem_layout, + &expr, + StoredValueKind::Variable, + ); + + elem_sym + } + + ListLiteralElement::Symbol(elem_sym) => *elem_sym, }; - let signature_index = self.module.types.insert(signature); - // Declare it as an import since it comes from a different .o file - let import_index = self.module.import.entries.len() as u32; - let import = Import { - module: BUILTINS_IMPORT_MODULE_NAME, - name: name.to_string(), - description: ImportDesc::Func { signature_index }, - }; - self.module.import.entries.push(import); + elem_offset += self.storage.copy_value_to_memory( + &mut self.code_builder, + heap_local_id, + elem_offset, + elem_sym, + ); + } + } else { + internal_error!("Unexpected storage for Array {:?}: {:?}", sym, storage) + } + } - // Provide symbol information for the linker - let sym_idx = self.linker_symbols.len(); - let sym_info = SymInfo::Function(WasmObjectSymbol::Imported { - flags: WASM_SYM_UNDEFINED, - index: import_index, - }); - self.linker_symbols.push(sym_info); + fn expr_empty_array(&mut self, sym: Symbol, storage: &StoredValue) { + if let StoredValue::StackMemory { location, .. } = storage { + let (local_id, offset) = location.local_and_offset(self.storage.stack_frame_pointer); - // Remember that we have created all of this data, and don't need to do it again - self.builtin_sym_index_map.insert(name, sym_idx); + // This is a minor cheat. + // What we want to write to stack memory is { elements: null, length: 0 } + // But instead of two 32-bit stores, we can do a single 64-bit store. + self.code_builder.get_local(local_id); + self.code_builder.i64_const(0); + self.code_builder.i64_store(Align::Bytes4, offset); + } else { + internal_error!("Unexpected storage for {:?}", sym) + } + } - (import_index, sym_idx as u32) + /******************************************************************* + * Tag Unions + *******************************************************************/ + + fn expr_tag( + &mut self, + union_layout: &UnionLayout<'a>, + tag_id: TagIdIntType, + arguments: &'a [Symbol], + symbol: Symbol, + stored: &StoredValue, + ) { + if union_layout.tag_is_null(tag_id) { + self.code_builder.i32_const(0); + return; + } + + let stores_tag_id_as_data = union_layout.stores_tag_id_as_data(TARGET_INFO); + let stores_tag_id_in_pointer = union_layout.stores_tag_id_in_pointer(TARGET_INFO); + let (data_size, data_alignment) = union_layout.data_size_and_alignment(TARGET_INFO); + + // We're going to use the pointer many times, so put it in a local variable + let stored_with_local = + self.storage + .ensure_value_has_local(&mut self.code_builder, symbol, stored.to_owned()); + + let (local_id, data_offset) = match stored_with_local { + StoredValue::StackMemory { location, .. } => { + location.local_and_offset(self.storage.stack_frame_pointer) + } + StoredValue::Local { local_id, .. } => { + // Tag is stored as a pointer to the heap. Call the allocator to get a memory address. + self.allocate_with_refcount(Some(data_size), data_alignment, 1); + self.code_builder.set_local(local_id); + (local_id, 0) + } + StoredValue::VirtualMachineStack { .. } => { + internal_error!("{:?} should have a local variable", symbol) } }; - self.code_builder - .call(fn_index, linker_symbol_index, num_wasm_args, has_return_val); - } + // Write the field values to memory + let mut field_offset = data_offset; + for field_symbol in arguments.iter() { + field_offset += self.storage.copy_value_to_memory( + &mut self.code_builder, + local_id, + field_offset, + *field_symbol, + ); + } - /// Debug utility - /// - /// if self._debug_current_proc_is("#UserApp_foo_1") { - /// self.code_builder._debug_assert_i32(0x1234); - /// } - fn _debug_current_proc_is(&self, linker_name: &'static str) -> bool { - let (_, linker_sym_index) = self.proc_symbols[self.debug_current_proc_index]; - let sym_info = &self.linker_symbols[linker_sym_index as usize]; - match sym_info { - SymInfo::Function(WasmObjectSymbol::Defined { name, .. }) => name == linker_name, - _ => false, + // Store the tag ID (if any) + if stores_tag_id_as_data { + let id_offset = data_offset + data_size - data_alignment; + + let id_align = union_layout.tag_id_builtin().alignment_bytes(TARGET_INFO); + let id_align = Align::from(id_align); + + self.code_builder.get_local(local_id); + + match id_align { + Align::Bytes1 => { + self.code_builder.i32_const(tag_id as i32); + self.code_builder.i32_store8(id_align, id_offset); + } + Align::Bytes2 => { + self.code_builder.i32_const(tag_id as i32); + self.code_builder.i32_store16(id_align, id_offset); + } + Align::Bytes4 => { + self.code_builder.i32_const(tag_id as i32); + self.code_builder.i32_store(id_align, id_offset); + } + Align::Bytes8 => { + self.code_builder.i64_const(tag_id as i64); + self.code_builder.i64_store(id_align, id_offset); + } + } + } else if stores_tag_id_in_pointer { + self.code_builder.get_local(local_id); + self.code_builder.i32_const(tag_id as i32); + self.code_builder.i32_or(); + self.code_builder.set_local(local_id); } } + + fn expr_get_tag_id( + &mut self, + structure: Symbol, + union_layout: &UnionLayout<'a>, + tag_id_symbol: Symbol, + stored_value: &StoredValue, + ) { + use UnionLayout::*; + + let block_result_id = match union_layout { + NonRecursive(_) => None, + Recursive(_) => None, + NonNullableUnwrapped(_) => { + self.code_builder.i32_const(0); + return; + } + NullableWrapped { nullable_id, .. } => { + let stored_with_local = self.storage.ensure_value_has_local( + &mut self.code_builder, + tag_id_symbol, + stored_value.to_owned(), + ); + let local_id = match stored_with_local { + StoredValue::Local { local_id, .. } => local_id, + _ => internal_error!("ensure_value_has_local didn't work"), + }; + + // load pointer + self.storage + .load_symbols(&mut self.code_builder, &[structure]); + + // null check + self.code_builder.i32_eqz(); + self.code_builder.if_(); + self.code_builder.i32_const(*nullable_id as i32); + self.code_builder.set_local(local_id); + self.code_builder.else_(); + Some(local_id) + } + NullableUnwrapped { nullable_id, .. } => { + self.code_builder.i32_const(!(*nullable_id) as i32); + self.code_builder.i32_const(*nullable_id as i32); + self.storage + .load_symbols(&mut self.code_builder, &[structure]); + self.code_builder.select(); + None + } + }; + + if union_layout.stores_tag_id_as_data(TARGET_INFO) { + let (data_size, data_alignment) = union_layout.data_size_and_alignment(TARGET_INFO); + let id_offset = data_size - data_alignment; + + let id_align = union_layout.tag_id_builtin().alignment_bytes(TARGET_INFO); + let id_align = Align::from(id_align); + + self.storage + .load_symbols(&mut self.code_builder, &[structure]); + + match union_layout.tag_id_builtin() { + Builtin::Bool | Builtin::Int(IntWidth::U8) => { + self.code_builder.i32_load8_u(id_align, id_offset) + } + Builtin::Int(IntWidth::U16) => self.code_builder.i32_load16_u(id_align, id_offset), + Builtin::Int(IntWidth::U32) => self.code_builder.i32_load(id_align, id_offset), + Builtin::Int(IntWidth::U64) => self.code_builder.i64_load(id_align, id_offset), + x => internal_error!("Unexpected layout for tag union id {:?}", x), + } + } else if union_layout.stores_tag_id_in_pointer(TARGET_INFO) { + self.storage + .load_symbols(&mut self.code_builder, &[structure]); + self.code_builder.i32_const(3); + self.code_builder.i32_and(); + } + + if let Some(local_id) = block_result_id { + self.code_builder.set_local(local_id); + self.code_builder.end(); + } + } + + fn expr_union_at_index( + &mut self, + structure: Symbol, + tag_id: TagIdIntType, + union_layout: &UnionLayout<'a>, + index: u64, + symbol: Symbol, + ) { + use UnionLayout::*; + + debug_assert!(!union_layout.tag_is_null(tag_id)); + + let tag_index = tag_id as usize; + let field_layouts = match union_layout { + NonRecursive(tags) => tags[tag_index], + Recursive(tags) => tags[tag_index], + NonNullableUnwrapped(layouts) => *layouts, + NullableWrapped { + other_tags, + nullable_id, + } => { + let index = if tag_index > *nullable_id as usize { + tag_index - 1 + } else { + tag_index + }; + other_tags[index] + } + NullableUnwrapped { other_fields, .. } => *other_fields, + }; + + let field_offset: u32 = field_layouts + .iter() + .take(index as usize) + .map(|field_layout| field_layout.stack_size(TARGET_INFO)) + .sum(); + + // Get pointer and offset to the tag's data + let structure_storage = self.storage.get(&structure).to_owned(); + let stored_with_local = self.storage.ensure_value_has_local( + &mut self.code_builder, + structure, + structure_storage, + ); + let (tag_local_id, tag_offset) = match stored_with_local { + StoredValue::StackMemory { location, .. } => { + location.local_and_offset(self.storage.stack_frame_pointer) + } + StoredValue::Local { local_id, .. } => (local_id, 0), + StoredValue::VirtualMachineStack { .. } => { + internal_error!("{:?} should have a local variable", structure) + } + }; + + let stores_tag_id_in_pointer = union_layout.stores_tag_id_in_pointer(TARGET_INFO); + + let from_ptr = if stores_tag_id_in_pointer { + let ptr = self.storage.create_anonymous_local(ValueType::I32); + self.code_builder.get_local(tag_local_id); + self.code_builder.i32_const(-4); // 11111111...1100 + self.code_builder.i32_and(); + self.code_builder.set_local(ptr); + ptr + } else { + tag_local_id + }; + + let from_offset = tag_offset + field_offset; + self.storage + .copy_value_from_memory(&mut self.code_builder, symbol, from_ptr, from_offset); + } } diff --git a/compiler/gen_wasm/src/layout.rs b/compiler/gen_wasm/src/layout.rs index 330de5261b..b7ad18b2ef 100644 --- a/compiler/gen_wasm/src/layout.rs +++ b/compiler/gen_wasm/src/layout.rs @@ -2,7 +2,7 @@ use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_mono::layout::{Layout, UnionLayout}; use crate::wasm_module::ValueType; -use crate::{PTR_SIZE, PTR_TYPE}; +use crate::{PTR_SIZE, PTR_TYPE, TARGET_INFO}; /// Manually keep up to date with the Zig version we are using for builtins pub const BUILTINS_ZIG_VERSION: ZigVersion = ZigVersion::Zig8; @@ -47,8 +47,8 @@ impl WasmLayout { use UnionLayout::*; use ValueType::*; - let size = layout.stack_size(PTR_SIZE); - let alignment_bytes = layout.alignment_bytes(PTR_SIZE); + let size = layout.stack_size(TARGET_INFO); + let alignment_bytes = layout.alignment_bytes(TARGET_INFO); match layout { Layout::Builtin(Int(int_width)) => { diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index ad2e9f051c..7030419e49 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -4,23 +4,35 @@ 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_builtins::bitcode::IntWidth; use roc_collections::all::{MutMap, MutSet}; -use roc_module::low_level::LowLevel; +use roc_module::low_level::LowLevelWrapperType; use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_mono::code_gen_help::CodeGenHelp; use roc_mono::ir::{Proc, ProcLayout}; use roc_mono::layout::LayoutIds; -use roc_reporting::internal_error; +use roc_target::TargetInfo; use crate::backend::WasmBackend; use crate::wasm_module::{ Align, CodeBuilder, Export, ExportType, LocalId, SymInfo, ValueType, WasmModule, }; -const PTR_SIZE: u32 = 4; +const TARGET_INFO: TargetInfo = TargetInfo::default_wasm32(); +const PTR_SIZE: u32 = { + let value = TARGET_INFO.ptr_width() as u32; + + // const assert that our pointer width is actually 4 + // the code relies on the pointer width being exactly 4 + assert!(value == 4); + + value +}; const PTR_TYPE: ValueType = ValueType::I32; pub const STACK_POINTER_GLOBAL_ID: u32 = 0; @@ -35,34 +47,45 @@ pub struct Env<'a> { pub exposed_to_host: MutSet, } +/// 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, String> { - let (mut wasm_module, _) = build_module_help(env, interns, procedures)?; - let mut buffer = std::vec::Vec::with_capacity(4096); - wasm_module.serialize_mut(&mut buffer); - Ok(buffer) +) -> std::vec::Vec { + let (mut wasm_module, called_preload_fns, _) = + 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); + buffer } -pub fn build_module_help<'a>( +/// Entry point for REPL (repl_wasm) and integration tests (test_gen) +pub fn build_module_without_wrapper<'a>( env: &'a Env<'a>, interns: &'a mut Interns, + preload_bytes: &[u8], procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, -) -> Result<(WasmModule<'a>, u32), String> { +) -> (WasmModule<'a>, Vec<'a, u32>, u32) { let mut layout_ids = LayoutIds::default(); let mut procs = Vec::with_capacity_in(procedures.len(), env.arena); let mut proc_symbols = Vec::with_capacity_in(procedures.len() * 2, env.arena); let mut linker_symbols = Vec::with_capacity_in(procedures.len() * 2, env.arena); let mut exports = Vec::with_capacity_in(4, env.arena); - let mut main_fn_index = None; + let mut maybe_main_fn_index = None; // Collect the symbols & names for the procedures, // and filter out procs we're going to inline let mut fn_index: u32 = 0; for ((sym, layout), proc) in procedures.into_iter() { - if LowLevel::from_inlined_wrapper(sym).is_some() { + if matches!( + LowLevelWrapperType::from_symbol(sym), + LowLevelWrapperType::CanBeReplacedBy(_) + ) { continue; } procs.push(proc); @@ -72,9 +95,9 @@ pub fn build_module_help<'a>( .to_symbol_string(sym, interns); if env.exposed_to_host.contains(&sym) { - main_fn_index = Some(fn_index); + maybe_main_fn_index = Some(fn_index); exports.push(Export { - name: fn_name.clone(), + name: env.arena.alloc_slice_copy(fn_name.as_bytes()), ty: ExportType::Func, index: fn_index, }); @@ -87,14 +110,21 @@ pub fn build_module_help<'a>( fn_index += 1; } + // Pre-load the WasmModule with data from the platform & builtins object file + let initial_module = WasmModule::preload(env.arena, preload_bytes); + + // Adjust Wasm function indices to account for functions from the object file + let fn_index_offset: u32 = + initial_module.import.function_count + initial_module.code.preloaded_count; + let mut backend = WasmBackend::new( env, interns, layout_ids, proc_symbols, - linker_symbols, - exports, - CodeGenHelp::new(env.arena, IntWidth::I32, env.module_id), + initial_module, + fn_index_offset, + CodeGenHelp::new(env.arena, TargetInfo::default_wasm32(), env.module_id), ); if DEBUG_LOG_SETTINGS.user_procs_ir { @@ -128,9 +158,10 @@ pub fn build_module_help<'a>( backend.build_proc(proc); } - let module = backend.finalize_module(); + let (module, called_preload_fns) = backend.finalize(); + let main_function_index = maybe_main_fn_index.unwrap() + fn_index_offset; - Ok((module, main_fn_index.unwrap())) + (module, called_preload_fns, main_function_index) } pub struct CopyMemoryConfig { @@ -195,10 +226,6 @@ macro_rules! round_up_to_alignment { }; } -pub fn debug_panic(error: E) { - internal_error!("{:?}", error); -} - pub struct WasmDebugLogSettings { proc_start_end: bool, user_procs_ir: bool, diff --git a/compiler/gen_wasm/src/low_level.rs b/compiler/gen_wasm/src/low_level.rs index cc5cff66f6..eae0a0cfb1 100644 --- a/compiler/gen_wasm/src/low_level.rs +++ b/compiler/gen_wasm/src/low_level.rs @@ -1,565 +1,908 @@ -use roc_builtins::bitcode::{self, FloatWidth}; +use bumpalo::collections::Vec; +use roc_builtins::bitcode::{self, FloatWidth, IntWidth}; +use roc_error_macros::internal_error; use roc_module::low_level::{LowLevel, LowLevel::*}; use roc_module::symbol::Symbol; -use roc_reporting::internal_error; +use roc_mono::layout::{Builtin, Layout, UnionLayout}; -use crate::layout::{StackMemoryFormat::*, WasmLayout}; -use crate::storage::{Storage, StoredValue}; -use crate::wasm_module::{Align, CodeBuilder, ValueType::*}; +use crate::backend::WasmBackend; +use crate::layout::CallConv; +use crate::layout::{StackMemoryFormat, WasmLayout}; +use crate::storage::{StackMemoryLocation, StoredValue}; +use crate::wasm_module::{Align, ValueType}; -#[derive(Debug)] -pub enum LowlevelBuildResult { - Done, - BuiltinCall(&'static str), - NotImplemented, +/// Number types used for Wasm code gen +/// Unlike other enums, this contains no details about layout or storage. +/// Its purpose is to help simplify the arms of the main lowlevel `match` below. +/// +/// Note: Wasm I32 is used for Roc I8, I16, I32, U8, U16, and U32, since it's +/// the smallest integer supported in the Wasm instruction set. +/// We may choose different instructions for signed and unsigned integers, +/// but they share the same Wasm value type. +#[derive(Clone, Copy, Debug)] +enum CodeGenNumType { + I32, // Supported in Wasm instruction set + I64, // Supported in Wasm instruction set + F32, // Supported in Wasm instruction set + F64, // Supported in Wasm instruction set + I128, // Bytes in memory, needs Zig builtins + F128, // Bytes in memory, needs Zig builtins + Decimal, // Bytes in memory, needs Zig builtins } -pub fn dispatch_low_level<'a>( - code_builder: &mut CodeBuilder<'a>, - storage: &mut Storage<'a>, - lowlevel: LowLevel, - args: &[Symbol], - ret_layout: &WasmLayout, -) -> LowlevelBuildResult { - use LowlevelBuildResult::*; +impl CodeGenNumType { + pub fn for_symbol(backend: &WasmBackend<'_>, symbol: Symbol) -> Self { + Self::from(backend.storage.get(&symbol)) + } +} - let panic_ret_type = - || internal_error!("Invalid return layout for {:?}: {:?}", lowlevel, ret_layout); +impl From> for CodeGenNumType { + fn from(layout: Layout) -> CodeGenNumType { + use CodeGenNumType::*; - match lowlevel { - // Str - StrConcat => return BuiltinCall(bitcode::STR_CONCAT), - StrJoinWith => return BuiltinCall(bitcode::STR_JOIN_WITH), - StrIsEmpty => { - code_builder.i64_const(i64::MIN); - code_builder.i64_eq(); - } - StrStartsWith => return BuiltinCall(bitcode::STR_STARTS_WITH), - StrStartsWithCodePt => return BuiltinCall(bitcode::STR_STARTS_WITH_CODE_PT), - StrEndsWith => return BuiltinCall(bitcode::STR_ENDS_WITH), - StrSplit => { - // Roughly we need to: - // 1. count segments - // 2. make a new pointer - // 3. split that pointer in place - // see: build_str.rs line 31 - return NotImplemented; - } - StrCountGraphemes => return BuiltinCall(bitcode::STR_COUNT_GRAPEHEME_CLUSTERS), - StrToNum => return NotImplemented, // choose builtin based on storage size - StrFromInt => { - // This does not get exposed in user space. We switched to NumToStr instead. - // We can probably just leave this as NotImplemented. We may want remove this LowLevel. - // see: https://github.com/rtfeldman/roc/pull/2108 - return NotImplemented; - } - StrFromFloat => { - // linker errors for __ashlti3, __fixunsdfti, __multi3, __udivti3, __umodti3 - // https://gcc.gnu.org/onlinedocs/gccint/Integer-library-routines.html - // https://gcc.gnu.org/onlinedocs/gccint/Soft-float-library-routines.html - return NotImplemented; - } - StrFromUtf8 => return BuiltinCall(bitcode::STR_FROM_UTF8), - StrTrimLeft => return BuiltinCall(bitcode::STR_TRIM_LEFT), - StrTrimRight => return BuiltinCall(bitcode::STR_TRIM_RIGHT), - StrFromUtf8Range => return BuiltinCall(bitcode::STR_FROM_UTF8_RANGE), // refcounting errors - StrToUtf8 => return BuiltinCall(bitcode::STR_TO_UTF8), // refcounting errors - StrRepeat => return BuiltinCall(bitcode::STR_REPEAT), - StrTrim => return BuiltinCall(bitcode::STR_TRIM), - - // List - ListLen => { - if let StoredValue::StackMemory { location, .. } = storage.get(&args[0]) { - let (local_id, offset) = location.local_and_offset(storage.stack_frame_pointer); - - code_builder.get_local(local_id); - code_builder.i32_load(Align::Bytes4, offset + 4); - } else { - internal_error!("Unexpected storage for {:?}", args[0]); - }; - } - - ListGetUnsafe | ListSet | ListSingle | ListRepeat | ListReverse | ListConcat - | ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListMap | ListMap2 - | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk | ListWalkUntil - | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith | ListSublist - | ListDropAt | ListSwap | ListAny | ListAll | ListFindUnsafe | DictSize | DictEmpty - | DictInsert | DictRemove | DictContains | DictGetUnsafe | DictKeys | DictValues - | DictUnion | DictIntersection | DictDifference | DictWalk | SetFromList => { - return NotImplemented; - } - - // Num - NumAdd => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => code_builder.i32_add(), - I64 => code_builder.i64_add(), - F32 => code_builder.f32_add(), - F64 => code_builder.f64_add(), - }, - WasmLayout::StackMemory { format, .. } => match format { - DataStructure => return NotImplemented, - Int128 => return NotImplemented, - Float128 => return NotImplemented, - Decimal => return BuiltinCall(bitcode::DEC_ADD_WITH_OVERFLOW), - }, - }, - NumAddWrap => match ret_layout { - WasmLayout::Primitive(value_type, size) => match value_type { - I32 => { - code_builder.i32_add(); - wrap_i32(code_builder, *size); - } - I64 => code_builder.i64_add(), - F32 => code_builder.f32_add(), - F64 => code_builder.f64_add(), - }, - WasmLayout::StackMemory { format, .. } => match format { - DataStructure => return NotImplemented, - Int128 => return NotImplemented, - Float128 => return NotImplemented, - Decimal => return BuiltinCall(bitcode::DEC_ADD_WITH_OVERFLOW), - }, - }, - NumToStr => return NotImplemented, - NumAddChecked => return NotImplemented, - NumSub => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => code_builder.i32_sub(), - I64 => code_builder.i64_sub(), - F32 => code_builder.f32_sub(), - F64 => code_builder.f64_sub(), - }, - WasmLayout::StackMemory { format, .. } => match format { - DataStructure => return NotImplemented, - Int128 => return NotImplemented, - Float128 => return NotImplemented, - Decimal => return BuiltinCall(bitcode::DEC_SUB_WITH_OVERFLOW), - }, - }, - NumSubWrap => match ret_layout { - WasmLayout::Primitive(value_type, size) => match value_type { - I32 => { - code_builder.i32_sub(); - wrap_i32(code_builder, *size); - } - I64 => code_builder.i64_sub(), - F32 => code_builder.f32_sub(), - F64 => code_builder.f64_sub(), - }, - WasmLayout::StackMemory { format, .. } => match format { - DataStructure => return NotImplemented, - Int128 => return NotImplemented, - Float128 => return NotImplemented, - Decimal => return BuiltinCall(bitcode::DEC_SUB_WITH_OVERFLOW), - }, - }, - NumSubChecked => return NotImplemented, - NumMul => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => code_builder.i32_mul(), - I64 => code_builder.i64_mul(), - F32 => code_builder.f32_mul(), - F64 => code_builder.f64_mul(), - }, - WasmLayout::StackMemory { format, .. } => match format { - DataStructure => return NotImplemented, - Int128 => return NotImplemented, - Float128 => return NotImplemented, - Decimal => return BuiltinCall(bitcode::DEC_MUL_WITH_OVERFLOW), - }, - }, - NumMulWrap => match ret_layout { - WasmLayout::Primitive(value_type, size) => match value_type { - I32 => { - code_builder.i32_mul(); - wrap_i32(code_builder, *size); - } - I64 => code_builder.i64_mul(), - F32 => code_builder.f32_mul(), - F64 => code_builder.f64_mul(), - }, - WasmLayout::StackMemory { format, .. } => match format { - DataStructure => return NotImplemented, - Int128 => return NotImplemented, - Float128 => return NotImplemented, - Decimal => return BuiltinCall(bitcode::DEC_MUL_WITH_OVERFLOW), - }, - }, - NumMulChecked => return NotImplemented, - NumGt => match storage.get(&args[0]) { - StoredValue::VirtualMachineStack { value_type, .. } - | StoredValue::Local { value_type, .. } => match value_type { - I32 => code_builder.i32_gt_s(), - I64 => code_builder.i64_gt_s(), - F32 => code_builder.f32_gt(), - F64 => code_builder.f64_gt(), - }, - StoredValue::StackMemory { .. } => return NotImplemented, - }, - NumGte => match storage.get(&args[0]) { - StoredValue::VirtualMachineStack { value_type, .. } - | StoredValue::Local { value_type, .. } => match value_type { - I32 => code_builder.i32_ge_s(), - I64 => code_builder.i64_ge_s(), - F32 => code_builder.f32_ge(), - F64 => code_builder.f64_ge(), - }, - StoredValue::StackMemory { .. } => return NotImplemented, - }, - NumLt => match storage.get(&args[0]) { - StoredValue::VirtualMachineStack { value_type, .. } - | StoredValue::Local { value_type, .. } => match value_type { - I32 => code_builder.i32_lt_s(), - I64 => code_builder.i64_lt_s(), - F32 => code_builder.f32_lt(), - F64 => code_builder.f64_lt(), - }, - StoredValue::StackMemory { .. } => return NotImplemented, - }, - NumLte => match storage.get(&args[0]) { - StoredValue::VirtualMachineStack { value_type, .. } - | StoredValue::Local { value_type, .. } => match value_type { - I32 => code_builder.i32_le_s(), - I64 => code_builder.i64_le_s(), - F32 => code_builder.f32_le(), - F64 => code_builder.f64_le(), - }, - StoredValue::StackMemory { .. } => return NotImplemented, - }, - NumCompare => return NotImplemented, - NumDivUnchecked => match storage.get(&args[0]) { - StoredValue::VirtualMachineStack { value_type, .. } - | StoredValue::Local { value_type, .. } => match value_type { - I32 => code_builder.i32_div_s(), - I64 => code_builder.i64_div_s(), - F32 => code_builder.f32_div(), - F64 => code_builder.f64_div(), - }, - StoredValue::StackMemory { format, .. } => match format { - DataStructure => return NotImplemented, - Int128 => return NotImplemented, - Float128 => return NotImplemented, - Decimal => return BuiltinCall(bitcode::DEC_DIV), - }, - }, - NumDivCeilUnchecked => return NotImplemented, - NumRemUnchecked => match storage.get(&args[0]) { - StoredValue::VirtualMachineStack { value_type, .. } - | StoredValue::Local { value_type, .. } => match value_type { - I32 => code_builder.i32_rem_s(), - I64 => code_builder.i64_rem_s(), - F32 => return NotImplemented, - F64 => return NotImplemented, - }, - StoredValue::StackMemory { .. } => return NotImplemented, - }, - NumIsMultipleOf => return NotImplemented, - NumAbs => match storage.get(&args[0]) { - StoredValue::VirtualMachineStack { value_type, .. } - | StoredValue::Local { value_type, .. } => match value_type { - I32 => { - let arg_storage = storage.get(&args[0]).to_owned(); - storage.ensure_value_has_local(code_builder, args[0], arg_storage); - storage.load_symbols(code_builder, args); - code_builder.i32_const(0); - storage.load_symbols(code_builder, args); - code_builder.i32_sub(); - storage.load_symbols(code_builder, args); - code_builder.i32_const(0); - code_builder.i32_ge_s(); - code_builder.select(); - } - I64 => { - let arg_storage = storage.get(&args[0]).to_owned(); - storage.ensure_value_has_local(code_builder, args[0], arg_storage); - storage.load_symbols(code_builder, args); - code_builder.i64_const(0); - storage.load_symbols(code_builder, args); - code_builder.i64_sub(); - storage.load_symbols(code_builder, args); - code_builder.i64_const(0); - code_builder.i64_ge_s(); - code_builder.select(); - } - F32 => code_builder.f32_abs(), - F64 => code_builder.f64_abs(), - }, - StoredValue::StackMemory { .. } => return NotImplemented, - }, - NumNeg => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => { - code_builder.i32_const(0); - storage.load_symbols(code_builder, args); - code_builder.i32_sub(); - } - I64 => { - code_builder.i64_const(0); - storage.load_symbols(code_builder, args); - code_builder.i64_sub(); - } - F32 => code_builder.f32_neg(), - F64 => code_builder.f64_neg(), - }, - WasmLayout::StackMemory { .. } => return NotImplemented, - }, - NumSin => return NotImplemented, - NumCos => return NotImplemented, - NumSqrtUnchecked => return NotImplemented, - NumLogUnchecked => return NotImplemented, - NumRound => match storage.get(&args[0]) { - StoredValue::VirtualMachineStack { value_type, .. } - | StoredValue::Local { value_type, .. } => match value_type { - F32 => return BuiltinCall(&bitcode::NUM_ROUND[FloatWidth::F32]), - F64 => return BuiltinCall(&bitcode::NUM_ROUND[FloatWidth::F64]), - _ => return NotImplemented, - }, - StoredValue::StackMemory { .. } => return NotImplemented, - }, - NumToFloat => { - use StoredValue::*; - let stored = storage.get(&args[0]); - match ret_layout { - WasmLayout::Primitive(ret_type, _) => match stored { - VirtualMachineStack { value_type, .. } | Local { value_type, .. } => { - match (ret_type, value_type) { - (F32, I32) => code_builder.f32_convert_s_i32(), - (F32, I64) => code_builder.f32_convert_s_i64(), - (F32, F32) => {} - (F32, F64) => code_builder.f32_demote_f64(), - - (F64, I32) => code_builder.f64_convert_s_i32(), - (F64, I64) => code_builder.f64_convert_s_i64(), - (F64, F32) => code_builder.f64_promote_f32(), - (F64, F64) => {} - - _ => panic_ret_type(), - } - } - StackMemory { .. } => return NotImplemented, + let not_num_error = + || internal_error!("Tried to perform a Num low-level operation on {:?}", layout); + match layout { + Layout::Builtin(builtin) => match builtin { + Builtin::Int(int_width) => match int_width { + IntWidth::U8 => I32, + IntWidth::U16 => I32, + IntWidth::U32 => I32, + IntWidth::U64 => I64, + IntWidth::U128 => I128, + IntWidth::I8 => I32, + IntWidth::I16 => I32, + IntWidth::I32 => I32, + IntWidth::I64 => I64, + IntWidth::I128 => I128, }, - WasmLayout::StackMemory { .. } => return NotImplemented, - } + Builtin::Float(float_width) => match float_width { + FloatWidth::F32 => F32, + FloatWidth::F64 => F64, + FloatWidth::F128 => F128, + }, + Builtin::Decimal => Decimal, + _ => not_num_error(), + }, + _ => not_num_error(), } - NumPow => return NotImplemented, - NumCeiling => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => { - code_builder.f32_ceil(); - code_builder.i32_trunc_s_f32() - } - I64 => { - code_builder.f64_ceil(); - code_builder.i64_trunc_s_f64() - } - _ => panic_ret_type(), - }, - WasmLayout::StackMemory { .. } => return NotImplemented, - }, - NumPowInt => return NotImplemented, - NumFloor => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => { - code_builder.f32_floor(); - code_builder.i32_trunc_s_f32() - } - I64 => { - code_builder.f64_floor(); - code_builder.i64_trunc_s_f64() - } - _ => panic_ret_type(), - }, - WasmLayout::StackMemory { .. } => return NotImplemented, - }, - NumIsFinite => { - use StoredValue::*; - match storage.get(&args[0]) { - VirtualMachineStack { value_type, .. } | Local { value_type, .. } => { - match value_type { - I32 | I64 => code_builder.i32_const(1), // always true for integers - F32 => { - code_builder.i32_reinterpret_f32(); - code_builder.i32_const(0x7f80_0000); - code_builder.i32_and(); - code_builder.i32_const(0x7f80_0000); - code_builder.i32_ne(); - } - F64 => { - code_builder.i64_reinterpret_f64(); - code_builder.i64_const(0x7ff0_0000_0000_0000); - code_builder.i64_and(); - code_builder.i64_const(0x7ff0_0000_0000_0000); - code_builder.i64_ne(); - } - } - } - StackMemory { - format, location, .. - } => { - let (local_id, offset) = location.local_and_offset(storage.stack_frame_pointer); + } +} - match format { - Int128 => code_builder.i32_const(1), - Float128 => { - code_builder.get_local(local_id); - code_builder.i64_load(Align::Bytes4, offset + 8); - code_builder.i64_const(0x7fff_0000_0000_0000); - code_builder.i64_and(); - code_builder.i64_const(0x7fff_0000_0000_0000); - code_builder.i64_ne(); - } - Decimal => { - code_builder.get_local(local_id); - code_builder.i64_load(Align::Bytes4, offset + 8); - code_builder.i64_const(0x7100_0000_0000_0000); - code_builder.i64_and(); - code_builder.i64_const(0x7100_0000_0000_0000); - code_builder.i64_ne(); - } - DataStructure => return NotImplemented, +impl From for CodeGenNumType { + fn from(value_type: ValueType) -> CodeGenNumType { + match value_type { + ValueType::I32 => CodeGenNumType::I32, + ValueType::I64 => CodeGenNumType::I64, + ValueType::F32 => CodeGenNumType::F32, + ValueType::F64 => CodeGenNumType::F64, + } + } +} + +impl From for CodeGenNumType { + fn from(format: StackMemoryFormat) -> CodeGenNumType { + match format { + StackMemoryFormat::Int128 => CodeGenNumType::I128, + StackMemoryFormat::Float128 => CodeGenNumType::F128, + StackMemoryFormat::Decimal => CodeGenNumType::Decimal, + StackMemoryFormat::DataStructure => { + internal_error!("Tried to perform a Num low-level operation on a data structure") + } + } + } +} + +impl From for CodeGenNumType { + fn from(wasm_layout: WasmLayout) -> CodeGenNumType { + match wasm_layout { + WasmLayout::Primitive(value_type, _) => CodeGenNumType::from(value_type), + WasmLayout::StackMemory { format, .. } => CodeGenNumType::from(format), + } + } +} + +impl From<&StoredValue> for CodeGenNumType { + fn from(stored: &StoredValue) -> CodeGenNumType { + use StoredValue::*; + match stored { + VirtualMachineStack { value_type, .. } => CodeGenNumType::from(*value_type), + Local { value_type, .. } => CodeGenNumType::from(*value_type), + StackMemory { format, .. } => CodeGenNumType::from(*format), + } + } +} + +pub struct LowLevelCall<'a> { + pub lowlevel: LowLevel, + pub arguments: &'a [Symbol], + pub ret_symbol: Symbol, + pub ret_layout: Layout<'a>, + pub ret_storage: StoredValue, +} + +impl<'a> LowLevelCall<'a> { + /// Load symbol values for a Zig call or numerical operation + /// For numerical ops, this just pushes the arguments to the Wasm VM's value stack + /// It implements the calling convention used by Zig for both numbers and structs + /// Result is the type signature of the call + fn load_args(&self, backend: &mut WasmBackend<'a>) -> (Vec<'a, ValueType>, Option) { + backend.storage.load_symbols_for_call( + backend.env.arena, + &mut backend.code_builder, + self.arguments, + self.ret_symbol, + &WasmLayout::new(&self.ret_layout), + CallConv::Zig, + ) + } + + fn load_args_and_call_zig(&self, backend: &mut WasmBackend<'a>, name: &'a str) { + let (param_types, ret_type) = self.load_args(backend); + backend.call_zig_builtin_after_loading_args(name, param_types.len(), ret_type.is_some()); + } + + /// Wrap an integer whose Wasm representation is i32 + /// This may seem like deliberately introducing an error! + /// But we want all targets to behave the same, and hash algos rely on wrapping. + /// Discussion: https://github.com/rtfeldman/roc/pull/2117#discussion_r760723063 + fn wrap_i32(&self, backend: &mut WasmBackend<'a>) { + let invalid = + || internal_error!("Expected integer <= 32 bits, found {:?}", self.ret_layout); + + let (shift, is_signed) = match self.ret_layout { + Layout::Builtin(Builtin::Int(int_width)) => match int_width { + IntWidth::U8 => (24, false), + IntWidth::U16 => (16, false), + IntWidth::I8 => (24, true), + IntWidth::I16 => (16, true), + IntWidth::I32 | IntWidth::U32 => return, + _ => invalid(), + }, + _ => invalid(), + }; + + backend.code_builder.i32_const(shift); + backend.code_builder.i32_shl(); + backend.code_builder.i32_const(shift); + if is_signed { + backend.code_builder.i32_shr_s(); + } else { + backend.code_builder.i32_shr_u(); + } + } + + /// Main entrypoint from WasmBackend + pub fn generate(&self, backend: &mut WasmBackend<'a>) { + use CodeGenNumType::*; + + let panic_ret_type = || { + internal_error!( + "Invalid return layout for {:?}: {:?}", + self.lowlevel, + self.ret_layout + ) + }; + + match self.lowlevel { + // Str + StrConcat => self.load_args_and_call_zig(backend, bitcode::STR_CONCAT), + StrJoinWith => self.load_args_and_call_zig(backend, bitcode::STR_JOIN_WITH), + StrIsEmpty => { + self.load_args(backend); + backend.code_builder.i64_const(i64::MIN); + backend.code_builder.i64_eq(); + } + StrStartsWith => self.load_args_and_call_zig(backend, bitcode::STR_STARTS_WITH), + StrStartsWithCodePt => { + self.load_args_and_call_zig(backend, bitcode::STR_STARTS_WITH_CODE_PT) + } + StrEndsWith => self.load_args_and_call_zig(backend, bitcode::STR_ENDS_WITH), + StrSplit => { + // LLVM implementation (build_str.rs) does the following + // 1. Call bitcode::STR_COUNT_SEGMENTS + // 2. Allocate a `List Str` + // 3. Call bitcode::STR_STR_SPLIT_IN_PLACE + // 4. Write the elements and length of the List + // To do this here, we need full access to WasmBackend, or we could make a Zig wrapper + todo!("{:?}", self.lowlevel); + } + StrCountGraphemes => { + self.load_args_and_call_zig(backend, bitcode::STR_COUNT_GRAPEHEME_CLUSTERS) + } + StrToNum => { + let number_layout = match self.ret_layout { + Layout::Struct(fields) => fields[0], + _ => { + internal_error!("Unexpected mono layout {:?} for StrToNum", self.ret_layout) + } + }; + // match on the return layout to figure out which zig builtin we need + let intrinsic = match number_layout { + Layout::Builtin(Builtin::Int(int_width)) => &bitcode::STR_TO_INT[int_width], + Layout::Builtin(Builtin::Float(float_width)) => { + &bitcode::STR_TO_FLOAT[float_width] + } + Layout::Builtin(Builtin::Decimal) => bitcode::DEC_FROM_STR, + rest => internal_error!("Unexpected builtin {:?} for StrToNum", rest), + }; + + self.load_args_and_call_zig(backend, intrinsic); + } + StrFromInt => { + // This does not get exposed in user space. We switched to NumToStr instead. + // We can probably just leave this as NotImplemented. We may want remove this LowLevel. + // see: https://github.com/rtfeldman/roc/pull/2108 + todo!("{:?}", self.lowlevel); + } + StrFromFloat => { + // linker errors for __ashlti3, __fixunsdfti, __multi3, __udivti3, __umodti3 + // https://gcc.gnu.org/onlinedocs/gccint/Integer-library-routines.html + // https://gcc.gnu.org/onlinedocs/gccint/Soft-float-library-routines.html + todo!("{:?}", self.lowlevel); + } + StrFromUtf8 => self.load_args_and_call_zig(backend, bitcode::STR_FROM_UTF8), + StrTrimLeft => self.load_args_and_call_zig(backend, bitcode::STR_TRIM_LEFT), + StrTrimRight => self.load_args_and_call_zig(backend, bitcode::STR_TRIM_RIGHT), + StrFromUtf8Range => self.load_args_and_call_zig(backend, bitcode::STR_FROM_UTF8_RANGE), + StrToUtf8 => self.load_args_and_call_zig(backend, bitcode::STR_TO_UTF8), + StrRepeat => self.load_args_and_call_zig(backend, bitcode::STR_REPEAT), + StrTrim => self.load_args_and_call_zig(backend, bitcode::STR_TRIM), + + // List + ListLen => match backend.storage.get(&self.arguments[0]) { + StoredValue::StackMemory { location, .. } => { + let (local_id, offset) = + location.local_and_offset(backend.storage.stack_frame_pointer); + backend.code_builder.get_local(local_id); + backend.code_builder.i32_load(Align::Bytes4, offset + 4); + } + _ => internal_error!("invalid storage for List"), + }, + + ListGetUnsafe | ListSet | ListSingle | ListRepeat | ListReverse | ListConcat + | ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListMap + | ListMap2 | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk + | ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith + | ListSublist | ListDropAt | ListSwap | ListAny | ListAll | ListFindUnsafe + | DictSize | DictEmpty | DictInsert | DictRemove | DictContains | DictGetUnsafe + | DictKeys | DictValues | DictUnion | DictIntersection | DictDifference | DictWalk + | SetFromList => { + todo!("{:?}", self.lowlevel); + } + + // Num + NumAdd => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + CodeGenNumType::I32 => backend.code_builder.i32_add(), + CodeGenNumType::I64 => backend.code_builder.i64_add(), + CodeGenNumType::F32 => backend.code_builder.f32_add(), + CodeGenNumType::F64 => backend.code_builder.f64_add(), + CodeGenNumType::I128 => todo!("{:?}", self.lowlevel), + CodeGenNumType::F128 => todo!("{:?}", self.lowlevel), + CodeGenNumType::Decimal => { + self.load_args_and_call_zig(backend, bitcode::DEC_ADD_WITH_OVERFLOW) } } } - } - NumAtan => { - let width = float_width_from_layout(ret_layout); - return BuiltinCall(&bitcode::NUM_ATAN[width]); - } - NumAcos => { - let width = float_width_from_layout(ret_layout); - return BuiltinCall(&bitcode::NUM_ACOS[width]); - } - NumAsin => { - let width = float_width_from_layout(ret_layout); - return BuiltinCall(&bitcode::NUM_ASIN[width]); - } - NumBytesToU16 => return NotImplemented, - NumBytesToU32 => return NotImplemented, - NumBitwiseAnd => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => code_builder.i32_and(), - I64 => code_builder.i64_and(), - _ => panic_ret_type(), - }, - WasmLayout::StackMemory { .. } => return NotImplemented, - }, - NumBitwiseXor => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => code_builder.i32_xor(), - I64 => code_builder.i64_xor(), - _ => panic_ret_type(), - }, - WasmLayout::StackMemory { .. } => return NotImplemented, - }, - NumBitwiseOr => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => code_builder.i32_or(), - I64 => code_builder.i64_or(), - _ => panic_ret_type(), - }, - WasmLayout::StackMemory { .. } => return NotImplemented, - }, - NumShiftLeftBy => { - // Swap order of arguments - storage.load_symbols(code_builder, &[args[1], args[0]]); - match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => code_builder.i32_shl(), - I64 => code_builder.i64_shl(), + + NumAddWrap => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => { + backend.code_builder.i32_add(); + self.wrap_i32(backend); + } + I64 => backend.code_builder.i64_add(), + F32 => backend.code_builder.f32_add(), + F64 => backend.code_builder.f64_add(), + Decimal => self.load_args_and_call_zig(backend, bitcode::DEC_ADD_WITH_OVERFLOW), + x => todo!("{:?} for {:?}", self.lowlevel, x), + } + } + NumToStr => todo!("{:?}", self.lowlevel), + NumAddChecked => todo!("{:?}", self.lowlevel), + NumAddSaturated => todo!("{:?}", self.lowlevel), + NumSub => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => backend.code_builder.i32_sub(), + I64 => backend.code_builder.i64_sub(), + F32 => backend.code_builder.f32_sub(), + F64 => backend.code_builder.f64_sub(), + Decimal => self.load_args_and_call_zig(backend, bitcode::DEC_SUB_WITH_OVERFLOW), + x => todo!("{:?} for {:?}", self.lowlevel, x), + } + } + NumSubWrap => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => { + backend.code_builder.i32_sub(); + self.wrap_i32(backend); + } + I64 => backend.code_builder.i64_sub(), + F32 => backend.code_builder.f32_sub(), + F64 => backend.code_builder.f64_sub(), + Decimal => self.load_args_and_call_zig(backend, bitcode::DEC_SUB_WITH_OVERFLOW), + x => todo!("{:?} for {:?}", self.lowlevel, x), + } + } + NumSubChecked => todo!("{:?}", self.lowlevel), + NumSubSaturated => todo!("{:?}", self.lowlevel), + NumMul => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => backend.code_builder.i32_mul(), + I64 => backend.code_builder.i64_mul(), + F32 => backend.code_builder.f32_mul(), + F64 => backend.code_builder.f64_mul(), + Decimal => self.load_args_and_call_zig(backend, bitcode::DEC_MUL_WITH_OVERFLOW), + x => todo!("{:?} for {:?}", self.lowlevel, x), + } + } + NumMulWrap => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => { + backend.code_builder.i32_mul(); + self.wrap_i32(backend); + } + I64 => backend.code_builder.i64_mul(), + F32 => backend.code_builder.f32_mul(), + F64 => backend.code_builder.f64_mul(), + Decimal => self.load_args_and_call_zig(backend, bitcode::DEC_MUL_WITH_OVERFLOW), + x => todo!("{:?} for {:?}", self.lowlevel, x), + } + } + NumMulChecked => todo!("{:?}", self.lowlevel), + NumGt => { + self.load_args(backend); + match CodeGenNumType::for_symbol(backend, self.arguments[0]) { + I32 => backend.code_builder.i32_gt_s(), + I64 => backend.code_builder.i64_gt_s(), + F32 => backend.code_builder.f32_gt(), + F64 => backend.code_builder.f64_gt(), + x => todo!("{:?} for {:?}", self.lowlevel, x), + } + } + NumGte => { + self.load_args(backend); + match CodeGenNumType::for_symbol(backend, self.arguments[0]) { + I32 => backend.code_builder.i32_ge_s(), + I64 => backend.code_builder.i64_ge_s(), + F32 => backend.code_builder.f32_ge(), + F64 => backend.code_builder.f64_ge(), + x => todo!("{:?} for {:?}", self.lowlevel, x), + } + } + NumLt => { + self.load_args(backend); + match CodeGenNumType::for_symbol(backend, self.arguments[0]) { + I32 => backend.code_builder.i32_lt_s(), + I64 => backend.code_builder.i64_lt_s(), + F32 => backend.code_builder.f32_lt(), + F64 => backend.code_builder.f64_lt(), + x => todo!("{:?} for {:?}", self.lowlevel, x), + } + } + NumLte => { + self.load_args(backend); + match CodeGenNumType::for_symbol(backend, self.arguments[0]) { + I32 => backend.code_builder.i32_le_s(), + I64 => backend.code_builder.i64_le_s(), + F32 => backend.code_builder.f32_le(), + F64 => backend.code_builder.f64_le(), + x => todo!("{:?} for {:?}", self.lowlevel, x), + } + } + NumCompare => todo!("{:?}", self.lowlevel), + NumDivUnchecked => { + self.load_args(backend); + match CodeGenNumType::for_symbol(backend, self.arguments[0]) { + I32 => backend.code_builder.i32_div_s(), + I64 => backend.code_builder.i64_div_s(), + F32 => backend.code_builder.f32_div(), + F64 => backend.code_builder.f64_div(), + Decimal => self.load_args_and_call_zig(backend, bitcode::DEC_DIV), + x => todo!("{:?} for {:?}", self.lowlevel, x), + } + } + NumDivCeilUnchecked => todo!("{:?}", self.lowlevel), + NumRemUnchecked => { + self.load_args(backend); + match CodeGenNumType::for_symbol(backend, self.arguments[0]) { + I32 => backend.code_builder.i32_rem_s(), + I64 => backend.code_builder.i64_rem_s(), + _ => todo!("{:?} for {:?}", self.lowlevel, self.ret_layout), + } + } + NumIsMultipleOf => todo!("{:?}", self.lowlevel), + NumAbs => { + self.load_args(backend); + match CodeGenNumType::for_symbol(backend, self.arguments[0]) { + I32 => { + let code_builder = &mut backend.code_builder; + let arg_storage = backend.storage.get(&self.arguments[0]).to_owned(); + backend.storage.ensure_value_has_local( + code_builder, + self.arguments[0], + arg_storage, + ); + backend.storage.load_symbols(code_builder, self.arguments); + code_builder.i32_const(0); + backend.storage.load_symbols(code_builder, self.arguments); + code_builder.i32_sub(); + backend.storage.load_symbols(code_builder, self.arguments); + code_builder.i32_const(0); + code_builder.i32_ge_s(); + code_builder.select(); + } + I64 => { + let code_builder = &mut backend.code_builder; + let arg_storage = backend.storage.get(&self.arguments[0]).to_owned(); + backend.storage.ensure_value_has_local( + code_builder, + self.arguments[0], + arg_storage, + ); + backend.storage.load_symbols(code_builder, self.arguments); + code_builder.i64_const(0); + backend.storage.load_symbols(code_builder, self.arguments); + code_builder.i64_sub(); + backend.storage.load_symbols(code_builder, self.arguments); + code_builder.i64_const(0); + code_builder.i64_ge_s(); + code_builder.select(); + } + F32 => backend.code_builder.f32_abs(), + F64 => backend.code_builder.f64_abs(), + _ => todo!("{:?} for {:?}", self.lowlevel, self.ret_layout), + } + } + NumNeg => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => { + backend.code_builder.i32_const(0); + backend + .storage + .load_symbols(&mut backend.code_builder, self.arguments); + backend.code_builder.i32_sub(); + } + I64 => { + backend.code_builder.i64_const(0); + backend + .storage + .load_symbols(&mut backend.code_builder, self.arguments); + backend.code_builder.i64_sub(); + } + F32 => backend.code_builder.f32_neg(), + F64 => backend.code_builder.f64_neg(), + _ => todo!("{:?} for {:?}", self.lowlevel, self.ret_layout), + } + } + NumSin => todo!("{:?}", self.lowlevel), + NumCos => todo!("{:?}", self.lowlevel), + NumSqrtUnchecked => todo!("{:?}", self.lowlevel), + NumLogUnchecked => todo!("{:?}", self.lowlevel), + NumRound => { + self.load_args(backend); + match CodeGenNumType::for_symbol(backend, self.arguments[0]) { + F32 => { + self.load_args_and_call_zig(backend, &bitcode::NUM_ROUND[FloatWidth::F32]) + } + F64 => { + self.load_args_and_call_zig(backend, &bitcode::NUM_ROUND[FloatWidth::F64]) + } + _ => todo!("{:?} for {:?}", self.lowlevel, self.ret_layout), + } + } + NumToFloat => { + self.load_args(backend); + let ret_type = CodeGenNumType::from(self.ret_layout); + let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]); + match (ret_type, arg_type) { + (F32, I32) => backend.code_builder.f32_convert_s_i32(), + (F32, I64) => backend.code_builder.f32_convert_s_i64(), + (F32, F32) => {} + (F32, F64) => backend.code_builder.f32_demote_f64(), + + (F64, I32) => backend.code_builder.f64_convert_s_i32(), + (F64, I64) => backend.code_builder.f64_convert_s_i64(), + (F64, F32) => backend.code_builder.f64_promote_f32(), + (F64, F64) => {} + + _ => todo!("{:?}: {:?} -> {:?}", self.lowlevel, arg_type, ret_type), + } + } + NumPow => todo!("{:?}", self.lowlevel), + NumCeiling => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => { + backend.code_builder.f32_ceil(); + backend.code_builder.i32_trunc_s_f32() + } + I64 => { + backend.code_builder.f64_ceil(); + backend.code_builder.i64_trunc_s_f64() + } _ => panic_ret_type(), - }, - WasmLayout::StackMemory { .. } => return NotImplemented, + } } - } - NumShiftRightBy => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => code_builder.i32_shr_s(), - I64 => code_builder.i64_shr_s(), - _ => panic_ret_type(), - }, - WasmLayout::StackMemory { .. } => return NotImplemented, - }, - NumShiftRightZfBy => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => code_builder.i32_shr_u(), - I64 => code_builder.i64_shr_u(), - _ => panic_ret_type(), - }, - WasmLayout::StackMemory { .. } => return NotImplemented, - }, - NumIntCast => { - use StoredValue::*; - let stored = storage.get(&args[0]); - match ret_layout { - WasmLayout::Primitive(ret_type, _) => match stored { - VirtualMachineStack { value_type, .. } | Local { value_type, .. } => { - match (ret_type, value_type) { - (I32, I32) => {} - (I32, I64) => code_builder.i32_wrap_i64(), - (I32, F32) => code_builder.i32_trunc_s_f32(), - (I32, F64) => code_builder.i32_trunc_s_f64(), - - (I64, I32) => code_builder.i64_extend_s_i32(), - (I64, I64) => {} - (I64, F32) => code_builder.i64_trunc_s_f32(), - (I64, F64) => code_builder.i64_trunc_s_f64(), - - (F32, I32) => code_builder.f32_convert_s_i32(), - (F32, I64) => code_builder.f32_convert_s_i64(), - (F32, F32) => {} - (F32, F64) => code_builder.f32_demote_f64(), - - (F64, I32) => code_builder.f64_convert_s_i32(), - (F64, I64) => code_builder.f64_convert_s_i64(), - (F64, F32) => code_builder.f64_promote_f32(), - (F64, F64) => {} - } + NumPowInt => todo!("{:?}", self.lowlevel), + NumFloor => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => { + backend.code_builder.f32_floor(); + backend.code_builder.i32_trunc_s_f32() } + I64 => { + backend.code_builder.f64_floor(); + backend.code_builder.i64_trunc_s_f64() + } + _ => panic_ret_type(), + } + } + NumIsFinite => num_is_finite(backend, self.arguments[0]), - StackMemory { .. } => return NotImplemented, - }, - WasmLayout::StackMemory { .. } => return NotImplemented, + NumAtan => match self.ret_layout { + Layout::Builtin(Builtin::Float(width)) => { + self.load_args_and_call_zig(backend, &bitcode::NUM_ATAN[width]); + } + _ => panic_ret_type(), + }, + NumAcos => match self.ret_layout { + Layout::Builtin(Builtin::Float(width)) => { + self.load_args_and_call_zig(backend, &bitcode::NUM_ACOS[width]); + } + _ => panic_ret_type(), + }, + NumAsin => match self.ret_layout { + Layout::Builtin(Builtin::Float(width)) => { + self.load_args_and_call_zig(backend, &bitcode::NUM_ASIN[width]); + } + _ => panic_ret_type(), + }, + NumBytesToU16 => todo!("{:?}", self.lowlevel), + NumBytesToU32 => todo!("{:?}", self.lowlevel), + NumBitwiseAnd => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => backend.code_builder.i32_and(), + I64 => backend.code_builder.i64_and(), + I128 => todo!("{:?} for I128", self.lowlevel), + _ => panic_ret_type(), + } + } + NumBitwiseXor => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => backend.code_builder.i32_xor(), + I64 => backend.code_builder.i64_xor(), + I128 => todo!("{:?} for I128", self.lowlevel), + _ => panic_ret_type(), + } + } + NumBitwiseOr => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => backend.code_builder.i32_or(), + I64 => backend.code_builder.i64_or(), + I128 => todo!("{:?} for I128", self.lowlevel), + _ => panic_ret_type(), + } + } + NumShiftLeftBy => { + // Swap order of arguments + backend.storage.load_symbols( + &mut backend.code_builder, + &[self.arguments[1], self.arguments[0]], + ); + match CodeGenNumType::from(self.ret_layout) { + I32 => backend.code_builder.i32_shl(), + I64 => backend.code_builder.i64_shl(), + I128 => todo!("{:?} for I128", self.lowlevel), + _ => panic_ret_type(), + } + } + NumShiftRightBy => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => backend.code_builder.i32_shr_s(), + I64 => backend.code_builder.i64_shr_s(), + I128 => todo!("{:?} for I128", self.lowlevel), + _ => panic_ret_type(), + } + } + NumShiftRightZfBy => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => backend.code_builder.i32_shr_u(), + I64 => backend.code_builder.i64_shr_u(), + I128 => todo!("{:?} for I128", self.lowlevel), + _ => panic_ret_type(), + } + } + NumIntCast => { + self.load_args(backend); + let ret_type = CodeGenNumType::from(self.ret_layout); + let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]); + match (ret_type, arg_type) { + (I32, I32) => {} + (I32, I64) => backend.code_builder.i32_wrap_i64(), + (I32, F32) => backend.code_builder.i32_trunc_s_f32(), + (I32, F64) => backend.code_builder.i32_trunc_s_f64(), + + (I64, I32) => backend.code_builder.i64_extend_s_i32(), + (I64, I64) => {} + (I64, F32) => backend.code_builder.i64_trunc_s_f32(), + (I64, F64) => backend.code_builder.i64_trunc_s_f64(), + + (F32, I32) => backend.code_builder.f32_convert_s_i32(), + (F32, I64) => backend.code_builder.f32_convert_s_i64(), + (F32, F32) => {} + (F32, F64) => backend.code_builder.f32_demote_f64(), + + (F64, I32) => backend.code_builder.f64_convert_s_i32(), + (F64, I64) => backend.code_builder.f64_convert_s_i64(), + (F64, F32) => backend.code_builder.f64_promote_f32(), + (F64, F64) => {} + + _ => todo!("{:?}: {:?} -> {:?}", self.lowlevel, arg_type, ret_type), + } + } + And => { + self.load_args(backend); + backend.code_builder.i32_and(); + } + Or => { + self.load_args(backend); + backend.code_builder.i32_or(); + } + Not => { + self.load_args(backend); + backend.code_builder.i32_eqz(); + } + ExpectTrue => todo!("{:?}", self.lowlevel), + RefCountInc => self.load_args_and_call_zig(backend, bitcode::UTILS_INCREF), + RefCountDec => self.load_args_and_call_zig(backend, bitcode::UTILS_DECREF), + + PtrCast => { + let code_builder = &mut backend.code_builder; + backend.storage.load_symbols(code_builder, self.arguments); + } + + Hash => todo!("{:?}", self.lowlevel), + + Eq | NotEq => self.eq_or_neq(backend), + } + } + + /// Equality and inequality + /// These can operate on any data type (except functions) so they're more complex than other operators. + fn eq_or_neq(&self, backend: &mut WasmBackend<'a>) { + let arg_layout = backend.storage.symbol_layouts[&self.arguments[0]]; + let other_arg_layout = backend.storage.symbol_layouts[&self.arguments[1]]; + debug_assert!( + arg_layout == other_arg_layout, + "Cannot do `==` comparison on different types" + ); + + let invert_result = matches!(self.lowlevel, NotEq); + + match arg_layout { + Layout::Builtin( + Builtin::Int(_) | Builtin::Float(_) | Builtin::Bool | Builtin::Decimal, + ) => self.eq_or_neq_number(backend), + + Layout::Builtin(Builtin::Str) => { + self.load_args_and_call_zig(backend, bitcode::STR_EQUAL); + if invert_result { + backend.code_builder.i32_eqz(); + } + } + + // 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() => { + backend.code_builder.i32_const(!invert_result as i32); + } + + // Void is always equal to void. This is the type for the contents of the empty list in `[] == []` + // This instruction will never execute, but we need an i32 for module validation + Layout::Union(UnionLayout::NonRecursive(tags)) if tags.is_empty() => { + backend.code_builder.i32_const(!invert_result as i32); + } + + Layout::Builtin(Builtin::Dict(_, _) | Builtin::Set(_) | Builtin::List(_)) + | Layout::Struct(_) + | Layout::Union(_) + | Layout::LambdaSet(_) => { + // Don't want Zig calling convention here, we're calling internal Roc functions + backend + .storage + .load_symbols(&mut backend.code_builder, self.arguments); + + backend.call_eq_specialized( + self.arguments, + &arg_layout, + self.ret_symbol, + &self.ret_storage, + ); + + if invert_result { + backend.code_builder.i32_eqz(); + } + } + + Layout::RecursivePointer => { + internal_error!( + "Tried to apply `==` to RecursivePointer values {:?}", + self.arguments, + ) } } - And => code_builder.i32_and(), - Or => code_builder.i32_or(), - Not => code_builder.i32_eqz(), - ExpectTrue => return NotImplemented, - RefCountInc => return BuiltinCall(bitcode::UTILS_INCREF), - RefCountDec => return BuiltinCall(bitcode::UTILS_DECREF), - Eq | NotEq | Hash | PtrCast => { - internal_error!("{:?} should be handled in backend.rs", lowlevel) + } + + fn eq_or_neq_number(&self, backend: &mut WasmBackend<'a>) { + use StoredValue::*; + + match backend.storage.get(&self.arguments[0]).to_owned() { + VirtualMachineStack { value_type, .. } | Local { value_type, .. } => { + self.load_args(backend); + match self.lowlevel { + LowLevel::Eq => match value_type { + ValueType::I32 => backend.code_builder.i32_eq(), + ValueType::I64 => backend.code_builder.i64_eq(), + ValueType::F32 => backend.code_builder.f32_eq(), + ValueType::F64 => backend.code_builder.f64_eq(), + }, + LowLevel::NotEq => match value_type { + ValueType::I32 => backend.code_builder.i32_ne(), + ValueType::I64 => backend.code_builder.i64_ne(), + ValueType::F32 => backend.code_builder.f32_ne(), + ValueType::F64 => backend.code_builder.f64_ne(), + }, + _ => internal_error!("{:?} ended up in Equality code", self.lowlevel), + } + } + StackMemory { + format, + location: location0, + .. + } => { + if let StackMemory { + location: location1, + .. + } = backend.storage.get(&self.arguments[1]).to_owned() + { + self.eq_num128(backend, format, [location0, location1]); + if matches!(self.lowlevel, LowLevel::NotEq) { + backend.code_builder.i32_eqz(); + } + } + } } } - Done -} -/// Wrap an integer whose Wasm representation is i32 -fn wrap_i32(code_builder: &mut CodeBuilder, size: u32) { - match size { - 1 => { - // Underlying Roc value is i8 - code_builder.i32_const(24); - code_builder.i32_shl(); - code_builder.i32_const(24); - code_builder.i32_shr_s(); + /// Equality for 12-bit numbers. Checks if they're finite and contain the same bytes + /// Takes care of loading the arguments + fn eq_num128( + &self, + backend: &mut WasmBackend<'a>, + format: StackMemoryFormat, + locations: [StackMemoryLocation; 2], + ) { + match format { + StackMemoryFormat::Decimal => { + // Both args are finite + num_is_finite(backend, self.arguments[0]); + num_is_finite(backend, self.arguments[1]); + backend.code_builder.i32_and(); + + // AND they have the same bytes + Self::eq_num128_bytes(backend, locations); + backend.code_builder.i32_and(); + } + + StackMemoryFormat::Int128 => Self::eq_num128_bytes(backend, locations), + + StackMemoryFormat::Float128 => todo!("equality for f128"), + + StackMemoryFormat::DataStructure => { + internal_error!("Data structure equality is handled elsewhere") + } } - 2 => { - // Underlying Roc value is i16 - code_builder.i32_const(16); - code_builder.i32_shl(); - code_builder.i32_const(16); - code_builder.i32_shr_s(); - } - _ => {} // the only other possible value is 4, and i32 wraps natively + } + + /// Check that two 128-bit numbers contain the same bytes + /// Loads *half* an argument at a time + /// (Don't call "load arguments" or "load symbols" helpers before this, it'll just waste instructions) + fn eq_num128_bytes(backend: &mut WasmBackend<'a>, locations: [StackMemoryLocation; 2]) { + let (local0, offset0) = locations[0].local_and_offset(backend.storage.stack_frame_pointer); + let (local1, offset1) = locations[1].local_and_offset(backend.storage.stack_frame_pointer); + + // Load & compare the first half of each argument + backend.code_builder.get_local(local0); + backend.code_builder.i64_load(Align::Bytes8, offset0); + backend.code_builder.get_local(local1); + backend.code_builder.i64_load(Align::Bytes8, offset1); + backend.code_builder.i64_eq(); + + // Load & compare the second half of each argument + backend.code_builder.get_local(local0); + backend.code_builder.i64_load(Align::Bytes8, offset0 + 8); + backend.code_builder.get_local(local1); + backend.code_builder.i64_load(Align::Bytes8, offset1 + 8); + backend.code_builder.i64_eq(); + + // First half matches AND second half matches + backend.code_builder.i32_and(); } } -fn float_width_from_layout(wasm_layout: &WasmLayout) -> FloatWidth { - match wasm_layout { - WasmLayout::Primitive(F32, _) => FloatWidth::F32, - WasmLayout::Primitive(F64, _) => FloatWidth::F64, - _ => internal_error!("{:?} does not have a FloatWidth", wasm_layout), +/// Helper for NumIsFinite op, and also part of Eq/NotEq +fn num_is_finite(backend: &mut WasmBackend<'_>, argument: Symbol) { + use StoredValue::*; + let stored = backend.storage.get(&argument).to_owned(); + match stored { + VirtualMachineStack { value_type, .. } | Local { value_type, .. } => { + backend + .storage + .load_symbols(&mut backend.code_builder, &[argument]); + match value_type { + ValueType::I32 | ValueType::I64 => backend.code_builder.i32_const(1), // always true for integers + ValueType::F32 => { + backend.code_builder.i32_reinterpret_f32(); + backend.code_builder.i32_const(0x7f80_0000); + backend.code_builder.i32_and(); + backend.code_builder.i32_const(0x7f80_0000); + backend.code_builder.i32_ne(); + } + ValueType::F64 => { + backend.code_builder.i64_reinterpret_f64(); + backend.code_builder.i64_const(0x7ff0_0000_0000_0000); + backend.code_builder.i64_and(); + backend.code_builder.i64_const(0x7ff0_0000_0000_0000); + backend.code_builder.i64_ne(); + } + } + } + StackMemory { + format, location, .. + } => { + let (local_id, offset) = location.local_and_offset(backend.storage.stack_frame_pointer); + + match format { + StackMemoryFormat::Int128 => backend.code_builder.i32_const(1), + + // f128 is not supported anywhere else but it's easy to support it here, so why not... + StackMemoryFormat::Float128 => { + backend.code_builder.get_local(local_id); + backend.code_builder.i64_load(Align::Bytes4, offset + 8); + backend.code_builder.i64_const(0x7fff_0000_0000_0000); + backend.code_builder.i64_and(); + backend.code_builder.i64_const(0x7fff_0000_0000_0000); + backend.code_builder.i64_ne(); + } + + StackMemoryFormat::Decimal => { + backend.code_builder.get_local(local_id); + backend.code_builder.i64_load(Align::Bytes4, offset + 8); + backend.code_builder.i64_const(0x7100_0000_0000_0000); + backend.code_builder.i64_and(); + backend.code_builder.i64_const(0x7100_0000_0000_0000); + backend.code_builder.i64_ne(); + } + + StackMemoryFormat::DataStructure => { + internal_error!("Tried to perform NumIsFinite on a data structure") + } + } + } } } diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index 9caa0ba21e..c60220d7ae 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -2,9 +2,9 @@ use bumpalo::collections::Vec; use bumpalo::Bump; use roc_collections::all::MutMap; +use roc_error_macros::internal_error; use roc_module::symbol::Symbol; use roc_mono::layout::Layout; -use roc_reporting::internal_error; use crate::layout::{ CallConv, ReturnMethod, StackMemoryFormat, WasmLayout, ZigVersion, BUILTINS_ZIG_VERSION, diff --git a/compiler/gen_wasm/src/wasm32_result.rs b/compiler/gen_wasm/src/wasm32_result.rs new file mode 100644 index 0000000000..ce21c83683 --- /dev/null +++ b/compiler/gen_wasm/src/wasm32_result.rs @@ -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 Wasm32Result for RocList { + fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { + build_wrapper_body_stack_memory(code_builder, main_function_index, 12) + } +} + +impl Wasm32Result for &'_ T { + build_wrapper_body_primitive!(i32_store, Align::Bytes4); +} + +impl 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 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 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, + ) + } +} diff --git a/compiler/gen_wasm/src/wasm32_sized.rs b/compiler/gen_wasm/src/wasm32_sized.rs new file mode 100644 index 0000000000..2eea997b6d --- /dev/null +++ b/compiler/gen_wasm/src/wasm32_sized.rs @@ -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 Wasm32Sized for RocList { + const SIZE_OF_WASM: usize = 8; + const ALIGN_OF_WASM: usize = 4; +} + +impl Wasm32Sized for &'_ T { + const SIZE_OF_WASM: usize = 4; + const ALIGN_OF_WASM: usize = 4; +} + +impl 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 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 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) +} diff --git a/compiler/gen_wasm/src/wasm_module/code_builder.rs b/compiler/gen_wasm/src/wasm_module/code_builder.rs index 09510a070d..6ba1042cb1 100644 --- a/compiler/gen_wasm/src/wasm_module/code_builder.rs +++ b/compiler/gen_wasm/src/wasm_module/code_builder.rs @@ -1,7 +1,7 @@ use bumpalo::collections::vec::Vec; use bumpalo::Bump; use core::panic; -use roc_reporting::internal_error; +use roc_error_macros::internal_error; use roc_module::symbol::Symbol; @@ -37,6 +37,18 @@ impl Serialize for ValueType { } } +impl From for ValueType { + fn from(x: u8) -> Self { + match x { + 0x7f => Self::I32, + 0x7e => Self::I64, + 0x7d => Self::F32, + 0x7c => Self::F64, + _ => internal_error!("Invalid ValueType 0x{:02x}", x), + } + } +} + const BLOCK_NO_RESULT: u8 = 0x40; /// A control block in our model of the VM @@ -156,6 +168,12 @@ pub struct CodeBuilder<'a> { relocations: Vec<'a, RelocationEntry>, } +impl<'a> Serialize for CodeBuilder<'a> { + fn serialize(&self, buffer: &mut T) { + self.serialize_without_relocs(buffer); + } +} + #[allow(clippy::new_without_default)] impl<'a> CodeBuilder<'a> { pub fn new(arena: &'a Bump) -> Self { @@ -470,6 +488,26 @@ impl<'a> CodeBuilder<'a> { ***********************************************************/ + pub fn size(&self) -> usize { + self.inner_length.len() + self.preamble.len() + self.code.len() + self.insert_bytes.len() + } + + /// Serialize all byte vectors in the right order + /// Also update relocation offsets relative to the base offset (code section body start) + pub fn serialize_without_relocs(&self, buffer: &mut T) { + buffer.append_slice(&self.inner_length); + buffer.append_slice(&self.preamble); + + let mut code_pos = 0; + for Insertion { at, start, end } in self.insertions.iter() { + buffer.append_slice(&self.code[code_pos..(*at)]); + buffer.append_slice(&self.insert_bytes[*start..*end]); + code_pos = *at; + } + + buffer.append_slice(&self.code[code_pos..self.code.len()]); + } + /// Serialize all byte vectors in the right order /// Also update relocation offsets relative to the base offset (code section body start) pub fn serialize_with_relocs( @@ -917,17 +955,4 @@ impl<'a> CodeBuilder<'a> { instruction_no_args!(i64_reinterpret_f64, I64REINTERPRETF64, 1, true); instruction_no_args!(f32_reinterpret_i32, F32REINTERPRETI32, 1, true); instruction_no_args!(f64_reinterpret_i64, F64REINTERPRETI64, 1, true); - - /// Generate a debug assertion for an expected i32 value - pub fn _debug_assert_i32(&mut self, expected: i32) { - self.i32_const(expected); - self.i32_eq(); - self.i32_eqz(); - self.if_(); - self.unreachable_(); // Tell Wasm runtime to throw an exception - self.end(); - // It matches. Restore the original value to the VM stack and continue the program. - // We know it matched the expected value, so just use that! - self.i32_const(expected); - } } diff --git a/compiler/gen_wasm/src/wasm_module/dead_code.rs b/compiler/gen_wasm/src/wasm_module/dead_code.rs new file mode 100644 index 0000000000..382f306322 --- /dev/null +++ b/compiler/gen_wasm/src/wasm_module/dead_code.rs @@ -0,0 +1,211 @@ +use bumpalo::collections::vec::Vec; +use bumpalo::Bump; + +use super::opcodes::OpCode; +use super::serialize::{parse_u32_or_panic, SerialBuffer, Serialize, SkipBytes}; +use super::CodeBuilder; + +/* + +DEAD CODE ELIMINATION + +Or, more specifically, "dead function replacement" + +- On pre-loading the object file: + - Analyse its call graph by finding all `call` instructions in the Code section, + and checking which function index they refer to. Store this in a `PreloadsCallGraph` +- While compiling Roc code: + - Run the backend as usual, adding more data into various sections of the Wasm module + - Whenever a call to a builtin or platform function is made, record its index. + These are the "live" preloaded functions that we are not allowed to eliminate. +- Call graph analysis: + - Starting with the live preloaded functions, trace their call graphs using the info we + collected earlier in `PreloadsCallGraph`. Mark all function indices in the call graph as "live". +- Dead function replacement: + - We actually don't want to just *delete* dead functions, because that would change the indices + of the live functions, invalidating all references to them, such as `call` instructions. + - Instead, during serialization, we replace its body with a single `unreachable` instruction +*/ + +#[derive(Debug)] +pub struct PreloadsCallGraph<'a> { + num_preloads: usize, + /// Byte offset where each function body can be found + code_offsets: Vec<'a, u32>, + /// Vector with one entry per *call*, containing the called function's index + calls: Vec<'a, u32>, + /// Vector with one entry per *function*, indicating its offset in `calls` + calls_offsets: Vec<'a, u32>, +} + +impl<'a> PreloadsCallGraph<'a> { + pub fn new(arena: &'a Bump, import_fn_count: u32, fn_count: u32) -> Self { + let num_preloads = (import_fn_count + fn_count) as usize; + + let mut code_offsets = Vec::with_capacity_in(num_preloads, arena); + let calls = Vec::with_capacity_in(2 * num_preloads, arena); + let mut calls_offsets = Vec::with_capacity_in(1 + num_preloads, arena); + + // Imported functions have zero code length and no calls + code_offsets.extend(std::iter::repeat(0).take(import_fn_count as usize)); + calls_offsets.extend(std::iter::repeat(0).take(import_fn_count as usize)); + + PreloadsCallGraph { + num_preloads, + code_offsets, + calls, + calls_offsets, + } + } +} + +/// Parse a Code section, collecting metadata that we can use to figure out +/// which functions are actually called, and which are not. +/// This would normally be done in a linker optimisation, but we want to be able to +/// use this backend without a linker. +pub fn parse_preloads_call_graph<'a>( + arena: &'a Bump, + fn_count: u32, + code_section_body: &[u8], + import_fn_count: u32, +) -> PreloadsCallGraph<'a> { + let mut call_graph = PreloadsCallGraph::new(arena, import_fn_count, fn_count); + + // Iterate over the bytes of the Code section + let mut cursor: usize = 0; + while cursor < code_section_body.len() { + // Record the start of a function + call_graph.code_offsets.push(cursor as u32); + call_graph.calls_offsets.push(call_graph.calls.len() as u32); + + let func_size = parse_u32_or_panic(code_section_body, &mut cursor); + let func_end = cursor + func_size as usize; + + // Skip over local variable declarations + let local_groups_count = parse_u32_or_panic(code_section_body, &mut cursor); + for _ in 0..local_groups_count { + parse_u32_or_panic(code_section_body, &mut cursor); + cursor += 1; // ValueType + } + + // Parse `call` instructions and skip over all other instructions + while cursor < func_end { + let opcode_byte: u8 = code_section_body[cursor]; + if opcode_byte == OpCode::CALL as u8 { + cursor += 1; + let call_index = parse_u32_or_panic(code_section_body, &mut cursor); + call_graph.calls.push(call_index as u32); + } else { + OpCode::skip_bytes(code_section_body, &mut cursor); + } + } + } + + // Extra entries to mark the end of the last function + call_graph.code_offsets.push(cursor as u32); + call_graph.calls_offsets.push(call_graph.calls.len() as u32); + + call_graph +} + +/// Trace the dependencies of a list of functions +/// We've already collected call_graph saying which functions call each other +/// Now we need to trace the dependency graphs of a specific subset of them +/// Result is the full set of builtins and platform functions used in the app. +/// The rest are "dead code" and can be eliminated. +pub fn trace_call_graph<'a, Indices: IntoIterator>( + arena: &'a Bump, + call_graph: &PreloadsCallGraph<'a>, + exported_fns: &[u32], + called_from_app: Indices, +) -> Vec<'a, u32> { + let num_preloads = call_graph.num_preloads; + + // All functions that get called from the app, directly or indirectly + let mut live_fn_indices = Vec::with_capacity_in(num_preloads, arena); + + // Current & next batch of functions whose call graphs we want to trace through the call_graph + // (2 separate vectors so that we're not iterating over the same one we're changing) + // If the max call depth is N then we will do N traces or less + let mut current_trace = Vec::with_capacity_in(num_preloads, arena); + let mut next_trace = Vec::with_capacity_in(num_preloads, arena); + + // Start with preloaded functions called from the app or exported directly to Wasm host + current_trace.extend(called_from_app); + current_trace.extend( + exported_fns + .iter() + .filter(|idx| **idx < num_preloads as u32), + ); + current_trace.sort_unstable(); + current_trace.dedup(); + + // Fast per-function lookup table to see if its dependencies have already been traced + let mut already_traced = Vec::from_iter_in(std::iter::repeat(false).take(num_preloads), arena); + + loop { + live_fn_indices.extend_from_slice(¤t_trace); + + for func_idx in current_trace.iter() { + let i = *func_idx as usize; + already_traced[i] = true; + let calls_start = call_graph.calls_offsets[i] as usize; + let calls_end = call_graph.calls_offsets[i + 1] as usize; + let called_indices: &[u32] = &call_graph.calls[calls_start..calls_end]; + for called_idx in called_indices { + if !already_traced[*called_idx as usize] { + next_trace.push(*called_idx); + } + } + } + if next_trace.is_empty() { + break; + } + next_trace.sort_unstable(); + next_trace.dedup(); + + current_trace.clone_from(&next_trace); + next_trace.clear(); + } + + live_fn_indices +} + +/// Copy used functions from preloaded object file into our Code section +/// Replace unused functions with very small dummies, to avoid changing any indices +pub fn copy_preloads_shrinking_dead_fns<'a, T: SerialBuffer>( + arena: &'a Bump, + buffer: &mut T, + call_graph: &PreloadsCallGraph<'a>, + external_code: &[u8], + import_fn_count: u32, + mut live_preload_indices: Vec<'a, u32>, +) { + let preload_idx_start = import_fn_count as usize; + + // Create a dummy function with just a single `unreachable` instruction + let mut dummy_builder = CodeBuilder::new(arena); + dummy_builder.unreachable_(); + dummy_builder.build_fn_header_and_footer(&[], 0, None); + let mut dummy_bytes = Vec::with_capacity_in(dummy_builder.size(), arena); + dummy_builder.serialize(&mut dummy_bytes); + + live_preload_indices.sort_unstable(); + live_preload_indices.dedup(); + + let mut live_iter = live_preload_indices.iter(); + let mut next_live_idx = live_iter.next(); + for i in preload_idx_start..call_graph.num_preloads { + match next_live_idx { + Some(live) if *live as usize == i => { + next_live_idx = live_iter.next(); + let live_body_start = call_graph.code_offsets[i] as usize; + let live_body_end = call_graph.code_offsets[i + 1] as usize; + buffer.append_slice(&external_code[live_body_start..live_body_end]); + } + _ => { + buffer.append_slice(&dummy_bytes); + } + } + } +} diff --git a/compiler/gen_wasm/src/wasm_module/linking.rs b/compiler/gen_wasm/src/wasm_module/linking.rs index a8e62cceeb..048ede0418 100644 --- a/compiler/gen_wasm/src/wasm_module/linking.rs +++ b/compiler/gen_wasm/src/wasm_module/linking.rs @@ -1,6 +1,5 @@ use bumpalo::collections::vec::Vec; use bumpalo::Bump; -use roc_reporting::internal_error; use super::sections::{update_section_size, write_custom_section_header}; use super::serialize::{SerialBuffer, Serialize}; @@ -183,8 +182,12 @@ pub struct LinkingSegment { } impl Serialize for LinkingSegment { - fn serialize(&self, _buffer: &mut T) { - todo!(); + fn serialize(&self, buffer: &mut T) { + buffer.encode_u32(self.name.len() as u32); + buffer.append_slice(self.name.as_bytes()); + let align_bytes_pow2 = self.alignment as u32; + buffer.encode_u32(align_bytes_pow2); + buffer.encode_u32(self.flags); } } @@ -418,35 +421,25 @@ impl Serialize for SymInfo { // Linking subsections //---------------------------------------------------------------- +#[repr(u8)] #[derive(Debug)] -pub enum LinkingSubSection<'a> { - /// Extra metadata about the data segments. - SegmentInfo(Vec<'a, LinkingSegment>), - /// Specifies a list of constructor functions to be called at startup. - /// These constructors will be called in priority order after memory has been initialized. - InitFuncs(Vec<'a, LinkingInitFunc>), - /// Specifies the COMDAT groups of associated linking objects, which are linked only once and all together. - ComdatInfo(Vec<'a, LinkingComdat<'a>>), - /// Specifies extra information about the symbols present in the module. - SymbolTable(Vec<'a, SymInfo>), +enum SubSectionId { + SegmentInfo = 5, + InitFuncs = 6, + ComdatInfo = 7, + SymbolTable = 8, } -impl<'a> Serialize for LinkingSubSection<'a> { - fn serialize(&self, buffer: &mut T) { - buffer.append_u8(match self { - Self::SegmentInfo(_) => 5, - Self::InitFuncs(_) => 6, - Self::ComdatInfo(_) => 7, - Self::SymbolTable(_) => 8, - }); +fn serialize_subsection( + buffer: &mut T, + id: SubSectionId, + items: &[I], +) { + if !items.is_empty() { + buffer.append_u8(id as u8); let payload_len_index = buffer.reserve_padded_u32(); let payload_start_index = buffer.size(); - match self { - Self::SegmentInfo(items) => items.serialize(buffer), - Self::InitFuncs(items) => items.serialize(buffer), - Self::ComdatInfo(items) => items.serialize(buffer), - Self::SymbolTable(items) => items.serialize(buffer), - } + items.serialize(buffer); buffer.overwrite_padded_u32( payload_len_index, (buffer.size() - payload_start_index) as u32, @@ -460,35 +453,39 @@ impl<'a> Serialize for LinkingSubSection<'a> { const LINKING_VERSION: u8 = 2; +/// The spec describes this in very weird way, so we're doing something saner. +/// They call it an "array" of subsections with different variants, BUT this "array" +/// has an implicit length, and none of the items can be repeated, so a struct is better. +/// No point writing code to "find" the symbol table, when we know there's exactly one. #[derive(Debug)] pub struct LinkingSection<'a> { - pub subsections: Vec<'a, LinkingSubSection<'a>>, + pub symbol_table: Vec<'a, SymInfo>, + pub segment_info: Vec<'a, LinkingSegment>, + pub init_funcs: Vec<'a, LinkingInitFunc>, + pub comdat_info: Vec<'a, LinkingComdat<'a>>, } impl<'a> LinkingSection<'a> { pub fn new(arena: &'a Bump) -> Self { LinkingSection { - subsections: Vec::with_capacity_in(1, arena), + symbol_table: Vec::with_capacity_in(16, arena), + segment_info: Vec::with_capacity_in(16, arena), + init_funcs: Vec::with_capacity_in(0, arena), + comdat_info: Vec::with_capacity_in(0, arena), } } - - pub fn symbol_table_mut(&mut self) -> &mut Vec<'a, SymInfo> { - for sub in self.subsections.iter_mut() { - if let LinkingSubSection::SymbolTable(syminfos) = sub { - return syminfos; - } - } - internal_error!("Symbol table not found"); - } } impl<'a> Serialize for LinkingSection<'a> { fn serialize(&self, buffer: &mut T) { let header_indices = write_custom_section_header(buffer, "linking"); buffer.append_u8(LINKING_VERSION); - for subsection in self.subsections.iter() { - subsection.serialize(buffer); - } + + serialize_subsection(buffer, SubSectionId::SymbolTable, &self.symbol_table); + serialize_subsection(buffer, SubSectionId::SegmentInfo, &self.segment_info); + serialize_subsection(buffer, SubSectionId::InitFuncs, &self.init_funcs); + serialize_subsection(buffer, SubSectionId::ComdatInfo, &self.comdat_info); + update_section_size(buffer, header_indices); } } diff --git a/compiler/gen_wasm/src/wasm_module/mod.rs b/compiler/gen_wasm/src/wasm_module/mod.rs index 5ff36428ed..2ac492e620 100644 --- a/compiler/gen_wasm/src/wasm_module/mod.rs +++ b/compiler/gen_wasm/src/wasm_module/mod.rs @@ -1,9 +1,214 @@ pub mod code_builder; +mod dead_code; pub mod linking; pub mod opcodes; pub mod sections; pub mod serialize; +use bumpalo::Bump; pub use code_builder::{Align, CodeBuilder, LocalId, ValueType, VmSymbolState}; -pub use linking::{LinkingSubSection, SymInfo}; -pub use sections::{ConstExpr, Export, ExportType, Global, GlobalType, Signature, WasmModule}; +pub use linking::SymInfo; +use roc_error_macros::internal_error; +pub use sections::{ConstExpr, Export, ExportType, Global, GlobalType, Signature}; + +use crate::wasm_module::serialize::SkipBytes; + +use self::linking::{LinkingSection, RelocationSection}; +use self::sections::{ + CodeSection, DataSection, ExportSection, FunctionSection, GlobalSection, ImportSection, + MemorySection, NameSection, OpaqueSection, Section, SectionId, TypeSection, +}; +use self::serialize::{SerialBuffer, Serialize}; + +/// A representation of the WebAssembly binary file format +/// https://webassembly.github.io/spec/core/binary/modules.html +#[derive(Debug)] +pub struct WasmModule<'a> { + pub types: TypeSection<'a>, + pub import: ImportSection<'a>, + pub function: FunctionSection<'a>, + pub table: OpaqueSection<'a>, + pub memory: MemorySection<'a>, + pub global: GlobalSection<'a>, + pub export: ExportSection<'a>, + pub start: OpaqueSection<'a>, + pub element: OpaqueSection<'a>, + pub code: CodeSection<'a>, + pub data: DataSection<'a>, + pub names: NameSection<'a>, + pub linking: LinkingSection<'a>, + pub relocations: RelocationSection<'a>, +} + +impl<'a> WasmModule<'a> { + pub const WASM_VERSION: u32 = 1; + + /// Create entries in the Type and Function sections for a function signature + pub fn add_function_signature(&mut self, signature: Signature<'a>) { + let index = self.types.insert(signature); + self.function.add_sig(index); + } + + /// Serialize the module to bytes + pub fn serialize(&self, buffer: &mut T) { + buffer.append_u8(0); + buffer.append_slice("asm".as_bytes()); + buffer.write_unencoded_u32(Self::WASM_VERSION); + + self.types.serialize(buffer); + self.import.serialize(buffer); + self.function.serialize(buffer); + self.table.serialize(buffer); + self.memory.serialize(buffer); + self.global.serialize(buffer); + self.export.serialize(buffer); + self.start.serialize(buffer); + self.element.serialize(buffer); + self.code.serialize(buffer); + self.data.serialize(buffer); + } + + /// Serialize the module to bytes + /// (Mutates some data related to linking) + pub fn serialize_with_linker_data_mut(&mut self, buffer: &mut T) { + buffer.append_u8(0); + buffer.append_slice("asm".as_bytes()); + buffer.write_unencoded_u32(Self::WASM_VERSION); + + // Keep track of (non-empty) section indices for linking + let mut counter = SectionCounter { + buffer_size: buffer.size(), + section_index: 0, + }; + + counter.serialize_and_count(buffer, &self.types); + counter.serialize_and_count(buffer, &self.import); + counter.serialize_and_count(buffer, &self.function); + counter.serialize_and_count(buffer, &self.table); + counter.serialize_and_count(buffer, &self.memory); + counter.serialize_and_count(buffer, &self.global); + counter.serialize_and_count(buffer, &self.export); + counter.serialize_and_count(buffer, &self.start); + counter.serialize_and_count(buffer, &self.element); + + // Code section is the only one with relocations so we can stop counting + let code_section_index = counter.section_index; + self.code + .serialize_with_relocs(buffer, &mut self.relocations.entries); + + self.data.serialize(buffer); + + self.linking.serialize(buffer); + + self.relocations.target_section_index = Some(code_section_index); + self.relocations.serialize(buffer); + } + + /// Module size in bytes (assuming no linker data) + /// May be slightly overestimated. Intended for allocating buffer capacity. + pub fn size(&self) -> usize { + self.types.size() + + self.import.size() + + self.function.size() + + self.table.size() + + self.memory.size() + + self.global.size() + + self.export.size() + + self.start.size() + + self.element.size() + + self.code.size() + + self.data.size() + } + + pub fn preload(arena: &'a Bump, bytes: &[u8]) -> Self { + let is_valid_magic_number = &bytes[0..4] == "\0asm".as_bytes(); + let is_valid_version = bytes[4..8] == Self::WASM_VERSION.to_le_bytes(); + if !is_valid_magic_number || !is_valid_version { + internal_error!("Invalid Wasm object file header for platform & builtins"); + } + + let mut cursor: usize = 8; + + let mut types = TypeSection::preload(arena, bytes, &mut cursor); + types.parse_offsets(); + + let import = ImportSection::preload(arena, bytes, &mut cursor); + let function = FunctionSection::preload(arena, bytes, &mut cursor); + let table = OpaqueSection::preload(SectionId::Table, arena, bytes, &mut cursor); + let memory = MemorySection::preload(arena, bytes, &mut cursor); + let global = GlobalSection::preload(arena, bytes, &mut cursor); + + ExportSection::skip_bytes(bytes, &mut cursor); + let export = ExportSection::empty(arena); + + let start = OpaqueSection::preload(SectionId::Start, arena, bytes, &mut cursor); + let element = OpaqueSection::preload(SectionId::Element, arena, bytes, &mut cursor); + let code = CodeSection::preload(arena, bytes, &mut cursor, import.function_count); + + let data = DataSection::preload(arena, bytes, &mut cursor); + + // Metadata sections + let names = NameSection::parse(arena, bytes, &mut cursor); + let linking = LinkingSection::new(arena); + let relocations = RelocationSection::new(arena, "reloc.CODE"); + + WasmModule { + types, + import, + function, + table, + memory, + global, + export, + start, + element, + code, + data, + names, + linking, + relocations, + } + } + + pub fn remove_dead_preloads>( + &mut self, + arena: &'a Bump, + called_preload_fns: T, + ) { + self.code.remove_dead_preloads( + arena, + self.import.function_count, + &self.export.function_indices, + called_preload_fns, + ) + } +} + +/// Helper struct to count non-empty sections. +/// Needed to generate linking data, which refers to target sections by index. +struct SectionCounter { + buffer_size: usize, + section_index: u32, +} + +impl SectionCounter { + /// Update the section counter if buffer size increased since last call + #[inline] + fn update(&mut self, buffer: &mut SB) { + let new_size = buffer.size(); + if new_size > self.buffer_size { + self.section_index += 1; + self.buffer_size = new_size; + } + } + + #[inline] + fn serialize_and_count( + &mut self, + buffer: &mut SB, + section: &S, + ) { + section.serialize(buffer); + self.update(buffer); + } +} diff --git a/compiler/gen_wasm/src/wasm_module/opcodes.rs b/compiler/gen_wasm/src/wasm_module/opcodes.rs index 868b77375d..04f779dfbf 100644 --- a/compiler/gen_wasm/src/wasm_module/opcodes.rs +++ b/compiler/gen_wasm/src/wasm_module/opcodes.rs @@ -1,3 +1,7 @@ +use roc_error_macros::internal_error; + +use super::serialize::{parse_u32_or_panic, SkipBytes}; + #[repr(u8)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum OpCode { @@ -180,3 +184,122 @@ pub enum OpCode { F32REINTERPRETI32 = 0xbe, F64REINTERPRETI64 = 0xbf, } + +/// The format of the *immediate* operands of an operator +/// Immediates appear directly in the byte stream after the opcode, +/// rather than being popped off the value stack. These are the possible forms. +enum OpImmediates { + NoImmediate, + Byte1, + Bytes4, + Bytes8, + Leb32x1, + Leb64x1, + Leb32x2, + BrTable, +} + +impl From for OpImmediates { + fn from(op: OpCode) -> Self { + use OpCode::*; + use OpImmediates::*; + + match op { + UNREACHABLE => NoImmediate, + NOP => NoImmediate, + BLOCK | LOOP | IF => Byte1, + ELSE => NoImmediate, + END => NoImmediate, + BR | BRIF => Leb32x1, + BRTABLE => BrTable, + RETURN => NoImmediate, + CALL => Leb32x1, + CALLINDIRECT => Leb32x2, + DROP => NoImmediate, + SELECT => NoImmediate, + GETLOCAL | SETLOCAL | TEELOCAL => Leb32x1, + GETGLOBAL | SETGLOBAL => Leb32x1, + + I32LOAD | I64LOAD | F32LOAD | F64LOAD | I32LOAD8S | I32LOAD8U | I32LOAD16S + | I32LOAD16U | I64LOAD8S | I64LOAD8U | I64LOAD16S | I64LOAD16U | I64LOAD32S + | I64LOAD32U | I32STORE | I64STORE | F32STORE | F64STORE | I32STORE8 | I32STORE16 + | I64STORE8 | I64STORE16 | I64STORE32 => Leb32x2, + + CURRENTMEMORY | GROWMEMORY => Byte1, + + I32CONST => Leb32x1, + I64CONST => Leb64x1, + F32CONST => Bytes4, + F64CONST => Bytes8, + + I32EQZ | I32EQ | I32NE | I32LTS | I32LTU | I32GTS | I32GTU | I32LES | I32LEU + | I32GES | I32GEU | I64EQZ | I64EQ | I64NE | I64LTS | I64LTU | I64GTS | I64GTU + | I64LES | I64LEU | I64GES | I64GEU | F32EQ | F32NE | F32LT | F32GT | F32LE | F32GE + | F64EQ | F64NE | F64LT | F64GT | F64LE | F64GE | I32CLZ | I32CTZ | I32POPCNT + | I32ADD | I32SUB | I32MUL | I32DIVS | I32DIVU | I32REMS | I32REMU | I32AND | I32OR + | I32XOR | I32SHL | I32SHRS | I32SHRU | I32ROTL | I32ROTR | I64CLZ | I64CTZ + | I64POPCNT | I64ADD | I64SUB | I64MUL | I64DIVS | I64DIVU | I64REMS | I64REMU + | I64AND | I64OR | I64XOR | I64SHL | I64SHRS | I64SHRU | I64ROTL | I64ROTR | F32ABS + | F32NEG | F32CEIL | F32FLOOR | F32TRUNC | F32NEAREST | F32SQRT | F32ADD | F32SUB + | F32MUL | F32DIV | F32MIN | F32MAX | F32COPYSIGN | F64ABS | F64NEG | F64CEIL + | F64FLOOR | F64TRUNC | F64NEAREST | F64SQRT | F64ADD | F64SUB | F64MUL | F64DIV + | F64MIN | F64MAX | F64COPYSIGN | I32WRAPI64 | I32TRUNCSF32 | I32TRUNCUF32 + | I32TRUNCSF64 | I32TRUNCUF64 | I64EXTENDSI32 | I64EXTENDUI32 | I64TRUNCSF32 + | I64TRUNCUF32 | I64TRUNCSF64 | I64TRUNCUF64 | F32CONVERTSI32 | F32CONVERTUI32 + | F32CONVERTSI64 | F32CONVERTUI64 | F32DEMOTEF64 | F64CONVERTSI32 | F64CONVERTUI32 + | F64CONVERTSI64 | F64CONVERTUI64 | F64PROMOTEF32 | I32REINTERPRETF32 + | I64REINTERPRETF64 | F32REINTERPRETI32 | F64REINTERPRETI64 => NoImmediate, + + // Catch-all in case of an invalid cast from u8 to OpCode while parsing binary + // (rustc keeps this code, I verified in Compiler Explorer) + #[allow(unreachable_patterns)] + _ => internal_error!("Unknown Wasm instruction 0x{:02x}", op as u8), + } + } +} + +impl SkipBytes for OpCode { + fn skip_bytes(bytes: &[u8], cursor: &mut usize) { + use OpImmediates::*; + + let opcode_byte: u8 = bytes[*cursor]; + + let opcode: OpCode = unsafe { std::mem::transmute(opcode_byte) }; + let immediates = OpImmediates::from(opcode); // will panic if transmute was invalid + + match immediates { + NoImmediate => { + *cursor += 1; + } + Byte1 => { + *cursor += 1 + 1; + } + Bytes4 => { + *cursor += 1 + 4; + } + Bytes8 => { + *cursor += 1 + 8; + } + Leb32x1 => { + *cursor += 1; + u32::skip_bytes(bytes, cursor); + } + Leb64x1 => { + *cursor += 1; + u64::skip_bytes(bytes, cursor); + } + Leb32x2 => { + *cursor += 1; + u32::skip_bytes(bytes, cursor); + u32::skip_bytes(bytes, cursor); + } + BrTable => { + *cursor += 1; + let n_labels = 1 + parse_u32_or_panic(bytes, cursor); + for _ in 0..n_labels { + u32::skip_bytes(bytes, cursor); + } + } + } + } +} diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index f67f51a1bd..ea8ca5ec62 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -1,11 +1,17 @@ use bumpalo::collections::vec::Vec; use bumpalo::Bump; +use roc_collections::all::MutMap; +use roc_error_macros::internal_error; -use super::linking::{ - IndexRelocType, LinkingSection, RelocationEntry, RelocationSection, SymInfo, WasmObjectSymbol, +use super::dead_code::{ + copy_preloads_shrinking_dead_fns, parse_preloads_call_graph, trace_call_graph, + PreloadsCallGraph, }; +use super::linking::RelocationEntry; use super::opcodes::OpCode; -use super::serialize::{SerialBuffer, Serialize}; +use super::serialize::{ + parse_u32_or_panic, SerialBuffer, Serialize, SkipBytes, MAX_SIZE_ENCODED_U32, +}; use super::{CodeBuilder, ValueType}; /******************************************************************* @@ -34,6 +40,96 @@ pub enum SectionId { DataCount = 12, } +const MAX_SIZE_SECTION_HEADER: usize = std::mem::size_of::() + 2 * MAX_SIZE_ENCODED_U32; + +pub trait Section<'a>: Sized { + const ID: SectionId; + + fn get_bytes(&self) -> &[u8]; + fn get_count(&self) -> u32; + + fn size(&self) -> usize { + MAX_SIZE_SECTION_HEADER + self.get_bytes().len() + } + + fn preload(arena: &'a Bump, module_bytes: &[u8], cursor: &mut usize) -> Self; +} + +macro_rules! section_impl { + ($structname: ident, $id: expr, $from_count_and_bytes: expr) => { + impl<'a> Section<'a> for $structname<'a> { + const ID: SectionId = $id; + + fn get_bytes(&self) -> &[u8] { + &self.bytes + } + + fn get_count(&self) -> u32 { + self.count + } + + fn preload(arena: &'a Bump, module_bytes: &[u8], cursor: &mut usize) -> Self { + let (count, initial_bytes) = parse_section(Self::ID, module_bytes, cursor); + let mut bytes = Vec::with_capacity_in(initial_bytes.len() * 2, arena); + bytes.extend_from_slice(initial_bytes); + $from_count_and_bytes(count, bytes) + } + + fn size(&self) -> usize { + section_size(self.get_bytes()) + } + } + }; + + ($structname: ident, $id: expr) => { + section_impl!($structname, $id, |count, bytes| $structname { + bytes, + count + }); + }; +} + +impl<'a, Sec> Serialize for Sec +where + Sec: Section<'a>, +{ + fn serialize(&self, buffer: &mut B) { + if !self.get_bytes().is_empty() { + let header_indices = write_section_header(buffer, Self::ID); + buffer.encode_u32(self.get_count()); + buffer.append_slice(self.get_bytes()); + update_section_size(buffer, header_indices); + } + } +} + +fn section_size(bytes: &[u8]) -> usize { + let id = 1; + let encoded_length = MAX_SIZE_ENCODED_U32; + let encoded_count = MAX_SIZE_ENCODED_U32; + + id + encoded_length + encoded_count + bytes.len() +} + +fn parse_section<'a>(id: SectionId, module_bytes: &'a [u8], cursor: &mut usize) -> (u32, &'a [u8]) { + if module_bytes[*cursor] != id as u8 { + return (0, &[]); + } + *cursor += 1; + + let section_size = parse_u32_or_panic(module_bytes, cursor); + let count_start = *cursor; + let count = parse_u32_or_panic(module_bytes, cursor); + let body_start = *cursor; + + let next_section_start = count_start + section_size as usize; + let body = &module_bytes[body_start..next_section_start]; + + *cursor = next_section_start; + + (count, body) +} + pub struct SectionHeaderIndices { size_index: usize, body_index: usize, @@ -71,19 +167,6 @@ pub fn update_section_size(buffer: &mut T, header_indices: Sect buffer.overwrite_padded_u32(header_indices.size_index, size as u32); } -/// Serialize a section that is just a vector of some struct -fn serialize_vector_section( - buffer: &mut B, - section_id: SectionId, - subsections: &[T], -) { - if !subsections.is_empty() { - let header_indices = write_section_header(buffer, section_id); - subsections.serialize(buffer); - update_section_size(buffer, header_indices); - } -} - /******************************************************************* * * Type section @@ -97,9 +180,13 @@ pub struct Signature<'a> { pub ret_type: Option, } +impl Signature<'_> { + pub const SEPARATOR: u8 = 0x60; +} + impl<'a> Serialize for Signature<'a> { fn serialize(&self, buffer: &mut T) { - buffer.append_u8(0x60); + buffer.append_u8(Self::SEPARATOR); self.param_types.serialize(buffer); self.ret_type.serialize(buffer); } @@ -108,35 +195,75 @@ impl<'a> Serialize for Signature<'a> { #[derive(Debug)] pub struct TypeSection<'a> { /// Private. See WasmModule::add_function_signature - signatures: Vec<'a, Signature<'a>>, + arena: &'a Bump, + bytes: Vec<'a, u8>, + offsets: Vec<'a, usize>, } impl<'a> TypeSection<'a> { - pub fn new(arena: &'a Bump, capacity: usize) -> Self { - TypeSection { - signatures: Vec::with_capacity_in(capacity, arena), - } - } - /// Find a matching signature or insert a new one. Return the index. pub fn insert(&mut self, signature: Signature<'a>) -> u32 { - // Using linear search because we need to preserve indices stored in - // the Function section. (Also for practical sizes it's fast) - let maybe_index = self.signatures.iter().position(|s| *s == signature); - match maybe_index { - Some(index) => index as u32, - None => { - let index = self.signatures.len(); - self.signatures.push(signature); - index as u32 + let mut sig_bytes = Vec::with_capacity_in(signature.param_types.len() + 4, self.arena); + signature.serialize(&mut sig_bytes); + + let sig_len = sig_bytes.len(); + let bytes_len = self.bytes.len(); + + for (i, offset) in self.offsets.iter().enumerate() { + let end = offset + sig_len; + if end > bytes_len { + break; } + if &self.bytes[*offset..end] == sig_bytes.as_slice() { + return i as u32; + } + } + + let sig_id = self.offsets.len(); + self.offsets.push(bytes_len); + self.bytes.extend_from_slice(&sig_bytes); + + sig_id as u32 + } + + pub fn parse_offsets(&mut self) { + self.offsets.clear(); + + let mut i = 0; + while i < self.bytes.len() { + self.offsets.push(i); + + debug_assert!(self.bytes[i] == Signature::SEPARATOR); + i += 1; + + let n_params = parse_u32_or_panic(&self.bytes, &mut i); + i += n_params as usize; // skip over one byte per param type + + let n_return_values = self.bytes[i]; + i += 1 + n_return_values as usize; } } } -impl<'a> Serialize for TypeSection<'a> { - fn serialize(&self, buffer: &mut T) { - serialize_vector_section(buffer, SectionId::Type, &self.signatures); +impl<'a> Section<'a> for TypeSection<'a> { + const ID: SectionId = SectionId::Type; + + fn get_bytes(&self) -> &[u8] { + &self.bytes + } + fn get_count(&self) -> u32 { + self.offsets.len() as u32 + } + + fn preload(arena: &'a Bump, module_bytes: &[u8], cursor: &mut usize) -> Self { + let (count, initial_bytes) = parse_section(Self::ID, module_bytes, cursor); + let mut bytes = Vec::with_capacity_in(initial_bytes.len() * 2, arena); + bytes.extend_from_slice(initial_bytes); + TypeSection { + arena, + bytes, + offsets: Vec::with_capacity_in(2 * count as usize, arena), + } } } @@ -166,6 +293,13 @@ impl Serialize for TableType { } } +impl SkipBytes for TableType { + fn skip_bytes(bytes: &[u8], cursor: &mut usize) { + u8::skip_bytes(bytes, cursor); + Limits::skip_bytes(bytes, cursor); + } +} + #[derive(Debug)] pub enum ImportDesc { Func { signature_index: u32 }, @@ -181,25 +315,49 @@ pub struct Import { pub description: ImportDesc, } +#[repr(u8)] +#[derive(Debug)] +enum ImportTypeId { + Func = 0, + Table = 1, + Mem = 2, + Global = 3, +} + +impl From for ImportTypeId { + fn from(x: u8) -> Self { + match x { + 0 => Self::Func, + 1 => Self::Table, + 2 => Self::Mem, + 3 => Self::Global, + _ => internal_error!( + "Invalid ImportTypeId {} in platform/builtins object file", + x + ), + } + } +} + impl Serialize for Import { fn serialize(&self, buffer: &mut T) { self.module.serialize(buffer); self.name.serialize(buffer); match &self.description { ImportDesc::Func { signature_index } => { - buffer.append_u8(0); + buffer.append_u8(ImportTypeId::Func as u8); buffer.encode_u32(*signature_index); } ImportDesc::Table { ty } => { - buffer.append_u8(1); + buffer.append_u8(ImportTypeId::Table as u8); ty.serialize(buffer); } ImportDesc::Mem { limits } => { - buffer.append_u8(2); + buffer.append_u8(ImportTypeId::Mem as u8); limits.serialize(buffer); } ImportDesc::Global { ty } => { - buffer.append_u8(3); + buffer.append_u8(ImportTypeId::Global as u8); ty.serialize(buffer); } } @@ -208,29 +366,63 @@ impl Serialize for Import { #[derive(Debug)] pub struct ImportSection<'a> { - pub entries: Vec<'a, Import>, + pub count: u32, + pub function_count: u32, + pub bytes: Vec<'a, u8>, } impl<'a> ImportSection<'a> { - pub fn new(arena: &'a Bump) -> Self { - ImportSection { - entries: bumpalo::vec![in arena], + pub fn append(&mut self, import: Import) { + import.serialize(&mut self.bytes); + self.count += 1; + } + + fn update_function_count(&mut self) { + let mut f_count = 0; + let mut cursor = 0; + while cursor < self.bytes.len() { + String::skip_bytes(&self.bytes, &mut cursor); + String::skip_bytes(&self.bytes, &mut cursor); + + let type_id = ImportTypeId::from(self.bytes[cursor]); + cursor += 1; + + match type_id { + ImportTypeId::Func => { + f_count += 1; + u32::skip_bytes(&self.bytes, &mut cursor); + } + ImportTypeId::Table => { + TableType::skip_bytes(&self.bytes, &mut cursor); + } + ImportTypeId::Mem => { + Limits::skip_bytes(&self.bytes, &mut cursor); + } + ImportTypeId::Global => { + GlobalType::skip_bytes(&self.bytes, &mut cursor); + } + } } + + self.function_count = f_count; } - pub fn function_count(&self) -> usize { - self.entries - .iter() - .filter(|import| matches!(import.description, ImportDesc::Func { .. })) - .count() + pub fn from_count_and_bytes(count: u32, bytes: Vec<'a, u8>) -> Self { + let mut created = ImportSection { + bytes, + count, + function_count: 0, + }; + created.update_function_count(); + created } } -impl<'a> Serialize for ImportSection<'a> { - fn serialize(&self, buffer: &mut T) { - serialize_vector_section(buffer, SectionId::Import, &self.entries); - } -} +section_impl!( + ImportSection, + SectionId::Import, + ImportSection::from_count_and_bytes +); /******************************************************************* * @@ -241,22 +433,19 @@ impl<'a> Serialize for ImportSection<'a> { #[derive(Debug)] pub struct FunctionSection<'a> { - pub signature_indices: Vec<'a, u32>, + pub count: u32, + pub bytes: Vec<'a, u8>, } impl<'a> FunctionSection<'a> { - pub fn new(arena: &'a Bump, capacity: usize) -> Self { - FunctionSection { - signature_indices: Vec::with_capacity_in(capacity, arena), - } - } -} -impl<'a> Serialize for FunctionSection<'a> { - fn serialize(&self, buffer: &mut T) { - serialize_vector_section(buffer, SectionId::Function, &self.signature_indices); + pub fn add_sig(&mut self, sig_id: u32) { + self.bytes.encode_u32(sig_id); + self.count += 1; } } +section_impl!(FunctionSection, SectionId::Function); + /******************************************************************* * * Memory section @@ -269,15 +458,21 @@ pub enum Limits { MinMax(u32, u32), } +#[repr(u8)] +enum LimitsId { + Min = 0, + MinMax = 1, +} + impl Serialize for Limits { fn serialize(&self, buffer: &mut T) { match self { Self::Min(min) => { - buffer.append_u8(0); + buffer.append_u8(LimitsId::Min as u8); buffer.encode_u32(*min); } Self::MinMax(min, max) => { - buffer.append_u8(1); + buffer.append_u8(LimitsId::MinMax as u8); buffer.encode_u32(*min); buffer.encode_u32(*max); } @@ -285,41 +480,45 @@ impl Serialize for Limits { } } -#[derive(Debug)] -pub struct MemorySection(Option); +impl SkipBytes for Limits { + fn skip_bytes(bytes: &[u8], cursor: &mut usize) { + let variant_id = bytes[*cursor]; + u8::skip_bytes(bytes, cursor); // advance past the variant byte + u32::skip_bytes(bytes, cursor); // skip "min" + if variant_id == LimitsId::MinMax as u8 { + u32::skip_bytes(bytes, cursor); // skip "max" + } + } +} -impl MemorySection { +#[derive(Debug)] +pub struct MemorySection<'a> { + pub count: u32, + pub bytes: Vec<'a, u8>, +} + +impl<'a> MemorySection<'a> { pub const PAGE_SIZE: u32 = 64 * 1024; - pub fn new(bytes: u32) -> Self { - if bytes == 0 { - MemorySection(None) - } else { - let pages = (bytes + Self::PAGE_SIZE - 1) / Self::PAGE_SIZE; - MemorySection(Some(Limits::Min(pages))) - } - } - - pub fn min_size(&self) -> Option { - match self { - MemorySection(Some(Limits::Min(min))) | MemorySection(Some(Limits::MinMax(min, _))) => { - Some(min * Self::PAGE_SIZE) + pub fn new(arena: &'a Bump, memory_bytes: u32) -> Self { + if memory_bytes == 0 { + MemorySection { + count: 0, + bytes: bumpalo::vec![in arena], } - MemorySection(None) => None, + } else { + let pages = (memory_bytes + Self::PAGE_SIZE - 1) / Self::PAGE_SIZE; + let limits = Limits::Min(pages); + + let mut bytes = Vec::with_capacity_in(12, arena); + limits.serialize(&mut bytes); + + MemorySection { count: 1, bytes } } } } -impl Serialize for MemorySection { - fn serialize(&self, buffer: &mut T) { - if let Some(limits) = &self.0 { - let header_indices = write_section_header(buffer, SectionId::Memory); - buffer.append_u8(1); - limits.serialize(buffer); - update_section_size(buffer, header_indices); - } - } -} +section_impl!(MemorySection, SectionId::Memory); /******************************************************************* * @@ -340,6 +539,12 @@ impl Serialize for GlobalType { } } +impl SkipBytes for GlobalType { + fn skip_bytes(_bytes: &[u8], cursor: &mut usize) { + *cursor += 2; + } +} + /// Constant expression for initialising globals or data segments /// Note: This is restricted for simplicity, but the spec allows arbitrary constant expressions #[derive(Debug)] @@ -391,15 +596,31 @@ impl Serialize for Global { #[derive(Debug)] pub struct GlobalSection<'a> { - pub entries: Vec<'a, Global>, + pub count: u32, + pub bytes: Vec<'a, u8>, } -impl<'a> Serialize for GlobalSection<'a> { - fn serialize(&self, buffer: &mut T) { - serialize_vector_section(buffer, SectionId::Global, &self.entries); +impl<'a> GlobalSection<'a> { + pub fn new(arena: &'a Bump, globals: &[Global]) -> Self { + let capacity = 13 * globals.len(); + let mut bytes = Vec::with_capacity_in(capacity, arena); + for global in globals { + global.serialize(&mut bytes); + } + GlobalSection { + count: globals.len() as u32, + bytes, + } + } + + pub fn append(&mut self, global: Global) { + global.serialize(&mut self.bytes); + self.count += 1; } } +section_impl!(GlobalSection, SectionId::Global); + /******************************************************************* * * Export section @@ -416,12 +637,13 @@ pub enum ExportType { } #[derive(Debug)] -pub struct Export { - pub name: String, +pub struct Export<'a> { + pub name: &'a [u8], pub ty: ExportType, pub index: u32, } -impl Serialize for Export { + +impl Serialize for Export<'_> { fn serialize(&self, buffer: &mut T) { self.name.serialize(buffer); buffer.append_u8(self.ty as u8); @@ -431,12 +653,49 @@ impl Serialize for Export { #[derive(Debug)] pub struct ExportSection<'a> { - pub entries: Vec<'a, Export>, + pub count: u32, + pub bytes: Vec<'a, u8>, + pub function_indices: Vec<'a, u32>, +} + +impl<'a> ExportSection<'a> { + const ID: SectionId = SectionId::Export; + + pub fn append(&mut self, export: Export) { + export.serialize(&mut self.bytes); + self.count += 1; + if matches!(export.ty, ExportType::Func) { + self.function_indices.push(export.index); + } + } + + pub fn size(&self) -> usize { + section_size(&self.bytes) + } + + pub fn empty(arena: &'a Bump) -> Self { + ExportSection { + count: 0, + bytes: Vec::with_capacity_in(256, arena), + function_indices: Vec::with_capacity_in(4, arena), + } + } +} + +impl SkipBytes for ExportSection<'_> { + fn skip_bytes(bytes: &[u8], cursor: &mut usize) { + parse_section(Self::ID, bytes, cursor); + } } impl<'a> Serialize for ExportSection<'a> { fn serialize(&self, buffer: &mut T) { - serialize_vector_section(buffer, SectionId::Export, &self.entries); + if !self.bytes.is_empty() { + let header_indices = write_section_header(buffer, Self::ID); + buffer.encode_u32(self.count); + buffer.append_slice(&self.bytes); + update_section_size(buffer, header_indices); + } } } @@ -448,7 +707,10 @@ impl<'a> Serialize for ExportSection<'a> { #[derive(Debug)] pub struct CodeSection<'a> { + pub preloaded_count: u32, + pub preloaded_bytes: &'a [u8], pub code_builders: Vec<'a, CodeBuilder<'a>>, + dead_code_metadata: PreloadsCallGraph<'a>, } impl<'a> CodeSection<'a> { @@ -459,7 +721,7 @@ impl<'a> CodeSection<'a> { relocations: &mut Vec<'a, RelocationEntry>, ) -> usize { let header_indices = write_section_header(buffer, SectionId::Code); - buffer.encode_u32(self.code_builders.len() as u32); + buffer.encode_u32(self.preloaded_count + self.code_builders.len() as u32); for code_builder in self.code_builders.iter() { code_builder.serialize_with_relocs(buffer, relocations, header_indices.body_index); @@ -469,6 +731,76 @@ impl<'a> CodeSection<'a> { update_section_size(buffer, header_indices); code_section_body_index } + + pub fn size(&self) -> usize { + let builders_size: usize = self.code_builders.iter().map(|cb| cb.size()).sum(); + + MAX_SIZE_SECTION_HEADER + self.preloaded_bytes.len() + builders_size + } + + pub fn preload( + arena: &'a Bump, + module_bytes: &[u8], + cursor: &mut usize, + import_fn_count: u32, + ) -> Self { + let (preloaded_count, initial_bytes) = parse_section(SectionId::Code, module_bytes, cursor); + let preloaded_bytes = arena.alloc_slice_copy(initial_bytes); + + // TODO: Try to move this call_graph preparation to platform build time + let dead_code_metadata = + parse_preloads_call_graph(arena, preloaded_count, initial_bytes, import_fn_count); + + CodeSection { + preloaded_count, + preloaded_bytes, + code_builders: Vec::with_capacity_in(0, arena), + dead_code_metadata, + } + } + + pub(super) fn remove_dead_preloads>( + &mut self, + arena: &'a Bump, + import_fn_count: u32, + exported_fns: &[u32], + called_preload_fns: T, + ) { + let live_ext_fn_indices = trace_call_graph( + arena, + &self.dead_code_metadata, + exported_fns, + called_preload_fns, + ); + + let mut buffer = Vec::with_capacity_in(self.preloaded_bytes.len(), arena); + + copy_preloads_shrinking_dead_fns( + arena, + &mut buffer, + &self.dead_code_metadata, + self.preloaded_bytes, + import_fn_count, + live_ext_fn_indices, + ); + + self.preloaded_bytes = buffer.into_bump_slice(); + } +} + +impl<'a> Serialize for CodeSection<'a> { + fn serialize(&self, buffer: &mut T) { + let header_indices = write_section_header(buffer, SectionId::Code); + buffer.encode_u32(self.preloaded_count + self.code_builders.len() as u32); + + buffer.append_slice(self.preloaded_bytes); + + for code_builder in self.code_builders.iter() { + code_builder.serialize(buffer); + } + + update_section_size(buffer, header_indices); + } } /******************************************************************* @@ -485,6 +817,14 @@ pub enum DataMode { Passive, } +impl DataMode { + pub fn active_at(offset: u32) -> Self { + DataMode::Active { + offset: ConstExpr::I32(offset as i32), + } + } +} + #[derive(Debug)] pub struct DataSegment<'a> { pub mode: DataMode, @@ -495,11 +835,11 @@ impl Serialize for DataSegment<'_> { fn serialize(&self, buffer: &mut T) { match &self.mode { DataMode::Active { offset } => { - buffer.append_u8(0); + buffer.append_u8(0); // variant ID offset.serialize(buffer); } DataMode::Passive => { - buffer.append_u8(1); + buffer.append_u8(1); // variant ID } } @@ -509,196 +849,218 @@ impl Serialize for DataSegment<'_> { #[derive(Debug)] pub struct DataSection<'a> { - pub segments: Vec<'a, DataSegment<'a>>, + count: u32, + bytes: Vec<'a, u8>, } impl<'a> DataSection<'a> { - fn is_empty(&self) -> bool { - self.segments.is_empty() || self.segments.iter().all(|seg| seg.init.is_empty()) + pub fn append_segment(&mut self, segment: DataSegment<'a>) -> u32 { + let index = self.count; + self.count += 1; + segment.serialize(&mut self.bytes); + index } } -impl Serialize for DataSection<'_> { +section_impl!(DataSection, SectionId::Data); + +/******************************************************************* + * + * Opaque section + * + *******************************************************************/ + +/// A Wasm module section that we don't use for Roc code, +/// but may be present in a preloaded binary +#[derive(Debug, Default)] +pub struct OpaqueSection<'a> { + bytes: &'a [u8], +} + +impl<'a> OpaqueSection<'a> { + pub fn size(&self) -> usize { + self.bytes.len() + } + + pub fn preload( + id: SectionId, + arena: &'a Bump, + module_bytes: &[u8], + cursor: &mut usize, + ) -> Self { + let bytes: &[u8]; + + if module_bytes[*cursor] != id as u8 { + bytes = &[]; + } else { + let section_start = *cursor; + *cursor += 1; + let section_size = parse_u32_or_panic(module_bytes, cursor); + let next_section_start = *cursor + section_size as usize; + bytes = &module_bytes[section_start..next_section_start]; + *cursor = next_section_start; + }; + + OpaqueSection { + bytes: arena.alloc_slice_clone(bytes), + } + } +} + +impl Serialize for OpaqueSection<'_> { fn serialize(&self, buffer: &mut T) { - if !self.is_empty() { - serialize_vector_section(buffer, SectionId::Data, &self.segments); + buffer.append_slice(self.bytes); + } +} + +/******************************************************************* + * + * Name section + * https://webassembly.github.io/spec/core/appendix/custom.html#name-section + * + *******************************************************************/ + +#[repr(u8)] +#[allow(dead_code)] +enum NameSubSections { + ModuleName = 0, + FunctionNames = 1, + LocalNames = 2, +} + +#[derive(Debug, Default)] +pub struct NameSection<'a> { + pub functions: MutMap<&'a [u8], u32>, +} + +impl<'a> NameSection<'a> { + const ID: SectionId = SectionId::Custom; + const NAME: &'static str = "name"; + + pub fn parse(arena: &'a Bump, module_bytes: &[u8], cursor: &mut usize) -> Self { + let functions = MutMap::default(); + let mut section = NameSection { functions }; + section.parse_help(arena, module_bytes, cursor); + section + } + + fn parse_help(&mut self, arena: &'a Bump, module_bytes: &[u8], cursor: &mut usize) { + // Custom section ID + let section_id_byte = module_bytes[*cursor]; + if section_id_byte != Self::ID as u8 { + internal_error!( + "Expected section ID 0x{:x}, but found 0x{:x} at offset 0x{:x}", + Self::ID as u8, + section_id_byte, + *cursor + ); + } + *cursor += 1; + + // Section size + let section_size = parse_u32_or_panic(module_bytes, cursor); + let section_end = *cursor + section_size as usize; + + // Custom section name + let section_name_len = parse_u32_or_panic(module_bytes, cursor); + let section_name_end = *cursor + section_name_len as usize; + let section_name = &module_bytes[*cursor..section_name_end]; + if section_name != Self::NAME.as_bytes() { + internal_error!( + "Expected Custon section {:?}, found {:?}", + Self::NAME, + std::str::from_utf8(section_name) + ); + } + *cursor = section_name_end; + + // Find function names subsection + let mut found_function_names = false; + for _possible_subsection_id in 0..2 { + let subsection_id = module_bytes[*cursor]; + *cursor += 1; + let subsection_size = parse_u32_or_panic(module_bytes, cursor); + if subsection_id == NameSubSections::FunctionNames as u8 { + found_function_names = true; + break; + } + *cursor += subsection_size as usize; + if *cursor >= section_end { + internal_error!("Failed to parse Name section"); + } + } + if !found_function_names { + internal_error!("Failed to parse Name section"); + } + + // Function names + let num_entries = parse_u32_or_panic(module_bytes, cursor) as usize; + for _ in 0..num_entries { + let fn_index = parse_u32_or_panic(module_bytes, cursor); + let name_len = parse_u32_or_panic(module_bytes, cursor); + let name_end = *cursor + name_len as usize; + let name_bytes: &[u8] = &module_bytes[*cursor..name_end]; + *cursor = name_end; + + self.functions + .insert(arena.alloc_slice_copy(name_bytes), fn_index); } } } /******************************************************************* * - * Module - * - * https://webassembly.github.io/spec/core/binary/modules.html + * Unit tests * *******************************************************************/ -/// Helper struct to count non-empty sections. -/// Needed to generate linking data, which refers to target sections by index. -struct SectionCounter { - buffer_size: usize, - section_index: u32, -} +#[cfg(test)] +mod tests { + use super::*; + use bumpalo::{self, collections::Vec, Bump}; -impl SectionCounter { - /// Update the section counter if buffer size increased since last call - #[inline] - fn update(&mut self, buffer: &mut SB) { - let new_size = buffer.size(); - if new_size > self.buffer_size { - self.section_index += 1; - self.buffer_size = new_size; - } + fn test_assert_types_preload<'a>(arena: &'a Bump, original: &TypeSection<'a>) { + // Serialize the Type section that we built from Roc code + let mut original_serialized = Vec::with_capacity_in(6 + original.bytes.len(), arena); + original.serialize(&mut original_serialized); + + // Reconstruct a new TypeSection by "pre-loading" the bytes of the original + let mut cursor = 0; + let mut preloaded = TypeSection::preload(arena, &original_serialized, &mut cursor); + preloaded.parse_offsets(); + + debug_assert_eq!(original.offsets, preloaded.offsets); + debug_assert_eq!(original.bytes, preloaded.bytes); } - #[inline] - fn serialize_and_count( - &mut self, - buffer: &mut SB, - section: &S, - ) { - section.serialize(buffer); - self.update(buffer); - } -} - -#[derive(Debug)] -pub struct WasmModule<'a> { - pub types: TypeSection<'a>, - pub import: ImportSection<'a>, - pub function: FunctionSection<'a>, - /// Dummy placeholder for tables (used for function pointers and host references) - pub table: (), - pub memory: MemorySection, - pub global: GlobalSection<'a>, - pub export: ExportSection<'a>, - /// Dummy placeholder for start function. In Roc, this would be part of the platform. - pub start: (), - /// Dummy placeholder for table elements. Roc does not use tables. - pub element: (), - pub code: CodeSection<'a>, - pub data: DataSection<'a>, - pub linking: LinkingSection<'a>, - pub relocations: RelocationSection<'a>, -} - -impl<'a> WasmModule<'a> { - pub const WASM_VERSION: u32 = 1; - - /// Create entries in the Type and Function sections for a function signature - pub fn add_function_signature(&mut self, signature: Signature<'a>) { - let index = self.types.insert(signature); - self.function.signature_indices.push(index); - } - - /// Serialize the module to bytes - /// (Mutates some data related to linking) - #[allow(clippy::unit_arg)] - pub fn serialize_mut(&mut self, buffer: &mut T) { - buffer.append_u8(0); - buffer.append_slice("asm".as_bytes()); - buffer.write_unencoded_u32(Self::WASM_VERSION); - - // Keep track of (non-empty) section indices for linking - let mut counter = SectionCounter { - buffer_size: buffer.size(), - section_index: 0, + #[test] + fn test_type_section() { + use ValueType::*; + let arena = &Bump::new(); + let signatures = [ + Signature { + param_types: bumpalo::vec![in arena], + ret_type: None, + }, + Signature { + param_types: bumpalo::vec![in arena; I32, I64, F32, F64], + ret_type: None, + }, + Signature { + param_types: bumpalo::vec![in arena; I32, I32, I32], + ret_type: Some(I32), + }, + ]; + let capacity = signatures.len(); + let mut section = TypeSection { + arena, + bytes: Vec::with_capacity_in(capacity * 4, arena), + offsets: Vec::with_capacity_in(capacity, arena), }; - // If we have imports, then references to other functions need to be re-indexed. - // Modify exports before serializing them, since we don't have linker data for them - let n_imported_fns = self.import.function_count() as u32; - if n_imported_fns > 0 { - self.finalize_exported_fn_indices(n_imported_fns); - } - - counter.serialize_and_count(buffer, &self.types); - counter.serialize_and_count(buffer, &self.import); - counter.serialize_and_count(buffer, &self.function); - counter.serialize_and_count(buffer, &self.table); - counter.serialize_and_count(buffer, &self.memory); - counter.serialize_and_count(buffer, &self.global); - counter.serialize_and_count(buffer, &self.export); - counter.serialize_and_count(buffer, &self.start); - counter.serialize_and_count(buffer, &self.element); - - // Code section is the only one with relocations so we can stop counting - let code_section_index = counter.section_index; - let code_section_body_index = self - .code - .serialize_with_relocs(buffer, &mut self.relocations.entries); - - // If we have imports, references to other functions need to be re-indexed. - // Simplest to do after serialization, using linker data - if n_imported_fns > 0 { - self.finalize_code_fn_indices(buffer, code_section_body_index, n_imported_fns); - } - - self.data.serialize(buffer); - - self.linking.serialize(buffer); - - self.relocations.target_section_index = Some(code_section_index); - self.relocations.serialize(buffer); - } - - /// Shift indices of exported functions to make room for imported functions, - /// which come first in the function index space. - /// Must be called after traversing the full IR, but before export section is serialized. - fn finalize_exported_fn_indices(&mut self, n_imported_fns: u32) { - for export in self.export.entries.iter_mut() { - if export.ty == ExportType::Func { - export.index += n_imported_fns; - } - } - } - - /// Re-index internally-defined functions to make room for imported functions. - /// Imported functions come first in the index space, but we didn't know how many we needed until now. - /// We do this after serializing the code section, since we have linker data that is literally - /// *designed* for changing function indices in serialized code! - fn finalize_code_fn_indices( - &mut self, - buffer: &mut T, - code_section_body_index: usize, - n_imported_fns: u32, - ) { - let symbol_table = self.linking.symbol_table_mut(); - - // Lookup vector of symbol index to new function index - let mut new_index_lookup = std::vec::Vec::with_capacity(symbol_table.len()); - - // Modify symbol table entries and fill the lookup vector - for sym_info in symbol_table.iter_mut() { - match sym_info { - SymInfo::Function(WasmObjectSymbol::Defined { index, .. }) => { - let new_fn_index = *index + n_imported_fns; - *index = new_fn_index; - new_index_lookup.push(new_fn_index); - } - SymInfo::Function(WasmObjectSymbol::Imported { index, .. }) => { - new_index_lookup.push(*index); - } - _ => { - // Symbol is not a function, so we won't look it up. Use a dummy value. - new_index_lookup.push(u32::MAX); - } - } - } - - // Modify call instructions, using linker data - for reloc in &self.relocations.entries { - if let RelocationEntry::Index { - type_id: IndexRelocType::FunctionIndexLeb, - offset, - symbol_index, - } = reloc - { - let new_fn_index = new_index_lookup[*symbol_index as usize]; - let buffer_index = code_section_body_index + (*offset as usize); - buffer.overwrite_padded_u32(buffer_index, new_fn_index); - } + for sig in signatures { + section.insert(sig); } + test_assert_types_preload(arena, §ion); } } diff --git a/compiler/gen_wasm/src/wasm_module/serialize.rs b/compiler/gen_wasm/src/wasm_module/serialize.rs index 9445d8c30b..99561309c5 100644 --- a/compiler/gen_wasm/src/wasm_module/serialize.rs +++ b/compiler/gen_wasm/src/wasm_module/serialize.rs @@ -1,8 +1,15 @@ -use std::fmt::Debug; +use std::{fmt::Debug, iter::FromIterator}; use bumpalo::collections::vec::Vec; +use roc_error_macros::internal_error; -pub trait Serialize { +/// In the WebAssembly binary format, all integers are variable-length encoded (using LEB-128) +/// A small value like 3 or 100 is encoded as 1 byte. The value 128 needs 2 bytes, etc. +/// In practice, this saves space, since small numbers used more often than large numbers. +/// Of course there is a price for this - an encoded U32 can be up to 5 bytes wide. +pub const MAX_SIZE_ENCODED_U32: usize = 5; + +pub(super) trait Serialize { fn serialize(&self, buffer: &mut T); } @@ -121,7 +128,7 @@ macro_rules! encode_padded_sleb128 { /// write a maximally-padded SLEB128 integer (only used in relocations) fn $name(&mut self, value: $ty) { let mut x = value; - let size = (std::mem::size_of::<$ty>() / 4) * 5; + let size = (std::mem::size_of::<$ty>() / 4) * MAX_SIZE_ENCODED_U32; for _ in 0..(size - 1) { self.append_u8(0x80 | (x & 0x7f) as u8); x >>= 7; @@ -186,18 +193,18 @@ impl SerialBuffer for std::vec::Vec { } fn reserve_padded_u32(&mut self) -> usize { let index = self.len(); - self.resize(index + 5, 0xff); + self.resize(index + MAX_SIZE_ENCODED_U32, 0xff); index } fn encode_padded_u32(&mut self, value: u32) -> usize { let index = self.len(); - let new_len = index + 5; + let new_len = index + MAX_SIZE_ENCODED_U32; self.resize(new_len, 0); overwrite_padded_u32_help(&mut self[index..new_len], value); index } fn overwrite_padded_u32(&mut self, index: usize, value: u32) { - overwrite_padded_u32_help(&mut self[index..(index + 5)], value); + overwrite_padded_u32_help(&mut self[index..(index + MAX_SIZE_ENCODED_U32)], value); } } @@ -216,18 +223,97 @@ impl<'a> SerialBuffer for Vec<'a, u8> { } fn reserve_padded_u32(&mut self) -> usize { let index = self.len(); - self.resize(index + 5, 0xff); + self.resize(index + MAX_SIZE_ENCODED_U32, 0xff); index } fn encode_padded_u32(&mut self, value: u32) -> usize { let index = self.len(); - let new_len = index + 5; + let new_len = index + MAX_SIZE_ENCODED_U32; self.resize(new_len, 0); overwrite_padded_u32_help(&mut self[index..new_len], value); index } fn overwrite_padded_u32(&mut self, index: usize, value: u32) { - overwrite_padded_u32_help(&mut self[index..(index + 5)], value); + overwrite_padded_u32_help(&mut self[index..(index + MAX_SIZE_ENCODED_U32)], value); + } +} + +/// Decode an unsigned 32-bit integer from the provided buffer in LEB-128 format +/// Return the integer itself and the offset after it ends +pub fn decode_u32(bytes: &[u8]) -> Result<(u32, usize), String> { + let mut value = 0; + let mut shift = 0; + for (i, byte) in bytes.iter().take(MAX_SIZE_ENCODED_U32).enumerate() { + value += ((byte & 0x7f) as u32) << shift; + if (byte & 0x80) == 0 { + return Ok((value, i + 1)); + } + shift += 7; + } + Err(format!( + "Failed to decode u32 as LEB-128 from bytes: {:2x?}", + std::vec::Vec::from_iter(bytes.iter().take(MAX_SIZE_ENCODED_U32)) + )) +} + +pub fn parse_u32_or_panic(bytes: &[u8], cursor: &mut usize) -> u32 { + let (value, len) = decode_u32(&bytes[*cursor..]).unwrap_or_else(|e| internal_error!("{}", e)); + *cursor += len; + value +} + +/// Skip over serialized bytes for a type +/// This may, or may not, require looking at the byte values +pub trait SkipBytes { + fn skip_bytes(bytes: &[u8], cursor: &mut usize); +} + +impl SkipBytes for u32 { + fn skip_bytes(bytes: &[u8], cursor: &mut usize) { + const MAX_LEN: usize = 5; + for (i, byte) in bytes.iter().enumerate().skip(*cursor).take(MAX_LEN) { + if byte & 0x80 == 0 { + *cursor = i + 1; + return; + } + } + internal_error!("Invalid LEB encoding"); + } +} + +impl SkipBytes for u64 { + fn skip_bytes(bytes: &[u8], cursor: &mut usize) { + const MAX_LEN: usize = 10; + for (i, byte) in bytes.iter().enumerate().skip(*cursor).take(MAX_LEN) { + if byte & 0x80 == 0 { + *cursor = i + 1; + return; + } + } + internal_error!("Invalid LEB encoding"); + } +} + +impl SkipBytes for u8 { + fn skip_bytes(_bytes: &[u8], cursor: &mut usize) { + *cursor += 1; + } +} + +/// Note: This is just for skipping over Wasm bytes. We don't actually care about String vs str! +impl SkipBytes for String { + fn skip_bytes(bytes: &[u8], cursor: &mut usize) { + let len = parse_u32_or_panic(bytes, cursor); + + if false { + let str_bytes = &bytes[*cursor..(*cursor + len as usize)]; + println!( + "Skipping string {:?}", + std::str::from_utf8(str_bytes).unwrap() + ); + } + + *cursor += len as usize; } } @@ -237,7 +323,7 @@ mod tests { use bumpalo::{self, collections::Vec, Bump}; fn help_u32<'a>(arena: &'a Bump, value: u32) -> Vec<'a, u8> { - let mut buffer = Vec::with_capacity_in(5, arena); + let mut buffer = Vec::with_capacity_in(MAX_SIZE_ENCODED_U32, arena); buffer.encode_u32(value); buffer } @@ -276,7 +362,7 @@ mod tests { } fn help_i32<'a>(arena: &'a Bump, value: i32) -> Vec<'a, u8> { - let mut buffer = Vec::with_capacity_in(5, arena); + let mut buffer = Vec::with_capacity_in(MAX_SIZE_ENCODED_U32, arena); buffer.encode_i32(value); buffer } @@ -363,7 +449,7 @@ mod tests { } fn help_pad_i32(val: i32) -> std::vec::Vec { - let mut buffer = std::vec::Vec::with_capacity(5); + let mut buffer = std::vec::Vec::with_capacity(MAX_SIZE_ENCODED_U32); buffer.encode_padded_i32(val); buffer } @@ -406,4 +492,37 @@ mod tests { &[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f], ); } + + #[test] + fn test_decode_u32() { + assert_eq!(decode_u32(&[0]), Ok((0, 1))); + assert_eq!(decode_u32(&[64]), Ok((64, 1))); + assert_eq!(decode_u32(&[0x7f]), Ok((0x7f, 1))); + assert_eq!(decode_u32(&[0x80, 0x01]), Ok((0x80, 2))); + assert_eq!(decode_u32(&[0xff, 0x7f]), Ok((0x3fff, 2))); + assert_eq!(decode_u32(&[0x80, 0x80, 0x01]), Ok((0x4000, 3))); + assert_eq!( + decode_u32(&[0xff, 0xff, 0xff, 0xff, 0x0f]), + Ok((u32::MAX, MAX_SIZE_ENCODED_U32)) + ); + assert!(matches!(decode_u32(&[0x80; 6]), Err(_))); + assert!(matches!(decode_u32(&[0x80; 2]), Err(_))); + assert!(matches!(decode_u32(&[]), Err(_))); + } + + #[test] + fn test_parse_u32_sequence() { + let bytes = &[0, 0x80, 0x01, 0xff, 0xff, 0xff, 0xff, 0x0f]; + let expected = [0, 128, u32::MAX]; + let mut cursor = 0; + + assert_eq!(parse_u32_or_panic(bytes, &mut cursor), expected[0]); + assert_eq!(cursor, 1); + + assert_eq!(parse_u32_or_panic(bytes, &mut cursor), expected[1]); + assert_eq!(cursor, 3); + + assert_eq!(parse_u32_or_panic(bytes, &mut cursor), expected[2]); + assert_eq!(cursor, 8); + } } diff --git a/compiler/load/Cargo.toml b/compiler/load/Cargo.toml index ac6505b016..57f4ffd530 100644 --- a/compiler/load/Cargo.toml +++ b/compiler/load/Cargo.toml @@ -18,11 +18,12 @@ roc_unify = { path = "../unify" } roc_parse = { path = "../parse" } roc_solve = { path = "../solve" } roc_mono = { path = "../mono" } +roc_target = { path = "../roc_target" } 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" diff --git a/compiler/load/src/effect_module.rs b/compiler/load/src/effect_module.rs deleted file mode 100644 index 738330faf4..0000000000 --- a/compiler/load/src/effect_module.rs +++ /dev/null @@ -1,819 +0,0 @@ -use roc_can::annotation::IntroducedVariables; -use roc_can::def::{Declaration, Def}; -use roc_can::env::Env; -use roc_can::expr::{ClosureData, Expr, Recursive}; -use roc_can::pattern::Pattern; -use roc_can::scope::Scope; -use roc_collections::all::{MutSet, SendMap}; -use roc_module::called_via::CalledVia; -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; - -/// 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); - -pub const BUILTIN_EFFECT_FUNCTIONS: [(&str, Builder); 3] = [ - // 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), -]; - -// 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( - env: &mut Env, - scope: &mut Scope, - effect_symbol: Symbol, - var_store: &mut VarStore, - exposed_symbols: &mut MutSet, - declarations: &mut Vec, -) { - for (_, f) in BUILTIN_EFFECT_FUNCTIONS.iter() { - let (symbol, def) = f( - env, - scope, - effect_symbol, - TagName::Private(effect_symbol), - var_store, - ); - - exposed_symbols.insert(symbol); - declarations.push(Declaration::Declare(def)); - } -} - -fn build_effect_always( - env: &mut Env, - scope: &mut Scope, - effect_symbol: Symbol, - effect_tag_name: TagName, - var_store: &mut VarStore, -) -> (Symbol, Def) { - // Effect.always = \value -> @Effect \{} -> value - - let value_symbol = { - scope - .introduce( - "effect_always_value".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() - }; - - let inner_closure_symbol = { - scope - .introduce( - "effect_always_inner".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() - }; - - let always_symbol = { - scope - .introduce( - "always".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() - }; - - // \{} -> value - let const_closure = { - let arguments = vec![( - var_store.fresh(), - Loc::at_zero(empty_record_pattern(var_store)), - )]; - - let body = Expr::Var(value_symbol); - - Expr::Closure(ClosureData { - function_type: var_store.fresh(), - closure_type: var_store.fresh(), - closure_ext_var: var_store.fresh(), - return_type: var_store.fresh(), - name: inner_closure_symbol, - captured_symbols: vec![(value_symbol, var_store.fresh())], - recursive: Recursive::NotRecursive, - arguments, - loc_body: Box::new(Loc::at_zero(body)), - }) - }; - - // \value -> @Effect \{} -> value - let (function_var, always_closure) = { - // `@Effect \{} -> value` - let body = Expr::Tag { - variant_var: var_store.fresh(), - ext_var: var_store.fresh(), - name: effect_tag_name.clone(), - arguments: vec![(var_store.fresh(), Loc::at_zero(const_closure))], - }; - - let arguments = vec![( - var_store.fresh(), - Loc::at_zero(Pattern::Identifier(value_symbol)), - )]; - - let function_var = var_store.fresh(); - let closure = Expr::Closure(ClosureData { - function_type: function_var, - closure_type: var_store.fresh(), - closure_ext_var: var_store.fresh(), - return_type: var_store.fresh(), - name: always_symbol, - captured_symbols: Vec::new(), - recursive: Recursive::NotRecursive, - arguments, - loc_body: Box::new(Loc::at_zero(body)), - }); - - (function_var, closure) - }; - - let mut introduced_variables = IntroducedVariables::default(); - - let signature = { - // Effect.always : a -> Effect a - let var_a = var_store.fresh(); - introduced_variables.insert_named("a".into(), var_a); - - let effect_a = build_effect_alias( - effect_symbol, - effect_tag_name, - "a", - var_a, - Type::Variable(var_a), - var_store, - &mut introduced_variables, - ); - - let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(closure_var); - - Type::Function( - vec![Type::Variable(var_a)], - Box::new(Type::Variable(closure_var)), - Box::new(effect_a), - ) - }; - - let def_annotation = roc_can::def::Annotation { - signature, - introduced_variables, - aliases: SendMap::default(), - region: Region::zero(), - }; - - let pattern = Pattern::Identifier(always_symbol); - let mut pattern_vars = SendMap::default(); - pattern_vars.insert(always_symbol, function_var); - let def = Def { - loc_pattern: Loc::at_zero(pattern), - loc_expr: Loc::at_zero(always_closure), - expr_var: function_var, - pattern_vars, - annotation: Some(def_annotation), - }; - - (always_symbol, def) -} - -fn build_effect_map( - env: &mut Env, - scope: &mut Scope, - effect_symbol: Symbol, - effect_tag_name: TagName, - var_store: &mut VarStore, -) -> (Symbol, Def) { - // Effect.map = \@Effect thunk, mapper -> @Effect \{} -> mapper (thunk {}) - - let thunk_symbol = { - scope - .introduce( - "effect_map_thunk".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() - }; - - let mapper_symbol = { - scope - .introduce( - "effect_map_mapper".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() - }; - - let map_symbol = { - scope - .introduce( - "map".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() - }; - - // `thunk {}` - let force_thunk_call = { - let boxed = ( - var_store.fresh(), - Loc::at_zero(Expr::Var(thunk_symbol)), - var_store.fresh(), - var_store.fresh(), - ); - - let arguments = vec![(var_store.fresh(), Loc::at_zero(Expr::EmptyRecord))]; - Expr::Call(Box::new(boxed), arguments, CalledVia::Space) - }; - - // `toEffect (thunk {})` - let mapper_call = { - let boxed = ( - var_store.fresh(), - Loc::at_zero(Expr::Var(mapper_symbol)), - var_store.fresh(), - var_store.fresh(), - ); - - let arguments = vec![(var_store.fresh(), Loc::at_zero(force_thunk_call))]; - Expr::Call(Box::new(boxed), arguments, CalledVia::Space) - }; - - let inner_closure_symbol = { - scope - .introduce( - "effect_map_inner".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() - }; - - // \{} -> mapper (thunk {}) - let inner_closure = { - let arguments = vec![( - var_store.fresh(), - Loc::at_zero(empty_record_pattern(var_store)), - )]; - - Expr::Closure(ClosureData { - function_type: var_store.fresh(), - closure_type: var_store.fresh(), - closure_ext_var: var_store.fresh(), - return_type: var_store.fresh(), - name: inner_closure_symbol, - captured_symbols: vec![ - (thunk_symbol, var_store.fresh()), - (mapper_symbol, var_store.fresh()), - ], - recursive: Recursive::NotRecursive, - arguments, - loc_body: Box::new(Loc::at_zero(mapper_call)), - }) - }; - - let arguments = vec![ - ( - var_store.fresh(), - Loc::at_zero(Pattern::AppliedTag { - whole_var: var_store.fresh(), - ext_var: var_store.fresh(), - tag_name: effect_tag_name.clone(), - arguments: vec![( - var_store.fresh(), - Loc::at_zero(Pattern::Identifier(thunk_symbol)), - )], - }), - ), - ( - var_store.fresh(), - Loc::at_zero(Pattern::Identifier(mapper_symbol)), - ), - ]; - - // `@Effect \{} -> (mapper (thunk {}))` - let body = Expr::Tag { - variant_var: var_store.fresh(), - ext_var: var_store.fresh(), - name: effect_tag_name.clone(), - arguments: vec![(var_store.fresh(), Loc::at_zero(inner_closure))], - }; - - let function_var = var_store.fresh(); - let map_closure = Expr::Closure(ClosureData { - function_type: function_var, - closure_type: var_store.fresh(), - closure_ext_var: var_store.fresh(), - return_type: var_store.fresh(), - name: map_symbol, - captured_symbols: Vec::new(), - recursive: Recursive::NotRecursive, - arguments, - loc_body: Box::new(Loc::at_zero(body)), - }); - - let mut introduced_variables = IntroducedVariables::default(); - - let signature = { - // Effect.map : Effect a, (a -> b) -> Effect b - let var_a = var_store.fresh(); - let var_b = var_store.fresh(); - - introduced_variables.insert_named("a".into(), var_a); - introduced_variables.insert_named("b".into(), var_b); - - let effect_a = build_effect_alias( - effect_symbol, - effect_tag_name.clone(), - "a", - var_a, - Type::Variable(var_a), - var_store, - &mut introduced_variables, - ); - - let effect_b = build_effect_alias( - effect_symbol, - effect_tag_name, - "b", - var_b, - Type::Variable(var_b), - var_store, - &mut introduced_variables, - ); - - let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(closure_var); - let a_to_b = { - Type::Function( - vec![Type::Variable(var_a)], - Box::new(Type::Variable(closure_var)), - Box::new(Type::Variable(var_b)), - ) - }; - - let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(closure_var); - Type::Function( - vec![effect_a, a_to_b], - Box::new(Type::Variable(closure_var)), - Box::new(effect_b), - ) - }; - - let def_annotation = roc_can::def::Annotation { - signature, - introduced_variables, - aliases: SendMap::default(), - region: Region::zero(), - }; - - let pattern = Pattern::Identifier(map_symbol); - let mut pattern_vars = SendMap::default(); - pattern_vars.insert(map_symbol, function_var); - let def = Def { - loc_pattern: Loc::at_zero(pattern), - loc_expr: Loc::at_zero(map_closure), - expr_var: function_var, - pattern_vars, - annotation: Some(def_annotation), - }; - - (map_symbol, def) -} - -fn build_effect_after( - env: &mut Env, - scope: &mut Scope, - effect_symbol: Symbol, - effect_tag_name: TagName, - var_store: &mut VarStore, -) -> (Symbol, Def) { - // Effect.after = \@Effect effect, toEffect -> toEffect (effect {}) - - let thunk_symbol = { - scope - .introduce( - "effect_after_thunk".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() - }; - - let to_effect_symbol = { - scope - .introduce( - "effect_after_toEffect".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() - }; - - let after_symbol = { - scope - .introduce( - "after".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() - }; - - // `thunk {}` - let force_thunk_call = { - let boxed = ( - var_store.fresh(), - Loc::at_zero(Expr::Var(thunk_symbol)), - var_store.fresh(), - var_store.fresh(), - ); - - let arguments = vec![(var_store.fresh(), Loc::at_zero(Expr::EmptyRecord))]; - Expr::Call(Box::new(boxed), arguments, CalledVia::Space) - }; - - // `toEffect (thunk {})` - let to_effect_call = { - let boxed = ( - var_store.fresh(), - Loc::at_zero(Expr::Var(to_effect_symbol)), - var_store.fresh(), - var_store.fresh(), - ); - - let arguments = vec![(var_store.fresh(), Loc::at_zero(force_thunk_call))]; - Expr::Call(Box::new(boxed), arguments, CalledVia::Space) - }; - - let arguments = vec![ - ( - var_store.fresh(), - Loc::at_zero(Pattern::AppliedTag { - whole_var: var_store.fresh(), - ext_var: var_store.fresh(), - tag_name: effect_tag_name.clone(), - arguments: vec![( - var_store.fresh(), - Loc::at_zero(Pattern::Identifier(thunk_symbol)), - )], - }), - ), - ( - var_store.fresh(), - Loc::at_zero(Pattern::Identifier(to_effect_symbol)), - ), - ]; - - let function_var = var_store.fresh(); - let after_closure = Expr::Closure(ClosureData { - function_type: function_var, - closure_type: var_store.fresh(), - closure_ext_var: var_store.fresh(), - return_type: var_store.fresh(), - name: after_symbol, - captured_symbols: Vec::new(), - recursive: Recursive::NotRecursive, - arguments, - loc_body: Box::new(Loc::at_zero(to_effect_call)), - }); - - let mut introduced_variables = IntroducedVariables::default(); - - let signature = { - let var_a = var_store.fresh(); - let var_b = var_store.fresh(); - - introduced_variables.insert_named("a".into(), var_a); - introduced_variables.insert_named("b".into(), var_b); - - let effect_a = build_effect_alias( - effect_symbol, - effect_tag_name.clone(), - "a", - var_a, - Type::Variable(var_a), - var_store, - &mut introduced_variables, - ); - - let effect_b = build_effect_alias( - effect_symbol, - effect_tag_name, - "b", - var_b, - Type::Variable(var_b), - var_store, - &mut introduced_variables, - ); - - let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(closure_var); - let a_to_effect_b = Type::Function( - vec![Type::Variable(var_a)], - Box::new(Type::Variable(closure_var)), - Box::new(effect_b.clone()), - ); - - let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(closure_var); - Type::Function( - vec![effect_a, a_to_effect_b], - Box::new(Type::Variable(closure_var)), - Box::new(effect_b), - ) - }; - - let def_annotation = roc_can::def::Annotation { - signature, - introduced_variables, - aliases: SendMap::default(), - region: Region::zero(), - }; - - let pattern = Pattern::Identifier(after_symbol); - let mut pattern_vars = SendMap::default(); - pattern_vars.insert(after_symbol, function_var); - let def = Def { - loc_pattern: Loc::at_zero(pattern), - loc_expr: Loc::at_zero(after_closure), - expr_var: function_var, - pattern_vars, - annotation: Some(def_annotation), - }; - - (after_symbol, def) -} - -pub fn build_host_exposed_def( - env: &mut Env, - scope: &mut Scope, - symbol: Symbol, - ident: &str, - effect_tag_name: TagName, - var_store: &mut VarStore, - annotation: roc_can::annotation::Annotation, -) -> Def { - let expr_var = var_store.fresh(); - let pattern = Pattern::Identifier(symbol); - let mut pattern_vars = SendMap::default(); - pattern_vars.insert(symbol, expr_var); - - let mut arguments: Vec<(Variable, Loc)> = Vec::new(); - let mut linked_symbol_arguments: Vec<(Variable, Expr)> = Vec::new(); - let mut captured_symbols: Vec<(Symbol, Variable)> = Vec::new(); - - let def_body = { - match annotation.typ.shallow_dealias() { - Type::Function(args, _, _) => { - for i in 0..args.len() { - let name = format!("closure_arg_{}_{}", ident, i); - - let arg_symbol = { - let ident = name.clone().into(); - scope - .introduce( - ident, - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() - }; - - let arg_var = var_store.fresh(); - - arguments.push((arg_var, Loc::at_zero(Pattern::Identifier(arg_symbol)))); - - captured_symbols.push((arg_symbol, arg_var)); - linked_symbol_arguments.push((arg_var, Expr::Var(arg_symbol))); - } - - let foreign_symbol_name = format!("roc_fx_{}", ident); - let low_level_call = Expr::ForeignCall { - foreign_symbol: foreign_symbol_name.into(), - args: linked_symbol_arguments, - ret_var: var_store.fresh(), - }; - - let effect_closure_symbol = { - let name = format!("effect_closure_{}", ident); - - let ident = name.into(); - scope - .introduce( - ident, - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() - }; - - let effect_closure = Expr::Closure(ClosureData { - function_type: var_store.fresh(), - closure_type: var_store.fresh(), - closure_ext_var: var_store.fresh(), - return_type: var_store.fresh(), - name: effect_closure_symbol, - captured_symbols, - recursive: Recursive::NotRecursive, - arguments: vec![( - var_store.fresh(), - Loc::at_zero(empty_record_pattern(var_store)), - )], - loc_body: Box::new(Loc::at_zero(low_level_call)), - }); - - let body = Expr::Tag { - variant_var: var_store.fresh(), - ext_var: var_store.fresh(), - name: effect_tag_name, - arguments: vec![(var_store.fresh(), Loc::at_zero(effect_closure))], - }; - - Expr::Closure(ClosureData { - function_type: var_store.fresh(), - closure_type: var_store.fresh(), - closure_ext_var: var_store.fresh(), - return_type: var_store.fresh(), - name: symbol, - captured_symbols: std::vec::Vec::new(), - recursive: Recursive::NotRecursive, - arguments, - loc_body: Box::new(Loc::at_zero(body)), - }) - } - _ => { - // not a function - - let foreign_symbol_name = format!("roc_fx_{}", ident); - let low_level_call = Expr::ForeignCall { - foreign_symbol: foreign_symbol_name.into(), - args: linked_symbol_arguments, - ret_var: var_store.fresh(), - }; - - let effect_closure_symbol = { - let name = format!("effect_closure_{}", ident); - - let ident = name.into(); - scope - .introduce( - ident, - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() - }; - - let empty_record_pattern = Pattern::RecordDestructure { - whole_var: var_store.fresh(), - ext_var: var_store.fresh(), - destructs: vec![], - }; - - let effect_closure = Expr::Closure(ClosureData { - function_type: var_store.fresh(), - closure_type: var_store.fresh(), - closure_ext_var: var_store.fresh(), - return_type: var_store.fresh(), - name: effect_closure_symbol, - captured_symbols, - recursive: Recursive::NotRecursive, - arguments: vec![(var_store.fresh(), Loc::at_zero(empty_record_pattern))], - loc_body: Box::new(Loc::at_zero(low_level_call)), - }); - - Expr::Tag { - variant_var: var_store.fresh(), - ext_var: var_store.fresh(), - name: effect_tag_name, - arguments: vec![(var_store.fresh(), Loc::at_zero(effect_closure))], - } - } - } - }; - - let def_annotation = roc_can::def::Annotation { - signature: annotation.typ, - introduced_variables: annotation.introduced_variables, - aliases: annotation.aliases, - region: Region::zero(), - }; - - Def { - loc_pattern: Loc::at_zero(pattern), - loc_expr: Loc::at_zero(def_body), - expr_var, - pattern_vars, - annotation: Some(def_annotation), - } -} - -fn build_effect_alias( - effect_symbol: Symbol, - effect_tag_name: TagName, - a_name: &str, - a_var: Variable, - a_type: Type, - var_store: &mut VarStore, - introduced_variables: &mut IntroducedVariables, -) -> Type { - let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(closure_var); - - let actual = { - Type::TagUnion( - vec![( - effect_tag_name, - vec![Type::Function( - vec![Type::EmptyRec], - Box::new(Type::Variable(closure_var)), - Box::new(a_type), - )], - )], - Box::new(Type::EmptyTagUnion), - ) - }; - - Type::Alias { - symbol: effect_symbol, - 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), - } -} - -pub fn build_effect_actual( - effect_tag_name: TagName, - a_type: Type, - var_store: &mut VarStore, -) -> Type { - let closure_var = var_store.fresh(); - - Type::TagUnion( - vec![( - effect_tag_name, - vec![Type::Function( - vec![Type::EmptyRec], - Box::new(Type::Variable(closure_var)), - Box::new(a_type), - )], - )], - Box::new(Type::EmptyTagUnion), - ) -} - -#[inline(always)] -fn empty_record_pattern(var_store: &mut VarStore) -> Pattern { - Pattern::RecordDestructure { - whole_var: var_store.fresh(), - ext_var: var_store.fresh(), - destructs: vec![], - } -} diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index fed569eabb..1bc1c1a8de 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -13,7 +13,7 @@ use roc_constrain::module::{ constrain_imports, pre_constrain_imports, ConstrainableImports, Import, }; use roc_constrain::module::{constrain_module, ExposedModuleTypes, SubsByModule}; -use roc_module::ident::{Ident, ModuleName, QualifiedModuleName, TagName}; +use roc_module::ident::{Ident, ModuleName, QualifiedModuleName}; use roc_module::symbol::{ IdentIds, Interns, ModuleId, ModuleIds, PQModuleName, PackageModuleIds, PackageQualified, Symbol, @@ -23,17 +23,19 @@ use roc_mono::ir::{ UpdateModeIds, }; use roc_mono::layout::{Layout, LayoutCache, LayoutProblem}; -use roc_parse::ast::{self, ExtractSpaces, Spaced, StrLiteral, TypeAnnotation}; -use roc_parse::header::PackageName; +use roc_parse::ast::{self, ExtractSpaces, Spaced, StrLiteral}; use roc_parse::header::{ExposedName, ImportsEntry, PackageEntry, PlatformHeader, To, TypedIdent}; +use roc_parse::header::{HeaderFor, ModuleNameEnum, PackageName}; +use roc_parse::ident::UppercaseIdent; use roc_parse::module::module_defs; -use roc_parse::parser::{ParseProblem, Parser, SyntaxError}; -use roc_region::all::{Loc, Region}; +use roc_parse::parser::{FileError, Parser, SyntaxError}; +use roc_region::all::{LineInfo, Loc, Region}; use roc_solve::module::SolvedModule; use roc_solve::solve; +use roc_target::TargetInfo; use roc_types::solved_types::Solved; use roc_types::subs::{Subs, VarStore, Variable}; -use roc_types::types::{Alias, Type}; +use roc_types::types::Alias; use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::{HashMap, HashSet}; use std::io; @@ -41,9 +43,13 @@ use std::iter; use std::path::{Path, PathBuf}; use std::str::from_utf8_unchecked; use std::sync::Arc; -use std::time::{Duration, SystemTime}; use std::{env, fs}; +#[cfg(not(target_family = "wasm"))] +use std::time::{Duration, SystemTime}; +#[cfg(target_family = "wasm")] +use wasm_system_time::{Duration, SystemTime}; + /// Default name for the binary generated for an app, if an invalid one was specified. const DEFAULT_APP_OUTPUT_PATH: &str = "app"; @@ -163,37 +169,6 @@ impl<'a> Dependencies<'a> { output } - pub fn add_effect_module( - &mut self, - module_id: ModuleId, - dependencies: &MutSet, - goal_phase: Phase, - ) -> MutSet<(ModuleId, Phase)> { - // add dependencies for self - // phase i + 1 of a file always depends on phase i being completed - { - let mut i = 2; - - // platform modules should only start at CanonicalizeAndConstrain - debug_assert!(PHASES[i] == Phase::CanonicalizeAndConstrain); - 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); - - let mut output = MutSet::default(); - - // all the dependencies can be loaded - for dep in dependencies { - output.insert((*dep, Phase::LoadHeader)); - } - - output - } - fn add_to_status(&mut self, module_id: ModuleId, goal_phase: Phase) { for phase in PHASES.iter() { if *phase > goal_phase { @@ -366,7 +341,6 @@ struct ModuleCache<'a> { mono_problems: MutMap>, sources: MutMap, - header_sources: MutMap, } fn start_phase<'a>( @@ -625,7 +599,6 @@ pub struct LoadedModule { pub dep_idents: MutMap, pub exposed_aliases: MutMap, pub exposed_values: Vec, - pub header_sources: MutMap)>, pub sources: MutMap)>, pub timings: MutMap, pub documentation: MutMap, @@ -672,27 +645,9 @@ struct ModuleHeader<'a> { package_qualified_imported_modules: MutSet>, exposes: Vec, exposed_imports: MutMap, - header_src: &'a str, parse_state: roc_parse::state::State<'a>, module_timing: ModuleTiming, -} - -#[derive(Debug)] -enum HeaderFor<'a> { - App { - to_platform: To<'a>, - }, - PkgConfig { - /// usually `pf` - config_shorthand: &'a str, - /// the type scheme of the main function (required by the platform) - /// (currently unused) - #[allow(dead_code)] - platform_main_type: TypedIdent<'a>, - /// provided symbol to host (commonly `mainForHost`) - main_for_host: Symbol, - }, - Interface, + header_for: HeaderFor<'a>, } #[derive(Debug)] @@ -739,12 +694,19 @@ pub struct MonomorphizedModule<'a> { pub mono_problems: MutMap>, pub procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, pub entry_point: EntryPoint<'a>, - pub exposed_to_host: MutMap, - pub header_sources: MutMap)>, + pub exposed_to_host: ExposedToHost, pub sources: MutMap)>, pub timings: MutMap, } +#[derive(Clone, Debug, Default)] +pub struct ExposedToHost { + /// usually `mainForHost` + pub values: MutMap, + /// exposed closure types, typically `Fx` + pub closure_types: Vec, +} + impl<'a> MonomorphizedModule<'a> { pub fn total_problems(&self) -> usize { let mut total = 0; @@ -768,7 +730,6 @@ impl<'a> MonomorphizedModule<'a> { #[derive(Debug)] struct ParsedModule<'a> { module_id: ModuleId, - module_name: ModuleNameEnum<'a>, module_path: PathBuf, src: &'a str, module_timing: ModuleTiming, @@ -777,6 +738,8 @@ struct ParsedModule<'a> { exposed_ident_ids: IdentIds, exposed_imports: MutMap, parsed_defs: &'a [Loc>], + module_name: ModuleNameEnum<'a>, + header_for: HeaderFor<'a>, } /// A message sent out _from_ a worker thread, @@ -784,19 +747,13 @@ struct ParsedModule<'a> { #[derive(Debug)] enum Msg<'a> { Many(Vec>), - Header(ModuleHeader<'a>, HeaderFor<'a>), + Header(ModuleHeader<'a>), Parsed(ParsedModule<'a>), CanonicalizedAndConstrained { constrained_module: ConstrainedModule, canonicalization_problems: Vec, module_docs: Option, }, - MadeEffectModule { - type_shortname: &'a str, - constrained_module: ConstrainedModule, - canonicalization_problems: Vec, - module_docs: ModuleDocumentation, - }, SolvedTypes { module_id: ModuleId, ident_ids: IdentIds, @@ -840,10 +797,10 @@ enum Msg<'a> { /// all modules are now monomorphized, we are done FinishedAllSpecialization { subs: Subs, - exposed_to_host: MutMap, + exposed_to_host: ExposedToHost, }, - FailedToParse(ParseProblem<'a, SyntaxError<'a>>), + FailedToParse(FileError<'a, SyntaxError<'a>>), FailedToReadFile { filename: PathBuf, error: io::ErrorKind, @@ -855,6 +812,7 @@ enum PlatformPath<'a> { NotSpecified, Valid(To<'a>), RootIsInterface, + RootIsHosted, RootIsPkgConfig, } @@ -873,12 +831,12 @@ struct State<'a> { pub exposed_types: SubsByModule, pub output_path: Option<&'a str>, pub platform_path: PlatformPath<'a>, - pub ptr_bytes: u32, + pub target_info: TargetInfo, pub module_cache: ModuleCache<'a>, pub dependencies: Dependencies<'a>, pub procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, - pub exposed_to_host: MutMap, + pub exposed_to_host: ExposedToHost, /// This is the "final" list of IdentIds, after canonicalization and constraint gen /// have completed for a given module. @@ -1011,7 +969,7 @@ enum BuildTask<'a> { module_id: ModuleId, ident_ids: IdentIds, decls: Vec, - exposed_to_host: MutMap, + exposed_to_host: ExposedToHost, }, MakeSpecializations { module_id: ModuleId, @@ -1035,7 +993,7 @@ pub enum LoadingProblem<'a> { filename: PathBuf, error: io::ErrorKind, }, - ParsingFailed(ParseProblem<'a, SyntaxError<'a>>), + ParsingFailed(FileError<'a, SyntaxError<'a>>), UnexpectedHeader(String), MsgChannelDied, @@ -1078,7 +1036,7 @@ pub fn load_and_typecheck<'a, F>( stdlib: &'a StdLib, src_dir: &Path, exposed_types: SubsByModule, - ptr_bytes: u32, + target_info: TargetInfo, look_up_builtin: F, ) -> Result> where @@ -1095,7 +1053,7 @@ where src_dir, exposed_types, Phase::SolveTypes, - ptr_bytes, + target_info, look_up_builtin, )? { Monomorphized(_) => unreachable!(""), @@ -1110,7 +1068,7 @@ pub fn load_and_monomorphize<'a, F>( stdlib: &'a StdLib, src_dir: &Path, exposed_types: SubsByModule, - ptr_bytes: u32, + target_info: TargetInfo, look_up_builtin: F, ) -> Result, LoadingProblem<'a>> where @@ -1127,7 +1085,7 @@ where src_dir, exposed_types, Phase::MakeSpecializations, - ptr_bytes, + target_info, look_up_builtin, )? { Monomorphized(module) => Ok(module), @@ -1143,7 +1101,7 @@ pub fn load_and_monomorphize_from_str<'a, F>( stdlib: &'a StdLib, src_dir: &Path, exposed_types: SubsByModule, - ptr_bytes: u32, + target_info: TargetInfo, look_up_builtin: F, ) -> Result, LoadingProblem<'a>> where @@ -1160,7 +1118,7 @@ where src_dir, exposed_types, Phase::MakeSpecializations, - ptr_bytes, + target_info, look_up_builtin, )? { Monomorphized(module) => Ok(module), @@ -1211,6 +1169,10 @@ impl<'a> LoadStart<'a> { let buf = to_parse_problem_report(problem, module_ids, root_exposed_ident_ids); return Err(LoadingProblem::FormattedReport(buf)); } + Err(LoadingProblem::FileProblem { filename, error }) => { + let buf = to_file_problem_report(&filename, error); + return Err(LoadingProblem::FormattedReport(buf)); + } Err(e) => return Err(e), } }; @@ -1312,7 +1274,7 @@ fn load<'a, F>( src_dir: &Path, exposed_types: SubsByModule, goal_phase: Phase, - ptr_bytes: u32, + target_info: TargetInfo, look_up_builtins: F, ) -> Result, LoadingProblem<'a>> where @@ -1361,7 +1323,7 @@ where // We need to allocate worker *queues* on the main thread and then move them // into the worker threads, because those workers' stealers need to be - // shared bet,een all threads, and this coordination work is much easier + // shared between all threads, and this coordination work is much easier // on the main thread. let mut worker_queues = bumpalo::collections::Vec::with_capacity_in(num_workers, arena); let mut stealers = bumpalo::collections::Vec::with_capacity_in(num_workers, arena); @@ -1428,7 +1390,7 @@ where worker_arena, src_dir, msg_tx.clone(), - ptr_bytes, + target_info, look_up_builtins, ); @@ -1469,7 +1431,7 @@ where let mut state = State { root_id, - ptr_bytes, + target_info, platform_data: None, goal_phase, stdlib, @@ -1478,7 +1440,7 @@ where module_cache: ModuleCache::default(), dependencies: Dependencies::default(), procedures: MutMap::default(), - exposed_to_host: MutMap::default(), + exposed_to_host: ExposedToHost::default(), exposed_types, arc_modules, arc_shorthands, @@ -1567,13 +1529,7 @@ where Msg::FailedToParse(problem) => { shut_down_worker_threads!(); - let module_ids = Arc::try_unwrap(state.arc_modules) - .unwrap_or_else(|_| { - panic!("There were still outstanding Arc references to module_ids") - }) - .into_inner() - .into_module_ids(); - + let module_ids = (*state.arc_modules).lock().clone().into_module_ids(); let buf = to_parse_problem_report( problem, module_ids, @@ -1605,11 +1561,11 @@ where shut_down_worker_threads!(); let module_ids = Arc::try_unwrap(arc_modules) - .unwrap_or_else(|_| { - panic!(r"There were still outstanding Arc references to module_ids") - }) - .into_inner() - .into_module_ids(); + .unwrap_or_else(|_| { + panic!(r"There were still outstanding Arc references to module_ids") + }) + .into_inner() + .into_module_ids(); let buf = to_parse_problem_report( problem, @@ -1647,6 +1603,23 @@ fn start_tasks<'a>( Ok(()) } +#[cfg(debug_assertions)] +fn debug_print_ir(state: &State, flag: &str) { + if env::var(flag) != Ok("1".into()) { + return; + } + + let procs_string = state + .procedures + .values() + .map(|proc| proc.to_pretty(200)) + .collect::>(); + + let result = procs_string.join("\n"); + + println!("{}", result); +} + fn update<'a>( mut state: State<'a>, msg: Msg<'a>, @@ -1668,7 +1641,7 @@ fn update<'a>( Ok(state) } - Header(header, header_extra) => { + Header(header) => { use HeaderFor::*; log!("loaded header for {:?}", header.module_id); @@ -1685,13 +1658,13 @@ fn update<'a>( if let PkgConfig { config_shorthand, .. - } = header_extra + } = header.header_for { work.extend(state.dependencies.notify_package(config_shorthand)); } } - match header_extra { + match header.header_for { App { to_platform } => { debug_assert!(matches!(state.platform_path, PlatformPath::NotSpecified)); state.platform_path = PlatformPath::Valid(to_platform); @@ -1715,6 +1688,12 @@ fn update<'a>( state.platform_path = PlatformPath::RootIsInterface; } } + Hosted { .. } => { + if header.is_root_module { + debug_assert!(matches!(state.platform_path, PlatformPath::NotSpecified)); + state.platform_path = PlatformPath::RootIsHosted; + } + } } // store an ID to name mapping, so we know the file to read when fetching dependencies' headers @@ -1722,7 +1701,7 @@ fn update<'a>( state.module_cache.module_names.insert(*id, name.clone()); } - // This was a dependency. Write it down and keep processing messaages. + // This was a dependency. Write it down and keep processing messages. let mut exposed_symbols: MutSet = HashSet::with_capacity_and_hasher(header.exposes.len(), default_hasher()); @@ -1739,11 +1718,6 @@ fn update<'a>( .exposed_symbols_by_module .insert(home, exposed_symbols); - state - .module_cache - .header_sources - .insert(home, (header.module_path.clone(), header.header_src)); - state .module_cache .imports @@ -1783,7 +1757,6 @@ fn update<'a>( // // e.g. for `app "blah"` we should generate an output file named "blah" match &parsed.module_name { - ModuleNameEnum::PkgConfig => {} ModuleNameEnum::App(output_str) => match output_str { StrLiteral::PlainLine(path) => { state.output_path = Some(path); @@ -1792,7 +1765,9 @@ fn update<'a>( todo!("TODO gracefully handle a malformed string literal after `app` keyword."); } }, - ModuleNameEnum::Interface(_) => {} + ModuleNameEnum::PkgConfig + | ModuleNameEnum::Interface(_) + | ModuleNameEnum::Hosted(_) => {} } let module_id = parsed.module_id; @@ -1840,57 +1815,6 @@ fn update<'a>( Ok(state) } - MadeEffectModule { - constrained_module, - canonicalization_problems, - module_docs, - type_shortname, - } => { - let module_id = constrained_module.module.module_id; - - log!("made effect module for {:?}", module_id); - state - .module_cache - .can_problems - .insert(module_id, canonicalization_problems); - - state - .module_cache - .documentation - .insert(module_id, module_docs); - - state - .module_cache - .aliases - .insert(module_id, constrained_module.module.aliases.clone()); - - state - .module_cache - .constrained - .insert(module_id, constrained_module); - - let mut work = state.dependencies.add_effect_module( - module_id, - &MutSet::default(), - state.goal_phase, - ); - - work.extend(state.dependencies.notify_package(type_shortname)); - - work.extend(state.dependencies.notify(module_id, Phase::LoadHeader)); - - work.extend(state.dependencies.notify(module_id, Phase::Parse)); - - work.extend( - state - .dependencies - .notify(module_id, Phase::CanonicalizeAndConstrain), - ); - - start_tasks(arena, &mut state, work, injector, worker_listeners)?; - - Ok(state) - } SolvedTypes { module_id, ident_ids, @@ -1928,12 +1852,17 @@ fn update<'a>( }; if is_host_exposed { - state.exposed_to_host.extend( + state.exposed_to_host.values.extend( solved_module .exposed_vars_by_symbol .iter() .map(|(k, v)| (*k, *v)), ); + + state + .exposed_to_host + .closure_types + .extend(solved_module.aliases.keys().copied()); } if is_host_exposed && state.goal_phase == Phase::SolveTypes { @@ -1977,7 +1906,7 @@ fn update<'a>( let layout_cache = state .layout_caches .pop() - .unwrap_or_else(|| LayoutCache::new(state.ptr_bytes)); + .unwrap_or_else(|| LayoutCache::new(state.target_info)); let typechecked = TypeCheckedModule { module_id, @@ -2075,6 +2004,9 @@ fn update<'a>( && state.dependencies.solved_all() && state.goal_phase == Phase::MakeSpecializations { + #[cfg(debug_assertions)] + debug_print_ir(&state, "PRINT_IR_AFTER_SPECIALIZATION"); + Proc::insert_reset_reuse_operations( arena, module_id, @@ -2083,21 +2015,14 @@ fn update<'a>( &mut state.procedures, ); - // display the mono IR of the module, for debug purposes - if roc_mono::ir::PRETTY_PRINT_IR_SYMBOLS { - let procs_string = state - .procedures - .values() - .map(|proc| proc.to_pretty(200)) - .collect::>(); - - let result = procs_string.join("\n"); - - println!("{}", result); - } + #[cfg(debug_assertions)] + debug_print_ir(&state, "PRINT_IR_AFTER_RESET_REUSE"); Proc::insert_refcount_operations(arena, &mut state.procedures); + #[cfg(debug_assertions)] + debug_print_ir(&state, "PRINT_IR_AFTER_REFCOUNT"); + // This is not safe with the new non-recursive RC updates that we do for tag unions // // Proc::optimize_refcount_operations( @@ -2172,7 +2097,7 @@ fn update<'a>( fn finish_specialization( state: State, subs: Subs, - exposed_to_host: MutMap, + exposed_to_host: ExposedToHost, ) -> Result { let module_ids = Arc::try_unwrap(state.arc_modules) .unwrap_or_else(|_| panic!("There were still outstanding Arc references to module_ids")) @@ -2198,7 +2123,6 @@ fn finish_specialization( type_problems, can_problems, sources, - header_sources, .. } = module_cache; @@ -2207,11 +2131,6 @@ fn finish_specialization( .map(|(id, (path, src))| (id, (path, src.into()))) .collect(); - let header_sources: MutMap)> = header_sources - .into_iter() - .map(|(id, (path, src))| (id, (path, src.into()))) - .collect(); - let path_to_platform = { use PlatformPath::*; let package_name = match platform_path { @@ -2236,8 +2155,8 @@ fn finish_specialization( let entry_point = { let symbol = match platform_data { None => { - debug_assert_eq!(exposed_to_host.len(), 1); - *exposed_to_host.iter().next().unwrap().0 + debug_assert_eq!(exposed_to_host.values.len(), 1); + *exposed_to_host.values.iter().next().unwrap().0 } Some(PlatformData { provides, .. }) => provides, }; @@ -2274,7 +2193,6 @@ fn finish_specialization( procedures, entry_point, sources, - header_sources, timings: state.timings, }) } @@ -2305,13 +2223,6 @@ fn finish( .map(|(id, (path, src))| (id, (path, src.into()))) .collect(); - let header_sources = state - .module_cache - .header_sources - .into_iter() - .map(|(id, (path, src))| (id, (path, src.into()))) - .collect(); - LoadedModule { module_id: state.root_id, interns, @@ -2323,7 +2234,6 @@ fn finish( exposed_aliases: exposed_aliases_by_symbol, exposed_values, exposed_to_host: exposed_vars_by_symbol.into_iter().collect(), - header_sources, sources, timings: state.timings, documentation, @@ -2352,19 +2262,15 @@ fn load_pkg_config<'a>( let parse_start = SystemTime::now(); let bytes = arena.alloc(bytes_vec); let parse_state = roc_parse::state::State::new(bytes); - let parsed = roc_parse::module::parse_header(arena, parse_state); + let parsed = roc_parse::module::parse_header(arena, parse_state.clone()); let parse_header_duration = parse_start.elapsed().unwrap(); // Insert the first entries for this module's timings let mut pkg_module_timing = ModuleTiming::new(module_start_time); - let mut effect_module_timing = ModuleTiming::new(module_start_time); pkg_module_timing.read_roc_file = file_io_duration; pkg_module_timing.parse_header = parse_header_duration; - effect_module_timing.read_roc_file = file_io_duration; - effect_module_timing.parse_header = parse_header_duration; - match parsed { Ok((ast::Module::Interface { header }, _parse_state)) => { Err(LoadingProblem::UnexpectedHeader(format!( @@ -2379,10 +2285,6 @@ fn load_pkg_config<'a>( ))) } Ok((ast::Module::Platform { header }, parser_state)) => { - let delta = bytes.len() - parser_state.bytes().len(); - let chomped = &bytes[..delta]; - let header_src = unsafe { std::str::from_utf8_unchecked(chomped) }; - // make a Package-Config module that ultimately exposes `main` to the host let pkg_config_module_msg = fabricate_pkg_config_module( arena, @@ -2391,27 +2293,23 @@ fn load_pkg_config<'a>( filename, parser_state, module_ids.clone(), - ident_ids_by_module.clone(), + ident_ids_by_module, &header, - header_src, pkg_module_timing, ) .1; - let effects_module_msg = fabricate_effects_module( - arena, - header.effects.effect_shortname, - module_ids, - ident_ids_by_module, - header, - effect_module_timing, - ) - .1; - - Ok(Msg::Many(vec![effects_module_msg, pkg_config_module_msg])) + Ok(pkg_config_module_msg) + } + Ok((ast::Module::Hosted { header }, _parse_state)) => { + Err(LoadingProblem::UnexpectedHeader(format!( + "expected platform/package module, got Hosted module with header\n{:?}", + header + ))) } Err(fail) => Err(LoadingProblem::ParsingFailed( - SyntaxError::Header(fail).into_parse_problem(filename, "", bytes), + fail.map_problem(SyntaxError::Header) + .into_file_error(filename), )), } } @@ -2518,7 +2416,7 @@ fn parse_header<'a>( ) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> { let parse_start = SystemTime::now(); let parse_state = roc_parse::state::State::new(src_bytes); - let parsed = roc_parse::module::parse_header(arena, parse_state); + let parsed = roc_parse::module::parse_header(arena, parse_state.clone()); let parse_header_duration = parse_start.elapsed().unwrap(); // Insert the first entries for this module's timings @@ -2529,11 +2427,6 @@ fn parse_header<'a>( match parsed { Ok((ast::Module::Interface { header }, parse_state)) => { - let header_src = unsafe { - let chomped = src_bytes.len() - parse_state.bytes().len(); - std::str::from_utf8_unchecked(&src_bytes[..chomped]) - }; - let info = HeaderInfo { loc_name: Loc { region: header.name.region, @@ -2542,11 +2435,36 @@ fn parse_header<'a>( filename, is_root_module, opt_shorthand, - header_src, packages: &[], exposes: unspace(arena, header.exposes.items), imports: unspace(arena, header.imports.items), - to_platform: None, + extra: HeaderFor::Interface, + }; + + Ok(send_header( + info, + parse_state, + module_ids, + ident_ids_by_module, + module_timing, + )) + } + Ok((ast::Module::Hosted { header }, parse_state)) => { + let info = HeaderInfo { + loc_name: Loc { + region: header.name.region, + value: ModuleNameEnum::Hosted(header.name.value), + }, + filename, + is_root_module, + opt_shorthand, + packages: &[], + exposes: unspace(arena, header.exposes.items), + imports: unspace(arena, header.imports.items), + extra: HeaderFor::Hosted { + generates: header.generates, + generates_with: unspace(arena, header.generates_with.items), + }, }; Ok(send_header( @@ -2561,13 +2479,22 @@ fn parse_header<'a>( let mut pkg_config_dir = filename.clone(); pkg_config_dir.pop(); - let header_src = unsafe { - let chomped = src_bytes.len() - parse_state.bytes().len(); - std::str::from_utf8_unchecked(&src_bytes[..chomped]) - }; - let packages = unspace(arena, header.packages.items); + let mut exposes = bumpalo::collections::Vec::new_in(arena); + exposes.extend(unspace(arena, header.provides.items)); + + if let Some(provided_types) = header.provides_types { + for provided_type in unspace(arena, provided_types.items) { + let string: &str = provided_type.value.into(); + let exposed_name = ExposedName::new(string); + + exposes.push(Loc::at(provided_type.region, exposed_name)); + } + } + + let exposes = exposes.into_bump_slice(); + let info = HeaderInfo { loc_name: Loc { region: header.name.region, @@ -2576,11 +2503,12 @@ fn parse_header<'a>( filename, is_root_module, opt_shorthand, - header_src, packages, - exposes: unspace(arena, header.provides.items), + exposes, imports: unspace(arena, header.imports.items), - to_platform: Some(header.to.value), + extra: HeaderFor::App { + to_platform: header.to.value, + }, }; let (module_id, app_module_header_msg) = send_header( @@ -2636,7 +2564,10 @@ fn parse_header<'a>( Msg::Many(vec![app_module_header_msg, load_pkg_config_msg]), )) } else { - Ok((module_id, app_module_header_msg)) + Err(LoadingProblem::FileProblem { + filename: pkg_config_roc, + error: io::ErrorKind::NotFound, + }) } } else { panic!("could not find base") @@ -2645,16 +2576,16 @@ fn parse_header<'a>( To::NewPackage(_package_name) => Ok((module_id, app_module_header_msg)), } } - Ok((ast::Module::Platform { header }, _parse_state)) => Ok(fabricate_effects_module( - arena, - "", - module_ids, - ident_ids_by_module, - header, - module_timing, - )), + Ok((ast::Module::Platform { header }, _parse_state)) => { + Err(LoadingProblem::UnexpectedHeader(format!( + "got an unexpected platform header\n{:?}", + header + ))) + } + Err(fail) => Err(LoadingProblem::ParsingFailed( - SyntaxError::Header(fail).into_parse_problem(filename, "", src_bytes), + fail.map_problem(SyntaxError::Header) + .into_file_error(filename), )), } } @@ -2720,25 +2651,16 @@ fn load_from_str<'a>( ) } -#[derive(Debug)] -enum ModuleNameEnum<'a> { - /// A filename - App(StrLiteral<'a>), - Interface(roc_parse::header::ModuleName<'a>), - PkgConfig, -} - #[derive(Debug)] struct HeaderInfo<'a> { loc_name: Loc>, filename: PathBuf, is_root_module: bool, opt_shorthand: Option<&'a str>, - header_src: &'a str, packages: &'a [Loc>], exposes: &'a [Loc>], imports: &'a [Loc>], - to_platform: Option>, + extra: HeaderFor<'a>, } #[allow(clippy::too_many_arguments)] @@ -2759,14 +2681,13 @@ fn send_header<'a>( packages, exposes, imports, - to_platform, - header_src, + extra, } = info; let declared_name: ModuleName = match &loc_name.value { PkgConfig => unreachable!(), App(_) => ModuleName::APP.into(), - Interface(module_name) => { + Interface(module_name) | Hosted(module_name) => { // TODO check to see if module_name is consistent with filename. // If it isn't, report a problem! @@ -2897,11 +2818,6 @@ fn send_header<'a>( // We always need to send these, even if deps is empty, // because the coordinator thread needs to receive this message // to decrement its "pending" count. - let extra = match to_platform { - Some(to_platform) => HeaderFor::App { to_platform }, - None => HeaderFor::Interface, - }; - let mut package_qualified_imported_modules = MutSet::default(); for (pq_module_name, module_id) in &deps_by_name { match pq_module_name { @@ -2918,25 +2834,22 @@ fn send_header<'a>( ( home, - Msg::Header( - ModuleHeader { - module_id: home, - module_path: filename, - is_root_module, - exposed_ident_ids: ident_ids, - module_name: loc_name.value, - packages: package_entries, - imported_modules, - package_qualified_imported_modules, - deps_by_name, - exposes: exposed, - header_src, - parse_state, - exposed_imports: scope, - module_timing, - }, - extra, - ), + Msg::Header(ModuleHeader { + module_id: home, + module_path: filename, + is_root_module, + exposed_ident_ids: ident_ids, + module_name: loc_name.value, + packages: package_entries, + imported_modules, + package_qualified_imported_modules, + deps_by_name, + exposes: exposed, + parse_state, + exposed_imports: scope, + module_timing, + header_for: extra, + }), ) } @@ -2945,11 +2858,11 @@ struct PlatformHeaderInfo<'a> { filename: PathBuf, is_root_module: bool, shorthand: &'a str, - header_src: &'a str, app_module_id: ModuleId, packages: &'a [Loc>], provides: &'a [Loc>], requires: &'a [Loc>], + requires_types: &'a [Loc>], imports: &'a [Loc>], } @@ -2966,11 +2879,11 @@ fn send_header_two<'a>( filename, shorthand, is_root_module, - header_src, app_module_id, packages, provides, requires, + requires_types, imports, } = info; @@ -2985,7 +2898,6 @@ fn send_header_two<'a>( HashMap::with_capacity_and_hasher(num_exposes, default_hasher()); // add standard imports - // TODO add Effect by default imported_modules.insert(app_module_id, Region::zero()); deps_by_name.insert( PQModuleName::Unqualified(ModuleName::APP.into()), @@ -3075,6 +2987,18 @@ fn send_header_two<'a>( scope.insert(ident, (symbol, entry.ident.region)); } + + for entry in requires_types { + let string: &str = entry.value.into(); + let ident: Ident = string.into(); + let ident_id = ident_ids.get_or_insert(&ident); + let symbol = Symbol::new(app_module_id, ident_id); + + // Since this value is exposed, add it to our module's default scope. + debug_assert!(!scope.contains_key(&ident.clone())); + + scope.insert(ident, (symbol, entry.region)); + } } let ident_ids = ident_ids_by_module.get_mut(&home).unwrap(); @@ -3148,25 +3072,22 @@ fn send_header_two<'a>( ( home, - Msg::Header( - ModuleHeader { - module_id: home, - module_path: filename, - is_root_module, - exposed_ident_ids: ident_ids, - module_name, - packages: package_entries, - imported_modules, - package_qualified_imported_modules, - deps_by_name, - exposes: exposed, - header_src, - parse_state, - exposed_imports: scope, - module_timing, - }, - extra, - ), + Msg::Header(ModuleHeader { + module_id: home, + module_path: filename, + is_root_module, + exposed_ident_ids: ident_ids, + module_name, + packages: package_entries, + imported_modules, + package_qualified_imported_modules, + deps_by_name, + exposes: exposed, + parse_state, + exposed_imports: scope, + module_timing, + header_for: extra, + }), ) } @@ -3308,14 +3229,12 @@ fn fabricate_pkg_config_module<'a>( module_ids: Arc>>, ident_ids_by_module: Arc>>, header: &PlatformHeader<'a>, - header_src: &'a str, module_timing: ModuleTiming, ) -> (ModuleId, Msg<'a>) { let info = PlatformHeaderInfo { filename, is_root_module: false, shorthand, - header_src, app_module_id, packages: &[], provides: unspace(arena, header.provides.items), @@ -3323,6 +3242,7 @@ fn fabricate_pkg_config_module<'a>( header.requires.signature.region, header.requires.signature.extract_spaces().item, )]), + requires_types: unspace(arena, header.requires.rigids.items), imports: unspace(arena, header.imports.items), }; @@ -3335,272 +3255,6 @@ fn fabricate_pkg_config_module<'a>( ) } -#[allow(clippy::too_many_arguments)] -fn fabricate_effects_module<'a>( - arena: &'a Bump, - shorthand: &'a str, - module_ids: Arc>>, - ident_ids_by_module: Arc>>, - header: PlatformHeader<'a>, - module_timing: ModuleTiming, -) -> (ModuleId, Msg<'a>) { - let num_exposes = header.provides.len() + 1; - let mut exposed: Vec = Vec::with_capacity(num_exposes); - - let effects = header.effects; - - let module_id: ModuleId; - - let effect_entries = unpack_exposes_entries(arena, effects.entries.items); - let name = effects.effect_type_name; - let declared_name: ModuleName = name.into(); - - let hardcoded_effect_symbols = { - let mut functions: Vec<_> = crate::effect_module::BUILTIN_EFFECT_FUNCTIONS - .iter() - .map(|x| x.0) - .collect(); - functions.push(name); - - functions - }; - - { - let mut module_ids = (*module_ids).lock(); - - for exposed in header.exposes.iter() { - let module_name = exposed.value.extract_spaces().item; - - module_ids.get_or_insert(&PQModuleName::Qualified( - shorthand, - module_name.as_str().into(), - )); - } - } - - let exposed_ident_ids = { - // Lock just long enough to perform the minimal operations necessary. - let mut module_ids = (*module_ids).lock(); - let mut ident_ids_by_module = (*ident_ids_by_module).lock(); - - let name = PQModuleName::Qualified(shorthand, declared_name); - module_id = module_ids.get_or_insert(&name); - - // Ensure this module has an entry in the exposed_ident_ids map. - ident_ids_by_module - .entry(module_id) - .or_insert_with(IdentIds::default); - - let ident_ids = ident_ids_by_module.get_mut(&module_id).unwrap(); - - // Generate IdentIds entries for all values this module exposes. - // This way, when we encounter them in Defs later, they already - // have an IdentIds entry. - // - // We must *not* add them to scope yet, or else the Defs will - // incorrectly think they're shadowing them! - for (loc_exposed, _) in effect_entries.iter() { - // Use get_or_insert here because the ident_ids may already - // created an IdentId for this, when it was imported exposed - // in a dependent module. - // - // For example, if module A has [ B.{ foo } ], then - // when we get here for B, `foo` will already have - // an IdentId. We must reuse that! - let ident_id = ident_ids.get_or_insert(&loc_exposed.value.into()); - let symbol = Symbol::new(module_id, ident_id); - - exposed.push(symbol); - } - - for hardcoded in hardcoded_effect_symbols { - // Use get_or_insert here because the ident_ids may already - // created an IdentId for this, when it was imported exposed - // in a dependent module. - // - // For example, if module A has [ B.{ foo } ], then - // when we get here for B, `foo` will already have - // an IdentId. We must reuse that! - let ident_id = ident_ids.get_or_insert(&hardcoded.into()); - let symbol = Symbol::new(module_id, ident_id); - - exposed.push(symbol); - } - - if cfg!(debug_assertions) { - module_id.register_debug_idents(ident_ids); - } - - ident_ids.clone() - }; - - // a platform module has no dependencies, hence empty - let dep_idents: MutMap = IdentIds::exposed_builtins(0); - - let mut var_store = VarStore::default(); - - let module_ids = { (*module_ids).lock().clone() }.into_module_ids(); - - let mut scope = roc_can::scope::Scope::new(module_id, &mut var_store); - let mut can_env = - roc_can::env::Env::new(module_id, &dep_idents, &module_ids, exposed_ident_ids); - - let effect_symbol = scope - .introduce( - name.into(), - &can_env.exposed_ident_ids, - &mut can_env.ident_ids, - Region::zero(), - ) - .unwrap(); - - let effect_tag_name = TagName::Private(effect_symbol); - - let mut aliases = MutMap::default(); - let alias = { - let a_var = var_store.fresh(); - - let actual = crate::effect_module::build_effect_actual( - effect_tag_name, - Type::Variable(a_var), - &mut var_store, - ); - - scope.add_alias( - effect_symbol, - Region::zero(), - vec![Loc::at_zero(("a".into(), a_var))], - actual, - ); - - scope.lookup_alias(effect_symbol).unwrap().clone() - }; - - aliases.insert(effect_symbol, alias); - - let mut declarations = Vec::new(); - - let exposed_symbols: MutSet = { - let mut exposed_symbols = MutSet::default(); - - { - for (ident, ann) in effect_entries { - let symbol = { - scope - .introduce( - ident.value.into(), - &can_env.exposed_ident_ids, - &mut can_env.ident_ids, - Region::zero(), - ) - .unwrap() - }; - - let annotation = roc_can::annotation::canonicalize_annotation( - &mut can_env, - &mut scope, - &ann.value, - Region::zero(), - &mut var_store, - ); - - let def = crate::effect_module::build_host_exposed_def( - &mut can_env, - &mut scope, - symbol, - ident.value, - TagName::Private(effect_symbol), - &mut var_store, - annotation, - ); - exposed_symbols.insert(symbol); - - declarations.push(Declaration::Declare(def)); - } - } - - // define Effect.after, Effect.map etc. - crate::effect_module::build_effect_builtins( - &mut can_env, - &mut scope, - effect_symbol, - &mut var_store, - &mut exposed_symbols, - &mut declarations, - ); - - exposed_symbols - }; - - use roc_can::module::ModuleOutput; - let module_output = ModuleOutput { - aliases, - rigid_variables: MutMap::default(), - declarations, - exposed_imports: MutMap::default(), - lookups: Vec::new(), - problems: can_env.problems, - ident_ids: can_env.ident_ids, - references: MutSet::default(), - scope, - }; - - let constraint = constrain_module(&module_output.declarations, module_id); - - let module = Module { - module_id, - exposed_imports: module_output.exposed_imports, - exposed_symbols, - references: module_output.references, - aliases: module_output.aliases, - rigid_variables: module_output.rigid_variables, - }; - - let imported_modules = MutMap::default(); - - // Should a effect module ever have a ModuleDocumentation? - let module_docs = ModuleDocumentation { - name: String::from(name), - entries: Vec::new(), - scope: module_output.scope, - }; - - let constrained_module = ConstrainedModule { - module, - declarations: module_output.declarations, - imported_modules, - var_store, - constraint, - ident_ids: module_output.ident_ids, - dep_idents, - module_timing, - }; - - ( - module_id, - Msg::MadeEffectModule { - type_shortname: effects.effect_shortname, - constrained_module, - canonicalization_problems: module_output.problems, - module_docs, - }, - ) -} - -fn unpack_exposes_entries<'a>( - arena: &'a Bump, - entries: &'a [Loc>>], -) -> bumpalo::collections::Vec<'a, (Loc<&'a str>, Loc>)> { - use bumpalo::collections::Vec; - - let iter = entries.iter().map(|entry| { - let entry: TypedIdent<'a> = entry.value.extract_spaces().item; - (entry.ident, entry.ann) - }); - - Vec::from_iter_in(iter, arena) -} - #[allow(clippy::too_many_arguments)] #[allow(clippy::unnecessary_wraps)] fn canonicalize_and_constrain<'a, F>( @@ -3620,6 +3274,7 @@ where let ParsedModule { module_id, module_name, + header_for, exposed_ident_ids, parsed_defs, exposed_imports, @@ -3632,6 +3287,7 @@ where let canonicalized = canonicalize_module_defs( arena, parsed_defs, + &header_for, module_id, module_ids, exposed_ident_ids, @@ -3653,12 +3309,16 @@ where let module_docs = match module_name { ModuleNameEnum::PkgConfig => None, ModuleNameEnum::App(_) => None, - ModuleNameEnum::Interface(name) => Some(crate::docs::generate_module_docs( - module_output.scope, - name.as_str().into(), - &module_output.ident_ids, - parsed_defs, - )), + ModuleNameEnum::Interface(name) | ModuleNameEnum::Hosted(name) => { + let docs = crate::docs::generate_module_docs( + module_output.scope, + name.as_str().into(), + &module_output.ident_ids, + parsed_defs, + ); + + Some(docs) + } }; let constraint = constrain_module(&module_output.declarations, module_id); @@ -3701,16 +3361,14 @@ where fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result, LoadingProblem<'a>> { let mut module_timing = header.module_timing; let parse_start = SystemTime::now(); - let source = header.parse_state.bytes(); + let source = header.parse_state.original_bytes(); let parse_state = header.parse_state; let parsed_defs = match module_defs().parse(arena, parse_state) { Ok((_, success, _state)) => success, - Err((_, fail, _)) => { - return Err(LoadingProblem::ParsingFailed(fail.into_parse_problem( - header.module_path, - header.header_src, - source, - ))); + Err((_, fail, state)) => { + return Err(LoadingProblem::ParsingFailed( + fail.into_file_error(header.module_path, &state), + )); } }; @@ -3736,6 +3394,7 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result, Loadi exposed_ident_ids, exposed_imports, module_path, + header_for, .. } = header; @@ -3750,6 +3409,7 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result, Loadi exposed_ident_ids, exposed_imports, parsed_defs, + header_for, }; Ok(Msg::Parsed(parsed)) @@ -3805,7 +3465,7 @@ fn make_specializations<'a>( mut layout_cache: LayoutCache<'a>, specializations_we_must_make: Vec, mut module_timing: ModuleTiming, - ptr_bytes: u32, + target_info: TargetInfo, ) -> Msg<'a> { let make_specializations_start = SystemTime::now(); let mut mono_problems = Vec::new(); @@ -3817,7 +3477,7 @@ fn make_specializations<'a>( subs: &mut subs, home, ident_ids: &mut ident_ids, - ptr_bytes, + target_info, update_mode_ids: &mut update_mode_ids, // call_specialization_counter=0 is reserved call_specialization_counter: 1, @@ -3888,9 +3548,9 @@ fn build_pending_specializations<'a>( decls: Vec, mut module_timing: ModuleTiming, mut layout_cache: LayoutCache<'a>, - ptr_bytes: u32, + target_info: TargetInfo, // TODO remove - exposed_to_host: MutMap, + exposed_to_host: ExposedToHost, ) -> Msg<'a> { let find_specializations_start = SystemTime::now(); @@ -3913,7 +3573,7 @@ fn build_pending_specializations<'a>( subs: &mut subs, home, ident_ids: &mut ident_ids, - ptr_bytes, + target_info, update_mode_ids: &mut update_mode_ids, // call_specialization_counter=0 is reserved call_specialization_counter: 1, @@ -3930,7 +3590,7 @@ fn build_pending_specializations<'a>( &mut module_thunks, &mut mono_env, def, - &exposed_to_host, + &exposed_to_host.values, false, ), DeclareRec(defs) => { @@ -3941,7 +3601,7 @@ fn build_pending_specializations<'a>( &mut module_thunks, &mut mono_env, def, - &exposed_to_host, + &exposed_to_host.values, true, ) } @@ -4121,7 +3781,7 @@ fn run_task<'a, F>( arena: &'a Bump, src_dir: &Path, msg_tx: MsgSender<'a>, - ptr_bytes: u32, + target_info: TargetInfo, look_up_builtins: F, ) -> Result<(), LoadingProblem<'a>> where @@ -4199,7 +3859,7 @@ where decls, module_timing, layout_cache, - ptr_bytes, + target_info, exposed_to_host, )), MakeSpecializations { @@ -4219,7 +3879,7 @@ where layout_cache, specializations_we_must_make, module_timing, - ptr_bytes, + target_info, )), }?; @@ -4309,16 +3969,17 @@ fn to_file_problem_report(filename: &Path, error: io::ErrorKind) -> String { } fn to_parse_problem_report<'a>( - problem: ParseProblem<'a, SyntaxError<'a>>, + problem: FileError<'a, SyntaxError<'a>>, mut module_ids: ModuleIds, all_ident_ids: MutMap, ) -> String { use roc_reporting::report::{parse_problem, RocDocAllocator, DEFAULT_PALETTE}; // TODO this is not in fact safe - let src = unsafe { from_utf8_unchecked(problem.bytes) }; - let mut src_lines: Vec<&str> = problem.prefix.lines().collect(); - src_lines.extend(src.lines().skip(1)); + let src = unsafe { from_utf8_unchecked(problem.problem.bytes) }; + let src_lines = src.lines().collect::>(); + // let mut src_lines: Vec<&str> = problem.prefix.lines().collect(); + // src_lines.extend(src.lines().skip(1)); let module_id = module_ids.get_or_insert(&"find module name somehow?".into()); @@ -4331,7 +3992,16 @@ fn to_parse_problem_report<'a>( let alloc = RocDocAllocator::new(&src_lines, module_id, &interns); let starting_line = 0; - let report = parse_problem(&alloc, problem.filename.clone(), starting_line, problem); + + let lines = LineInfo::new(src); + + let report = parse_problem( + &alloc, + &lines, + problem.filename.clone(), + starting_line, + problem, + ); let mut buf = String::new(); let palette = DEFAULT_PALETTE; @@ -4372,7 +4042,23 @@ fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> Strin } RootIsInterface => { let doc = alloc.stack(vec![ - alloc.reflow(r"The input file is a interface file, but only app modules can be ran."), + alloc.reflow(r"The input file is an interface module, but only app modules can be ran."), + alloc.concat(vec![ + alloc.reflow(r"I will still parse and typecheck the input file and its dependencies, "), + alloc.reflow(r"but won't output any executable."), + ]) + ]); + + Report { + filename: "UNKNOWN.roc".into(), + doc, + title: "NO PLATFORM".to_string(), + severity: Severity::RuntimeError, + } + } + RootIsHosted => { + let doc = alloc.stack(vec![ + alloc.reflow(r"The input file is a hosted module, but only app modules can be ran."), alloc.concat(vec![ alloc.reflow(r"I will still parse and typecheck the input file and its dependencies, "), alloc.reflow(r"but won't output any executable."), diff --git a/compiler/load/src/lib.rs b/compiler/load/src/lib.rs index 7b73ccefd5..90f9140930 100644 --- a/compiler/load/src/lib.rs +++ b/compiler/load/src/lib.rs @@ -2,5 +2,7 @@ // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. #![allow(clippy::large_enum_variant)] pub mod docs; -pub mod effect_module; pub mod file; + +#[cfg(target_family = "wasm")] +mod wasm_system_time; diff --git a/compiler/load/src/wasm_system_time.rs b/compiler/load/src/wasm_system_time.rs new file mode 100644 index 0000000000..9709c1e22d --- /dev/null +++ b/compiler/load/src/wasm_system_time.rs @@ -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 { + fn now() -> Self { + SystemTime + } + fn duration_since(&self, _: SystemTime) -> Result { + Ok(Duration) + } + fn elapsed(&self) -> Result { + Ok(Duration) + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Duration; + +impl Duration { + fn checked_sub(&self, _: Duration) -> Option { + Some(Duration) + } +} + +impl Default for Duration { + fn default() -> Self { + Duration + } +} + +impl std::ops::AddAssign for Duration { + fn add_assign(&mut self, _: Duration) {} +} diff --git a/compiler/load/tests/fixtures/build/app_with_deps/ManualAttr.roc b/compiler/load/tests/fixtures/build/app_with_deps/ManualAttr.roc index 7fd1e010c8..5e51189cbb 100644 --- a/compiler/load/tests/fixtures/build/app_with_deps/ManualAttr.roc +++ b/compiler/load/tests/fixtures/build/app_with_deps/ManualAttr.roc @@ -3,7 +3,7 @@ interface ManualAttr imports [] # manually replicates the Attr wrapping that uniqueness inference uses, to try and find out why they are different -# It is very important that there are no signatures here! elm uses an optimization that leads to less copying when +# It is very important that there are no signatures here! elm uses an optimization that leads to less copying when # signatures are given. map = diff --git a/compiler/load/tests/fixtures/build/app_with_deps/Primary.roc b/compiler/load/tests/fixtures/build/app_with_deps/Primary.roc index 6fc1d9ff3a..7d473dbc90 100644 --- a/compiler/load/tests/fixtures/build/app_with_deps/Primary.roc +++ b/compiler/load/tests/fixtures/build/app_with_deps/Primary.roc @@ -1,7 +1,6 @@ -app "primary" - packages { blah: "./blah" } +interface Primary + exposes [ blah2, blah3, str, alwaysThree, identity, z, w, succeed, withDefault, yay ] imports [ Dep1, Dep2.{ two, foo }, Dep3.Blah.{ bar }, Res ] - provides [ blah2, blah3, str, alwaysThree, identity, z, w, succeed, withDefault, yay ] to blah blah2 = Dep2.two blah3 = bar diff --git a/compiler/load/tests/fixtures/build/app_with_deps/WithBuiltins.roc b/compiler/load/tests/fixtures/build/app_with_deps/WithBuiltins.roc index 23ba8842f4..4082739f17 100644 --- a/compiler/load/tests/fixtures/build/app_with_deps/WithBuiltins.roc +++ b/compiler/load/tests/fixtures/build/app_with_deps/WithBuiltins.roc @@ -10,7 +10,7 @@ x = 5.0 divisionTest = Num.maxFloat / x -intTest = Num.maxInt +intTest = Num.maxI64 constantNum = 5 diff --git a/compiler/load/tests/fixtures/build/interface_with_deps/ManualAttr.roc b/compiler/load/tests/fixtures/build/interface_with_deps/ManualAttr.roc index 7fd1e010c8..5e51189cbb 100644 --- a/compiler/load/tests/fixtures/build/interface_with_deps/ManualAttr.roc +++ b/compiler/load/tests/fixtures/build/interface_with_deps/ManualAttr.roc @@ -3,7 +3,7 @@ interface ManualAttr imports [] # manually replicates the Attr wrapping that uniqueness inference uses, to try and find out why they are different -# It is very important that there are no signatures here! elm uses an optimization that leads to less copying when +# It is very important that there are no signatures here! elm uses an optimization that leads to less copying when # signatures are given. map = diff --git a/compiler/load/tests/fixtures/build/interface_with_deps/WithBuiltins.roc b/compiler/load/tests/fixtures/build/interface_with_deps/WithBuiltins.roc index 23ba8842f4..4082739f17 100644 --- a/compiler/load/tests/fixtures/build/interface_with_deps/WithBuiltins.roc +++ b/compiler/load/tests/fixtures/build/interface_with_deps/WithBuiltins.roc @@ -10,7 +10,7 @@ x = 5.0 divisionTest = Num.maxFloat / x -intTest = Num.maxInt +intTest = Num.maxI64 constantNum = 5 diff --git a/compiler/load/tests/test_load.rs b/compiler/load/tests/test_load.rs index 373446c3a9..8b4abdd7ca 100644 --- a/compiler/load/tests/test_load.rs +++ b/compiler/load/tests/test_load.rs @@ -28,6 +28,8 @@ mod test_load { use roc_types::subs::Subs; use std::collections::HashMap; + const TARGET_INFO: roc_target::TargetInfo = roc_target::TargetInfo::default_x86_64(); + // HELPERS fn multiple_modules(files: Vec<(&str, &str)>) -> Result { @@ -64,7 +66,7 @@ mod test_load { arena: &'a Bump, mut files: Vec<(&str, &str)>, ) -> Result>, std::io::Error> { - use std::fs::File; + use std::fs::{self, File}; use std::io::Write; use std::path::PathBuf; use tempfile::tempdir; @@ -78,17 +80,15 @@ mod test_load { let dir = tempdir()?; let app_module = files.pop().unwrap(); - let interfaces = files; - debug_assert!( - app_module.1.starts_with("app"), - "The final module should be the application module" - ); - - 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); @@ -110,7 +110,7 @@ mod test_load { arena.alloc(stdlib), dir.path(), exposed_types, - 8, + TARGET_INFO, builtin_defs_map, ) }; @@ -134,7 +134,7 @@ mod test_load { arena.alloc(roc_builtins::std::standard_stdlib()), src_dir.as_path(), subs_by_module, - 8, + TARGET_INFO, builtin_defs_map, ); let mut loaded_module = match loaded { @@ -276,15 +276,10 @@ mod test_load { "Main", indoc!( r#" - app "test-app" - packages { blah: "./blah" } - imports [ RBTree ] - provides [ main ] to blah + interface Other exposes [ empty ] imports [ RBTree ] empty : RBTree.RedBlackTree I64 I64 empty = RBTree.empty - - main = empty "# ), ), @@ -305,7 +300,7 @@ mod test_load { arena.alloc(roc_builtins::std::standard_stdlib()), src_dir.as_path(), subs_by_module, - 8, + TARGET_INFO, builtin_defs_map, ); @@ -379,7 +374,7 @@ mod test_load { "floatTest" => "Float *", "divisionFn" => "Float a, Float a -> Result (Float a) [ DivByZero ]*", "divisionTest" => "Result (Float *) [ DivByZero ]*", - "intTest" => "Int *", + "intTest" => "I64", "x" => "Float *", "constantNum" => "Num *", "divDep1ByDep2" => "Result (Float *) [ DivByZero ]*", @@ -528,10 +523,10 @@ mod test_load { "Main", indoc!( r#" - app "test-app" packages { blah: "./blah" } provides [ main ] to blah + interface Main exposes [ main ] imports [] - main = [ - "# + main = [ + "# ), )]; @@ -541,7 +536,7 @@ mod test_load { indoc!( " \u{1b}[36m── UNFINISHED LIST ─────────────────────────────────────────────────────────────\u{1b}[0m - + I cannot find the end of this list: \u{1b}[36m3\u{1b}[0m\u{1b}[36m│\u{1b}[0m \u{1b}[37mmain = [\u{1b}[0m @@ -559,9 +554,7 @@ mod test_load { } #[test] - #[should_panic( - expected = "FileProblem { filename: \"tests/fixtures/build/interface_with_deps/invalid$name.roc\", error: NotFound }" - )] + #[should_panic(expected = "FILE NOT FOUND")] fn file_not_found() { let subs_by_module = MutMap::default(); let loaded_module = load_fixture("interface_with_deps", "invalid$name", subs_by_module); @@ -587,4 +580,110 @@ mod test_load { }, ); } + + #[test] + fn platform_does_not_exist() { + let modules = vec![( + "Main", + indoc!( + r#" + app "example" + packages { pf: "./zzz-does-not-exist" } + imports [ ] + provides [ main ] to pf + + main = "" + "# + ), + )]; + + match multiple_modules(modules) { + Err(report) => { + assert!(report.contains("FILE NOT FOUND")); + assert!(report.contains("zzz-does-not-exist/Package-Config.roc")); + } + 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()); + } } diff --git a/compiler/module/Cargo.toml b/compiler/module/Cargo.toml index c05ca69ed1..9b8c272d54 100644 --- a/compiler/module/Cargo.toml +++ b/compiler/module/Cargo.toml @@ -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" diff --git a/compiler/module/src/called_via.rs b/compiler/module/src/called_via.rs index a53b20ab08..62d1ecbe3f 100644 --- a/compiler/module/src/called_via.rs +++ b/compiler/module/src/called_via.rs @@ -45,10 +45,11 @@ pub enum BinOp { GreaterThanOrEq, And, Or, - Pizza, // lowest precedence + Pizza, Assignment, HasType, Backpassing, + // lowest precedence } impl BinOp { diff --git a/compiler/module/src/ident.rs b/compiler/module/src/ident.rs index 6dcf965313..73a5fc9737 100644 --- a/compiler/module/src/ident.rs +++ b/compiler/module/src/ident.rs @@ -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 { diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index 1673c55d3d..57f05e194d 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -69,9 +69,11 @@ pub enum LowLevel { NumAdd, NumAddWrap, NumAddChecked, + NumAddSaturated, NumSub, NumSubWrap, NumSubChecked, + NumSubSaturated, NumMul, NumMulWrap, NumMulChecked, @@ -175,138 +177,151 @@ impl LowLevel { _ => unreachable!(), } } +} - /// Used in dev backends to inline some lowlevel wrapper functions - /// For wrappers that contain logic, we return None to prevent inlining - /// (Mention each explicitly rather than using `_`, to show they have not been forgotten) - pub fn from_inlined_wrapper(symbol: Symbol) -> Option { +/// Some wrapper functions can just be replaced by lowlevels in the backend for performance. +/// For example, Num.add should be an instruction, not a function call. +/// Variant names are chosen to help explain what to do when adding new lowlevels +pub enum LowLevelWrapperType { + /// This wrapper function contains no logic and we can remove it in code gen + CanBeReplacedBy(LowLevel), + /// This wrapper function contains important logic and we cannot remove it in code gen + WrapperIsRequired, + NotALowLevelWrapper, +} + +impl LowLevelWrapperType { + pub fn from_symbol(symbol: Symbol) -> LowLevelWrapperType { use LowLevel::*; + use LowLevelWrapperType::*; match symbol { - Symbol::STR_CONCAT => Some(StrConcat), - Symbol::STR_JOIN_WITH => Some(StrJoinWith), - Symbol::STR_IS_EMPTY => Some(StrIsEmpty), - Symbol::STR_STARTS_WITH => Some(StrStartsWith), - Symbol::STR_STARTS_WITH_CODE_PT => Some(StrStartsWithCodePt), - Symbol::STR_ENDS_WITH => Some(StrEndsWith), - Symbol::STR_SPLIT => Some(StrSplit), - Symbol::STR_COUNT_GRAPHEMES => Some(StrCountGraphemes), - Symbol::STR_FROM_UTF8 => None, - Symbol::STR_FROM_UTF8_RANGE => None, - Symbol::STR_TO_UTF8 => Some(StrToUtf8), - Symbol::STR_REPEAT => Some(StrRepeat), - Symbol::STR_TRIM => Some(StrTrim), - Symbol::STR_TRIM_LEFT => Some(StrTrimLeft), - Symbol::STR_TRIM_RIGHT => Some(StrTrimRight), - Symbol::STR_TO_DEC => Some(StrToNum), - Symbol::STR_TO_F64 => Some(StrToNum), - Symbol::STR_TO_F32 => Some(StrToNum), - Symbol::STR_TO_NAT => Some(StrToNum), - Symbol::STR_TO_U128 => Some(StrToNum), - Symbol::STR_TO_I128 => Some(StrToNum), - Symbol::STR_TO_U64 => Some(StrToNum), - Symbol::STR_TO_I64 => Some(StrToNum), - Symbol::STR_TO_U32 => Some(StrToNum), - Symbol::STR_TO_I32 => Some(StrToNum), - Symbol::STR_TO_U16 => Some(StrToNum), - Symbol::STR_TO_I16 => Some(StrToNum), - Symbol::STR_TO_U8 => Some(StrToNum), - Symbol::STR_TO_I8 => Some(StrToNum), - Symbol::LIST_LEN => Some(ListLen), - Symbol::LIST_GET => None, - Symbol::LIST_SET => None, - Symbol::LIST_SINGLE => Some(ListSingle), - Symbol::LIST_REPEAT => Some(ListRepeat), - Symbol::LIST_REVERSE => Some(ListReverse), - Symbol::LIST_CONCAT => Some(ListConcat), - Symbol::LIST_CONTAINS => Some(ListContains), - Symbol::LIST_APPEND => Some(ListAppend), - Symbol::LIST_PREPEND => Some(ListPrepend), - Symbol::LIST_JOIN => Some(ListJoin), - Symbol::LIST_RANGE => Some(ListRange), - Symbol::LIST_MAP => Some(ListMap), - Symbol::LIST_MAP2 => Some(ListMap2), - Symbol::LIST_MAP3 => Some(ListMap3), - Symbol::LIST_MAP4 => Some(ListMap4), - Symbol::LIST_MAP_WITH_INDEX => Some(ListMapWithIndex), - Symbol::LIST_KEEP_IF => Some(ListKeepIf), - Symbol::LIST_WALK => Some(ListWalk), - Symbol::LIST_WALK_UNTIL => Some(ListWalkUntil), - Symbol::LIST_WALK_BACKWARDS => Some(ListWalkBackwards), - Symbol::LIST_KEEP_OKS => Some(ListKeepOks), - Symbol::LIST_KEEP_ERRS => Some(ListKeepErrs), - Symbol::LIST_SORT_WITH => Some(ListSortWith), - Symbol::LIST_SUBLIST => Some(ListSublist), - Symbol::LIST_DROP_AT => Some(ListDropAt), - Symbol::LIST_SWAP => Some(ListSwap), - Symbol::LIST_ANY => Some(ListAny), - Symbol::LIST_ALL => Some(ListAll), - Symbol::LIST_FIND => None, - Symbol::DICT_LEN => Some(DictSize), - Symbol::DICT_EMPTY => Some(DictEmpty), - Symbol::DICT_INSERT => Some(DictInsert), - Symbol::DICT_REMOVE => Some(DictRemove), - Symbol::DICT_CONTAINS => Some(DictContains), - Symbol::DICT_GET => None, - Symbol::DICT_KEYS => Some(DictKeys), - Symbol::DICT_VALUES => Some(DictValues), - Symbol::DICT_UNION => Some(DictUnion), - Symbol::DICT_INTERSECTION => Some(DictIntersection), - Symbol::DICT_DIFFERENCE => Some(DictDifference), - Symbol::DICT_WALK => Some(DictWalk), - Symbol::SET_FROM_LIST => Some(SetFromList), - Symbol::NUM_ADD => Some(NumAdd), - Symbol::NUM_ADD_WRAP => Some(NumAddWrap), - Symbol::NUM_ADD_CHECKED => None, - Symbol::NUM_SUB => Some(NumSub), - Symbol::NUM_SUB_WRAP => Some(NumSubWrap), - Symbol::NUM_SUB_CHECKED => None, - Symbol::NUM_MUL => Some(NumMul), - Symbol::NUM_MUL_WRAP => Some(NumMulWrap), - Symbol::NUM_MUL_CHECKED => None, - Symbol::NUM_GT => Some(NumGt), - Symbol::NUM_GTE => Some(NumGte), - Symbol::NUM_LT => Some(NumLt), - Symbol::NUM_LTE => Some(NumLte), - Symbol::NUM_COMPARE => Some(NumCompare), - Symbol::NUM_DIV_FLOAT => None, - Symbol::NUM_DIV_CEIL => None, - Symbol::NUM_REM => None, - Symbol::NUM_IS_MULTIPLE_OF => Some(NumIsMultipleOf), - Symbol::NUM_ABS => Some(NumAbs), - Symbol::NUM_NEG => Some(NumNeg), - Symbol::NUM_SIN => Some(NumSin), - Symbol::NUM_COS => Some(NumCos), - Symbol::NUM_SQRT => None, - Symbol::NUM_LOG => None, - Symbol::NUM_ROUND => Some(NumRound), - Symbol::NUM_TO_FLOAT => Some(NumToFloat), - Symbol::NUM_POW => Some(NumPow), - Symbol::NUM_CEILING => Some(NumCeiling), - Symbol::NUM_POW_INT => Some(NumPowInt), - Symbol::NUM_FLOOR => Some(NumFloor), - Symbol::NUM_TO_STR => Some(NumToStr), - // => Some(NumIsFinite), - Symbol::NUM_ATAN => Some(NumAtan), - Symbol::NUM_ACOS => Some(NumAcos), - Symbol::NUM_ASIN => Some(NumAsin), - Symbol::NUM_BYTES_TO_U16 => None, - Symbol::NUM_BYTES_TO_U32 => None, - Symbol::NUM_BITWISE_AND => Some(NumBitwiseAnd), - Symbol::NUM_BITWISE_XOR => Some(NumBitwiseXor), - Symbol::NUM_BITWISE_OR => Some(NumBitwiseOr), - Symbol::NUM_SHIFT_LEFT => Some(NumShiftLeftBy), - Symbol::NUM_SHIFT_RIGHT => Some(NumShiftRightBy), - Symbol::NUM_SHIFT_RIGHT_ZERO_FILL => Some(NumShiftRightZfBy), - Symbol::NUM_INT_CAST => Some(NumIntCast), - Symbol::BOOL_EQ => Some(Eq), - Symbol::BOOL_NEQ => Some(NotEq), - Symbol::BOOL_AND => Some(And), - Symbol::BOOL_OR => Some(Or), - Symbol::BOOL_NOT => Some(Not), - // => Some(Hash), - // => Some(ExpectTrue), - _ => None, + Symbol::STR_CONCAT => CanBeReplacedBy(StrConcat), + Symbol::STR_JOIN_WITH => CanBeReplacedBy(StrJoinWith), + Symbol::STR_IS_EMPTY => CanBeReplacedBy(StrIsEmpty), + Symbol::STR_STARTS_WITH => CanBeReplacedBy(StrStartsWith), + Symbol::STR_STARTS_WITH_CODE_PT => CanBeReplacedBy(StrStartsWithCodePt), + Symbol::STR_ENDS_WITH => CanBeReplacedBy(StrEndsWith), + Symbol::STR_SPLIT => CanBeReplacedBy(StrSplit), + Symbol::STR_COUNT_GRAPHEMES => CanBeReplacedBy(StrCountGraphemes), + Symbol::STR_FROM_UTF8 => WrapperIsRequired, + Symbol::STR_FROM_UTF8_RANGE => WrapperIsRequired, + Symbol::STR_TO_UTF8 => CanBeReplacedBy(StrToUtf8), + Symbol::STR_REPEAT => CanBeReplacedBy(StrRepeat), + Symbol::STR_TRIM => CanBeReplacedBy(StrTrim), + Symbol::STR_TRIM_LEFT => CanBeReplacedBy(StrTrimLeft), + Symbol::STR_TRIM_RIGHT => CanBeReplacedBy(StrTrimRight), + Symbol::STR_TO_DEC => WrapperIsRequired, + Symbol::STR_TO_F64 => WrapperIsRequired, + Symbol::STR_TO_F32 => WrapperIsRequired, + Symbol::STR_TO_NAT => WrapperIsRequired, + Symbol::STR_TO_U128 => WrapperIsRequired, + Symbol::STR_TO_I128 => WrapperIsRequired, + Symbol::STR_TO_U64 => WrapperIsRequired, + Symbol::STR_TO_I64 => WrapperIsRequired, + Symbol::STR_TO_U32 => WrapperIsRequired, + Symbol::STR_TO_I32 => WrapperIsRequired, + Symbol::STR_TO_U16 => WrapperIsRequired, + Symbol::STR_TO_I16 => WrapperIsRequired, + Symbol::STR_TO_U8 => WrapperIsRequired, + Symbol::STR_TO_I8 => WrapperIsRequired, + Symbol::LIST_LEN => CanBeReplacedBy(ListLen), + Symbol::LIST_GET => WrapperIsRequired, + Symbol::LIST_SET => WrapperIsRequired, + Symbol::LIST_SINGLE => CanBeReplacedBy(ListSingle), + Symbol::LIST_REPEAT => CanBeReplacedBy(ListRepeat), + Symbol::LIST_REVERSE => CanBeReplacedBy(ListReverse), + Symbol::LIST_CONCAT => CanBeReplacedBy(ListConcat), + Symbol::LIST_CONTAINS => CanBeReplacedBy(ListContains), + Symbol::LIST_APPEND => CanBeReplacedBy(ListAppend), + Symbol::LIST_PREPEND => CanBeReplacedBy(ListPrepend), + Symbol::LIST_JOIN => CanBeReplacedBy(ListJoin), + Symbol::LIST_RANGE => CanBeReplacedBy(ListRange), + Symbol::LIST_MAP => CanBeReplacedBy(ListMap), + Symbol::LIST_MAP2 => CanBeReplacedBy(ListMap2), + Symbol::LIST_MAP3 => CanBeReplacedBy(ListMap3), + Symbol::LIST_MAP4 => CanBeReplacedBy(ListMap4), + Symbol::LIST_MAP_WITH_INDEX => CanBeReplacedBy(ListMapWithIndex), + Symbol::LIST_KEEP_IF => CanBeReplacedBy(ListKeepIf), + Symbol::LIST_WALK => CanBeReplacedBy(ListWalk), + Symbol::LIST_WALK_UNTIL => CanBeReplacedBy(ListWalkUntil), + Symbol::LIST_WALK_BACKWARDS => CanBeReplacedBy(ListWalkBackwards), + Symbol::LIST_KEEP_OKS => CanBeReplacedBy(ListKeepOks), + Symbol::LIST_KEEP_ERRS => CanBeReplacedBy(ListKeepErrs), + Symbol::LIST_SORT_WITH => CanBeReplacedBy(ListSortWith), + Symbol::LIST_SUBLIST => CanBeReplacedBy(ListSublist), + Symbol::LIST_DROP_AT => CanBeReplacedBy(ListDropAt), + Symbol::LIST_SWAP => CanBeReplacedBy(ListSwap), + Symbol::LIST_ANY => CanBeReplacedBy(ListAny), + Symbol::LIST_ALL => CanBeReplacedBy(ListAll), + Symbol::LIST_FIND => WrapperIsRequired, + Symbol::DICT_LEN => CanBeReplacedBy(DictSize), + Symbol::DICT_EMPTY => CanBeReplacedBy(DictEmpty), + Symbol::DICT_INSERT => CanBeReplacedBy(DictInsert), + Symbol::DICT_REMOVE => CanBeReplacedBy(DictRemove), + Symbol::DICT_CONTAINS => CanBeReplacedBy(DictContains), + Symbol::DICT_GET => WrapperIsRequired, + Symbol::DICT_KEYS => CanBeReplacedBy(DictKeys), + Symbol::DICT_VALUES => CanBeReplacedBy(DictValues), + Symbol::DICT_UNION => CanBeReplacedBy(DictUnion), + Symbol::DICT_INTERSECTION => CanBeReplacedBy(DictIntersection), + Symbol::DICT_DIFFERENCE => CanBeReplacedBy(DictDifference), + Symbol::DICT_WALK => CanBeReplacedBy(DictWalk), + Symbol::SET_FROM_LIST => CanBeReplacedBy(SetFromList), + Symbol::NUM_ADD => CanBeReplacedBy(NumAdd), + Symbol::NUM_ADD_WRAP => CanBeReplacedBy(NumAddWrap), + Symbol::NUM_ADD_CHECKED => WrapperIsRequired, + Symbol::NUM_ADD_SATURATED => CanBeReplacedBy(NumAddSaturated), + Symbol::NUM_SUB => CanBeReplacedBy(NumSub), + Symbol::NUM_SUB_WRAP => CanBeReplacedBy(NumSubWrap), + Symbol::NUM_SUB_CHECKED => WrapperIsRequired, + Symbol::NUM_SUB_SATURATED => CanBeReplacedBy(NumSubSaturated), + Symbol::NUM_MUL => CanBeReplacedBy(NumMul), + Symbol::NUM_MUL_WRAP => CanBeReplacedBy(NumMulWrap), + Symbol::NUM_MUL_CHECKED => WrapperIsRequired, + Symbol::NUM_GT => CanBeReplacedBy(NumGt), + Symbol::NUM_GTE => CanBeReplacedBy(NumGte), + Symbol::NUM_LT => CanBeReplacedBy(NumLt), + Symbol::NUM_LTE => CanBeReplacedBy(NumLte), + Symbol::NUM_COMPARE => CanBeReplacedBy(NumCompare), + Symbol::NUM_DIV_FLOAT => WrapperIsRequired, + Symbol::NUM_DIV_CEIL => WrapperIsRequired, + Symbol::NUM_REM => WrapperIsRequired, + Symbol::NUM_IS_MULTIPLE_OF => CanBeReplacedBy(NumIsMultipleOf), + Symbol::NUM_ABS => CanBeReplacedBy(NumAbs), + Symbol::NUM_NEG => CanBeReplacedBy(NumNeg), + Symbol::NUM_SIN => CanBeReplacedBy(NumSin), + Symbol::NUM_COS => CanBeReplacedBy(NumCos), + Symbol::NUM_SQRT => WrapperIsRequired, + Symbol::NUM_LOG => WrapperIsRequired, + Symbol::NUM_ROUND => CanBeReplacedBy(NumRound), + Symbol::NUM_TO_FLOAT => CanBeReplacedBy(NumToFloat), + Symbol::NUM_POW => CanBeReplacedBy(NumPow), + Symbol::NUM_CEILING => CanBeReplacedBy(NumCeiling), + Symbol::NUM_POW_INT => CanBeReplacedBy(NumPowInt), + Symbol::NUM_FLOOR => CanBeReplacedBy(NumFloor), + Symbol::NUM_TO_STR => CanBeReplacedBy(NumToStr), + // => CanBeReplacedBy(NumIsFinite), + Symbol::NUM_ATAN => CanBeReplacedBy(NumAtan), + Symbol::NUM_ACOS => CanBeReplacedBy(NumAcos), + Symbol::NUM_ASIN => CanBeReplacedBy(NumAsin), + Symbol::NUM_BYTES_TO_U16 => WrapperIsRequired, + Symbol::NUM_BYTES_TO_U32 => WrapperIsRequired, + Symbol::NUM_BITWISE_AND => CanBeReplacedBy(NumBitwiseAnd), + Symbol::NUM_BITWISE_XOR => CanBeReplacedBy(NumBitwiseXor), + Symbol::NUM_BITWISE_OR => CanBeReplacedBy(NumBitwiseOr), + Symbol::NUM_SHIFT_LEFT => CanBeReplacedBy(NumShiftLeftBy), + Symbol::NUM_SHIFT_RIGHT => CanBeReplacedBy(NumShiftRightBy), + Symbol::NUM_SHIFT_RIGHT_ZERO_FILL => CanBeReplacedBy(NumShiftRightZfBy), + Symbol::NUM_INT_CAST => CanBeReplacedBy(NumIntCast), + Symbol::BOOL_EQ => CanBeReplacedBy(Eq), + Symbol::BOOL_NEQ => CanBeReplacedBy(NotEq), + Symbol::BOOL_AND => CanBeReplacedBy(And), + Symbol::BOOL_OR => CanBeReplacedBy(Or), + Symbol::BOOL_NOT => CanBeReplacedBy(Not), + // => CanBeReplacedBy(Hash), + // => CanBeReplacedBy(ExpectTrue), + _ => NotALowLevelWrapper, } } } diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index a1f6706576..6d9c7461cd 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -903,78 +903,78 @@ define_builtins! { 15 NUM_F32: "F32" imported // the Num.F32 type alias 16 NUM_FLOATINGPOINT: "FloatingPoint" imported // Float : Num FloatingPoint 17 NUM_AT_FLOATINGPOINT: "@FloatingPoint" // the Float.@FloatingPoint private tag - 18 NUM_MAX_INT: "maxInt" - 19 NUM_MIN_INT: "minInt" - 20 NUM_MAX_FLOAT: "maxFloat" - 21 NUM_MIN_FLOAT: "minFloat" - 22 NUM_ABS: "abs" - 23 NUM_NEG: "neg" - 24 NUM_ADD: "add" - 25 NUM_SUB: "sub" - 26 NUM_MUL: "mul" - 27 NUM_LT: "isLt" - 28 NUM_LTE: "isLte" - 29 NUM_GT: "isGt" - 30 NUM_GTE: "isGte" - 31 NUM_TO_FLOAT: "toFloat" - 32 NUM_SIN: "sin" - 33 NUM_COS: "cos" - 34 NUM_TAN: "tan" - 35 NUM_IS_ZERO: "isZero" - 36 NUM_IS_EVEN: "isEven" - 37 NUM_IS_ODD: "isOdd" - 38 NUM_IS_POSITIVE: "isPositive" - 39 NUM_IS_NEGATIVE: "isNegative" - 40 NUM_REM: "rem" - 41 NUM_DIV_FLOAT: "div" - 42 NUM_DIV_INT: "divFloor" - 43 NUM_MOD_INT: "modInt" - 44 NUM_MOD_FLOAT: "modFloat" - 45 NUM_SQRT: "sqrt" - 46 NUM_LOG: "log" - 47 NUM_ROUND: "round" - 48 NUM_COMPARE: "compare" - 49 NUM_POW: "pow" - 50 NUM_CEILING: "ceiling" - 51 NUM_POW_INT: "powInt" - 52 NUM_FLOOR: "floor" - 53 NUM_ADD_WRAP: "addWrap" - 54 NUM_ADD_CHECKED: "addChecked" - 55 NUM_ATAN: "atan" - 56 NUM_ACOS: "acos" - 57 NUM_ASIN: "asin" - 58 NUM_AT_SIGNED128: "@Signed128" - 59 NUM_SIGNED128: "Signed128" imported - 60 NUM_AT_SIGNED64: "@Signed64" - 61 NUM_SIGNED64: "Signed64" imported - 62 NUM_AT_SIGNED32: "@Signed32" - 63 NUM_SIGNED32: "Signed32" imported - 64 NUM_AT_SIGNED16: "@Signed16" - 65 NUM_SIGNED16: "Signed16" imported - 66 NUM_AT_SIGNED8: "@Signed8" - 67 NUM_SIGNED8: "Signed8" imported - 68 NUM_AT_UNSIGNED128: "@Unsigned128" - 69 NUM_UNSIGNED128: "Unsigned128" imported - 70 NUM_AT_UNSIGNED64: "@Unsigned64" - 71 NUM_UNSIGNED64: "Unsigned64" imported - 72 NUM_AT_UNSIGNED32: "@Unsigned32" - 73 NUM_UNSIGNED32: "Unsigned32" imported - 74 NUM_AT_UNSIGNED16: "@Unsigned16" - 75 NUM_UNSIGNED16: "Unsigned16" imported - 76 NUM_AT_UNSIGNED8: "@Unsigned8" - 77 NUM_UNSIGNED8: "Unsigned8" imported - 78 NUM_AT_BINARY64: "@Binary64" - 79 NUM_BINARY64: "Binary64" imported - 80 NUM_AT_BINARY32: "@Binary32" - 81 NUM_BINARY32: "Binary32" imported - 82 NUM_BITWISE_AND: "bitwiseAnd" - 83 NUM_BITWISE_XOR: "bitwiseXor" - 84 NUM_BITWISE_OR: "bitwiseOr" - 85 NUM_SHIFT_LEFT: "shiftLeftBy" - 86 NUM_SHIFT_RIGHT: "shiftRightBy" - 87 NUM_SHIFT_RIGHT_ZERO_FILL: "shiftRightZfBy" - 88 NUM_SUB_WRAP: "subWrap" - 89 NUM_SUB_CHECKED: "subChecked" + 18 NUM_MAX_FLOAT: "maxFloat" + 19 NUM_MIN_FLOAT: "minFloat" + 20 NUM_ABS: "abs" + 21 NUM_NEG: "neg" + 22 NUM_ADD: "add" + 23 NUM_SUB: "sub" + 24 NUM_MUL: "mul" + 25 NUM_LT: "isLt" + 26 NUM_LTE: "isLte" + 27 NUM_GT: "isGt" + 28 NUM_GTE: "isGte" + 29 NUM_TO_FLOAT: "toFloat" + 30 NUM_SIN: "sin" + 31 NUM_COS: "cos" + 32 NUM_TAN: "tan" + 33 NUM_IS_ZERO: "isZero" + 34 NUM_IS_EVEN: "isEven" + 35 NUM_IS_ODD: "isOdd" + 36 NUM_IS_POSITIVE: "isPositive" + 37 NUM_IS_NEGATIVE: "isNegative" + 38 NUM_REM: "rem" + 39 NUM_DIV_FLOAT: "div" + 40 NUM_DIV_INT: "divFloor" + 41 NUM_MOD_INT: "modInt" + 42 NUM_MOD_FLOAT: "modFloat" + 43 NUM_SQRT: "sqrt" + 44 NUM_LOG: "log" + 45 NUM_ROUND: "round" + 46 NUM_COMPARE: "compare" + 47 NUM_POW: "pow" + 48 NUM_CEILING: "ceiling" + 49 NUM_POW_INT: "powInt" + 50 NUM_FLOOR: "floor" + 51 NUM_ADD_WRAP: "addWrap" + 52 NUM_ADD_CHECKED: "addChecked" + 53 NUM_ADD_SATURATED: "addSaturated" + 54 NUM_ATAN: "atan" + 55 NUM_ACOS: "acos" + 56 NUM_ASIN: "asin" + 57 NUM_AT_SIGNED128: "@Signed128" + 58 NUM_SIGNED128: "Signed128" imported + 59 NUM_AT_SIGNED64: "@Signed64" + 60 NUM_SIGNED64: "Signed64" imported + 61 NUM_AT_SIGNED32: "@Signed32" + 62 NUM_SIGNED32: "Signed32" imported + 63 NUM_AT_SIGNED16: "@Signed16" + 64 NUM_SIGNED16: "Signed16" imported + 65 NUM_AT_SIGNED8: "@Signed8" + 66 NUM_SIGNED8: "Signed8" imported + 67 NUM_AT_UNSIGNED128: "@Unsigned128" + 68 NUM_UNSIGNED128: "Unsigned128" imported + 69 NUM_AT_UNSIGNED64: "@Unsigned64" + 70 NUM_UNSIGNED64: "Unsigned64" imported + 71 NUM_AT_UNSIGNED32: "@Unsigned32" + 72 NUM_UNSIGNED32: "Unsigned32" imported + 73 NUM_AT_UNSIGNED16: "@Unsigned16" + 74 NUM_UNSIGNED16: "Unsigned16" imported + 75 NUM_AT_UNSIGNED8: "@Unsigned8" + 76 NUM_UNSIGNED8: "Unsigned8" imported + 77 NUM_AT_BINARY64: "@Binary64" + 78 NUM_BINARY64: "Binary64" imported + 79 NUM_AT_BINARY32: "@Binary32" + 80 NUM_BINARY32: "Binary32" imported + 81 NUM_BITWISE_AND: "bitwiseAnd" + 82 NUM_BITWISE_XOR: "bitwiseXor" + 83 NUM_BITWISE_OR: "bitwiseOr" + 84 NUM_SHIFT_LEFT: "shiftLeftBy" + 85 NUM_SHIFT_RIGHT: "shiftRightBy" + 86 NUM_SHIFT_RIGHT_ZERO_FILL: "shiftRightZfBy" + 87 NUM_SUB_WRAP: "subWrap" + 88 NUM_SUB_CHECKED: "subChecked" + 89 NUM_SUB_SATURATED: "subSaturated" 90 NUM_MUL_WRAP: "mulWrap" 91 NUM_MUL_CHECKED: "mulChecked" 92 NUM_INT: "Int" imported @@ -983,16 +983,33 @@ define_builtins! { 95 NUM_NATURAL: "Natural" imported 96 NUM_NAT: "Nat" imported 97 NUM_INT_CAST: "intCast" - 98 NUM_MAX_I128: "maxI128" - 99 NUM_IS_MULTIPLE_OF: "isMultipleOf" - 100 NUM_AT_DECIMAL: "@Decimal" - 101 NUM_DECIMAL: "Decimal" imported - 102 NUM_DEC: "Dec" imported // the Num.Dectype alias - 103 NUM_BYTES_TO_U16: "bytesToU16" - 104 NUM_BYTES_TO_U32: "bytesToU32" - 105 NUM_CAST_TO_NAT: "#castToNat" - 106 NUM_DIV_CEIL: "divCeil" - 107 NUM_TO_STR: "toStr" + 98 NUM_IS_MULTIPLE_OF: "isMultipleOf" + 99 NUM_AT_DECIMAL: "@Decimal" + 100 NUM_DECIMAL: "Decimal" imported + 101 NUM_DEC: "Dec" imported // the Num.Dectype alias + 102 NUM_BYTES_TO_U16: "bytesToU16" + 103 NUM_BYTES_TO_U32: "bytesToU32" + 104 NUM_CAST_TO_NAT: "#castToNat" + 105 NUM_DIV_CEIL: "divCeil" + 106 NUM_TO_STR: "toStr" + 107 NUM_MIN_I8: "minI8" + 108 NUM_MAX_I8: "maxI8" + 109 NUM_MIN_U8: "minU8" + 110 NUM_MAX_U8: "maxU8" + 111 NUM_MIN_I16: "minI16" + 112 NUM_MAX_I16: "maxI16" + 113 NUM_MIN_U16: "minU16" + 114 NUM_MAX_U16: "maxU16" + 115 NUM_MIN_I32: "minI32" + 116 NUM_MAX_I32: "maxI32" + 117 NUM_MIN_U32: "minU32" + 118 NUM_MAX_U32: "maxU32" + 119 NUM_MIN_I64: "minI64" + 120 NUM_MAX_I64: "maxI64" + 121 NUM_MIN_U64: "minU64" + 122 NUM_MAX_U64: "maxU64" + 123 NUM_MIN_I128: "minI128" + 124 NUM_MAX_I128: "maxI128" } 2 BOOL: "Bool" => { 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias @@ -1073,36 +1090,37 @@ define_builtins! { 24 LIST_MAP2: "map2" 25 LIST_MAP3: "map3" 26 LIST_PRODUCT: "product" - 27 LIST_SUM_ADD: "#sumadd" - 28 LIST_PRODUCT_MUL: "#productmul" - 29 LIST_WALK_UNTIL: "walkUntil" - 30 LIST_RANGE: "range" - 31 LIST_SORT_WITH: "sortWith" - 32 LIST_DROP: "drop" - 33 LIST_SWAP: "swap" - 34 LIST_DROP_AT: "dropAt" - 35 LIST_DROP_LAST: "dropLast" - 36 LIST_MIN: "min" - 37 LIST_MIN_LT: "#minlt" - 38 LIST_MAX: "max" - 39 LIST_MAX_GT: "#maxGt" - 40 LIST_MAP4: "map4" - 41 LIST_DROP_FIRST: "dropFirst" - 42 LIST_JOIN_MAP: "joinMap" - 43 LIST_JOIN_MAP_CONCAT: "#joinMapConcat" - 44 LIST_ANY: "any" - 45 LIST_TAKE_FIRST: "takeFirst" - 46 LIST_TAKE_LAST: "takeLast" - 47 LIST_FIND: "find" - 48 LIST_FIND_RESULT: "#find_result" // symbol used in the definition of List.find - 49 LIST_SUBLIST: "sublist" - 50 LIST_INTERSPERSE: "intersperse" - 51 LIST_INTERSPERSE_CLOS: "#intersperseClos" - 52 LIST_SPLIT: "split" - 53 LIST_SPLIT_CLOS: "#splitClos" - 54 LIST_ALL: "all" - 55 LIST_DROP_IF: "dropIf" - 56 LIST_DROP_IF_PREDICATE: "#dropIfPred" + 27 LIST_WALK_UNTIL: "walkUntil" + 28 LIST_RANGE: "range" + 29 LIST_SORT_WITH: "sortWith" + 30 LIST_DROP: "drop" + 31 LIST_SWAP: "swap" + 32 LIST_DROP_AT: "dropAt" + 33 LIST_DROP_LAST: "dropLast" + 34 LIST_MIN: "min" + 35 LIST_MIN_LT: "#minlt" + 36 LIST_MAX: "max" + 37 LIST_MAX_GT: "#maxGt" + 38 LIST_MAP4: "map4" + 39 LIST_DROP_FIRST: "dropFirst" + 40 LIST_JOIN_MAP: "joinMap" + 41 LIST_JOIN_MAP_CONCAT: "#joinMapConcat" + 42 LIST_ANY: "any" + 43 LIST_TAKE_FIRST: "takeFirst" + 44 LIST_TAKE_LAST: "takeLast" + 45 LIST_FIND: "find" + 46 LIST_FIND_RESULT: "#find_result" // symbol used in the definition of List.find + 47 LIST_SUBLIST: "sublist" + 48 LIST_INTERSPERSE: "intersperse" + 49 LIST_INTERSPERSE_CLOS: "#intersperseClos" + 50 LIST_SPLIT: "split" + 51 LIST_SPLIT_CLOS: "#splitClos" + 52 LIST_ALL: "all" + 53 LIST_DROP_IF: "dropIf" + 54 LIST_DROP_IF_PREDICATE: "#dropIfPred" + 55 LIST_SORT_ASC: "sortAsc" + 56 LIST_SORT_DESC: "sortDesc" + 57 LIST_SORT_DESC_COMPARE: "#sortDescCompare" } 5 RESULT: "Result" => { 0 RESULT_RESULT: "Result" imported // the Result.Result type alias diff --git a/compiler/mono/Cargo.toml b/compiler/mono/Cargo.toml index 6cd3129ed3..5bdf3723fa 100644 --- a/compiler/mono/Cargo.toml +++ b/compiler/mono/Cargo.toml @@ -16,6 +16,8 @@ roc_solve = { path = "../solve" } roc_std = { path = "../../roc_std" } roc_problem = { path = "../problem" } roc_builtins = { path = "../builtins" } +roc_target = { path = "../roc_target" } +roc_error_macros = {path="../../error_macros"} ven_pretty = { path = "../../vendor/pretty" } morphic_lib = { path = "../../vendor/morphic_lib" } bumpalo = { version = "3.8.0", features = ["collections"] } diff --git a/compiler/mono/src/alias_analysis.rs b/compiler/mono/src/alias_analysis.rs index 6481f924c4..e52409c240 100644 --- a/compiler/mono/src/alias_analysis.rs +++ b/compiler/mono/src/alias_analysis.rs @@ -1638,7 +1638,9 @@ fn literal_spec( match literal { Str(_) => new_static_string(builder, block), - Int(_) | Float(_) | Decimal(_) | Bool(_) | Byte(_) => builder.add_make_tuple(block, &[]), + Int(_) | U128(_) | Float(_) | Decimal(_) | Bool(_) | Byte(_) => { + builder.add_make_tuple(block, &[]) + } } } diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index 50b5e4498c..2109204d7a 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -972,11 +972,13 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { Eq | NotEq => arena.alloc_slice_copy(&[borrowed, borrowed]), - And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap | NumSubChecked - | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumCompare - | NumDivUnchecked | NumDivCeilUnchecked | NumRemUnchecked | NumIsMultipleOf | NumPow - | NumPowInt | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy - | NumShiftRightBy | NumShiftRightZfBy => arena.alloc_slice_copy(&[irrelevant, irrelevant]), + And | Or | NumAdd | NumAddWrap | NumAddChecked | NumAddSaturated | NumSub | NumSubWrap + | NumSubChecked | NumSubSaturated | NumMul | NumMulWrap | NumMulChecked | NumGt + | NumGte | NumLt | NumLte | NumCompare | NumDivUnchecked | NumDivCeilUnchecked + | NumRemUnchecked | NumIsMultipleOf | NumPow | NumPowInt | NumBitwiseAnd + | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumShiftRightBy | NumShiftRightZfBy => { + arena.alloc_slice_copy(&[irrelevant, irrelevant]) + } NumToStr | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked | NumRound | NumCeiling | NumFloor | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos diff --git a/compiler/mono/src/code_gen_help/equality.rs b/compiler/mono/src/code_gen_help/equality.rs index 16fe3b1ba2..d58d095274 100644 --- a/compiler/mono/src/code_gen_help/equality.rs +++ b/compiler/mono/src/code_gen_help/equality.rs @@ -151,12 +151,14 @@ fn eq_struct<'a>( }; let field2_stmt = |next| Stmt::Let(field2_sym, field2_expr, *layout, next); - let eq_call_expr = root.call_specialized_op( - ident_ids, - ctx, - *layout, - root.arena.alloc([field1_sym, field2_sym]), - ); + let eq_call_expr = root + .call_specialized_op( + ident_ids, + ctx, + *layout, + root.arena.alloc([field1_sym, field2_sym]), + ) + .unwrap(); let eq_call_name = format!("eq_call_{}", i); let eq_call_sym = root.create_symbol(ident_ids, &eq_call_name); @@ -478,12 +480,14 @@ fn eq_tag_fields<'a>( structure: operands[1], }; - let eq_call_expr = root.call_specialized_op( - ident_ids, - ctx, - *layout, - root.arena.alloc([field1_sym, field2_sym]), - ); + let eq_call_expr = root + .call_specialized_op( + ident_ids, + ctx, + *layout, + root.arena.alloc([field1_sym, field2_sym]), + ) + .unwrap(); let eq_call_name = format!("eq_call_{}", i); let eq_call_sym = root.create_symbol(ident_ids, &eq_call_name); @@ -586,7 +590,9 @@ fn eq_list<'a>( // let size = literal int let size = root.create_symbol(ident_ids, "size"); - let size_expr = Expr::Literal(Literal::Int(elem_layout.stack_size(root.ptr_size) as i128)); + let size_expr = Expr::Literal(Literal::Int( + elem_layout.stack_size(root.target_info) as i128 + )); let size_stmt = |next| Stmt::Let(size, size_expr, layout_isize, next); // let list_size = len_1 * size @@ -657,7 +663,10 @@ fn eq_list<'a>( // Compare the two current elements let eq_elems = root.create_symbol(ident_ids, "eq_elems"); - let eq_elems_expr = root.call_specialized_op(ident_ids, ctx, *elem_layout, &[elem1, elem2]); + let eq_elems_args = root.arena.alloc([elem1, elem2]); + let eq_elems_expr = root + .call_specialized_op(ident_ids, ctx, *elem_layout, eq_elems_args) + .unwrap(); let eq_elems_stmt = |next| Stmt::Let(eq_elems, eq_elems_expr, LAYOUT_BOOL, next); diff --git a/compiler/mono/src/code_gen_help/mod.rs b/compiler/mono/src/code_gen_help/mod.rs index f5943ba935..e74e4058ed 100644 --- a/compiler/mono/src/code_gen_help/mod.rs +++ b/compiler/mono/src/code_gen_help/mod.rs @@ -1,12 +1,12 @@ use bumpalo::collections::vec::Vec; use bumpalo::Bump; -use roc_builtins::bitcode::IntWidth; use roc_module::ident::Ident; use roc_module::low_level::LowLevel; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; +use roc_target::TargetInfo; use crate::ir::{ - Call, CallSpecId, CallType, Expr, HostExposedLayouts, Literal, ModifyRc, Proc, ProcLayout, + Call, CallSpecId, CallType, Expr, HostExposedLayouts, JoinPointId, ModifyRc, Proc, ProcLayout, SelfRecursive, Stmt, UpdateModeId, }; use crate::layout::{Builtin, Layout, UnionLayout}; @@ -28,17 +28,13 @@ pub const REFCOUNT_MAX: usize = 0; enum HelperOp { Inc, Dec, - DecRef, + DecRef(JoinPointId), Eq, } -impl From<&ModifyRc> for HelperOp { - fn from(modify: &ModifyRc) -> Self { - match modify { - ModifyRc::Inc(..) => Self::Inc, - ModifyRc::Dec(_) => Self::Dec, - ModifyRc::DecRef(_) => Self::DecRef, - } +impl HelperOp { + fn is_decref(&self) -> bool { + matches!(self, Self::DecRef(_)) } } @@ -78,19 +74,19 @@ pub struct Context<'a> { pub struct CodeGenHelp<'a> { arena: &'a Bump, home: ModuleId, - ptr_size: u32, + target_info: TargetInfo, layout_isize: Layout<'a>, specializations: Vec<'a, Specialization<'a>>, debug_recursion_depth: usize, } impl<'a> CodeGenHelp<'a> { - pub fn new(arena: &'a Bump, intwidth_isize: IntWidth, home: ModuleId) -> Self { + pub fn new(arena: &'a Bump, target_info: TargetInfo, home: ModuleId) -> Self { CodeGenHelp { arena, home, - ptr_size: intwidth_isize.stack_size(), - layout_isize: Layout::Builtin(Builtin::Int(intwidth_isize)), + target_info, + layout_isize: Layout::usize(target_info), specializations: Vec::with_capacity_in(16, arena), debug_recursion_depth: 0, } @@ -129,83 +125,23 @@ impl<'a> CodeGenHelp<'a> { return (following, Vec::new_in(self.arena)); } - let arena = self.arena; + let op = match modify { + ModifyRc::Inc(..) => HelperOp::Inc, + ModifyRc::Dec(_) => HelperOp::Dec, + ModifyRc::DecRef(_) => { + let jp_decref = JoinPointId(self.create_symbol(ident_ids, "jp_decref")); + HelperOp::DecRef(jp_decref) + } + }; let mut ctx = Context { new_linker_data: Vec::new_in(self.arena), recursive_union: None, - op: HelperOp::from(modify), + op, }; - match modify { - ModifyRc::Inc(structure, amount) => { - let layout_isize = self.layout_isize; - - // Define a constant for the amount to increment - let amount_sym = self.create_symbol(ident_ids, "amount"); - let amount_expr = Expr::Literal(Literal::Int(*amount as i128)); - let amount_stmt = |next| Stmt::Let(amount_sym, amount_expr, layout_isize, next); - - // Call helper proc, passing the Roc structure and constant amount - let call_result_empty = self.create_symbol(ident_ids, "call_result_empty"); - let call_expr = self.call_specialized_op( - ident_ids, - &mut ctx, - layout, - arena.alloc([*structure, amount_sym]), - ); - let call_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following); - let rc_stmt = arena.alloc(amount_stmt(arena.alloc(call_stmt))); - - (rc_stmt, ctx.new_linker_data) - } - - ModifyRc::Dec(structure) => { - // Call helper proc, passing the Roc structure - let call_result_empty = self.create_symbol(ident_ids, "call_result_empty"); - let call_expr = self.call_specialized_op( - ident_ids, - &mut ctx, - layout, - arena.alloc([*structure]), - ); - - let rc_stmt = arena.alloc(Stmt::Let( - call_result_empty, - call_expr, - LAYOUT_UNIT, - following, - )); - - (rc_stmt, ctx.new_linker_data) - } - - ModifyRc::DecRef(structure) => { - // No generated procs for DecRef, just lowlevel ops - let rc_ptr_sym = self.create_symbol(ident_ids, "rc_ptr"); - - // Pass the refcount pointer to the lowlevel call (see utils.zig) - let call_result_empty = self.create_symbol(ident_ids, "call_result_empty"); - let call_expr = Expr::Call(Call { - call_type: CallType::LowLevel { - op: LowLevel::RefCountDec, - update_mode: UpdateModeId::BACKEND_DUMMY, - }, - arguments: arena.alloc([rc_ptr_sym]), - }); - let call_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following); - - let rc_stmt = arena.alloc(refcount::rc_ptr_from_struct( - self, - ident_ids, - *structure, - rc_ptr_sym, - arena.alloc(call_stmt), - )); - - (rc_stmt, ctx.new_linker_data) - } - } + let rc_stmt = refcount::refcount_stmt(self, ident_ids, &mut ctx, layout, modify, following); + (rc_stmt, ctx.new_linker_data) } /// Replace a generic `Lowlevel::Eq` call with a specialized helper proc. @@ -222,7 +158,9 @@ impl<'a> CodeGenHelp<'a> { op: HelperOp::Eq, }; - let expr = self.call_specialized_op(ident_ids, &mut ctx, *layout, arguments); + let expr = self + .call_specialized_op(ident_ids, &mut ctx, *layout, arguments) + .unwrap(); (expr, ctx.new_linker_data) } @@ -238,11 +176,11 @@ impl<'a> CodeGenHelp<'a> { ident_ids: &mut IdentIds, ctx: &mut Context<'a>, called_layout: Layout<'a>, - arguments: &[Symbol], - ) -> Expr<'a> { + arguments: &'a [Symbol], + ) -> Option> { use HelperOp::*; - debug_assert!(self.debug_recursion_depth < 10); + // debug_assert!(self.debug_recursion_depth < 100); self.debug_recursion_depth += 1; let layout = if matches!(called_layout, Layout::RecursivePointer) { @@ -257,29 +195,31 @@ impl<'a> CodeGenHelp<'a> { let (ret_layout, arg_layouts): (&'a Layout<'a>, &'a [Layout<'a>]) = { match ctx.op { - Dec | DecRef => (&LAYOUT_UNIT, self.arena.alloc([layout])), + Dec | DecRef(_) => (&LAYOUT_UNIT, self.arena.alloc([layout])), Inc => (&LAYOUT_UNIT, self.arena.alloc([layout, self.layout_isize])), Eq => (&LAYOUT_BOOL, self.arena.alloc([layout, layout])), } }; - Expr::Call(Call { + Some(Expr::Call(Call { call_type: CallType::ByName { name: proc_name, ret_layout, arg_layouts, specialization_id: CallSpecId::BACKEND_DUMMY, }, - arguments: self.arena.alloc_slice_copy(arguments), - }) - } else { - Expr::Call(Call { + arguments, + })) + } else if ctx.op == HelperOp::Eq { + Some(Expr::Call(Call { call_type: CallType::LowLevel { op: LowLevel::Eq, update_mode: UpdateModeId::BACKEND_DUMMY, }, - arguments: self.arena.alloc_slice_copy(arguments), - }) + arguments, + })) + } else { + None } } @@ -291,6 +231,8 @@ impl<'a> CodeGenHelp<'a> { ) -> Symbol { use HelperOp::*; + let layout = self.replace_rec_ptr(ctx, layout); + let found = self .specializations .iter() @@ -315,9 +257,9 @@ impl<'a> CodeGenHelp<'a> { // Recursively generate the body of the Proc and sub-procs let (ret_layout, body) = match ctx.op { - Inc | Dec | DecRef => ( + Inc | Dec | DecRef(_) => ( LAYOUT_UNIT, - refcount::refcount_generic(self, ident_ids, ctx, layout), + refcount::refcount_generic(self, ident_ids, ctx, layout, Symbol::ARG_1), ), Eq => ( LAYOUT_BOOL, @@ -332,7 +274,7 @@ impl<'a> CodeGenHelp<'a> { let inc_amount = (self.layout_isize, ARG_2); self.arena.alloc([roc_value, inc_amount]) } - Dec | DecRef => self.arena.alloc([roc_value]), + Dec | DecRef(_) => self.arena.alloc([roc_value]), Eq => self.arena.alloc([roc_value, (layout, ARG_2)]), } }; @@ -375,7 +317,7 @@ impl<'a> CodeGenHelp<'a> { arguments: self.arena.alloc([*layout]), result: LAYOUT_UNIT, }, - HelperOp::DecRef => unreachable!("No generated Proc for DecRef"), + HelperOp::DecRef(_) => unreachable!("No generated Proc for DecRef"), HelperOp::Eq => ProcLayout { arguments: self.arena.alloc([*layout, *layout]), result: LAYOUT_BOOL, @@ -389,6 +331,98 @@ impl<'a> CodeGenHelp<'a> { let ident_id = ident_ids.add(Ident::from(debug_name)); Symbol::new(self.home, ident_id) } + + // When creating or looking up Specializations, we need to replace RecursivePointer + // with the particular Union layout it represents at this point in the tree. + // For example if a program uses `RoseTree a : [ Tree a (List (RoseTree a)) ]` + // then it could have both `RoseTree I64` and `RoseTree Str`. In this case it + // needs *two* specializations for `List(RecursivePointer)`, not just one. + fn replace_rec_ptr(&self, ctx: &Context<'a>, layout: Layout<'a>) -> Layout<'a> { + match layout { + Layout::Builtin(Builtin::Dict(k, v)) => Layout::Builtin(Builtin::Dict( + self.arena.alloc(self.replace_rec_ptr(ctx, *k)), + self.arena.alloc(self.replace_rec_ptr(ctx, *v)), + )), + + Layout::Builtin(Builtin::Set(k)) => Layout::Builtin(Builtin::Set( + self.arena.alloc(self.replace_rec_ptr(ctx, *k)), + )), + + Layout::Builtin(Builtin::List(v)) => Layout::Builtin(Builtin::List( + self.arena.alloc(self.replace_rec_ptr(ctx, *v)), + )), + + Layout::Builtin(_) => layout, + + Layout::Struct(fields) => { + let new_fields_iter = fields.iter().map(|f| self.replace_rec_ptr(ctx, *f)); + Layout::Struct(self.arena.alloc_slice_fill_iter(new_fields_iter)) + } + + Layout::Union(UnionLayout::NonRecursive(tags)) => { + let mut new_tags = Vec::with_capacity_in(tags.len(), self.arena); + for fields in tags { + let mut new_fields = Vec::with_capacity_in(fields.len(), self.arena); + for field in fields.iter() { + new_fields.push(self.replace_rec_ptr(ctx, *field)) + } + new_tags.push(new_fields.into_bump_slice()); + } + Layout::Union(UnionLayout::NonRecursive(new_tags.into_bump_slice())) + } + + Layout::Union(_) => layout, + + Layout::LambdaSet(lambda_set) => { + self.replace_rec_ptr(ctx, lambda_set.runtime_representation()) + } + + // This line is the whole point of the function + Layout::RecursivePointer => Layout::Union(ctx.recursive_union.unwrap()), + } + } + + fn union_tail_recursion_fields( + &self, + union: UnionLayout<'a>, + ) -> (bool, Vec<'a, Option>) { + use UnionLayout::*; + match union { + NonRecursive(_) => return (false, bumpalo::vec![in self.arena]), + + Recursive(tags) => self.union_tail_recursion_fields_help(tags), + + NonNullableUnwrapped(field_layouts) => { + self.union_tail_recursion_fields_help(&[field_layouts]) + } + + NullableWrapped { + other_tags: tags, .. + } => self.union_tail_recursion_fields_help(tags), + + NullableUnwrapped { other_fields, .. } => { + self.union_tail_recursion_fields_help(&[other_fields]) + } + } + } + + fn union_tail_recursion_fields_help( + &self, + tags: &[&'a [Layout<'a>]], + ) -> (bool, Vec<'a, Option>) { + let mut can_use_tailrec = false; + let mut tailrec_indices = Vec::with_capacity_in(tags.len(), self.arena); + + for fields in tags.iter() { + let found_index = fields + .iter() + .position(|f| matches!(f, Layout::RecursivePointer)); + tailrec_indices.push(found_index); + can_use_tailrec |= found_index.is_some(); + } + + (can_use_tailrec, tailrec_indices) + } } fn let_lowlevel<'a>( @@ -423,7 +457,7 @@ fn layout_needs_helper_proc(layout: &Layout, op: HelperOp) -> bool { // Str type can use either Zig functions or generated IR, since it's not generic. // Eq uses a Zig function, refcount uses generated IR. // Both are fine, they were just developed at different times. - matches!(op, HelperOp::Inc | HelperOp::Dec | HelperOp::DecRef) + matches!(op, HelperOp::Inc | HelperOp::Dec | HelperOp::DecRef(_)) } Layout::Builtin(Builtin::Dict(_, _) | Builtin::Set(_) | Builtin::List(_)) => true, diff --git a/compiler/mono/src/code_gen_help/refcount.rs b/compiler/mono/src/code_gen_help/refcount.rs index f5f0e8cfe6..272e503d18 100644 --- a/compiler/mono/src/code_gen_help/refcount.rs +++ b/compiler/mono/src/code_gen_help/refcount.rs @@ -1,9 +1,13 @@ +use bumpalo::collections::vec::Vec; use roc_builtins::bitcode::IntWidth; -use roc_module::low_level::LowLevel; +use roc_module::low_level::{LowLevel, LowLevel::*}; use roc_module::symbol::{IdentIds, Symbol}; -use crate::ir::{BranchInfo, Call, CallType, Expr, Literal, Stmt, UpdateModeId}; -use crate::layout::{Builtin, Layout}; +use crate::code_gen_help::let_lowlevel; +use crate::ir::{ + BranchInfo, Call, CallType, Expr, JoinPointId, Literal, ModifyRc, Param, Stmt, UpdateModeId, +}; +use crate::layout::{Builtin, Layout, TagIdIntType, UnionLayout}; use super::{CodeGenHelp, Context, HelperOp}; @@ -12,14 +16,88 @@ const LAYOUT_UNIT: Layout = Layout::Struct(&[]); const LAYOUT_PTR: Layout = Layout::RecursivePointer; const LAYOUT_U32: Layout = Layout::Builtin(Builtin::Int(IntWidth::U32)); -const ARG_1: Symbol = Symbol::ARG_1; -const ARG_2: Symbol = Symbol::ARG_2; - -pub fn refcount_generic<'a>( - root: &CodeGenHelp<'a>, +pub fn refcount_stmt<'a>( + root: &mut CodeGenHelp<'a>, ident_ids: &mut IdentIds, ctx: &mut Context<'a>, layout: Layout<'a>, + modify: &ModifyRc, + following: &'a Stmt<'a>, +) -> &'a Stmt<'a> { + let arena = root.arena; + + match modify { + ModifyRc::Inc(structure, amount) => { + let layout_isize = root.layout_isize; + + // Define a constant for the amount to increment + let amount_sym = root.create_symbol(ident_ids, "amount"); + let amount_expr = Expr::Literal(Literal::Int(*amount as i128)); + let amount_stmt = |next| Stmt::Let(amount_sym, amount_expr, layout_isize, next); + + // Call helper proc, passing the Roc structure and constant amount + let call_result_empty = root.create_symbol(ident_ids, "call_result_empty"); + let call_expr = root + .call_specialized_op( + ident_ids, + ctx, + layout, + arena.alloc([*structure, amount_sym]), + ) + .unwrap(); + + let call_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following); + arena.alloc(amount_stmt(arena.alloc(call_stmt))) + } + + ModifyRc::Dec(structure) => { + // Call helper proc, passing the Roc structure + let call_result_empty = root.create_symbol(ident_ids, "call_result_empty"); + let call_expr = root + .call_specialized_op(ident_ids, ctx, layout, arena.alloc([*structure])) + .unwrap(); + let call_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following); + arena.alloc(call_stmt) + } + + ModifyRc::DecRef(structure) => { + match layout { + // Str has no children, so we might as well do what we normally do and call the helper. + Layout::Builtin(Builtin::Str) => { + ctx.op = HelperOp::Dec; + refcount_stmt(root, ident_ids, ctx, layout, modify, following) + } + + // Struct and non-recursive Unions are stack-only, so DecRef is a no-op + Layout::Struct(_) => following, + Layout::Union(UnionLayout::NonRecursive(_)) => following, + + // Inline the refcounting code instead of making a function. Don't iterate fields, + // and replace any return statements with jumps to the `following` statement. + _ => match ctx.op { + HelperOp::DecRef(jp_decref) => { + let rc_stmt = refcount_generic(root, ident_ids, ctx, layout, *structure); + let join = Stmt::Join { + id: jp_decref, + parameters: &[], + body: following, + remainder: arena.alloc(rc_stmt), + }; + arena.alloc(join) + } + _ => unreachable!(), + }, + } + } + } +} + +pub fn refcount_generic<'a>( + root: &mut CodeGenHelp<'a>, + ident_ids: &mut IdentIds, + ctx: &mut Context<'a>, + layout: Layout<'a>, + structure: Symbol, ) -> Stmt<'a> { debug_assert!(is_rc_implemented_yet(&layout)); let rc_todo = || todo!("Please update is_rc_implemented_yet for `{:?}`", layout); @@ -29,11 +107,19 @@ pub fn refcount_generic<'a>( unreachable!("Not refcounted: {:?}", layout) } Layout::Builtin(Builtin::Str) => refcount_str(root, ident_ids, ctx), - Layout::Builtin(Builtin::Dict(_, _) | Builtin::Set(_) | Builtin::List(_)) => rc_todo(), - Layout::Struct(_) => rc_todo(), - Layout::Union(_) => rc_todo(), - Layout::LambdaSet(_) => { - unreachable!("Refcounting on LambdaSet is invalid. Should be a Union at runtime.") + Layout::Builtin(Builtin::List(elem_layout)) => { + refcount_list(root, ident_ids, ctx, &layout, elem_layout, structure) + } + Layout::Builtin(Builtin::Dict(_, _) | Builtin::Set(_)) => rc_todo(), + Layout::Struct(field_layouts) => { + refcount_struct(root, ident_ids, ctx, field_layouts, structure) + } + Layout::Union(union_layout) => { + refcount_union(root, ident_ids, ctx, union_layout, structure) + } + Layout::LambdaSet(lambda_set) => { + let runtime_layout = lambda_set.runtime_representation(); + refcount_generic(root, ident_ids, ctx, runtime_layout, structure) } Layout::RecursivePointer => rc_todo(), } @@ -43,24 +129,68 @@ pub fn refcount_generic<'a>( // In the short term, it helps us to skip refcounting and let it leak, so we can make // progress incrementally. Kept in sync with generate_procs using assertions. pub fn is_rc_implemented_yet(layout: &Layout) -> bool { - matches!(layout, Layout::Builtin(Builtin::Str)) + use UnionLayout::*; + + match layout { + Layout::Builtin(Builtin::Dict(..) | Builtin::Set(_)) => false, + Layout::Builtin(Builtin::List(elem_layout)) => is_rc_implemented_yet(elem_layout), + Layout::Builtin(_) => true, + Layout::Struct(fields) => fields.iter().all(is_rc_implemented_yet), + Layout::Union(union_layout) => match union_layout { + NonRecursive(tags) => tags + .iter() + .all(|fields| fields.iter().all(is_rc_implemented_yet)), + Recursive(tags) => tags + .iter() + .all(|fields| fields.iter().all(is_rc_implemented_yet)), + NonNullableUnwrapped(fields) => fields.iter().all(is_rc_implemented_yet), + NullableWrapped { other_tags, .. } => other_tags + .iter() + .all(|fields| fields.iter().all(is_rc_implemented_yet)), + NullableUnwrapped { other_fields, .. } => { + other_fields.iter().all(is_rc_implemented_yet) + } + }, + Layout::LambdaSet(lambda_set) => { + is_rc_implemented_yet(&lambda_set.runtime_representation()) + } + Layout::RecursivePointer => true, + } } -fn return_unit<'a>(root: &CodeGenHelp<'a>, ident_ids: &mut IdentIds) -> Stmt<'a> { - let unit = root.create_symbol(ident_ids, "unit"); - let ret_stmt = root.arena.alloc(Stmt::Ret(unit)); - Stmt::Let(unit, Expr::Struct(&[]), LAYOUT_UNIT, ret_stmt) +fn rc_return_stmt<'a>( + root: &CodeGenHelp<'a>, + ident_ids: &mut IdentIds, + ctx: &mut Context<'a>, +) -> Stmt<'a> { + if let HelperOp::DecRef(jp_decref) = ctx.op { + Stmt::Jump(jp_decref, &[]) + } else { + let unit = root.create_symbol(ident_ids, "unit"); + let ret_stmt = root.arena.alloc(Stmt::Ret(unit)); + Stmt::Let(unit, Expr::Struct(&[]), LAYOUT_UNIT, ret_stmt) + } +} + +fn refcount_args<'a>(root: &CodeGenHelp<'a>, ctx: &Context<'a>, structure: Symbol) -> &'a [Symbol] { + if ctx.op == HelperOp::Inc { + // second argument is always `amount`, passed down through the call stack + root.arena.alloc([structure, Symbol::ARG_2]) + } else { + root.arena.alloc([structure]) + } } // Subtract a constant from a pointer to find the refcount // Also does some type casting, so that we have different Symbols and Layouts // for the 'pointer' and 'integer' versions of the address. // This helps to avoid issues with the backends Symbol->Layout mapping. -pub fn rc_ptr_from_struct<'a>( +pub fn rc_ptr_from_data_ptr<'a>( root: &CodeGenHelp<'a>, ident_ids: &mut IdentIds, structure: Symbol, rc_ptr_sym: Symbol, + mask_lower_bits: bool, following: &'a Stmt<'a>, ) -> Stmt<'a> { // Typecast the structure pointer to an integer @@ -75,45 +205,135 @@ pub fn rc_ptr_from_struct<'a>( }); let addr_stmt = |next| Stmt::Let(addr_sym, addr_expr, root.layout_isize, next); + // Mask for lower bits (for tag union id) + let mask_sym = root.create_symbol(ident_ids, "mask"); + let mask_expr = Expr::Literal(Literal::Int(-(root.target_info.ptr_width() as i128))); + let mask_stmt = |next| Stmt::Let(mask_sym, mask_expr, root.layout_isize, next); + + let masked_sym = root.create_symbol(ident_ids, "masked"); + let and_expr = Expr::Call(Call { + call_type: CallType::LowLevel { + op: LowLevel::And, + update_mode: UpdateModeId::BACKEND_DUMMY, + }, + arguments: root.arena.alloc([addr_sym, mask_sym]), + }); + let and_stmt = |next| Stmt::Let(masked_sym, and_expr, root.layout_isize, next); + // Pointer size constant let ptr_size_sym = root.create_symbol(ident_ids, "ptr_size"); - let ptr_size_expr = Expr::Literal(Literal::Int(root.ptr_size as i128)); + let ptr_size_expr = Expr::Literal(Literal::Int(root.target_info.ptr_width() as i128)); let ptr_size_stmt = |next| Stmt::Let(ptr_size_sym, ptr_size_expr, root.layout_isize, next); // Refcount address let rc_addr_sym = root.create_symbol(ident_ids, "rc_addr"); - let rc_addr_expr = Expr::Call(Call { + let sub_expr = Expr::Call(Call { call_type: CallType::LowLevel { op: LowLevel::NumSub, update_mode: UpdateModeId::BACKEND_DUMMY, }, - arguments: root.arena.alloc([structure, ptr_size_sym]), + arguments: root.arena.alloc([ + if mask_lower_bits { + masked_sym + } else { + addr_sym + }, + ptr_size_sym, + ]), }); - let rc_addr_stmt = |next| Stmt::Let(rc_addr_sym, rc_addr_expr, root.layout_isize, next); + let sub_stmt = |next| Stmt::Let(rc_addr_sym, sub_expr, root.layout_isize, next); // Typecast the refcount address from integer to pointer - let rc_ptr_expr = Expr::Call(Call { + let cast_expr = Expr::Call(Call { call_type: CallType::LowLevel { op: LowLevel::PtrCast, update_mode: UpdateModeId::BACKEND_DUMMY, }, arguments: root.arena.alloc([rc_addr_sym]), }); - let rc_ptr_stmt = |next| Stmt::Let(rc_ptr_sym, rc_ptr_expr, LAYOUT_PTR, next); + let cast_stmt = |next| Stmt::Let(rc_ptr_sym, cast_expr, LAYOUT_PTR, next); - addr_stmt(root.arena.alloc( - // - ptr_size_stmt(root.arena.alloc( + if mask_lower_bits { + addr_stmt(root.arena.alloc( // - rc_addr_stmt(root.arena.alloc( + mask_stmt(root.arena.alloc( // - rc_ptr_stmt(root.arena.alloc( + and_stmt(root.arena.alloc( // - following, + ptr_size_stmt(root.arena.alloc( + // + sub_stmt(root.arena.alloc( + // + cast_stmt(root.arena.alloc( + // + following, + )), + )), + )), )), )), - )), - )) + )) + } else { + addr_stmt(root.arena.alloc( + // + ptr_size_stmt(root.arena.alloc( + // + sub_stmt(root.arena.alloc( + // + cast_stmt(root.arena.alloc( + // + following, + )), + )), + )), + )) + } +} + +fn modify_refcount<'a>( + root: &CodeGenHelp<'a>, + ident_ids: &mut IdentIds, + ctx: &mut Context<'a>, + rc_ptr: Symbol, + alignment: u32, + following: &'a Stmt<'a>, +) -> Stmt<'a> { + // Call the relevant Zig lowlevel to actually modify the refcount + let zig_call_result = root.create_symbol(ident_ids, "zig_call_result"); + match ctx.op { + HelperOp::Inc => { + let zig_call_expr = Expr::Call(Call { + call_type: CallType::LowLevel { + op: LowLevel::RefCountInc, + update_mode: UpdateModeId::BACKEND_DUMMY, + }, + arguments: root.arena.alloc([rc_ptr, Symbol::ARG_2]), + }); + Stmt::Let(zig_call_result, zig_call_expr, LAYOUT_UNIT, following) + } + + HelperOp::Dec | HelperOp::DecRef(_) => { + let alignment_sym = root.create_symbol(ident_ids, "alignment"); + let alignment_expr = Expr::Literal(Literal::Int(alignment as i128)); + let alignment_stmt = |next| Stmt::Let(alignment_sym, alignment_expr, LAYOUT_U32, next); + + let zig_call_expr = Expr::Call(Call { + call_type: CallType::LowLevel { + op: LowLevel::RefCountDec, + update_mode: UpdateModeId::BACKEND_DUMMY, + }, + arguments: root.arena.alloc([rc_ptr, alignment_sym]), + }); + let zig_call_stmt = Stmt::Let(zig_call_result, zig_call_expr, LAYOUT_UNIT, following); + + alignment_stmt(root.arena.alloc( + // + zig_call_stmt, + )) + } + + _ => unreachable!(), + } } /// Generate a procedure to modify the reference count of a Str @@ -122,9 +342,7 @@ fn refcount_str<'a>( ident_ids: &mut IdentIds, ctx: &mut Context<'a>, ) -> Stmt<'a> { - let op = ctx.op; - - let string = ARG_1; + let string = Symbol::ARG_1; let layout_isize = root.layout_isize; // Get the string length as a signed int @@ -164,60 +382,41 @@ fn refcount_str<'a>( // A pointer to the refcount value itself let rc_ptr = root.create_symbol(ident_ids, "rc_ptr"); + let alignment = root.target_info.ptr_width() as u32; - // Alignment constant (same value as ptr_size but different layout) - let alignment = root.create_symbol(ident_ids, "alignment"); - let alignment_expr = Expr::Literal(Literal::Int(root.ptr_size as i128)); - let alignment_stmt = |next| Stmt::Let(alignment, alignment_expr, LAYOUT_U32, next); - - // Call the relevant Zig lowlevel to actually modify the refcount - let zig_call_result = root.create_symbol(ident_ids, "zig_call_result"); - let zig_call_expr = match op { - HelperOp::Inc => Expr::Call(Call { - call_type: CallType::LowLevel { - op: LowLevel::RefCountInc, - update_mode: UpdateModeId::BACKEND_DUMMY, - }, - arguments: root.arena.alloc([rc_ptr, ARG_2]), - }), - HelperOp::Dec | HelperOp::DecRef => Expr::Call(Call { - call_type: CallType::LowLevel { - op: LowLevel::RefCountDec, - update_mode: UpdateModeId::BACKEND_DUMMY, - }, - arguments: root.arena.alloc([rc_ptr, alignment]), - }), - _ => unreachable!(), - }; - let zig_call_stmt = |next| Stmt::Let(zig_call_result, zig_call_expr, LAYOUT_UNIT, next); + let ret_unit_stmt = rc_return_stmt(root, ident_ids, ctx); + let mod_rc_stmt = modify_refcount( + root, + ident_ids, + ctx, + rc_ptr, + alignment, + root.arena.alloc(ret_unit_stmt), + ); // Generate an `if` to skip small strings but modify big strings let then_branch = elements_stmt(root.arena.alloc( // - rc_ptr_from_struct( + rc_ptr_from_data_ptr( root, ident_ids, elements, rc_ptr, + false, root.arena.alloc( // - alignment_stmt(root.arena.alloc( - // - zig_call_stmt(root.arena.alloc( - // - Stmt::Ret(zig_call_result), - )), - )), + mod_rc_stmt, ), ), )); + let if_stmt = Stmt::Switch { cond_symbol: is_big_str, cond_layout: LAYOUT_BOOL, branches: root.arena.alloc([(1, BranchInfo::None, then_branch)]), default_branch: ( BranchInfo::None, - root.arena.alloc(return_unit(root, ident_ids)), + root.arena.alloc(rc_return_stmt(root, ident_ids, ctx)), ), ret_layout: LAYOUT_UNIT, }; @@ -234,3 +433,839 @@ fn refcount_str<'a>( )), )) } + +fn refcount_list<'a>( + root: &mut CodeGenHelp<'a>, + ident_ids: &mut IdentIds, + ctx: &mut Context<'a>, + layout: &Layout, + elem_layout: &'a Layout, + structure: Symbol, +) -> Stmt<'a> { + let layout_isize = root.layout_isize; + let arena = root.arena; + + // A "Box" layout (heap pointer to a single list element) + let box_union_layout = UnionLayout::NonNullableUnwrapped(arena.alloc([*elem_layout])); + let box_layout = Layout::Union(box_union_layout); + + // + // Check if the list is empty + // + + let len = root.create_symbol(ident_ids, "len"); + let len_stmt = |next| let_lowlevel(arena, layout_isize, len, ListLen, &[structure], next); + + // Zero + let zero = root.create_symbol(ident_ids, "zero"); + let zero_expr = Expr::Literal(Literal::Int(0)); + let zero_stmt = |next| Stmt::Let(zero, zero_expr, layout_isize, next); + + // let is_empty = lowlevel Eq len zero + let is_empty = root.create_symbol(ident_ids, "is_empty"); + let is_empty_expr = Expr::Call(Call { + call_type: CallType::LowLevel { + op: LowLevel::Eq, + update_mode: UpdateModeId::BACKEND_DUMMY, + }, + arguments: root.arena.alloc([len, zero]), + }); + let is_empty_stmt = |next| Stmt::Let(is_empty, is_empty_expr, LAYOUT_BOOL, next); + + // get elements pointer + let elements = root.create_symbol(ident_ids, "elements"); + let elements_expr = Expr::StructAtIndex { + index: 0, + field_layouts: arena.alloc([box_layout, layout_isize]), + structure, + }; + let elements_stmt = |next| Stmt::Let(elements, elements_expr, box_layout, next); + + // + // modify refcount of the list and its elements + // (elements first, to avoid use-after-free for Dec) + // + + let rc_ptr = root.create_symbol(ident_ids, "rc_ptr"); + let alignment = layout.alignment_bytes(root.target_info); + + let ret_stmt = rc_return_stmt(root, ident_ids, ctx); + let modify_list = modify_refcount( + root, + ident_ids, + ctx, + rc_ptr, + alignment, + arena.alloc(ret_stmt), + ); + + let get_rc_and_modify_list = rc_ptr_from_data_ptr( + root, + ident_ids, + elements, + rc_ptr, + false, + arena.alloc(modify_list), + ); + + let modify_elems_and_list = if elem_layout.is_refcounted() && !ctx.op.is_decref() { + refcount_list_elems( + root, + ident_ids, + ctx, + elem_layout, + LAYOUT_UNIT, + box_union_layout, + len, + elements, + get_rc_and_modify_list, + ) + } else { + get_rc_and_modify_list + }; + + // + // Do nothing if the list is empty + // + + let non_empty_branch = root.arena.alloc( + // + elements_stmt(root.arena.alloc( + // + modify_elems_and_list, + )), + ); + + let if_stmt = Stmt::Switch { + cond_symbol: is_empty, + cond_layout: LAYOUT_BOOL, + branches: root + .arena + .alloc([(1, BranchInfo::None, rc_return_stmt(root, ident_ids, ctx))]), + default_branch: (BranchInfo::None, non_empty_branch), + ret_layout: LAYOUT_UNIT, + }; + + len_stmt(arena.alloc( + // + zero_stmt(arena.alloc( + // + is_empty_stmt(arena.alloc( + // + if_stmt, + )), + )), + )) +} + +#[allow(clippy::too_many_arguments)] +fn refcount_list_elems<'a>( + root: &mut CodeGenHelp<'a>, + ident_ids: &mut IdentIds, + ctx: &mut Context<'a>, + elem_layout: &Layout<'a>, + ret_layout: Layout<'a>, + box_union_layout: UnionLayout<'a>, + length: Symbol, + elements: Symbol, + following: Stmt<'a>, +) -> Stmt<'a> { + use LowLevel::*; + let layout_isize = root.layout_isize; + let arena = root.arena; + + // Cast to integer + let start = root.create_symbol(ident_ids, "start"); + let start_stmt = |next| let_lowlevel(arena, layout_isize, start, PtrCast, &[elements], next); + + // + // Loop initialisation + // + + // let size = literal int + let elem_size = root.create_symbol(ident_ids, "elem_size"); + let elem_size_expr = Expr::Literal(Literal::Int( + elem_layout.stack_size(root.target_info) as i128 + )); + let elem_size_stmt = |next| Stmt::Let(elem_size, elem_size_expr, layout_isize, next); + + // let list_size = len * size + let list_size = root.create_symbol(ident_ids, "list_size"); + let list_size_stmt = |next| { + let_lowlevel( + arena, + layout_isize, + list_size, + NumMul, + &[length, elem_size], + next, + ) + }; + + // let end = start + list_size + let end = root.create_symbol(ident_ids, "end"); + let end_stmt = |next| let_lowlevel(arena, layout_isize, end, NumAdd, &[start, list_size], next); + + // + // Loop name & parameter + // + + let elems_loop = JoinPointId(root.create_symbol(ident_ids, "elems_loop")); + let addr = root.create_symbol(ident_ids, "addr"); + + let param_addr = Param { + symbol: addr, + borrow: false, + layout: layout_isize, + }; + + // + // if we haven't reached the end yet... + // + + // Cast integer to box pointer + let box_ptr = root.create_symbol(ident_ids, "box"); + let box_layout = Layout::Union(box_union_layout); + let box_stmt = |next| let_lowlevel(arena, box_layout, box_ptr, PtrCast, &[addr], next); + + // Dereference the box pointer to get the current element + let elem = root.create_symbol(ident_ids, "elem"); + let elem_expr = Expr::UnionAtIndex { + structure: box_ptr, + union_layout: box_union_layout, + tag_id: 0, + index: 0, + }; + let elem_stmt = |next| Stmt::Let(elem, elem_expr, *elem_layout, next); + + // + // Modify element refcount + // + + let mod_elem_unit = root.create_symbol(ident_ids, "mod_elem_unit"); + let mod_elem_args = refcount_args(root, ctx, elem); + let mod_elem_expr = root + .call_specialized_op(ident_ids, ctx, *elem_layout, mod_elem_args) + .unwrap(); + let mod_elem_stmt = |next| Stmt::Let(mod_elem_unit, mod_elem_expr, LAYOUT_UNIT, next); + + // + // Next loop iteration + // + let next_addr = root.create_symbol(ident_ids, "next_addr"); + let next_addr_stmt = |next| { + let_lowlevel( + arena, + layout_isize, + next_addr, + NumAdd, + &[addr, elem_size], + next, + ) + }; + + // + // Control flow + // + + let is_end = root.create_symbol(ident_ids, "is_end"); + let is_end_stmt = |next| let_lowlevel(arena, LAYOUT_BOOL, is_end, NumGte, &[addr, end], next); + + let if_end_of_list = Stmt::Switch { + cond_symbol: is_end, + cond_layout: LAYOUT_BOOL, + ret_layout, + branches: root.arena.alloc([(1, BranchInfo::None, following)]), + default_branch: ( + BranchInfo::None, + arena.alloc(box_stmt(arena.alloc( + // + elem_stmt(arena.alloc( + // + mod_elem_stmt(arena.alloc( + // + next_addr_stmt(arena.alloc( + // + Stmt::Jump(elems_loop, arena.alloc([next_addr])), + )), + )), + )), + ))), + ), + }; + + let joinpoint_loop = Stmt::Join { + id: elems_loop, + parameters: arena.alloc([param_addr]), + body: arena.alloc( + // + is_end_stmt( + // + arena.alloc(if_end_of_list), + ), + ), + remainder: root + .arena + .alloc(Stmt::Jump(elems_loop, arena.alloc([start]))), + }; + + start_stmt(arena.alloc( + // + elem_size_stmt(arena.alloc( + // + list_size_stmt(arena.alloc( + // + end_stmt(arena.alloc( + // + joinpoint_loop, + )), + )), + )), + )) +} + +fn refcount_struct<'a>( + root: &mut CodeGenHelp<'a>, + ident_ids: &mut IdentIds, + ctx: &mut Context<'a>, + field_layouts: &'a [Layout<'a>], + structure: Symbol, +) -> Stmt<'a> { + let mut stmt = rc_return_stmt(root, ident_ids, ctx); + + for (i, field_layout) in field_layouts.iter().enumerate().rev() { + if field_layout.contains_refcounted() { + let field_val = root.create_symbol(ident_ids, &format!("field_val_{}", i)); + let field_val_expr = Expr::StructAtIndex { + index: i as u64, + field_layouts, + structure, + }; + let field_val_stmt = |next| Stmt::Let(field_val, field_val_expr, *field_layout, next); + + let mod_unit = root.create_symbol(ident_ids, &format!("mod_field_{}", i)); + let mod_args = refcount_args(root, ctx, field_val); + let mod_expr = root + .call_specialized_op(ident_ids, ctx, *field_layout, mod_args) + .unwrap(); + let mod_stmt = |next| Stmt::Let(mod_unit, mod_expr, LAYOUT_UNIT, next); + + stmt = field_val_stmt(root.arena.alloc( + // + mod_stmt(root.arena.alloc( + // + stmt, + )), + )) + } + } + + stmt +} + +fn refcount_union<'a>( + root: &mut CodeGenHelp<'a>, + ident_ids: &mut IdentIds, + ctx: &mut Context<'a>, + union: UnionLayout<'a>, + structure: Symbol, +) -> Stmt<'a> { + use UnionLayout::*; + + let parent_rec_ptr_layout = ctx.recursive_union; + if !matches!(union, NonRecursive(_)) { + ctx.recursive_union = Some(union); + } + + let body = match union { + NonRecursive(tags) => refcount_union_nonrec(root, ident_ids, ctx, union, tags, structure), + + Recursive(tags) => { + let (is_tailrec, tail_idx) = root.union_tail_recursion_fields(union); + if is_tailrec && !ctx.op.is_decref() { + refcount_union_tailrec(root, ident_ids, ctx, union, tags, None, tail_idx, structure) + } else { + refcount_union_rec(root, ident_ids, ctx, union, tags, None, structure) + } + } + + NonNullableUnwrapped(field_layouts) => { + // We don't do tail recursion on NonNullableUnwrapped. + // Its RecursionPointer is always nested inside a List, Option, or other sub-layout, since + // a direct RecursionPointer is only possible if there's at least one non-recursive variant. + // This nesting makes it harder to do tail recursion, so we just don't. + let tags = root.arena.alloc([field_layouts]); + refcount_union_rec(root, ident_ids, ctx, union, tags, None, structure) + } + + NullableWrapped { + other_tags: tags, + nullable_id, + } => { + let null_id = Some(nullable_id); + let (is_tailrec, tail_idx) = root.union_tail_recursion_fields(union); + if is_tailrec && !ctx.op.is_decref() { + refcount_union_tailrec( + root, ident_ids, ctx, union, tags, null_id, tail_idx, structure, + ) + } else { + refcount_union_rec(root, ident_ids, ctx, union, tags, null_id, structure) + } + } + + NullableUnwrapped { + other_fields, + nullable_id, + } => { + let null_id = Some(nullable_id as TagIdIntType); + let tags = root.arena.alloc([other_fields]); + let (is_tailrec, tail_idx) = root.union_tail_recursion_fields(union); + if is_tailrec && !ctx.op.is_decref() { + refcount_union_tailrec( + root, ident_ids, ctx, union, tags, null_id, tail_idx, structure, + ) + } else { + refcount_union_rec(root, ident_ids, ctx, union, tags, null_id, structure) + } + } + }; + + ctx.recursive_union = parent_rec_ptr_layout; + + body +} + +fn refcount_union_nonrec<'a>( + root: &mut CodeGenHelp<'a>, + ident_ids: &mut IdentIds, + ctx: &mut Context<'a>, + union_layout: UnionLayout<'a>, + tag_layouts: &'a [&'a [Layout<'a>]], + structure: Symbol, +) -> Stmt<'a> { + let tag_id_layout = union_layout.tag_id_layout(); + + let tag_id_sym = root.create_symbol(ident_ids, "tag_id"); + let tag_id_stmt = |next| { + Stmt::Let( + tag_id_sym, + Expr::GetTagId { + structure, + union_layout, + }, + tag_id_layout, + next, + ) + }; + + let continuation = rc_return_stmt(root, ident_ids, ctx); + + let switch_stmt = refcount_union_contents( + root, + ident_ids, + ctx, + union_layout, + tag_layouts, + None, + structure, + tag_id_sym, + tag_id_layout, + continuation, + ); + + tag_id_stmt(root.arena.alloc( + // + switch_stmt, + )) +} + +#[allow(clippy::too_many_arguments)] +fn refcount_union_contents<'a>( + root: &mut CodeGenHelp<'a>, + ident_ids: &mut IdentIds, + ctx: &mut Context<'a>, + union_layout: UnionLayout<'a>, + tag_layouts: &'a [&'a [Layout<'a>]], + null_id: Option, + structure: Symbol, + tag_id_sym: Symbol, + tag_id_layout: Layout<'a>, + modify_union_stmt: Stmt<'a>, +) -> Stmt<'a> { + let jp_modify_union = JoinPointId(root.create_symbol(ident_ids, "jp_modify_union")); + let mut tag_branches = Vec::with_capacity_in(tag_layouts.len() + 1, root.arena); + + if let Some(id) = null_id { + let ret = rc_return_stmt(root, ident_ids, ctx); + tag_branches.push((id as u64, BranchInfo::None, ret)); + } + + let mut tag_id: TagIdIntType = 0; + for field_layouts in tag_layouts.iter() { + match null_id { + Some(id) if id == tag_id => { + tag_id += 1; + } + _ => {} + } + + // After refcounting the fields, jump to modify the union itself + // (Order is important, to avoid use-after-free for Dec) + let following = Stmt::Jump(jp_modify_union, &[]); + + let fields_stmt = refcount_tag_fields( + root, + ident_ids, + ctx, + union_layout, + field_layouts, + structure, + tag_id, + following, + ); + + tag_branches.push((tag_id as u64, BranchInfo::None, fields_stmt)); + + tag_id += 1; + } + + let default_stmt: Stmt<'a> = tag_branches.pop().unwrap().2; + + let tag_id_switch = Stmt::Switch { + cond_symbol: tag_id_sym, + cond_layout: tag_id_layout, + branches: tag_branches.into_bump_slice(), + default_branch: (BranchInfo::None, root.arena.alloc(default_stmt)), + ret_layout: LAYOUT_UNIT, + }; + + Stmt::Join { + id: jp_modify_union, + parameters: &[], + body: root.arena.alloc(modify_union_stmt), + remainder: root.arena.alloc(tag_id_switch), + } +} + +fn refcount_union_rec<'a>( + root: &mut CodeGenHelp<'a>, + ident_ids: &mut IdentIds, + ctx: &mut Context<'a>, + union_layout: UnionLayout<'a>, + tag_layouts: &'a [&'a [Layout<'a>]], + null_id: Option, + structure: Symbol, +) -> Stmt<'a> { + let tag_id_layout = union_layout.tag_id_layout(); + + let tag_id_sym = root.create_symbol(ident_ids, "tag_id"); + let tag_id_stmt = |next| { + Stmt::Let( + tag_id_sym, + Expr::GetTagId { + structure, + union_layout, + }, + tag_id_layout, + next, + ) + }; + + let rc_structure_stmt = { + let rc_ptr = root.create_symbol(ident_ids, "rc_ptr"); + + let alignment = Layout::Union(union_layout).alignment_bytes(root.target_info); + let ret_stmt = rc_return_stmt(root, ident_ids, ctx); + let modify_structure_stmt = modify_refcount( + root, + ident_ids, + ctx, + rc_ptr, + alignment, + root.arena.alloc(ret_stmt), + ); + + rc_ptr_from_data_ptr( + root, + ident_ids, + structure, + rc_ptr, + union_layout.stores_tag_id_in_pointer(root.target_info), + root.arena.alloc(modify_structure_stmt), + ) + }; + + let rc_contents_then_structure = if ctx.op.is_decref() { + rc_structure_stmt + } else { + refcount_union_contents( + root, + ident_ids, + ctx, + union_layout, + tag_layouts, + null_id, + structure, + tag_id_sym, + tag_id_layout, + rc_structure_stmt, + ) + }; + + if ctx.op.is_decref() && null_id.is_none() { + rc_contents_then_structure + } else { + tag_id_stmt(root.arena.alloc( + // + rc_contents_then_structure, + )) + } +} + +// Refcount a recursive union using tail-call elimination to limit stack growth +#[allow(clippy::too_many_arguments)] +fn refcount_union_tailrec<'a>( + root: &mut CodeGenHelp<'a>, + ident_ids: &mut IdentIds, + ctx: &mut Context<'a>, + union_layout: UnionLayout<'a>, + tag_layouts: &'a [&'a [Layout<'a>]], + null_id: Option, + tailrec_indices: Vec<'a, Option>, + initial_structure: Symbol, +) -> Stmt<'a> { + let tailrec_loop = JoinPointId(root.create_symbol(ident_ids, "tailrec_loop")); + let current = root.create_symbol(ident_ids, "current"); + let next_ptr = root.create_symbol(ident_ids, "next_ptr"); + let layout = Layout::Union(union_layout); + + let tag_id_layout = union_layout.tag_id_layout(); + + let tag_id_sym = root.create_symbol(ident_ids, "tag_id"); + let tag_id_stmt = |next| { + Stmt::Let( + tag_id_sym, + Expr::GetTagId { + structure: current, + union_layout, + }, + tag_id_layout, + next, + ) + }; + + // Do refcounting on the structure itself + // In the control flow, this comes *after* refcounting the fields + // It receives a `next` parameter to pass through to the outer joinpoint + let rc_structure_stmt = { + let rc_ptr = root.create_symbol(ident_ids, "rc_ptr"); + let next_addr = root.create_symbol(ident_ids, "next_addr"); + + let exit_stmt = rc_return_stmt(root, ident_ids, ctx); + let jump_to_loop = Stmt::Jump(tailrec_loop, root.arena.alloc([next_ptr])); + + let loop_or_exit = Stmt::Switch { + cond_symbol: next_addr, + cond_layout: root.layout_isize, + branches: root.arena.alloc([(0, BranchInfo::None, exit_stmt)]), + default_branch: (BranchInfo::None, root.arena.alloc(jump_to_loop)), + ret_layout: LAYOUT_UNIT, + }; + let loop_or_exit_based_on_next_addr = { + let_lowlevel( + root.arena, + root.layout_isize, + next_addr, + PtrCast, + &[next_ptr], + root.arena.alloc(loop_or_exit), + ) + }; + + let alignment = layout.alignment_bytes(root.target_info); + let modify_structure_stmt = modify_refcount( + root, + ident_ids, + ctx, + rc_ptr, + alignment, + root.arena.alloc(loop_or_exit_based_on_next_addr), + ); + + rc_ptr_from_data_ptr( + root, + ident_ids, + current, + rc_ptr, + union_layout.stores_tag_id_in_pointer(root.target_info), + root.arena.alloc(modify_structure_stmt), + ) + }; + + let rc_contents_then_structure = { + let jp_modify_union = JoinPointId(root.create_symbol(ident_ids, "jp_modify_union")); + let mut tag_branches = Vec::with_capacity_in(tag_layouts.len() + 1, root.arena); + + // If this is null, there is no refcount, no `next`, no fields. Just return. + if let Some(id) = null_id { + let ret = rc_return_stmt(root, ident_ids, ctx); + tag_branches.push((id as u64, BranchInfo::None, ret)); + } + + let mut tag_id: TagIdIntType = 0; + for (field_layouts, opt_tailrec_index) in tag_layouts.iter().zip(tailrec_indices) { + match null_id { + Some(id) if id == tag_id => { + tag_id += 1; + } + _ => {} + } + + // After refcounting the fields, jump to modify the union itself. + // The loop param is a pointer to the next union. It gets passed through two jumps. + let (non_tailrec_fields, jump_to_modify_union) = + if let Some(tailrec_index) = opt_tailrec_index { + let mut filtered = Vec::with_capacity_in(field_layouts.len() - 1, root.arena); + let mut tail_stmt = None; + for (i, field) in field_layouts.iter().enumerate() { + if i != tailrec_index { + filtered.push(*field); + } else { + let field_val = + root.create_symbol(ident_ids, &format!("field_{}_{}", tag_id, i)); + let field_val_expr = Expr::UnionAtIndex { + union_layout, + tag_id, + index: i as u64, + structure: current, + }; + let jump_params = root.arena.alloc([field_val]); + let jump = root.arena.alloc(Stmt::Jump(jp_modify_union, jump_params)); + tail_stmt = Some(Stmt::Let(field_val, field_val_expr, *field, jump)); + } + } + + (filtered.into_bump_slice(), tail_stmt.unwrap()) + } else { + let zero = root.create_symbol(ident_ids, "zero"); + let zero_expr = Expr::Literal(Literal::Int(0)); + let zero_stmt = |next| Stmt::Let(zero, zero_expr, root.layout_isize, next); + + let null = root.create_symbol(ident_ids, "null"); + let null_stmt = + |next| let_lowlevel(root.arena, layout, null, PtrCast, &[zero], next); + + let tail_stmt = zero_stmt(root.arena.alloc( + // + null_stmt(root.arena.alloc( + // + Stmt::Jump(jp_modify_union, root.arena.alloc([null])), + )), + )); + + (*field_layouts, tail_stmt) + }; + + let fields_stmt = refcount_tag_fields( + root, + ident_ids, + ctx, + union_layout, + non_tailrec_fields, + current, + tag_id, + jump_to_modify_union, + ); + + tag_branches.push((tag_id as u64, BranchInfo::None, fields_stmt)); + + tag_id += 1; + } + + let default_stmt: Stmt<'a> = tag_branches.pop().unwrap().2; + + let tag_id_switch = Stmt::Switch { + cond_symbol: tag_id_sym, + cond_layout: tag_id_layout, + branches: tag_branches.into_bump_slice(), + default_branch: (BranchInfo::None, root.arena.alloc(default_stmt)), + ret_layout: LAYOUT_UNIT, + }; + + let jp_param = Param { + symbol: next_ptr, + borrow: true, + layout, + }; + + Stmt::Join { + id: jp_modify_union, + parameters: root.arena.alloc([jp_param]), + body: root.arena.alloc(rc_structure_stmt), + remainder: root.arena.alloc(tag_id_switch), + } + }; + + let loop_body = tag_id_stmt(root.arena.alloc( + // + rc_contents_then_structure, + )); + + let loop_init = Stmt::Jump(tailrec_loop, root.arena.alloc([initial_structure])); + let loop_param = Param { + symbol: current, + borrow: true, + layout: Layout::Union(union_layout), + }; + + Stmt::Join { + id: tailrec_loop, + parameters: root.arena.alloc([loop_param]), + body: root.arena.alloc(loop_body), + remainder: root.arena.alloc(loop_init), + } +} + +#[allow(clippy::too_many_arguments)] +fn refcount_tag_fields<'a>( + root: &mut CodeGenHelp<'a>, + ident_ids: &mut IdentIds, + ctx: &mut Context<'a>, + union_layout: UnionLayout<'a>, + field_layouts: &'a [Layout<'a>], + structure: Symbol, + tag_id: TagIdIntType, + following: Stmt<'a>, +) -> Stmt<'a> { + let mut stmt = following; + + for (i, field_layout) in field_layouts.iter().enumerate().rev() { + if field_layout.contains_refcounted() { + let field_val = root.create_symbol(ident_ids, &format!("field_{}_{}", tag_id, i)); + let field_val_expr = Expr::UnionAtIndex { + union_layout, + tag_id, + index: i as u64, + structure, + }; + let field_val_stmt = |next| Stmt::Let(field_val, field_val_expr, *field_layout, next); + + let mod_unit = root.create_symbol(ident_ids, &format!("mod_field_{}_{}", tag_id, i)); + let mod_args = refcount_args(root, ctx, field_val); + let mod_expr = root + .call_specialized_op(ident_ids, ctx, *field_layout, mod_args) + .unwrap(); + let mod_stmt = |next| Stmt::Let(mod_unit, mod_expr, LAYOUT_UNIT, next); + + stmt = field_val_stmt(root.arena.alloc( + // + mod_stmt(root.arena.alloc( + // + stmt, + )), + )) + } + } + + stmt +} diff --git a/compiler/mono/src/decision_tree.rs b/compiler/mono/src/decision_tree.rs index 8f78a32458..b37391d9cd 100644 --- a/compiler/mono/src/decision_tree.rs +++ b/compiler/mono/src/decision_tree.rs @@ -87,6 +87,7 @@ enum Test<'a> { arguments: Vec<(Pattern<'a>, Layout<'a>)>, }, IsInt(i128, IntWidth), + IsU128(u128), IsFloat(u64, FloatWidth), IsDecimal(RocDec), IsStr(Box), @@ -136,6 +137,10 @@ impl<'a> Hash for Test<'a> { state.write_u8(6); v.0.hash(state); } + IsU128(v) => { + state.write_u8(7); + v.hash(state); + } } } } @@ -311,6 +316,7 @@ fn tests_are_complete_help(last_test: &Test, number_of_tests: usize) -> bool { Test::IsByte { num_alts, .. } => number_of_tests == *num_alts, Test::IsBit(_) => number_of_tests == 2, Test::IsInt(_, _) => false, + Test::IsU128(_) => false, Test::IsFloat(_, _) => false, Test::IsDecimal(_) => false, Test::IsStr(_) => false, @@ -565,6 +571,7 @@ fn test_at_path<'a>( num_alts: union.alternatives.len(), }, IntLiteral(v, precision) => IsInt(*v, *precision), + U128Literal(v) => IsU128(*v), FloatLiteral(v, precision) => IsFloat(*v, *precision), DecimalLiteral(v) => IsDecimal(*v), StrLiteral(v) => IsStr(v.clone()), @@ -823,6 +830,18 @@ fn to_relevant_branch_help<'a>( _ => None, }, + U128Literal(int) => match test { + IsU128(is_int) if int == *is_int => { + start.extend(end); + Some(Branch { + goal: branch.goal, + guard: branch.guard.clone(), + patterns: start, + }) + } + _ => None, + }, + FloatLiteral(float, p1) => match test { IsFloat(test_float, p2) if float == *test_float => { debug_assert_eq!(p1, *p2); @@ -934,6 +953,7 @@ fn needs_tests(pattern: &Pattern) -> bool { | BitLiteral { .. } | EnumLiteral { .. } | IntLiteral(_, _) + | U128Literal(_) | FloatLiteral(_, _) | DecimalLiteral(_) | StrLiteral(_) => true, @@ -1305,6 +1325,14 @@ fn test_to_equality<'a>( (stores, lhs_symbol, rhs_symbol, None) } + Test::IsU128(test_int) => { + let lhs = Expr::Literal(Literal::U128(test_int)); + let lhs_symbol = env.unique_symbol(); + stores.push((lhs_symbol, Layout::int_width(IntWidth::U128), lhs)); + + (stores, lhs_symbol, rhs_symbol, None) + } + Test::IsFloat(test_int, precision) => { // TODO maybe we can actually use i64 comparison here? let test_float = f64::from_bits(test_int as u64); diff --git a/compiler/mono/src/exhaustive.rs b/compiler/mono/src/exhaustive.rs index 58ddf05064..2fac242662 100644 --- a/compiler/mono/src/exhaustive.rs +++ b/compiler/mono/src/exhaustive.rs @@ -54,6 +54,7 @@ pub enum Pattern { #[derive(Clone, Debug, PartialEq)] pub enum Literal { Int(i128), + U128(u128), Bit(bool), Byte(u8), Float(u64), @@ -66,6 +67,7 @@ fn simplify(pattern: &crate::ir::Pattern) -> Pattern { match pattern { IntLiteral(v, _) => Literal(Literal::Int(*v)), + U128Literal(v) => Literal(Literal::U128(*v)), FloatLiteral(v, _) => Literal(Literal::Float(*v)), DecimalLiteral(v) => Literal(Literal::Decimal(*v)), StrLiteral(v) => Literal(Literal::Str(v.clone())), @@ -498,7 +500,7 @@ fn specialize_row_by_ctor2( patterns.extend(args); matrix.push(patterns); } else { - // do nothing + // do nothing } Some(Anything) => { // TODO order! diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 03ba31a296..0e0c39019a 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -8,7 +8,7 @@ use crate::layout::{ use bumpalo::collections::Vec; use bumpalo::Bump; use roc_builtins::bitcode::{FloatWidth, IntWidth}; -use roc_can::expr::ClosureData; +use roc_can::expr::{ClosureData, IntValue}; use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap}; use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::low_level::LowLevel; @@ -16,29 +16,44 @@ use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_problem::can::RuntimeError; use roc_region::all::{Loc, Region}; use roc_std::RocDec; +use roc_target::TargetInfo; use roc_types::subs::{Content, FlatType, StorageSubs, Subs, Variable, VariableSubsSlice}; use std::collections::HashMap; use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder}; -pub const PRETTY_PRINT_IR_SYMBOLS: bool = false; +pub fn pretty_print_ir_symbols() -> bool { + #[cfg(debug_assertions)] + if std::env::var("PRETTY_PRINT_IR_SYMBOLS") == Ok("1".into()) { + return true; + } + false +} // if your changes cause this number to go down, great! // please change it to the lower number. // if it went up, maybe check that the change is really required // i128 alignment is different on arm -#[cfg(target_arch = "aarch64")] -static_assertions::assert_eq_size!([u8; 4 * 8], Literal); -#[cfg(not(target_arch = "aarch64"))] -static_assertions::assert_eq_size!([u8; 3 * 8], Literal); -static_assertions::assert_eq_size!([u8; 10 * 8], Expr); -#[cfg(not(target_arch = "aarch64"))] -static_assertions::assert_eq_size!([u8; 19 * 8], Stmt); -#[cfg(target_arch = "aarch64")] -static_assertions::assert_eq_size!([u8; 20 * 8], Stmt); -static_assertions::assert_eq_size!([u8; 6 * 8], ProcLayout); -static_assertions::assert_eq_size!([u8; 7 * 8], Call); -static_assertions::assert_eq_size!([u8; 5 * 8], CallType); +roc_error_macros::assert_sizeof_aarch64!(Literal, 4 * 8); +roc_error_macros::assert_sizeof_aarch64!(Expr, 10 * 8); +roc_error_macros::assert_sizeof_aarch64!(Stmt, 20 * 8); +roc_error_macros::assert_sizeof_aarch64!(ProcLayout, 6 * 8); +roc_error_macros::assert_sizeof_aarch64!(Call, 7 * 8); +roc_error_macros::assert_sizeof_aarch64!(CallType, 5 * 8); + +roc_error_macros::assert_sizeof_wasm!(Literal, 24); +roc_error_macros::assert_sizeof_wasm!(Expr, 56); +roc_error_macros::assert_sizeof_wasm!(Stmt, 96); +roc_error_macros::assert_sizeof_wasm!(ProcLayout, 24); +roc_error_macros::assert_sizeof_wasm!(Call, 40); +roc_error_macros::assert_sizeof_wasm!(CallType, 32); + +roc_error_macros::assert_sizeof_default!(Literal, 3 * 8); +roc_error_macros::assert_sizeof_default!(Expr, 10 * 8); +roc_error_macros::assert_sizeof_default!(Stmt, 19 * 8); +roc_error_macros::assert_sizeof_default!(ProcLayout, 6 * 8); +roc_error_macros::assert_sizeof_default!(Call, 7 * 8); +roc_error_macros::assert_sizeof_default!(CallType, 5 * 8); macro_rules! return_on_layout_error { ($env:expr, $layout_result:expr) => { @@ -201,6 +216,72 @@ impl<'a> PartialProc<'a> { } } +#[derive(Clone, Debug)] +enum PartialExprLink { + Aliases(Symbol), + Expr(roc_can::expr::Expr, Variable), +} + +/// A table of symbols to polymorphic expressions. For example, in the program +/// +/// n = 1 +/// +/// asU8 : U8 -> U8 +/// asU8 = \_ -> 1 +/// +/// asU32 : U32 -> U8 +/// asU32 = \_ -> 1 +/// +/// asU8 n + asU32 n +/// +/// The expression bound by `n` doesn't have a definite layout until it is used +/// at the call sites `asU8 n`, `asU32 n`. +/// +/// Polymorphic *functions* are stored in `PartialProc`s, since functions are +/// non longer first-class once we finish lowering to the IR. +#[derive(Clone, Debug)] +struct PartialExprs(BumpMap); + +impl PartialExprs { + fn new_in(arena: &Bump) -> Self { + Self(BumpMap::new_in(arena)) + } + + fn insert(&mut self, symbol: Symbol, expr: roc_can::expr::Expr, expr_var: Variable) { + self.0.insert(symbol, PartialExprLink::Expr(expr, expr_var)); + } + + fn insert_alias(&mut self, symbol: Symbol, aliases: Symbol) { + self.0.insert(symbol, PartialExprLink::Aliases(aliases)); + } + + fn contains(&self, symbol: Symbol) -> bool { + self.0.contains_key(&symbol) + } + + fn get(&mut self, mut symbol: Symbol) -> Option<(&roc_can::expr::Expr, Variable)> { + // In practice the alias chain is very short + loop { + match self.0.get(&symbol) { + None => { + return None; + } + Some(&PartialExprLink::Aliases(real_symbol)) => { + symbol = real_symbol; + } + Some(PartialExprLink::Expr(expr, var)) => { + return Some((expr, *var)); + } + } + } + } + + fn remove(&mut self, symbol: Symbol) { + debug_assert!(self.contains(symbol)); + self.0.remove(&symbol); + } +} + #[derive(Clone, Copy, Debug, PartialEq)] pub enum CapturedSymbols<'a> { None, @@ -268,7 +349,7 @@ impl<'a> Proc<'a> { .iter() .map(|(_, symbol)| symbol_to_doc(alloc, *symbol)); - if PRETTY_PRINT_IR_SYMBOLS { + if pretty_print_ir_symbols() { alloc .text("procedure : ") .append(symbol_to_doc(alloc, self.name)) @@ -662,6 +743,7 @@ impl<'a> Specialized<'a> { #[derive(Clone, Debug)] pub struct Procs<'a> { pub partial_procs: PartialProcs<'a>, + partial_exprs: PartialExprs, pub imported_module_thunks: &'a [Symbol], pub module_thunks: &'a [Symbol], pending_specializations: PendingSpecializations<'a>, @@ -674,6 +756,7 @@ impl<'a> Procs<'a> { pub fn new_in(arena: &'a Bump) -> Self { Self { partial_procs: PartialProcs::new_in(arena), + partial_exprs: PartialExprs::new_in(arena), imported_module_thunks: &[], module_thunks: &[], pending_specializations: PendingSpecializations::Finding(Suspended::new_in(arena)), @@ -732,15 +815,19 @@ impl<'a> Procs<'a> { ret_var: Variable, layout_cache: &mut LayoutCache<'a>, ) -> Result, RuntimeError> { - // anonymous functions cannot reference themselves, therefore cannot be tail-recursive - let is_self_recursive = false; - let raw_layout = layout_cache .raw_from_var(env.arena, annotation, env.subs) .unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err)); let top_level = ProcLayout::from_raw(env.arena, raw_layout); + // anonymous functions cannot reference themselves, therefore cannot be tail-recursive + // EXCEPT when the closure conversion makes it tail-recursive. + let is_self_recursive = match top_level.arguments.last() { + Some(Layout::LambdaSet(lambda_set)) => lambda_set.contains(symbol), + _ => false, + }; + match patterns_to_when(env, layout_cache, loc_args, ret_var, loc_body) { Ok((_, pattern_symbols, body)) => { // an anonymous closure. These will always be specialized already @@ -993,7 +1080,7 @@ pub struct Env<'a, 'i> { pub problems: &'i mut std::vec::Vec, pub home: ModuleId, pub ident_ids: &'i mut IdentIds, - pub ptr_bytes: u32, + pub target_info: TargetInfo, pub update_mode_ids: &'i mut UpdateModeIds, pub call_specialization_counter: u32, } @@ -1119,7 +1206,7 @@ impl<'a> BranchInfo<'a> { tag_id, scrutinee, layout: _, - } if PRETTY_PRINT_IR_SYMBOLS => alloc + } if pretty_print_ir_symbols() => alloc .hardline() .append(" BranchInfo: { scrutinee: ") .append(symbol_to_doc(alloc, *scrutinee)) @@ -1128,7 +1215,7 @@ impl<'a> BranchInfo<'a> { .append("} "), _ => { - if PRETTY_PRINT_IR_SYMBOLS { + if pretty_print_ir_symbols() { alloc.text(" ") } else { alloc.text("") @@ -1170,7 +1257,7 @@ impl ModifyRc { .append(";"), Inc(symbol, n) => alloc .text("inc ") - .append(alloc.text(format!("{}", n))) + .append(alloc.text(format!("{} ", n))) .append(symbol_to_doc(alloc, symbol)) .append(";"), Dec(symbol) => alloc @@ -1199,6 +1286,7 @@ impl ModifyRc { pub enum Literal<'a> { // Literals Int(i128), + U128(u128), Float(f64), Decimal(RocDec), Str(&'a str), @@ -1445,6 +1533,7 @@ impl<'a> Literal<'a> { match self { Int(lit) => alloc.text(format!("{}i64", lit)), + U128(lit) => alloc.text(format!("{}u128", lit)), Float(lit) => alloc.text(format!("{}f64", lit)), // TODO: Add proper Dec.to_str Decimal(lit) => alloc.text(format!("{}Dec", lit.0)), @@ -1455,26 +1544,30 @@ impl<'a> Literal<'a> { } } +pub(crate) fn symbol_to_doc_string(symbol: Symbol) -> String { + use roc_module::ident::ModuleName; + + if pretty_print_ir_symbols() { + format!("{:?}", symbol) + } else { + let text = format!("{}", symbol); + + if text.starts_with(ModuleName::APP) { + let name: String = text.trim_start_matches(ModuleName::APP).into(); + format!("Test{}", name) + } else { + text + } + } +} + fn symbol_to_doc<'b, D, A>(alloc: &'b D, symbol: Symbol) -> DocBuilder<'b, D, A> where D: DocAllocator<'b, A>, D::Doc: Clone, A: Clone, { - use roc_module::ident::ModuleName; - - if PRETTY_PRINT_IR_SYMBOLS { - alloc.text(format!("{:?}", symbol)) - } else { - let text = format!("{}", symbol); - - if text.starts_with(ModuleName::APP) { - let name: String = text.trim_start_matches(ModuleName::APP).into(); - alloc.text("Test").append(name) - } else { - alloc.text(text) - } - } + alloc.text(symbol_to_doc_string(symbol)) } fn join_point_to_doc<'b, D, A>(alloc: &'b D, symbol: JoinPointId) -> DocBuilder<'b, D, A> @@ -1635,8 +1728,8 @@ impl<'a> Stmt<'a> { Let(symbol, expr, _layout, cont) => alloc .text("let ") .append(symbol_to_doc(alloc, *symbol)) - //.append(" : ") - //.append(alloc.text(format!("{:?}", _layout))) + .append(" : ") + .append(alloc.text(format!("{:?}", _layout))) .append(" = ") .append(expr.to_doc(alloc)) .append(";") @@ -1803,33 +1896,9 @@ fn patterns_to_when<'a>( for (pattern_var, pattern) in patterns.into_iter() { let context = crate::exhaustive::Context::BadArg; let mono_pattern = match from_can_pattern(env, layout_cache, &pattern.value) { - Ok((pat, assignments)) => { - for (symbol, variable, expr) in assignments.into_iter().rev() { - if let Ok(old_body) = body { - let def = roc_can::def::Def { - annotation: None, - expr_var: variable, - loc_expr: Loc::at(pattern.region, expr), - loc_pattern: Loc::at( - pattern.region, - roc_can::pattern::Pattern::Identifier(symbol), - ), - pattern_vars: std::iter::once((symbol, variable)).collect(), - }; - let new_expr = roc_can::expr::Expr::LetNonRec( - Box::new(def), - Box::new(old_body), - variable, - ); - let new_body = Loc { - region: pattern.region, - value: new_expr, - }; - - body = Ok(new_body); - } - } - + Ok((pat, _assignments)) => { + // Don't apply any assignments (e.g. to initialize optional variables) yet. + // We'll take care of that later when expanding the new "when" branch. pat } Err(runtime_error) => { @@ -1922,12 +1991,12 @@ fn pattern_to_when<'a>( // for underscore we generate a dummy Symbol (env.unique_symbol(), body) } - Shadowed(region, loc_ident) => { + Shadowed(region, loc_ident, new_symbol) => { let error = roc_problem::can::RuntimeError::Shadowing { original_region: *region, shadow: loc_ident.clone(), }; - (env.unique_symbol(), Loc::at_zero(RuntimeError(error))) + (*new_symbol, Loc::at_zero(RuntimeError(error))) } UnsupportedPattern(region) => { @@ -1961,7 +2030,7 @@ fn pattern_to_when<'a>( (symbol, Loc::at_zero(wrapped_body)) } - IntLiteral(_, _, _) | NumLiteral(_, _, _) | FloatLiteral(_, _, _) | StrLiteral(_) => { + IntLiteral(..) | NumLiteral(..) | FloatLiteral(..) | StrLiteral(_) => { // These patters are refutable, and thus should never occur outside a `when` expression // They should have been replaced with `UnsupportedPattern` during canonicalization unreachable!("refutable pattern {:?} where irrefutable pattern is expected. This should never happen!", pattern.value) @@ -2231,7 +2300,7 @@ fn specialize_external<'a>( env.subs, partial_proc.annotation, fn_var, - roc_unify::unify::Mode::Eq, + roc_unify::unify::Mode::EQ, ); // This will not hold for programs with type errors @@ -2413,7 +2482,7 @@ fn specialize_external<'a>( env.arena, ); - let ptr_bytes = env.ptr_bytes; + let ptr_bytes = env.target_info; combined.sort_by(|(_, layout1), (_, layout2)| { let size1 = layout1.alignment_bytes(ptr_bytes); @@ -2446,7 +2515,7 @@ fn specialize_external<'a>( env.arena, ); - let ptr_bytes = env.ptr_bytes; + let ptr_bytes = env.target_info; combined.sort_by(|(_, layout1), (_, layout2)| { let size1 = layout1.alignment_bytes(ptr_bytes); @@ -2880,8 +2949,7 @@ fn specialize_naked_symbol<'a>( symbol: Symbol, ) -> Stmt<'a> { if procs.is_module_thunk(symbol) { - let partial_proc = procs.get_partial_proc(symbol).unwrap(); - let fn_var = partial_proc.annotation; + let fn_var = variable; // This is a top-level declaration, which will code gen to a 0-arity thunk. let result = call_by_name( @@ -2909,8 +2977,13 @@ fn specialize_naked_symbol<'a>( std::vec::Vec::new(), layout_cache, assigned, - env.arena.alloc(Stmt::Ret(assigned)), + env.arena.alloc(match hole { + Stmt::Jump(id, _) => Stmt::Jump(*id, env.arena.alloc([assigned])), + Stmt::Ret(_) => Stmt::Ret(assigned), + _ => unreachable!(), + }), ); + return result; } } @@ -2945,15 +3018,18 @@ fn try_make_literal<'a>( use roc_can::expr::Expr::*; match can_expr { - Int(_, precision, _, int) => { - match num_argument_to_int_or_float(env.subs, env.ptr_bytes, *precision, false) { - IntOrFloat::Int(_) => Some(Literal::Int(*int)), + Int(_, precision, _, int, _bound) => { + match num_argument_to_int_or_float(env.subs, env.target_info, *precision, false) { + IntOrFloat::Int(_) => Some(match *int { + IntValue::I128(n) => Literal::Int(n), + IntValue::U128(n) => Literal::U128(n), + }), _ => unreachable!("unexpected float precision for integer"), } } - Float(_, precision, float_str, float) => { - match num_argument_to_int_or_float(env.subs, env.ptr_bytes, *precision, true) { + Float(_, precision, float_str, float, _bound) => { + match num_argument_to_int_or_float(env.subs, env.target_info, *precision, true) { IntOrFloat::Float(_) => Some(Literal::Float(*float)), IntOrFloat::DecimalFloatType => { let dec = match RocDec::from_str(float_str) { @@ -2972,11 +3048,17 @@ fn try_make_literal<'a>( // TODO investigate lifetime trouble // Str(string) => Some(Literal::Str(env.arena.alloc(string))), - Num(var, num_str, num) => { + Num(var, num_str, num, _bound) => { // first figure out what kind of number this is - match num_argument_to_int_or_float(env.subs, env.ptr_bytes, *var, false) { - IntOrFloat::Int(_) => Some(Literal::Int((*num).into())), - IntOrFloat::Float(_) => Some(Literal::Float(*num as f64)), + match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) { + IntOrFloat::Int(_) => Some(match *num { + IntValue::I128(n) => Literal::Int(n), + IntValue::U128(n) => Literal::U128(n), + }), + IntOrFloat::Float(_) => Some(match *num { + IntValue::I128(n) => Literal::Float(n as f64), + IntValue::U128(n) => Literal::Float(n as f64), + }), IntOrFloat::DecimalFloatType => { let dec = match RocDec::from_str(num_str) { Some(d) => d, @@ -3008,11 +3090,14 @@ pub fn with_hole<'a>( let arena = env.arena; match can_expr { - Int(_, precision, _, int) => { - match num_argument_to_int_or_float(env.subs, env.ptr_bytes, precision, false) { + Int(_, precision, _, int, _bound) => { + match num_argument_to_int_or_float(env.subs, env.target_info, precision, false) { IntOrFloat::Int(precision) => Stmt::Let( assigned, - Expr::Literal(Literal::Int(int)), + Expr::Literal(match int { + IntValue::I128(n) => Literal::Int(n), + IntValue::U128(n) => Literal::U128(n), + }), Layout::Builtin(Builtin::Int(precision)), hole, ), @@ -3020,8 +3105,8 @@ pub fn with_hole<'a>( } } - Float(_, precision, float_str, float) => { - match num_argument_to_int_or_float(env.subs, env.ptr_bytes, precision, true) { + Float(_, precision, float_str, float, _bound) => { + match num_argument_to_int_or_float(env.subs, env.target_info, precision, true) { IntOrFloat::Float(precision) => Stmt::Let( assigned, Expr::Literal(Literal::Float(float)), @@ -3051,18 +3136,24 @@ pub fn with_hole<'a>( hole, ), - Num(var, num_str, num) => { + Num(var, num_str, num, _bound) => { // first figure out what kind of number this is - match num_argument_to_int_or_float(env.subs, env.ptr_bytes, var, false) { + match num_argument_to_int_or_float(env.subs, env.target_info, var, false) { IntOrFloat::Int(precision) => Stmt::Let( assigned, - Expr::Literal(Literal::Int(num.into())), + Expr::Literal(match num { + IntValue::I128(n) => Literal::Int(n), + IntValue::U128(n) => Literal::U128(n), + }), Layout::int_width(precision), hole, ), IntOrFloat::Float(precision) => Stmt::Let( assigned, - Expr::Literal(Literal::Float(num as f64)), + Expr::Literal(match num { + IntValue::I128(n) => Literal::Float(n as f64), + IntValue::U128(n) => Literal::Float(n as f64), + }), Layout::float_width(precision), hole, ), @@ -3112,16 +3203,20 @@ pub fn with_hole<'a>( _ => {} } - // continue with the default path - let mut stmt = with_hole( - env, - cont.value, - variable, - procs, - layout_cache, - assigned, - hole, - ); + let build_rest = + |env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>| { + with_hole( + env, + cont.value, + variable, + procs, + layout_cache, + assigned, + hole, + ) + }; // a variable is aliased if let roc_can::expr::Expr::Var(original) = def.loc_expr.value { @@ -3133,18 +3228,17 @@ pub fn with_hole<'a>( // // foo = RBTRee.empty - stmt = handle_variable_aliasing( + handle_variable_aliasing( env, procs, layout_cache, def.expr_var, symbol, original, - stmt, - ); - - stmt + build_rest, + ) } else { + let rest = build_rest(env, procs, layout_cache); with_hole( env, def.loc_expr.value, @@ -3152,7 +3246,7 @@ pub fn with_hole<'a>( procs, layout_cache, symbol, - env.arena.alloc(stmt), + env.arena.alloc(rest), ) } } else { @@ -3327,7 +3421,7 @@ pub fn with_hole<'a>( env.arena, record_var, env.subs, - env.ptr_bytes, + env.target_info, ) { Ok(fields) => fields, Err(_) => return Stmt::RuntimeError("Can't create record with improper layout"), @@ -3337,6 +3431,7 @@ pub fn with_hole<'a>( let mut can_fields = Vec::with_capacity_in(fields.len(), env.arena); enum Field { + // TODO: rename this since it can handle unspecialized expressions now too Function(Symbol, Variable), ValueSymbol, Field(roc_can::expr::Field), @@ -3347,7 +3442,7 @@ pub fn with_hole<'a>( use ReuseSymbol::*; match fields.remove(&label) { Some(field) => match can_reuse_symbol(env, procs, &field.loc_expr.value) { - Imported(symbol) | LocalFunction(symbol) => { + Imported(symbol) | LocalFunction(symbol) | UnspecializedExpr(symbol) => { field_symbols.push(symbol); can_fields.push(Field::Function(symbol, variable)); } @@ -3687,7 +3782,7 @@ pub fn with_hole<'a>( env.arena, record_var, env.subs, - env.ptr_bytes, + env.target_info, ) { Ok(fields) => fields, Err(_) => return Stmt::RuntimeError("Can't access record with improper layout"), @@ -3803,9 +3898,16 @@ pub fn with_hole<'a>( ); match raw_layout { - RawFunctionLayout::Function(_, lambda_set, _) => { - construct_closure_data(env, lambda_set, name, &[], assigned, hole) - } + RawFunctionLayout::Function(_, lambda_set, _) => construct_closure_data( + env, + procs, + layout_cache, + lambda_set, + name, + &[], + assigned, + hole, + ), RawFunctionLayout::ZeroArgumentThunk(_) => unreachable!(), } } @@ -3837,7 +3939,7 @@ pub fn with_hole<'a>( env.arena, record_var, env.subs, - env.ptr_bytes, + env.target_info, ) { Ok(fields) => fields, Err(_) => return Stmt::RuntimeError("Can't update record with improper layout"), @@ -3938,13 +4040,36 @@ pub fn with_hole<'a>( ); } CopyExisting(index) => { + let record_needs_specialization = + procs.partial_exprs.contains(structure); + let specialized_structure_sym = if record_needs_specialization { + // We need to specialize the record now; create a new one for it. + // TODO: reuse this symbol for all updates + env.unique_symbol() + } else { + // The record is already good. + structure + }; + let access_expr = Expr::StructAtIndex { - structure, + structure: specialized_structure_sym, index, field_layouts, }; stmt = Stmt::Let(*symbol, access_expr, *field_layout, arena.alloc(stmt)); + + if record_needs_specialization { + stmt = reuse_function_symbol( + env, + procs, + layout_cache, + Some(record_var), + specialized_structure_sym, + stmt, + structure, + ); + } } } } @@ -3999,10 +4124,18 @@ pub fn with_hole<'a>( // define the closure data let symbols = - Vec::from_iter_in(captured_symbols.iter().map(|x| x.0), env.arena) - .into_bump_slice(); + Vec::from_iter_in(captured_symbols.iter(), env.arena).into_bump_slice(); - construct_closure_data(env, lambda_set, name, symbols, assigned, hole) + construct_closure_data( + env, + procs, + layout_cache, + lambda_set, + name, + symbols, + assigned, + hole, + ) } } } @@ -4017,7 +4150,8 @@ pub fn with_hole<'a>( // if it's in there, it's a call by name, otherwise it's a call by pointer let is_known = |key| { // a proc in this module, or an imported symbol - procs.partial_procs.contains_key(key) || env.is_imported_symbol(key) + procs.partial_procs.contains_key(key) + || (env.is_imported_symbol(key) && !procs.is_imported_module_thunk(key)) }; match loc_expr.value { @@ -4072,8 +4206,44 @@ pub fn with_hole<'a>( LocalFunction(_) => { unreachable!("if this was known to be a function, we would not be here") } - Imported(_) => { - unreachable!("an imported value is never an anonymous function") + Imported(thunk_name) => { + debug_assert!(procs.is_imported_module_thunk(thunk_name)); + + add_needed_external(procs, env, fn_var, thunk_name); + + let function_symbol = env.unique_symbol(); + + match full_layout { + RawFunctionLayout::Function( + arg_layouts, + lambda_set, + ret_layout, + ) => { + let closure_data_symbol = function_symbol; + + result = match_on_lambda_set( + env, + lambda_set, + closure_data_symbol, + arg_symbols, + arg_layouts, + ret_layout, + assigned, + hole, + ); + + result = force_thunk( + env, + thunk_name, + Layout::LambdaSet(lambda_set), + function_symbol, + env.arena.alloc(result), + ); + } + RawFunctionLayout::ZeroArgumentThunk(_) => { + unreachable!("calling a non-closure layout") + } + } } Value(function_symbol) => match full_layout { RawFunctionLayout::Function(arg_layouts, lambda_set, ret_layout) => { @@ -4094,6 +4264,49 @@ pub fn with_hole<'a>( unreachable!("calling a non-closure layout") } }, + UnspecializedExpr(symbol) => match full_layout { + RawFunctionLayout::Function(arg_layouts, lambda_set, ret_layout) => { + let closure_data_symbol = env.unique_symbol(); + + result = match_on_lambda_set( + env, + lambda_set, + closure_data_symbol, + arg_symbols, + arg_layouts, + ret_layout, + assigned, + hole, + ); + + let (lambda_expr, lambda_expr_var) = + procs.partial_exprs.get(symbol).unwrap(); + + let snapshot = env.subs.snapshot(); + let cache_snapshot = layout_cache.snapshot(); + let _unified = roc_unify::unify::unify( + env.subs, + fn_var, + lambda_expr_var, + roc_unify::unify::Mode::EQ, + ); + + result = with_hole( + env, + lambda_expr.clone(), + fn_var, + procs, + layout_cache, + closure_data_symbol, + env.arena.alloc(result), + ); + env.subs.rollback_to(snapshot); + layout_cache.rollback_to(cache_snapshot); + } + RawFunctionLayout::ZeroArgumentThunk(_) => { + unreachable!("calling a non-closure layout") + } + }, NotASymbol => { // the expression is not a symbol. That means it's an expression // evaluating to a function value. @@ -4414,17 +4627,20 @@ pub fn with_hole<'a>( } } +#[allow(clippy::too_many_arguments)] fn construct_closure_data<'a>( env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, lambda_set: LambdaSet<'a>, name: Symbol, - symbols: &'a [Symbol], + symbols: &'a [&(Symbol, Variable)], assigned: Symbol, hole: &'a Stmt<'a>, ) -> Stmt<'a> { let lambda_set_layout = Layout::LambdaSet(lambda_set); - match lambda_set.layout_for_member(name) { + let mut result = match lambda_set.layout_for_member(name) { ClosureRepresentation::Union { tag_id, alphabetic_order_fields: field_layouts, @@ -4433,10 +4649,12 @@ fn construct_closure_data<'a>( } => { // captured variables are in symbol-alphabetic order, but now we want // them ordered by their alignment requirements - let mut combined = - Vec::from_iter_in(symbols.iter().zip(field_layouts.iter()), env.arena); + let mut combined = Vec::from_iter_in( + symbols.iter().map(|&&(s, _)| s).zip(field_layouts.iter()), + env.arena, + ); - let ptr_bytes = env.ptr_bytes; + let ptr_bytes = env.target_info; combined.sort_by(|(_, layout1), (_, layout2)| { let size1 = layout1.alignment_bytes(ptr_bytes); @@ -4446,7 +4664,7 @@ fn construct_closure_data<'a>( }); let symbols = - Vec::from_iter_in(combined.iter().map(|(a, _)| **a), env.arena).into_bump_slice(); + Vec::from_iter_in(combined.iter().map(|(a, _)| *a), env.arena).into_bump_slice(); let expr = Expr::Tag { tag_id, @@ -4462,10 +4680,12 @@ fn construct_closure_data<'a>( // captured variables are in symbol-alphabetic order, but now we want // them ordered by their alignment requirements - let mut combined = - Vec::from_iter_in(symbols.iter().zip(field_layouts.iter()), env.arena); + let mut combined = Vec::from_iter_in( + symbols.iter().map(|&(s, _)| s).zip(field_layouts.iter()), + env.arena, + ); - let ptr_bytes = env.ptr_bytes; + let ptr_bytes = env.target_info; combined.sort_by(|(_, layout1), (_, layout2)| { let size1 = layout1.alignment_bytes(ptr_bytes); @@ -4507,7 +4727,23 @@ fn construct_closure_data<'a>( Stmt::Let(assigned, expr, lambda_set_layout, hole) } _ => unreachable!(), + }; + + // Some of the captured symbols may be references to polymorphic expressions, + // which have not been specialized yet. We need to perform those + // specializations now so that there are real symbols to capture. + // + // TODO: this is not quite right. What we should actually be doing is removing references to + // polymorphic expressions from the captured symbols, and allowing the specializations of those + // symbols to be inlined when specializing the closure body elsewhere. + for &&(symbol, var) in symbols { + if procs.partial_exprs.contains(symbol) { + result = + reuse_function_symbol(env, procs, layout_cache, Some(var), symbol, result, symbol); + } } + + result } #[allow(clippy::too_many_arguments)] @@ -4524,7 +4760,7 @@ fn convert_tag_union<'a>( ) -> Stmt<'a> { use crate::layout::UnionVariant::*; let res_variant = - crate::layout::union_sorted_tags(env.arena, variant_var, env.subs, env.ptr_bytes); + crate::layout::union_sorted_tags(env.arena, variant_var, env.subs, env.target_info); let variant = match res_variant { Ok(cached) => cached, Err(LayoutProblem::UnresolvedTypeVar(_)) => { @@ -4810,9 +5046,16 @@ fn tag_union_to_function<'a>( ); match raw_layout { - RawFunctionLayout::Function(_, lambda_set, _) => { - construct_closure_data(env, lambda_set, proc_symbol, &[], assigned, hole) - } + RawFunctionLayout::Function(_, lambda_set, _) => construct_closure_data( + env, + procs, + layout_cache, + lambda_set, + proc_symbol, + &[], + assigned, + hole, + ), RawFunctionLayout::ZeroArgumentThunk(_) => unreachable!(), } } @@ -4860,7 +5103,7 @@ fn sorted_field_symbols<'a>( } }; - let alignment = layout.alignment_bytes(env.ptr_bytes); + let alignment = layout.alignment_bytes(env.target_info); let symbol = possible_reuse_symbol(env, procs, &arg.value); field_symbols_temp.push((alignment, symbol, ((var, arg), &*env.arena.alloc(symbol)))); @@ -4943,41 +5186,42 @@ fn register_capturing_closure<'a>( let is_self_recursive = !matches!(recursive, roc_can::expr::Recursive::NotRecursive); - // does this function capture any local values? - let function_layout = layout_cache.raw_from_var(env.arena, function_type, env.subs); - - let captured_symbols = match function_layout { - Ok(RawFunctionLayout::Function(_, lambda_set, _)) => { - if let Layout::Struct(&[]) = lambda_set.runtime_representation() { - CapturedSymbols::None - } else { - let mut temp = Vec::from_iter_in(captured_symbols, env.arena); - temp.sort(); - CapturedSymbols::Captured(temp.into_bump_slice()) + let captured_symbols = match *env.subs.get_content_without_compacting(function_type) { + Content::Structure(FlatType::Func(_, closure_var, _)) => { + match LambdaSet::from_var(env.arena, env.subs, closure_var, env.target_info) { + Ok(lambda_set) => { + if let Layout::Struct(&[]) = lambda_set.runtime_representation() { + CapturedSymbols::None + } else { + let mut temp = Vec::from_iter_in(captured_symbols, env.arena); + temp.sort(); + CapturedSymbols::Captured(temp.into_bump_slice()) + } + } + Err(_) => { + // just allow this. see https://github.com/rtfeldman/roc/issues/1585 + if captured_symbols.is_empty() { + CapturedSymbols::None + } else { + let mut temp = Vec::from_iter_in(captured_symbols, env.arena); + temp.sort(); + CapturedSymbols::Captured(temp.into_bump_slice()) + } + } } } - Ok(RawFunctionLayout::ZeroArgumentThunk(_)) => { - // top-level thunks cannot capture any variables + _ => { + // This is a value (zero-argument thunk); it cannot capture any variables. debug_assert!( captured_symbols.is_empty(), "{:?} with layout {:?} {:?} {:?}", &captured_symbols, - function_layout, + layout_cache.raw_from_var(env.arena, function_type, env.subs), env.subs, (function_type, closure_type, closure_ext_var), ); CapturedSymbols::None } - Err(_) => { - // just allow this. see https://github.com/rtfeldman/roc/issues/1585 - if captured_symbols.is_empty() { - CapturedSymbols::None - } else { - let mut temp = Vec::from_iter_in(captured_symbols, env.arena); - temp.sort(); - CapturedSymbols::Captured(temp.into_bump_slice()) - } - } }; let partial_proc = PartialProc::from_named_function( @@ -4995,6 +5239,32 @@ fn register_capturing_closure<'a>( } } +fn is_literal_like(expr: &roc_can::expr::Expr) -> bool { + use roc_can::expr::Expr::*; + matches!( + expr, + Num(..) + | Int(..) + | Float(..) + | List { .. } + | Str(_) + | ZeroArgumentTag { .. } + | Tag { .. } + | Record { .. } + | Call(..) + ) +} + +fn expr_is_polymorphic<'a>( + env: &mut Env<'a, '_>, + expr: &roc_can::expr::Expr, + expr_var: Variable, +) -> bool { + // TODO: I don't think we need the `is_literal_like` check, but taking it slow for now... + let is_flex_or_rigid = |c: &Content| matches!(c, Content::FlexVar(_) | Content::RigidVar(_)); + is_literal_like(expr) && env.subs.var_contains_content(expr_var, is_flex_or_rigid) +} + pub fn from_can<'a>( env: &mut Env<'a, '_>, variable: Variable, @@ -5149,19 +5419,26 @@ pub fn from_can<'a>( // or // // foo = RBTRee.empty - let mut rest = from_can(env, def.expr_var, cont.value, procs, layout_cache); - rest = handle_variable_aliasing( + // TODO: right now we need help out rustc with the closure types; + // it isn't able to infer the right lifetime bounds. See if we + // can remove the annotations in the future. + let build_rest = + |env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>| { + from_can(env, def.expr_var, cont.value, procs, layout_cache) + }; + + return handle_variable_aliasing( env, procs, layout_cache, def.expr_var, *symbol, original, - rest, + build_rest, ); - - return rest; } roc_can::expr::Expr::LetNonRec(nested_def, nested_cont, nested_annotation) => { use roc_can::expr::Expr::*; @@ -5245,6 +5522,26 @@ pub fn from_can<'a>( return from_can(env, variable, new_outer, procs, layout_cache); } + ref body if expr_is_polymorphic(env, body, def.expr_var) => { + // This is a pattern like + // + // n = 1 + // asU8 n + // + // At the definition site `n = 1` we only know `1` to have the type `[Int *]`, + // which won't be refined until the call `asU8 n`. Add it as a partial expression + // that will be specialized at each concrete usage site. + procs + .partial_exprs + .insert(*symbol, def.loc_expr.value, def.expr_var); + + let result = from_can(env, variable, cont.value, procs, layout_cache); + + // We won't see this symbol again. + procs.partial_exprs.remove(*symbol); + + return result; + } _ => { let rest = from_can(env, variable, cont.value, procs, layout_cache); return with_hole( @@ -5941,6 +6238,7 @@ fn store_pattern_help<'a>( return StorePattern::NotProductive(stmt); } IntLiteral(_, _) + | U128Literal(_) | FloatLiteral(_, _) | DecimalLiteral(_) | EnumLiteral { .. } @@ -6262,6 +6560,7 @@ enum ReuseSymbol { Imported(Symbol), LocalFunction(Symbol), Value(Symbol), + UnspecializedExpr(Symbol), NotASymbol, } @@ -6279,6 +6578,8 @@ fn can_reuse_symbol<'a>( Imported(symbol) } else if procs.partial_procs.contains_key(symbol) { LocalFunction(symbol) + } else if procs.partial_exprs.contains(symbol) { + UnspecializedExpr(symbol) } else { Value(symbol) } @@ -6298,23 +6599,44 @@ fn possible_reuse_symbol<'a>( } } -fn handle_variable_aliasing<'a>( +fn handle_variable_aliasing<'a, BuildRest>( env: &mut Env<'a, '_>, procs: &mut Procs<'a>, layout_cache: &mut LayoutCache<'a>, variable: Variable, left: Symbol, right: Symbol, - mut result: Stmt<'a>, -) -> Stmt<'a> { - if env.is_imported_symbol(right) { + build_rest: BuildRest, +) -> Stmt<'a> +where + BuildRest: FnOnce(&mut Env<'a, '_>, &mut Procs<'a>, &mut LayoutCache<'a>) -> Stmt<'a>, +{ + if procs.partial_exprs.contains(right) { + // If `right` links to a partial expression, make sure we link `left` to it as well, so + // that usages of it will be specialized when building the rest of the program. + procs.partial_exprs.insert_alias(left, right); + return build_rest(env, procs, layout_cache); + } + + // Otherwise we're dealing with an alias to something that doesn't need to be specialized, or + // whose usages will already be specialized in the rest of the program. Let's just build the + // rest of the program now to get our hole. + let mut result = build_rest(env, procs, layout_cache); + if procs.is_imported_module_thunk(right) { // if this is an imported symbol, then we must make sure it is // specialized, and wrap the original in a function pointer. add_needed_external(procs, env, variable, right); - // then we must construct its closure; since imported symbols have no closure, we use the - // empty struct + let res_layout = layout_cache.from_var(env.arena, variable, env.subs); + let layout = return_on_layout_error!(env, res_layout); + force_thunk(env, right, layout, left, env.arena.alloc(result)) + } else if env.is_imported_symbol(right) { + // if this is an imported symbol, then we must make sure it is + // specialized, and wrap the original in a function pointer. + add_needed_external(procs, env, variable, right); + + // then we must construct its closure; since imported symbols have no closure, we use the empty struct let_empty_struct(left, env.arena.alloc(result)) } else { substitute_in_exprs(env.arena, &mut result, left, right); @@ -6357,6 +6679,7 @@ fn let_empty_struct<'a>(assigned: Symbol, hole: &'a Stmt<'a>) -> Stmt<'a> { } /// If the symbol is a function, make sure it is properly specialized +// TODO: rename this now that we handle polymorphic non-function expressions too fn reuse_function_symbol<'a>( env: &mut Env<'a, '_>, procs: &mut Procs<'a>, @@ -6366,6 +6689,35 @@ fn reuse_function_symbol<'a>( result: Stmt<'a>, original: Symbol, ) -> Stmt<'a> { + if let Some((expr, expr_var)) = procs.partial_exprs.get(original) { + // Specialize the expression type now, based off the `arg_var` we've been given. + // TODO: cache the specialized result + let snapshot = env.subs.snapshot(); + let cache_snapshot = layout_cache.snapshot(); + let _unified = roc_unify::unify::unify( + env.subs, + arg_var.unwrap(), + expr_var, + roc_unify::unify::Mode::EQ, + ); + + let result = with_hole( + env, + expr.clone(), + expr_var, + procs, + layout_cache, + symbol, + env.arena.alloc(result), + ); + + // Restore the prior state so as not to interfere with future specializations. + env.subs.rollback_to(snapshot); + layout_cache.rollback_to(cache_snapshot); + + return result; + } + match procs.get_partial_proc(original) { None => { match arg_var { @@ -6456,7 +6808,7 @@ fn reuse_function_symbol<'a>( let symbols = match captured { CapturedSymbols::Captured(captured_symbols) => { - Vec::from_iter_in(captured_symbols.iter().map(|x| x.0), env.arena) + Vec::from_iter_in(captured_symbols.iter(), env.arena) .into_bump_slice() } CapturedSymbols::None => unreachable!(), @@ -6464,6 +6816,8 @@ fn reuse_function_symbol<'a>( construct_closure_data( env, + procs, + layout_cache, lambda_set, original, symbols, @@ -6500,6 +6854,8 @@ fn reuse_function_symbol<'a>( // unification may still cause it to have an extra argument construct_closure_data( env, + procs, + layout_cache, lambda_set, original, &[], @@ -6531,7 +6887,7 @@ fn assign_to_symbol<'a>( ) -> Stmt<'a> { use ReuseSymbol::*; match can_reuse_symbol(env, procs, &loc_arg.value) { - Imported(original) | LocalFunction(original) => { + Imported(original) | LocalFunction(original) | UnspecializedExpr(original) => { // for functions we must make sure they are specialized correctly reuse_function_symbol( env, @@ -7183,8 +7539,8 @@ fn call_specialized_proc<'a>( .map(|pp| &pp.captured_symbols) { Some(&CapturedSymbols::Captured(captured_symbols)) => { - let symbols = Vec::from_iter_in(captured_symbols.iter().map(|x| x.0), env.arena) - .into_bump_slice(); + let symbols = + Vec::from_iter_in(captured_symbols.iter(), env.arena).into_bump_slice(); let closure_data_symbol = env.unique_symbol(); @@ -7209,6 +7565,8 @@ fn call_specialized_proc<'a>( let result = construct_closure_data( env, + procs, + layout_cache, lambda_set, proc_name, symbols, @@ -7253,6 +7611,7 @@ fn call_specialized_proc<'a>( pub enum Pattern<'a> { Identifier(Symbol), Underscore, + U128Literal(u128), IntLiteral(i128, IntWidth), FloatLiteral(u64, FloatWidth), DecimalLiteral(RocDec), @@ -7332,9 +7691,15 @@ fn from_can_pattern_help<'a>( match can_pattern { Underscore => Ok(Pattern::Underscore), Identifier(symbol) => Ok(Pattern::Identifier(*symbol)), - IntLiteral(var, _, int) => { - match num_argument_to_int_or_float(env.subs, env.ptr_bytes, *var, false) { - IntOrFloat::Int(precision) => Ok(Pattern::IntLiteral(*int as i128, precision)), + IntLiteral(_, precision_var, _, int, _bound) => { + match num_argument_to_int_or_float(env.subs, env.target_info, *precision_var, false) { + IntOrFloat::Int(precision) => { + let int = match *int { + IntValue::I128(n) => Pattern::IntLiteral(n, precision), + IntValue::U128(n) => Pattern::U128Literal(n), + }; + Ok(int) + } other => { panic!( "Invalid precision for int pattern: {:?} has {:?}", @@ -7343,11 +7708,11 @@ fn from_can_pattern_help<'a>( } } } - FloatLiteral(var, float_str, float) => { + FloatLiteral(_, precision_var, float_str, float, _bound) => { // TODO: Can I reuse num_argument_to_int_or_float here if I pass in true? - match num_argument_to_int_or_float(env.subs, env.ptr_bytes, *var, true) { + match num_argument_to_int_or_float(env.subs, env.target_info, *precision_var, true) { IntOrFloat::Int(_) => { - panic!("Invalid precision for float pattern {:?}", var) + panic!("Invalid precision for float pattern {:?}", precision_var) } IntOrFloat::Float(precision) => { Ok(Pattern::FloatLiteral(f64::to_bits(*float), precision)) @@ -7365,7 +7730,7 @@ fn from_can_pattern_help<'a>( } } StrLiteral(v) => Ok(Pattern::StrLiteral(v.clone())), - Shadowed(region, ident) => Err(RuntimeError::Shadowing { + Shadowed(region, ident, _new_symbol) => Err(RuntimeError::Shadowing { original_region: *region, shadow: ident.clone(), }), @@ -7374,10 +7739,20 @@ fn from_can_pattern_help<'a>( // TODO preserve malformed problem information here? Err(RuntimeError::UnsupportedPattern(*region)) } - NumLiteral(var, num_str, num) => { - match num_argument_to_int_or_float(env.subs, env.ptr_bytes, *var, false) { - IntOrFloat::Int(precision) => Ok(Pattern::IntLiteral(*num as i128, precision)), - IntOrFloat::Float(precision) => Ok(Pattern::FloatLiteral(*num as u64, precision)), + NumLiteral(var, num_str, num, _bound) => { + match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) { + IntOrFloat::Int(precision) => Ok(match num { + IntValue::I128(num) => Pattern::IntLiteral(*num, precision), + IntValue::U128(num) => Pattern::U128Literal(*num), + }), + IntOrFloat::Float(precision) => { + // TODO: this may be lossy + let num = match *num { + IntValue::I128(n) => f64::to_bits(n as f64), + IntValue::U128(n) => f64::to_bits(n as f64), + }; + Ok(Pattern::FloatLiteral(num, precision)) + } IntOrFloat::DecimalFloatType => { let dec = match RocDec::from_str(num_str) { Some(d) => d, @@ -7398,7 +7773,7 @@ fn from_can_pattern_help<'a>( use crate::layout::UnionVariant::*; let res_variant = - crate::layout::union_sorted_tags(env.arena, *whole_var, env.subs, env.ptr_bytes) + crate::layout::union_sorted_tags(env.arena, *whole_var, env.subs, env.target_info) .map_err(Into::into); let variant = match res_variant { @@ -7480,12 +7855,12 @@ fn from_can_pattern_help<'a>( arguments.sort_by(|arg1, arg2| { let size1 = layout_cache .from_var(env.arena, arg1.0, env.subs) - .map(|x| x.alignment_bytes(env.ptr_bytes)) + .map(|x| x.alignment_bytes(env.target_info)) .unwrap_or(0); let size2 = layout_cache .from_var(env.arena, arg2.0, env.subs) - .map(|x| x.alignment_bytes(env.ptr_bytes)) + .map(|x| x.alignment_bytes(env.target_info)) .unwrap_or(0); size2.cmp(&size1) @@ -7518,8 +7893,8 @@ fn from_can_pattern_help<'a>( let layout2 = layout_cache.from_var(env.arena, arg2.0, env.subs).unwrap(); - let size1 = layout1.alignment_bytes(env.ptr_bytes); - let size2 = layout2.alignment_bytes(env.ptr_bytes); + let size1 = layout1.alignment_bytes(env.target_info); + let size2 = layout2.alignment_bytes(env.target_info); size2.cmp(&size1) }); @@ -7819,7 +8194,7 @@ fn from_can_pattern_help<'a>( } => { // sorted fields based on the type let sorted_fields = - crate::layout::sort_record_fields(env.arena, *whole_var, env.subs, env.ptr_bytes) + crate::layout::sort_record_fields(env.arena, *whole_var, env.subs, env.target_info) .map_err(RuntimeError::from)?; // sorted fields based on the destruct @@ -7971,7 +8346,7 @@ pub enum IntOrFloat { /// Given the `a` in `Num a`, determines whether it's an int or a float pub fn num_argument_to_int_or_float( subs: &Subs, - ptr_bytes: u32, + target_info: TargetInfo, var: Variable, known_to_be_float: bool, ) -> IntOrFloat { @@ -7986,7 +8361,7 @@ pub fn num_argument_to_int_or_float( // Recurse on the second argument let var = subs[args.variables().into_iter().next().unwrap()]; - num_argument_to_int_or_float(subs, ptr_bytes, var, false) + num_argument_to_int_or_float(subs, target_info, var, false) } other @ Content::Alias(symbol, args, _) => { @@ -8004,16 +8379,15 @@ pub fn num_argument_to_int_or_float( // Recurse on the second argument let var = subs[args.variables().into_iter().next().unwrap()]; - num_argument_to_int_or_float(subs, ptr_bytes, var, true) + num_argument_to_int_or_float(subs, target_info, var, true) } Symbol::NUM_DECIMAL | Symbol::NUM_AT_DECIMAL => IntOrFloat::DecimalFloatType, Symbol::NUM_NAT | Symbol::NUM_NATURAL | Symbol::NUM_AT_NATURAL => { - let int_width = match ptr_bytes { - 4 => IntWidth::U32, - 8 => IntWidth::U64, - _ => panic!("unsupported word size"), + let int_width = match target_info.ptr_width() { + roc_target::PtrWidth::Bytes4 => IntWidth::U32, + roc_target::PtrWidth::Bytes8 => IntWidth::U64, }; IntOrFloat::Int(int_width) diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 2d5a541ee8..b04993c9d6 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -6,8 +6,9 @@ use roc_collections::all::{default_hasher, MutMap}; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::{Interns, Symbol}; use roc_problem::can::RuntimeError; +use roc_target::{PtrWidth, TargetInfo}; use roc_types::subs::{ - Content, FlatType, RecordFields, Subs, UnionTags, Variable, VariableSubsSlice, + Content, FlatType, RecordFields, Subs, UnionTags, UnsortedUnionTags, Variable, }; use roc_types::types::{gather_fields_unsorted_iter, RecordField}; use std::collections::hash_map::Entry; @@ -17,10 +18,10 @@ use ven_pretty::{DocAllocator, DocBuilder}; // if your changes cause this number to go down, great! // please change it to the lower number. // if it went up, maybe check that the change is really required -static_assertions::assert_eq_size!([u8; 3 * 8], Builtin); -static_assertions::assert_eq_size!([u8; 4 * 8], Layout); -static_assertions::assert_eq_size!([u8; 3 * 8], UnionLayout); -static_assertions::assert_eq_size!([u8; 3 * 8], LambdaSet); +static_assertions::assert_eq_size!([usize; 3], Builtin); +static_assertions::assert_eq_size!([usize; 4], Layout); +static_assertions::assert_eq_size!([usize; 3], UnionLayout); +static_assertions::assert_eq_size!([usize; 3], LambdaSet); pub type TagIdIntType = u16; pub const MAX_ENUM_SIZE: usize = (std::mem::size_of::() * 8) as usize; @@ -65,6 +66,7 @@ impl<'a> RawFunctionLayout<'a> { Self::new_help(env, structure, structure_content.clone()) } Structure(flat_type) => Self::layout_from_flat_type(env, flat_type), + RangedNumber(typ, _) => Self::from_var(env, typ), // Ints Alias(Symbol::NUM_I128, args, _) => { @@ -123,7 +125,7 @@ impl<'a> RawFunctionLayout<'a> { // Nat Alias(Symbol::NUM_NAT, args, _) => { debug_assert!(args.is_empty()); - Ok(Self::ZeroArgumentThunk(Layout::usize(env.ptr_bytes))) + Ok(Self::ZeroArgumentThunk(Layout::usize(env.target_info))) } Alias(symbol, _, _) if symbol.is_builtin() => Ok(Self::ZeroArgumentThunk( @@ -158,7 +160,7 @@ impl<'a> RawFunctionLayout<'a> { let ret = arena.alloc(ret); let lambda_set = - LambdaSet::from_var(env.arena, env.subs, closure_var, env.ptr_bytes)?; + LambdaSet::from_var(env.arena, env.subs, closure_var, env.target_info)?; Ok(Self::Function(fn_args, lambda_set, ret)) } @@ -360,29 +362,36 @@ impl<'a> UnionLayout<'a> { Layout::Builtin(self.tag_id_builtin()) } - fn stores_tag_id_in_pointer_bits(tags: &[&[Layout<'a>]], ptr_bytes: u32) -> bool { - tags.len() <= ptr_bytes as usize + fn stores_tag_id_in_pointer_bits(tags: &[&[Layout<'a>]], target_info: TargetInfo) -> bool { + tags.len() < target_info.ptr_width() as usize + } + + pub fn tag_id_pointer_bits_and_mask(target_info: TargetInfo) -> (usize, usize) { + match target_info.ptr_width() { + PtrWidth::Bytes8 => (3, 0b0000_0111), + PtrWidth::Bytes4 => (2, 0b0000_0011), + } } // i.e. it is not implicit and not stored in the pointer bits - pub fn stores_tag_id_as_data(&self, ptr_bytes: u32) -> bool { + pub fn stores_tag_id_as_data(&self, target_info: TargetInfo) -> bool { match self { UnionLayout::NonRecursive(_) => true, UnionLayout::Recursive(tags) | UnionLayout::NullableWrapped { other_tags: tags, .. - } => !Self::stores_tag_id_in_pointer_bits(tags, ptr_bytes), + } => !Self::stores_tag_id_in_pointer_bits(tags, target_info), UnionLayout::NonNullableUnwrapped(_) | UnionLayout::NullableUnwrapped { .. } => false, } } - pub fn stores_tag_id_in_pointer(&self, ptr_bytes: u32) -> bool { + pub fn stores_tag_id_in_pointer(&self, target_info: TargetInfo) -> bool { match self { UnionLayout::NonRecursive(_) => false, UnionLayout::Recursive(tags) | UnionLayout::NullableWrapped { other_tags: tags, .. - } => Self::stores_tag_id_in_pointer_bits(tags, ptr_bytes), + } => Self::stores_tag_id_in_pointer_bits(tags, target_info), UnionLayout::NonNullableUnwrapped(_) | UnionLayout::NullableUnwrapped { .. } => false, } } @@ -406,76 +415,73 @@ impl<'a> UnionLayout<'a> { } } - fn tags_alignment_bytes(tags: &[&[Layout]], pointer_size: u32) -> u32 { + fn tags_alignment_bytes(tags: &[&[Layout]], target_info: TargetInfo) -> u32 { tags.iter() - .map(|fields| Layout::Struct(fields).alignment_bytes(pointer_size)) + .map(|fields| Layout::Struct(fields).alignment_bytes(target_info)) .max() .unwrap_or(0) } - pub fn allocation_alignment_bytes(&self, pointer_size: u32) -> u32 { + pub fn allocation_alignment_bytes(&self, target_info: TargetInfo) -> u32 { let allocation = match self { UnionLayout::NonRecursive(_) => unreachable!("not heap-allocated"), - UnionLayout::Recursive(tags) => Self::tags_alignment_bytes(tags, pointer_size), + UnionLayout::Recursive(tags) => Self::tags_alignment_bytes(tags, target_info), UnionLayout::NonNullableUnwrapped(fields) => { - Layout::Struct(fields).alignment_bytes(pointer_size) + Layout::Struct(fields).alignment_bytes(target_info) } UnionLayout::NullableWrapped { other_tags, .. } => { - Self::tags_alignment_bytes(other_tags, pointer_size) + Self::tags_alignment_bytes(other_tags, target_info) } UnionLayout::NullableUnwrapped { other_fields, .. } => { - Layout::Struct(other_fields).alignment_bytes(pointer_size) + Layout::Struct(other_fields).alignment_bytes(target_info) } }; // because we store a refcount, the alignment must be at least the size of a pointer - allocation.max(pointer_size) + allocation.max(target_info.ptr_width() as u32) } /// Size of the data in memory, whether it's stack or heap (for non-null tag ids) - pub fn data_size_and_alignment(&self, pointer_size: u32) -> (u32, u32) { - let id_data_layout = if self.stores_tag_id_as_data(pointer_size) { + pub fn data_size_and_alignment(&self, target_info: TargetInfo) -> (u32, u32) { + let id_data_layout = if self.stores_tag_id_as_data(target_info) { Some(self.tag_id_layout()) } else { None }; - self.data_size_and_alignment_help_match(id_data_layout, pointer_size) + self.data_size_and_alignment_help_match(id_data_layout, target_info) } /// Size of the data before the tag_id, if it exists. /// Returns None if the tag_id is not stored as data in the layout. - pub fn data_size_without_tag_id(&self, pointer_size: u32) -> Option { - if !self.stores_tag_id_as_data(pointer_size) { + pub fn data_size_without_tag_id(&self, target_info: TargetInfo) -> Option { + if !self.stores_tag_id_as_data(target_info) { return None; }; - Some( - self.data_size_and_alignment_help_match(None, pointer_size) - .0, - ) + Some(self.data_size_and_alignment_help_match(None, target_info).0) } fn data_size_and_alignment_help_match( &self, id_data_layout: Option, - pointer_size: u32, + target_info: TargetInfo, ) -> (u32, u32) { match self { Self::NonRecursive(tags) => { - Self::data_size_and_alignment_help(tags, id_data_layout, pointer_size) + Self::data_size_and_alignment_help(tags, id_data_layout, target_info) } Self::Recursive(tags) => { - Self::data_size_and_alignment_help(tags, id_data_layout, pointer_size) + Self::data_size_and_alignment_help(tags, id_data_layout, target_info) } Self::NonNullableUnwrapped(fields) => { - Self::data_size_and_alignment_help(&[fields], id_data_layout, pointer_size) + Self::data_size_and_alignment_help(&[fields], id_data_layout, target_info) } Self::NullableWrapped { other_tags, .. } => { - Self::data_size_and_alignment_help(other_tags, id_data_layout, pointer_size) + Self::data_size_and_alignment_help(other_tags, id_data_layout, target_info) } Self::NullableUnwrapped { other_fields, .. } => { - Self::data_size_and_alignment_help(&[other_fields], id_data_layout, pointer_size) + Self::data_size_and_alignment_help(&[other_fields], id_data_layout, target_info) } } } @@ -483,7 +489,7 @@ impl<'a> UnionLayout<'a> { fn data_size_and_alignment_help( variant_field_layouts: &[&[Layout]], id_data_layout: Option, - pointer_size: u32, + target_info: TargetInfo, ) -> (u32, u32) { let mut size = 0; let mut alignment_bytes = 0; @@ -497,7 +503,7 @@ impl<'a> UnionLayout<'a> { data = Layout::Struct(&fields_and_id); } - let (variant_size, variant_alignment) = data.stack_size_and_alignment(pointer_size); + let (variant_size, variant_alignment) = data.stack_size_and_alignment(target_info); alignment_bytes = alignment_bytes.max(variant_alignment); size = size.max(variant_size); } @@ -506,7 +512,47 @@ impl<'a> UnionLayout<'a> { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +/// Custom type so we can get the numeric representation of a symbol in tests (so `#UserApp.3` +/// instead of `UserApp.foo`). The pretty name is not reliable when running many tests +/// concurrently. The number does not change and will give a reliable output. +struct SetElement<'a> { + symbol: Symbol, + layout: &'a [Layout<'a>], +} + +impl std::fmt::Debug for SetElement<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let symbol_string = crate::ir::symbol_to_doc_string(self.symbol); + + write!(f, "( {}, {:?})", symbol_string, self.layout) + } +} + +impl std::fmt::Debug for LambdaSet<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + struct Helper<'a> { + set: &'a [(Symbol, &'a [Layout<'a>])], + } + + impl std::fmt::Debug for Helper<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let entries = self.set.iter().map(|x| SetElement { + symbol: x.0, + layout: x.1, + }); + + f.debug_list().entries(entries).finish() + } + } + + f.debug_struct("LambdaSet") + .field("set", &Helper { set: self.set }) + .field("representation", &self.representation) + .finish() + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct LambdaSet<'a> { /// collection of function names and their closure arguments pub set: &'a [(Symbol, &'a [Layout<'a>])], @@ -538,6 +584,11 @@ impl<'a> LambdaSet<'a> { *self.representation } + /// Does the lambda set contain the given symbol? + pub fn contains(&self, symbol: Symbol) -> bool { + self.set.iter().any(|(s, _)| *s == symbol) + } + pub fn is_represented(&self) -> Option> { if let Layout::Struct(&[]) = self.representation { None @@ -647,7 +698,7 @@ impl<'a> LambdaSet<'a> { arena: &'a Bump, subs: &Subs, closure_var: Variable, - ptr_bytes: u32, + target_info: TargetInfo, ) -> Result { let mut tags = std::vec::Vec::new(); match roc_types::pretty_print::chase_ext_tag_union(subs, closure_var, &mut tags) { @@ -661,7 +712,7 @@ impl<'a> LambdaSet<'a> { arena, subs, seen: Vec::new_in(arena), - ptr_bytes, + target_info, }; for (tag_name, variables) in tags.iter() { @@ -679,7 +730,7 @@ impl<'a> LambdaSet<'a> { } let representation = - arena.alloc(Self::make_representation(arena, subs, tags, ptr_bytes)); + arena.alloc(Self::make_representation(arena, subs, tags, target_info)); Ok(LambdaSet { set: set.into_bump_slice(), @@ -702,10 +753,10 @@ impl<'a> LambdaSet<'a> { arena: &'a Bump, subs: &Subs, tags: std::vec::Vec<(TagName, std::vec::Vec)>, - ptr_bytes: u32, + target_info: TargetInfo, ) -> Layout<'a> { // otherwise, this is a closure with a payload - let variant = union_sorted_tags_help(arena, tags, None, subs, ptr_bytes); + let variant = union_sorted_tags_help(arena, tags, None, subs, target_info); use UnionVariant::*; match variant { @@ -745,8 +796,8 @@ impl<'a> LambdaSet<'a> { } } - pub fn stack_size(&self, pointer_size: u32) -> u32 { - self.representation.stack_size(pointer_size) + pub fn stack_size(&self, target_info: TargetInfo) -> u32 { + self.representation.stack_size(target_info) } pub fn contains_refcounted(&self) -> bool { self.representation.contains_refcounted() @@ -755,8 +806,8 @@ impl<'a> LambdaSet<'a> { self.representation.safe_to_memcpy() } - pub fn alignment_bytes(&self, pointer_size: u32) -> u32 { - self.representation.alignment_bytes(pointer_size) + pub fn alignment_bytes(&self, target_info: TargetInfo) -> u32 { + self.representation.alignment_bytes(target_info) } } @@ -773,7 +824,7 @@ pub enum Builtin<'a> { } pub struct Env<'a, 'b> { - ptr_bytes: u32, + target_info: TargetInfo, arena: &'a Bump, seen: Vec<'a, Variable>, subs: &'b Subs, @@ -845,13 +896,15 @@ impl<'a> Layout<'a> { } Symbol::NUM_NAT | Symbol::NUM_NATURAL | Symbol::NUM_AT_NATURAL => { - return Ok(Layout::usize(env.ptr_bytes)) + return Ok(Layout::usize(env.target_info)) } _ => Self::from_var(env, actual_var), } } + RangedNumber(typ, _) => Self::from_var(env, typ), + Error => Err(LayoutProblem::Erroneous), } } @@ -917,31 +970,31 @@ impl<'a> Layout<'a> { } } - pub fn stack_size(&self, pointer_size: u32) -> u32 { - let width = self.stack_size_without_alignment(pointer_size); - let alignment = self.alignment_bytes(pointer_size); + pub fn stack_size(&self, target_info: TargetInfo) -> u32 { + let width = self.stack_size_without_alignment(target_info); + let alignment = self.alignment_bytes(target_info); round_up_to_alignment(width, alignment) } - pub fn stack_size_and_alignment(&self, pointer_size: u32) -> (u32, u32) { - let width = self.stack_size_without_alignment(pointer_size); - let alignment = self.alignment_bytes(pointer_size); + pub fn stack_size_and_alignment(&self, target_info: TargetInfo) -> (u32, u32) { + let width = self.stack_size_without_alignment(target_info); + let alignment = self.alignment_bytes(target_info); let size = round_up_to_alignment(width, alignment); (size, alignment) } - fn stack_size_without_alignment(&self, pointer_size: u32) -> u32 { + fn stack_size_without_alignment(&self, target_info: TargetInfo) -> u32 { use Layout::*; match self { - Builtin(builtin) => builtin.stack_size(pointer_size), + Builtin(builtin) => builtin.stack_size(target_info), Struct(fields) => { let mut sum = 0; for field_layout in *fields { - sum += field_layout.stack_size(pointer_size); + sum += field_layout.stack_size(target_info); } sum @@ -950,26 +1003,26 @@ impl<'a> Layout<'a> { use UnionLayout::*; match variant { - NonRecursive(_) => variant.data_size_and_alignment(pointer_size).0, + NonRecursive(_) => variant.data_size_and_alignment(target_info).0, Recursive(_) | NullableWrapped { .. } | NullableUnwrapped { .. } - | NonNullableUnwrapped(_) => pointer_size, + | NonNullableUnwrapped(_) => target_info.ptr_width() as u32, } } LambdaSet(lambda_set) => lambda_set .runtime_representation() - .stack_size_without_alignment(pointer_size), - RecursivePointer => pointer_size, + .stack_size_without_alignment(target_info), + RecursivePointer => target_info.ptr_width() as u32, } } - pub fn alignment_bytes(&self, pointer_size: u32) -> u32 { + pub fn alignment_bytes(&self, target_info: TargetInfo) -> u32 { match self { Layout::Struct(fields) => fields .iter() - .map(|x| x.alignment_bytes(pointer_size)) + .map(|x| x.alignment_bytes(target_info)) .max() .unwrap_or(0), @@ -983,44 +1036,44 @@ impl<'a> Layout<'a> { .flat_map(|layouts| { layouts .iter() - .map(|layout| layout.alignment_bytes(pointer_size)) + .map(|layout| layout.alignment_bytes(target_info)) }) .max(); let tag_id_builtin = variant.tag_id_builtin(); match max_alignment { Some(align) => round_up_to_alignment( - align.max(tag_id_builtin.alignment_bytes(pointer_size)), - tag_id_builtin.alignment_bytes(pointer_size), + align.max(tag_id_builtin.alignment_bytes(target_info)), + tag_id_builtin.alignment_bytes(target_info), ), None => { // none of the tags had any payload, but the tag id still contains information - tag_id_builtin.alignment_bytes(pointer_size) + tag_id_builtin.alignment_bytes(target_info) } } } Recursive(_) | NullableWrapped { .. } | NullableUnwrapped { .. } - | NonNullableUnwrapped(_) => pointer_size, + | NonNullableUnwrapped(_) => target_info.ptr_width() as u32, } } Layout::LambdaSet(lambda_set) => lambda_set .runtime_representation() - .alignment_bytes(pointer_size), - Layout::Builtin(builtin) => builtin.alignment_bytes(pointer_size), - Layout::RecursivePointer => pointer_size, + .alignment_bytes(target_info), + Layout::Builtin(builtin) => builtin.alignment_bytes(target_info), + Layout::RecursivePointer => target_info.ptr_width() as u32, } } - pub fn allocation_alignment_bytes(&self, pointer_size: u32) -> u32 { + pub fn allocation_alignment_bytes(&self, target_info: TargetInfo) -> u32 { match self { - Layout::Builtin(builtin) => builtin.allocation_alignment_bytes(pointer_size), + Layout::Builtin(builtin) => builtin.allocation_alignment_bytes(target_info), Layout::Struct(_) => unreachable!("not heap-allocated"), - Layout::Union(union_layout) => union_layout.allocation_alignment_bytes(pointer_size), + Layout::Union(union_layout) => union_layout.allocation_alignment_bytes(target_info), Layout::LambdaSet(lambda_set) => lambda_set .runtime_representation() - .allocation_alignment_bytes(pointer_size), + .allocation_alignment_bytes(target_info), Layout::RecursivePointer => unreachable!("should be looked up to get an actual layout"), } } @@ -1104,7 +1157,7 @@ impl<'a> Layout<'a> { /// But if we're careful when to invalidate certain keys, we still get some benefit #[derive(Debug)] pub struct LayoutCache<'a> { - ptr_bytes: u32, + target_info: TargetInfo, _marker: std::marker::PhantomData<&'a u8>, } @@ -1116,9 +1169,9 @@ pub enum CachedLayout<'a> { } impl<'a> LayoutCache<'a> { - pub fn new(ptr_bytes: u32) -> Self { + pub fn new(target_info: TargetInfo) -> Self { Self { - ptr_bytes, + target_info, _marker: Default::default(), } } @@ -1136,7 +1189,7 @@ impl<'a> LayoutCache<'a> { arena, subs, seen: Vec::new_in(arena), - ptr_bytes: self.ptr_bytes, + target_info: self.target_info, }; Layout::from_var(&mut env, var) @@ -1155,7 +1208,7 @@ impl<'a> LayoutCache<'a> { arena, subs, seen: Vec::new_in(arena), - ptr_bytes: self.ptr_bytes, + target_info: self.target_info, }; RawFunctionLayout::from_var(&mut env, var) } @@ -1187,11 +1240,17 @@ impl<'a> Layout<'a> { Layout::Builtin(Builtin::Float(FloatWidth::F32)) } - pub fn usize(ptr_bytes: u32) -> Layout<'a> { - match ptr_bytes { - 4 => Self::u32(), - 8 => Self::u64(), - _ => panic!("width of usize {} not supported", ptr_bytes), + pub fn usize(target_info: TargetInfo) -> Layout<'a> { + match target_info.ptr_width() { + roc_target::PtrWidth::Bytes4 => Self::u32(), + roc_target::PtrWidth::Bytes8 => Self::u64(), + } + } + + pub fn isize(target_info: TargetInfo) -> Layout<'a> { + match target_info.ptr_width() { + roc_target::PtrWidth::Bytes4 => Self::i32(), + roc_target::PtrWidth::Bytes8 => Self::i64(), } } @@ -1265,42 +1324,46 @@ impl<'a> Builtin<'a> { pub const WRAPPER_PTR: u32 = 0; pub const WRAPPER_LEN: u32 = 1; - pub fn stack_size(&self, pointer_size: u32) -> u32 { + pub fn stack_size(&self, target_info: TargetInfo) -> u32 { use Builtin::*; + let ptr_width = target_info.ptr_width() as u32; + match self { Int(int) => int.stack_size(), Float(float) => float.stack_size(), Bool => Builtin::I1_SIZE, Decimal => Builtin::DECIMAL_SIZE, - Str => Builtin::STR_WORDS * pointer_size, - Dict(_, _) => Builtin::DICT_WORDS * pointer_size, - Set(_) => Builtin::SET_WORDS * pointer_size, - List(_) => Builtin::LIST_WORDS * pointer_size, + Str => Builtin::STR_WORDS * ptr_width, + Dict(_, _) => Builtin::DICT_WORDS * ptr_width, + Set(_) => Builtin::SET_WORDS * ptr_width, + List(_) => Builtin::LIST_WORDS * ptr_width, } } - pub fn alignment_bytes(&self, pointer_size: u32) -> u32 { + pub fn alignment_bytes(&self, target_info: TargetInfo) -> u32 { use std::mem::align_of; use Builtin::*; + let ptr_width = target_info.ptr_width() as u32; + // for our data structures, what counts is the alignment of the `( ptr, len )` tuple, and // since both of those are one pointer size, the alignment of that structure is a pointer // size match self { - Int(int_width) => int_width.alignment_bytes(), - Float(float_width) => float_width.alignment_bytes(), + Int(int_width) => int_width.alignment_bytes(target_info), + Float(float_width) => float_width.alignment_bytes(target_info), Bool => align_of::() as u32, - Decimal => align_of::() as u32, - Dict(_, _) => pointer_size, - Set(_) => pointer_size, + Decimal => IntWidth::I128.alignment_bytes(target_info), + Dict(_, _) => ptr_width, + Set(_) => ptr_width, // we often treat these as i128 (64-bit systems) // or i64 (32-bit systems). // // In webassembly, For that to be safe // they must be aligned to allow such access - List(_) => pointer_size, - Str => pointer_size, + List(_) => ptr_width, + Str => ptr_width, } } @@ -1380,21 +1443,23 @@ impl<'a> Builtin<'a> { } } - pub fn allocation_alignment_bytes(&self, pointer_size: u32) -> u32 { + pub fn allocation_alignment_bytes(&self, target_info: TargetInfo) -> u32 { + let ptr_width = target_info.ptr_width() as u32; + let allocation = match self { Builtin::Int(_) | Builtin::Float(_) | Builtin::Bool | Builtin::Decimal => { unreachable!("not heap-allocated") } - Builtin::Str => pointer_size, + Builtin::Str => ptr_width, Builtin::Dict(k, v) => k - .alignment_bytes(pointer_size) - .max(v.alignment_bytes(pointer_size)) - .max(pointer_size), - Builtin::Set(k) => k.alignment_bytes(pointer_size).max(pointer_size), - Builtin::List(e) => e.alignment_bytes(pointer_size).max(pointer_size), + .alignment_bytes(target_info) + .max(v.alignment_bytes(target_info)) + .max(ptr_width), + Builtin::Set(k) => k.alignment_bytes(target_info).max(ptr_width), + Builtin::List(e) => e.alignment_bytes(target_info).max(ptr_width), }; - allocation.max(pointer_size) + allocation.max(ptr_width) } } @@ -1406,7 +1471,7 @@ fn layout_from_flat_type<'a>( let arena = env.arena; let subs = env.subs; - let ptr_bytes = env.ptr_bytes; + let target_info = env.target_info; match flat_type { Apply(symbol, args) => { @@ -1416,7 +1481,7 @@ fn layout_from_flat_type<'a>( // Ints Symbol::NUM_NAT => { debug_assert_eq!(args.len(), 0); - Ok(Layout::usize(env.ptr_bytes)) + Ok(Layout::usize(env.target_info)) } Symbol::NUM_I128 => { @@ -1482,7 +1547,7 @@ fn layout_from_flat_type<'a>( let var = args[0]; let content = subs.get_content_without_compacting(var); - layout_from_num_content(content) + layout_from_num_content(content, target_info) } Symbol::STR_STR => Ok(Layout::Builtin(Builtin::Str)), @@ -1498,7 +1563,8 @@ fn layout_from_flat_type<'a>( } } Func(_, closure_var, _) => { - let lambda_set = LambdaSet::from_var(env.arena, env.subs, closure_var, env.ptr_bytes)?; + let lambda_set = + LambdaSet::from_var(env.arena, env.subs, closure_var, env.target_info)?; Ok(Layout::LambdaSet(lambda_set)) } @@ -1518,8 +1584,8 @@ fn layout_from_flat_type<'a>( } pairs.sort_by(|(label1, layout1), (label2, layout2)| { - let size1 = layout1.alignment_bytes(ptr_bytes); - let size2 = layout2.alignment_bytes(ptr_bytes); + let size1 = layout1.alignment_bytes(target_info); + let size2 = layout2.alignment_bytes(target_info); size2.cmp(&size1).then(label1.cmp(label2)) }); @@ -1535,18 +1601,26 @@ fn layout_from_flat_type<'a>( } } TagUnion(tags, ext_var) => { + let (tags, ext_var) = tags.unsorted_tags_and_ext(subs, ext_var); + debug_assert!(ext_var_is_empty_tag_union(subs, ext_var)); - Ok(layout_from_tag_union(arena, tags, subs, env.ptr_bytes)) + Ok(layout_from_tag_union(arena, &tags, subs, env.target_info)) } FunctionOrTagUnion(tag_name, _, ext_var) => { - debug_assert!(ext_var_is_empty_tag_union(subs, ext_var)); + debug_assert!( + ext_var_is_empty_tag_union(subs, ext_var), + "If ext_var wasn't empty, this wouldn't be a FunctionOrTagUnion!" + ); - let tags = UnionTags::from_tag_name_index(tag_name); + let union_tags = UnionTags::from_tag_name_index(tag_name); + let (tags, _) = union_tags.unsorted_tags_and_ext(subs, ext_var); - Ok(layout_from_tag_union(arena, tags, subs, env.ptr_bytes)) + Ok(layout_from_tag_union(arena, &tags, subs, env.target_info)) } RecursiveTagUnion(rec_var, tags, ext_var) => { + let (tags, ext_var) = tags.unsorted_tags_and_ext(subs, ext_var); + debug_assert!(ext_var_is_empty_tag_union(subs, ext_var)); // some observations @@ -1558,9 +1632,8 @@ fn layout_from_flat_type<'a>( // That means none of the optimizations for enums or single tag tag unions apply let rec_var = subs.get_root_key_without_compacting(rec_var); - let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena); - - let tags_vec = cheap_sort_tags(arena, tags, subs); + let tags_vec = tags.tags; + let mut tag_layouts = Vec::with_capacity_in(tags_vec.len(), arena); let mut nullable = None; @@ -1574,7 +1647,7 @@ fn layout_from_flat_type<'a>( } env.insert_seen(rec_var); - for (index, (_name, variables)) in tags_vec.into_iter().enumerate() { + for (index, &(_name, variables)) in tags_vec.iter().enumerate() { if matches!(nullable, Some(i) if i == index as TagIdIntType) { // don't add the nullable case continue; @@ -1582,8 +1655,7 @@ fn layout_from_flat_type<'a>( let mut tag_layout = Vec::with_capacity_in(variables.len() + 1, arena); - for var_index in variables { - let var = subs[var_index]; + for &var in variables { // TODO does this cause problems with mutually recursive unions? if rec_var == subs.get_root_key_without_compacting(var) { tag_layout.push(Layout::RecursivePointer); @@ -1594,8 +1666,8 @@ fn layout_from_flat_type<'a>( } tag_layout.sort_by(|layout1, layout2| { - let size1 = layout1.alignment_bytes(ptr_bytes); - let size2 = layout2.alignment_bytes(ptr_bytes); + let size1 = layout1.alignment_bytes(target_info); + let size2 = layout2.alignment_bytes(target_info); size2.cmp(&size1) }); @@ -1640,13 +1712,13 @@ pub fn sort_record_fields<'a>( arena: &'a Bump, var: Variable, subs: &Subs, - ptr_bytes: u32, + target_info: TargetInfo, ) -> Result>, LayoutProblem> { let mut env = Env { arena, subs, seen: Vec::new_in(arena), - ptr_bytes, + target_info, }; let (it, _) = match gather_fields_unsorted_iter(subs, RecordFields::empty(), var) { @@ -1665,7 +1737,7 @@ fn sort_record_fields_help<'a>( env: &mut Env<'a, '_>, fields_map: impl Iterator)>, ) -> Result>, LayoutProblem> { - let ptr_bytes = env.ptr_bytes; + let target_info = env.target_info; // Sort the fields by label let mut sorted_fields = Vec::with_capacity_in(fields_map.size_hint().0, env.arena); @@ -1687,8 +1759,8 @@ fn sort_record_fields_help<'a>( |(label1, _, res_layout1), (label2, _, res_layout2)| match res_layout1 { Ok(layout1) | Err(layout1) => match res_layout2 { Ok(layout2) | Err(layout2) => { - let size1 = layout1.alignment_bytes(ptr_bytes); - let size2 = layout2.alignment_bytes(ptr_bytes); + let size1 = layout1.alignment_bytes(target_info); + let size2 = layout2.alignment_bytes(target_info); size2.cmp(&size1).then(label1.cmp(label2)) } @@ -1822,7 +1894,7 @@ pub fn union_sorted_tags<'a>( arena: &'a Bump, var: Variable, subs: &Subs, - ptr_bytes: u32, + target_info: TargetInfo, ) -> Result, LayoutProblem> { let var = if let Content::RecursionVar { structure, .. } = subs.get_content_without_compacting(var) { @@ -1835,7 +1907,7 @@ pub fn union_sorted_tags<'a>( let result = match roc_types::pretty_print::chase_ext_tag_union(subs, var, &mut tags_vec) { Ok(()) | Err((_, Content::FlexVar(_))) | Err((_, Content::RecursionVar { .. })) => { let opt_rec_var = get_recursion_var(subs, var); - union_sorted_tags_help(arena, tags_vec, opt_rec_var, subs, ptr_bytes) + union_sorted_tags_help(arena, tags_vec, opt_rec_var, subs, target_info) } Err((_, Content::Error)) => return Err(LayoutProblem::Erroneous), Err(other) => panic!("invalid content in tag union variable: {:?}", other), @@ -1866,42 +1938,43 @@ fn is_recursive_tag_union(layout: &Layout) -> bool { fn union_sorted_tags_help_new<'a>( arena: &'a Bump, - mut tags_vec: Vec<(&'_ TagName, VariableSubsSlice)>, + tags_list: &[(&'_ TagName, &[Variable])], opt_rec_var: Option, subs: &Subs, - ptr_bytes: u32, + target_info: TargetInfo, ) -> UnionVariant<'a> { // sort up front; make sure the ordering stays intact! - tags_vec.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); + let mut tags_list = Vec::from_iter_in(tags_list.iter(), arena); + tags_list.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); let mut env = Env { arena, subs, seen: Vec::new_in(arena), - ptr_bytes, + target_info, }; - match tags_vec.len() { + match tags_list.len() { 0 => { // trying to instantiate a type with no values UnionVariant::Never } 1 => { - let (tag_name, arguments) = tags_vec.remove(0); + let &(tag_name, arguments) = tags_list.remove(0); let tag_name = tag_name.clone(); // just one tag in the union (but with arguments) can be a struct - let mut layouts = Vec::with_capacity_in(tags_vec.len(), arena); + let mut layouts = Vec::with_capacity_in(tags_list.len(), arena); // special-case NUM_AT_NUM: if its argument is a FlexVar, make it Int match tag_name { TagName::Private(Symbol::NUM_AT_NUM) => { - let var = subs[arguments.into_iter().next().unwrap()]; - layouts.push(unwrap_num_tag(subs, var, ptr_bytes).expect("invalid num layout")); + let var = arguments[0]; + layouts + .push(unwrap_num_tag(subs, var, target_info).expect("invalid num layout")); } _ => { - for var_index in arguments { - let var = subs[var_index]; + for &var in arguments { match Layout::from_var(&mut env, var) { Ok(layout) => { layouts.push(layout); @@ -1922,8 +1995,8 @@ fn union_sorted_tags_help_new<'a>( } layouts.sort_by(|layout1, layout2| { - let size1 = layout1.alignment_bytes(ptr_bytes); - let size2 = layout2.alignment_bytes(ptr_bytes); + let size1 = layout1.alignment_bytes(target_info); + let size2 = layout2.alignment_bytes(target_info); size2.cmp(&size1) }); @@ -1944,7 +2017,7 @@ fn union_sorted_tags_help_new<'a>( } num_tags => { // default path - let mut answer = Vec::with_capacity_in(tags_vec.len(), arena); + let mut answer = Vec::with_capacity_in(tags_list.len(), arena); let mut has_any_arguments = false; let mut nullable: Option<(TagIdIntType, TagName)> = None; @@ -1952,7 +2025,7 @@ fn union_sorted_tags_help_new<'a>( // only recursive tag unions can be nullable let is_recursive = opt_rec_var.is_some(); if is_recursive && GENERATE_NULLABLE { - for (index, (name, variables)) in tags_vec.iter().enumerate() { + for (index, (name, variables)) in tags_list.iter().enumerate() { if variables.is_empty() { nullable = Some((index as TagIdIntType, (*name).clone())); break; @@ -1960,7 +2033,7 @@ fn union_sorted_tags_help_new<'a>( } } - for (index, (tag_name, arguments)) in tags_vec.into_iter().enumerate() { + for (index, &(tag_name, arguments)) in tags_list.into_iter().enumerate() { // reserve space for the tag discriminant if matches!(nullable, Some((i, _)) if i as usize == index) { debug_assert!(arguments.is_empty()); @@ -1969,8 +2042,7 @@ fn union_sorted_tags_help_new<'a>( let mut arg_layouts = Vec::with_capacity_in(arguments.len() + 1, arena); - for var_index in arguments { - let var = subs[var_index]; + for &var in arguments { match Layout::from_var(&mut env, var) { Ok(layout) => { has_any_arguments = true; @@ -2001,8 +2073,8 @@ fn union_sorted_tags_help_new<'a>( } arg_layouts.sort_by(|layout1, layout2| { - let size1 = layout1.alignment_bytes(ptr_bytes); - let size2 = layout2.alignment_bytes(ptr_bytes); + let size1 = layout1.alignment_bytes(target_info); + let size2 = layout2.alignment_bytes(target_info); size2.cmp(&size1) }); @@ -2073,7 +2145,7 @@ pub fn union_sorted_tags_help<'a>( mut tags_vec: std::vec::Vec<(TagName, std::vec::Vec)>, opt_rec_var: Option, subs: &Subs, - ptr_bytes: u32, + target_info: TargetInfo, ) -> UnionVariant<'a> { // sort up front; make sure the ordering stays intact! tags_vec.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); @@ -2082,7 +2154,7 @@ pub fn union_sorted_tags_help<'a>( arena, subs, seen: Vec::new_in(arena), - ptr_bytes, + target_info, }; match tags_vec.len() { @@ -2101,7 +2173,8 @@ pub fn union_sorted_tags_help<'a>( match tag_name { TagName::Private(Symbol::NUM_AT_NUM) => { layouts.push( - unwrap_num_tag(subs, arguments[0], ptr_bytes).expect("invalid num layout"), + unwrap_num_tag(subs, arguments[0], target_info) + .expect("invalid num layout"), ); } _ => { @@ -2131,8 +2204,8 @@ pub fn union_sorted_tags_help<'a>( } layouts.sort_by(|layout1, layout2| { - let size1 = layout1.alignment_bytes(ptr_bytes); - let size2 = layout2.alignment_bytes(ptr_bytes); + let size1 = layout1.alignment_bytes(target_info); + let size2 = layout2.alignment_bytes(target_info); size2.cmp(&size1) }); @@ -2214,8 +2287,8 @@ pub fn union_sorted_tags_help<'a>( } arg_layouts.sort_by(|layout1, layout2| { - let size1 = layout1.alignment_bytes(ptr_bytes); - let size2 = layout2.alignment_bytes(ptr_bytes); + let size1 = layout1.alignment_bytes(target_info); + let size2 = layout2.alignment_bytes(target_info); size2.cmp(&size1) }); @@ -2281,47 +2354,24 @@ pub fn union_sorted_tags_help<'a>( } } -fn cheap_sort_tags<'a, 'b>( - arena: &'a Bump, - tags: UnionTags, - subs: &'b Subs, -) -> Vec<'a, (&'b TagName, VariableSubsSlice)> { - let mut tags_vec = Vec::with_capacity_in(tags.len(), arena); - - for (tag_index, index) in tags.iter_all() { - let tag = &subs[tag_index]; - let slice = subs[index]; - - tags_vec.push((tag, slice)); - } - - tags_vec -} - fn layout_from_newtype<'a>( arena: &'a Bump, - tags: UnionTags, + tags: &UnsortedUnionTags, subs: &Subs, - ptr_bytes: u32, + target_info: TargetInfo, ) -> Layout<'a> { debug_assert!(tags.is_newtype_wrapper(subs)); - let slice_index = tags.variables().into_iter().next().unwrap(); - let slice = subs[slice_index]; - let var_index = slice.into_iter().next().unwrap(); - let var = subs[var_index]; - - let tag_name_index = tags.tag_names().into_iter().next().unwrap(); - let tag_name = &subs[tag_name_index]; + let (tag_name, var) = tags.get_newtype(subs); if tag_name == &TagName::Private(Symbol::NUM_AT_NUM) { - unwrap_num_tag(subs, var, ptr_bytes).expect("invalid Num argument") + unwrap_num_tag(subs, var, target_info).expect("invalid Num argument") } else { let mut env = Env { arena, subs, seen: Vec::new_in(arena), - ptr_bytes, + target_info, }; match Layout::from_var(&mut env, var) { @@ -2343,30 +2393,30 @@ fn layout_from_newtype<'a>( fn layout_from_tag_union<'a>( arena: &'a Bump, - tags: UnionTags, + tags: &UnsortedUnionTags, subs: &Subs, - ptr_bytes: u32, + target_info: TargetInfo, ) -> Layout<'a> { use UnionVariant::*; if tags.is_newtype_wrapper(subs) { - return layout_from_newtype(arena, tags, subs, ptr_bytes); + return layout_from_newtype(arena, tags, subs, target_info); } - let tags_vec = cheap_sort_tags(arena, tags, subs); + let tags_vec = &tags.tags; match tags_vec.get(0) { Some((tag_name, arguments)) if *tag_name == &TagName::Private(Symbol::NUM_AT_NUM) => { debug_assert_eq!(arguments.len(), 1); - let var_index = arguments.into_iter().next().unwrap(); - let var = subs[var_index]; + let &var = arguments.iter().next().unwrap(); - unwrap_num_tag(subs, var, ptr_bytes).expect("invalid Num argument") + unwrap_num_tag(subs, var, target_info).expect("invalid Num argument") } _ => { let opt_rec_var = None; - let variant = union_sorted_tags_help_new(arena, tags_vec, opt_rec_var, subs, ptr_bytes); + let variant = + union_sorted_tags_help_new(arena, tags_vec, opt_rec_var, subs, target_info); match variant { Never => Layout::VOID, @@ -2464,12 +2514,13 @@ pub fn ext_var_is_empty_tag_union(_: &Subs, _: Variable) -> bool { unreachable!(); } -fn layout_from_num_content<'a>(content: &Content) -> Result, LayoutProblem> { +fn layout_from_num_content<'a>( + content: &Content, + target_info: TargetInfo, +) -> Result, LayoutProblem> { use roc_types::subs::Content::*; use roc_types::subs::FlatType::*; - let ptr_bytes = 8; - match content { RecursionVar { .. } => panic!("recursion var in num"), FlexVar(_) | RigidVar(_) => { @@ -2481,7 +2532,7 @@ fn layout_from_num_content<'a>(content: &Content) -> Result, LayoutPr } Structure(Apply(symbol, args)) => match *symbol { // Ints - Symbol::NUM_NAT => Ok(Layout::usize(ptr_bytes)), + Symbol::NUM_NAT => Ok(Layout::usize(target_info)), Symbol::NUM_INTEGER => Ok(Layout::i64()), Symbol::NUM_I128 => Ok(Layout::i128()), @@ -2514,7 +2565,7 @@ fn layout_from_num_content<'a>(content: &Content) -> Result, LayoutPr Alias(_, _, _) => { todo!("TODO recursively resolve type aliases in num_from_content"); } - Structure(_) => { + Structure(_) | RangedNumber(..) => { panic!("Invalid Num.Num type application: {:?}", content); } Error => Err(LayoutProblem::Erroneous), @@ -2524,7 +2575,7 @@ fn layout_from_num_content<'a>(content: &Content) -> Result, LayoutPr fn unwrap_num_tag<'a>( subs: &Subs, var: Variable, - ptr_bytes: u32, + target_info: TargetInfo, ) -> Result, LayoutProblem> { match subs.get_content_without_compacting(var) { Content::Alias(Symbol::NUM_INTEGER, args, _) => { @@ -2549,7 +2600,7 @@ fn unwrap_num_tag<'a>( Symbol::NUM_UNSIGNED32 => Layout::u32(), Symbol::NUM_UNSIGNED16 => Layout::u16(), Symbol::NUM_UNSIGNED8 => Layout::u8(), - Symbol::NUM_NATURAL => Layout::usize(ptr_bytes), + Symbol::NUM_NATURAL => Layout::usize(target_info), _ => unreachable!("not a valid int variant: {:?} {:?}", symbol, args), }; @@ -2795,8 +2846,8 @@ mod test { let layout = Layout::Union(UnionLayout::NonRecursive(&tt)); - let ptr_width = 8; - assert_eq!(layout.stack_size(ptr_width), 1); - assert_eq!(layout.alignment_bytes(ptr_width), 1); + let target_info = TargetInfo::default_x86_64(); + assert_eq!(layout.stack_size(target_info), 1); + assert_eq!(layout.alignment_bytes(target_info), 1); } } diff --git a/compiler/mono/src/layout_soa.rs b/compiler/mono/src/layout_soa.rs index 1481b38723..295ee07cf1 100644 --- a/compiler/mono/src/layout_soa.rs +++ b/compiler/mono/src/layout_soa.rs @@ -3,6 +3,7 @@ use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_collections::all::MutMap; use roc_module::ident::TagName; use roc_module::symbol::Symbol; +use roc_target::TargetInfo; use roc_types::subs::{Content, FlatType, Subs, Variable}; use roc_types::types::RecordField; use std::collections::hash_map::Entry; @@ -99,7 +100,7 @@ pub struct Layouts { lambda_sets: Vec, symbols: Vec, recursion_variable_to_structure_variable_map: MutMap>, - usize_int_width: IntWidth, + target_info: TargetInfo, } pub struct FunctionLayout { @@ -141,6 +142,7 @@ impl FunctionLayout { Content::RecursionVar { .. } => Err(TypeError(())), Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type), Content::Alias(_, _, actual) => Self::from_var_help(layouts, subs, *actual), + Content::RangedNumber(actual, _) => Self::from_var_help(layouts, subs, *actual), Content::Error => Err(TypeError(())), } } @@ -248,6 +250,7 @@ impl LambdaSet { } Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type), Content::Alias(_, _, actual) => Self::from_var_help(layouts, subs, *actual), + Content::RangedNumber(actual, _) => Self::from_var_help(layouts, subs, *actual), Content::Error => Err(TypeError(())), } } @@ -402,7 +405,7 @@ impl Layouts { const VOID_TUPLE: Index<(Layout, Layout)> = Index::new(0); const UNIT_INDEX: Index = Index::new(2); - pub fn new(usize_int_width: IntWidth) -> Self { + pub fn new(target_info: TargetInfo) -> Self { let mut layouts = Vec::with_capacity(64); layouts.push(Layout::VOID); @@ -420,7 +423,7 @@ impl Layouts { lambda_sets: Vec::default(), symbols: Vec::default(), recursion_variable_to_structure_variable_map: MutMap::default(), - usize_int_width, + target_info, } } @@ -443,7 +446,12 @@ impl Layouts { } fn usize(&self) -> Layout { - Layout::Int(self.usize_int_width) + let usize_int_width = match self.target_info.ptr_width() { + roc_target::PtrWidth::Bytes4 => IntWidth::U32, + roc_target::PtrWidth::Bytes8 => IntWidth::U64, + }; + + Layout::Int(usize_int_width) } fn align_of_layout_index(&self, index: Index) -> u16 { @@ -453,18 +461,23 @@ impl Layouts { } fn align_of_layout(&self, layout: Layout) -> u16 { - let ptr_alignment = self.usize_int_width.alignment_bytes() as u16; + let usize_int_width = match self.target_info.ptr_width() { + roc_target::PtrWidth::Bytes4 => IntWidth::U32, + roc_target::PtrWidth::Bytes8 => IntWidth::U64, + }; + + let ptr_alignment = usize_int_width.alignment_bytes(self.target_info) as u16; match layout { Layout::Reserved => unreachable!(), - Layout::Int(int_width) => int_width.alignment_bytes() as u16, - Layout::Float(float_width) => float_width.alignment_bytes() as u16, - Layout::Decimal => IntWidth::U128.alignment_bytes() as u16, + Layout::Int(int_width) => int_width.alignment_bytes(self.target_info) as u16, + Layout::Float(float_width) => float_width.alignment_bytes(self.target_info) as u16, + Layout::Decimal => IntWidth::U128.alignment_bytes(self.target_info) as u16, Layout::Str | Layout::Dict(_) | Layout::Set(_) | Layout::List(_) => ptr_alignment, Layout::Struct(slice) => self.align_of_layout_slice(slice), Layout::Boxed(_) | Layout::UnionRecursive(_) => ptr_alignment, Layout::UnionNonRecursive(slices) => { - let tag_id_align = IntWidth::I64.alignment_bytes() as u16; + let tag_id_align = IntWidth::I64.alignment_bytes(self.target_info) as u16; self.align_of_layout_slices(slices).max(tag_id_align) } @@ -518,7 +531,12 @@ impl Layouts { } pub fn size_of_layout(&self, layout: Layout) -> u16 { - let ptr_width = self.usize_int_width.stack_size() as u16; + let usize_int_width = match self.target_info.ptr_width() { + roc_target::PtrWidth::Bytes4 => IntWidth::U32, + roc_target::PtrWidth::Bytes8 => IntWidth::U64, + }; + + let ptr_width = usize_int_width.stack_size() as u16; match layout { Layout::Reserved => unreachable!(), @@ -666,6 +684,7 @@ impl Layout { } } } + Content::RangedNumber(typ, _) => Self::from_var_help(layouts, subs, *typ), Content::Error => Err(TypeError(())), } } diff --git a/compiler/mono/src/reset_reuse.rs b/compiler/mono/src/reset_reuse.rs index fc63890d73..d29597323f 100644 --- a/compiler/mono/src/reset_reuse.rs +++ b/compiler/mono/src/reset_reuse.rs @@ -1,3 +1,18 @@ +//! This module inserts reset/reuse statements into the mono IR. These statements provide an +//! opportunity to reduce memory pressure by reusing memory slots of non-shared values. From the +//! introduction of the relevant paper: +//! +//! > [We] have added two additional instructions to our IR: `let y = reset x` and +//! > `let z = (reuse y in ctor_i w)`. The two instructions are used together; if `x` +//! > is a shared value, then `y` is set to a special reference, and the reuse instruction +//! > just allocates a new constructor value `ctor_i w`. If `x` is not shared, then reset +//! > decrements the reference counters of the components of `x`, and `y` is set to `x`. +//! > Then, reuse reuses the memory cell used by `x` to store the constructor value `ctor_i w`. +//! +//! See also +//! - [Counting Immutable Beans](https://arxiv.org/pdf/1908.05647.pdf) (Ullrich and Moura, 2020) +//! - [The lean implementation](https://github.com/leanprover/lean4/blob/master/src/Lean/Compiler/IR/ResetReuse.lean) + use crate::inc_dec::{collect_stmt, occurring_variables_expr, JPLiveVarMap, LiveVarSet}; use crate::ir::{ BranchInfo, Call, Expr, ListLiteralElement, Proc, Stmt, UpdateModeId, UpdateModeIds, diff --git a/compiler/parse/src/ast.rs b/compiler/parse/src/ast.rs index 68c94b8217..3fc72c72fd 100644 --- a/compiler/parse/src/ast.rs +++ b/compiler/parse/src/ast.rs @@ -1,6 +1,6 @@ use std::fmt::Debug; -use crate::header::{AppHeader, InterfaceHeader, PlatformHeader}; +use crate::header::{AppHeader, HostedHeader, InterfaceHeader, PlatformHeader}; use crate::ident::Ident; use bumpalo::collections::{String, Vec}; use bumpalo::Bump; @@ -70,6 +70,7 @@ pub enum Module<'a> { Interface { header: InterfaceHeader<'a> }, App { header: AppHeader<'a> }, Platform { header: PlatformHeader<'a> }, + Hosted { header: HostedHeader<'a> }, } #[derive(Clone, Copy, Debug, PartialEq)] @@ -231,6 +232,16 @@ pub struct AliasHeader<'a> { pub vars: &'a [Loc>], } +impl<'a> AliasHeader<'a> { + pub fn region(&self) -> Region { + Region::across_all( + [self.name.region] + .iter() + .chain(self.vars.iter().map(|v| &v.region)), + ) + } +} + #[derive(Debug, Clone, Copy, PartialEq)] pub enum Def<'a> { // TODO in canonicalization, validate the pattern; only certain patterns @@ -791,6 +802,10 @@ impl<'a> Expr<'a> { value: self, } } + + pub fn is_tag(&self) -> bool { + matches!(self, Expr::GlobalTag(_) | Expr::PrivateTag(_)) + } } macro_rules! impl_extract_spaces { @@ -852,6 +867,7 @@ macro_rules! impl_extract_spaces { } impl_extract_spaces!(Expr); +impl_extract_spaces!(Pattern); impl_extract_spaces!(Tag); impl_extract_spaces!(AssignedField); diff --git a/compiler/parse/src/blankspace.rs b/compiler/parse/src/blankspace.rs index 12a4c1ed98..2f91aa648b 100644 --- a/compiler/parse/src/blankspace.rs +++ b/compiler/parse/src/blankspace.rs @@ -9,7 +9,7 @@ use roc_region::all::Position; pub fn space0_around_ee<'a, P, S, E>( parser: P, - min_indent: u16, + min_indent: u32, space_problem: fn(BadInputError, Position) -> E, indent_before_problem: fn(Position) -> E, indent_after_problem: fn(Position) -> E, @@ -35,7 +35,7 @@ where pub fn space0_before_optional_after<'a, P, S, E>( parser: P, - min_indent: u16, + min_indent: u32, space_problem: fn(BadInputError, Position) -> E, indent_before_problem: fn(Position) -> E, indent_after_problem: fn(Position) -> E, @@ -100,7 +100,7 @@ where pub fn space0_before_e<'a, P, S, E>( parser: P, - min_indent: u16, + min_indent: u32, space_problem: fn(BadInputError, Position) -> E, indent_problem: fn(Position) -> E, ) -> impl Parser<'a, Loc, E> @@ -127,7 +127,7 @@ where pub fn space0_after_e<'a, P, S, E>( parser: P, - min_indent: u16, + min_indent: u32, space_problem: fn(BadInputError, Position) -> E, indent_problem: fn(Position) -> E, ) -> impl Parser<'a, Loc, E> @@ -153,23 +153,23 @@ where } pub fn check_indent<'a, E>( - min_indent: u16, + min_indent: u32, indent_problem: fn(Position) -> E, ) -> impl Parser<'a, (), E> where E: 'a, { move |_, state: State<'a>| { - if state.pos.column >= min_indent { + if state.column() >= min_indent { Ok((NoProgress, (), state)) } else { - Err((NoProgress, indent_problem(state.pos), state)) + Err((NoProgress, indent_problem(state.pos()), state)) } } } pub fn space0_e<'a, E>( - min_indent: u16, + min_indent: u32, space_problem: fn(BadInputError, Position) -> E, indent_problem: fn(Position) -> E, ) -> impl Parser<'a, &'a [CommentOrNewline<'a>], E> @@ -181,7 +181,7 @@ where #[inline(always)] fn spaces_help_help<'a, E>( - min_indent: u16, + min_indent: u32, space_problem: fn(BadInputError, Position) -> E, indent_problem: fn(Position) -> E, ) -> impl Parser<'a, &'a [CommentOrNewline<'a>], E> @@ -190,49 +190,41 @@ where { use SpaceState::*; - move |arena, mut state: State<'a>| { + move |arena, state: State<'a>| { let comments_and_newlines = Vec::new_in(arena); - - match eat_spaces(state.bytes(), state.pos, comments_and_newlines) { - HasTab(pos) => { - // there was a tab character - let mut state = state; - state.pos = pos; - // TODO: it _seems_ like if we're changing the line/column, we should also be - // advancing the state by the corresponding number of bytes. - // Not doing this is likely a bug! - // state = state.advance(); - Err(( - MadeProgress, - space_problem(BadInputError::HasTab, pos), - state, - )) - } + match eat_spaces(state.clone(), false, comments_and_newlines) { + HasTab(state) => Err(( + MadeProgress, + space_problem(BadInputError::HasTab, state.pos()), + state, + )), Good { - pos, - bytes, + state: mut new_state, + multiline, comments_and_newlines, } => { - if bytes == state.bytes() { + if new_state.bytes() == state.bytes() { Ok((NoProgress, &[] as &[_], state)) - } else if state.pos.line != pos.line { + } else if multiline { // we parsed at least one newline - state.indent_column = pos.column; + new_state.indent_column = new_state.column(); - if pos.column >= min_indent { - state.pos = pos; - state = state.advance(state.bytes().len() - bytes.len()); - - Ok((MadeProgress, comments_and_newlines.into_bump_slice(), state)) + if new_state.column() >= min_indent { + Ok(( + MadeProgress, + comments_and_newlines.into_bump_slice(), + new_state, + )) } else { - Err((MadeProgress, indent_problem(state.pos), state)) + Err((MadeProgress, indent_problem(state.pos()), state)) } } else { - state.pos.column = pos.column; - state = state.advance(state.bytes().len() - bytes.len()); - - Ok((MadeProgress, comments_and_newlines.into_bump_slice(), state)) + Ok(( + MadeProgress, + comments_and_newlines.into_bump_slice(), + new_state, + )) } } } @@ -241,85 +233,81 @@ where enum SpaceState<'a> { Good { - pos: Position, - bytes: &'a [u8], + state: State<'a>, + multiline: bool, comments_and_newlines: Vec<'a, CommentOrNewline<'a>>, }, - HasTab(Position), + HasTab(State<'a>), } fn eat_spaces<'a>( - mut bytes: &'a [u8], - mut pos: Position, + mut state: State<'a>, + mut multiline: bool, mut comments_and_newlines: Vec<'a, CommentOrNewline<'a>>, ) -> SpaceState<'a> { use SpaceState::*; - for c in bytes { + for c in state.bytes() { match c { b' ' => { - bytes = &bytes[1..]; - pos.column += 1; + state = state.advance(1); } b'\n' => { - bytes = &bytes[1..]; - pos.line += 1; - pos.column = 0; + state = state.advance_newline(); + multiline = true; comments_and_newlines.push(CommentOrNewline::Newline); } b'\r' => { - bytes = &bytes[1..]; + state = state.advance_newline(); } b'\t' => { - return HasTab(pos); + return HasTab(state); } b'#' => { - pos.column += 1; - return eat_line_comment(&bytes[1..], pos, comments_and_newlines); + state = state.advance(1); + return eat_line_comment(state, multiline, comments_and_newlines); } _ => break, } } Good { - pos, - bytes, + state, + multiline, comments_and_newlines, } } fn eat_line_comment<'a>( - mut bytes: &'a [u8], - mut pos: Position, + mut state: State<'a>, + mut multiline: bool, mut comments_and_newlines: Vec<'a, CommentOrNewline<'a>>, ) -> SpaceState<'a> { use SpaceState::*; - let is_doc_comment = if let Some(b'#') = bytes.get(0) { - match bytes.get(1) { + let is_doc_comment = if let Some(b'#') = state.bytes().get(0) { + match state.bytes().get(1) { Some(b' ') => { - bytes = &bytes[2..]; - pos.column += 2; + state = state.advance(2); true } Some(b'\n') => { // consume the second # and the \n - bytes = &bytes[2..]; + state = state.advance(1); + state = state.advance_newline(); comments_and_newlines.push(CommentOrNewline::DocComment("")); - pos.line += 1; - pos.column = 0; - return eat_spaces(bytes, pos, comments_and_newlines); + multiline = true; + return eat_spaces(state, multiline, comments_and_newlines); } None => { // consume the second # - pos.column += 1; - bytes = &bytes[1..]; + state = state.advance(1); return Good { - pos, - bytes, + state, + multiline, comments_and_newlines, }; } @@ -330,14 +318,13 @@ fn eat_line_comment<'a>( false }; - let initial = bytes; - let initial_column = pos.column; + let initial = state.bytes(); - for c in bytes { + for c in state.bytes() { match c { - b'\t' => return HasTab(pos), + b'\t' => return HasTab(state), b'\n' => { - let delta = (pos.column - initial_column) as usize; + let delta = initial.len() - state.bytes().len(); let comment = unsafe { std::str::from_utf8_unchecked(&initial[..delta]) }; if is_doc_comment { @@ -345,19 +332,21 @@ fn eat_line_comment<'a>( } else { comments_and_newlines.push(CommentOrNewline::LineComment(comment)); } - pos.line += 1; - pos.column = 0; - return eat_spaces(&bytes[1..], pos, comments_and_newlines); + state = state.advance_newline(); + multiline = true; + return eat_spaces(state, multiline, comments_and_newlines); + } + b'\r' => { + state = state.advance_newline(); } _ => { - bytes = &bytes[1..]; - pos.column += 1; + state = state.advance(1); } } } // We made it to the end of the bytes. This means there's a comment without a trailing newline. - let delta = (pos.column - initial_column) as usize; + let delta = initial.len() - state.bytes().len(); let comment = unsafe { std::str::from_utf8_unchecked(&initial[..delta]) }; if is_doc_comment { @@ -367,8 +356,8 @@ fn eat_line_comment<'a>( } Good { - pos, - bytes, + state, + multiline, comments_and_newlines, } } diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index 5b5e19c352..f45cbe5c9a 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -25,13 +25,13 @@ fn expr_end<'a>() -> impl Parser<'a, (), EExpr<'a>> { if state.has_reached_end() { Ok((NoProgress, (), state)) } else { - Err((NoProgress, EExpr::BadExprEnd(state.pos), state)) + Err((NoProgress, EExpr::BadExprEnd(state.pos()), state)) } } } pub fn test_parse_expr<'a>( - min_indent: u16, + min_indent: u32, arena: &'a bumpalo::Bump, state: State<'a>, ) -> Result>, EExpr<'a>> { @@ -61,7 +61,7 @@ pub struct ExprParseOptions { /// Check for the `->` token, and raise an error if found /// This is usually true, but false in if-guards - /// + /// /// > Just foo if foo == 2 -> ... check_for_arrow: bool, } @@ -75,13 +75,13 @@ impl Default for ExprParseOptions { } } -pub fn expr_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, EExpr<'a>> { +pub fn expr_help<'a>(min_indent: u32) -> impl Parser<'a, Expr<'a>, EExpr<'a>> { move |arena, state: State<'a>| { parse_loc_expr(min_indent, arena, state).map(|(a, b, c)| (a, b.value, c)) } } -fn loc_expr_in_parens_help<'a>(min_indent: u16) -> impl Parser<'a, Loc>, EInParens<'a>> { +fn loc_expr_in_parens_help<'a>(min_indent: u32) -> impl Parser<'a, Loc>, EInParens<'a>> { move |arena, state| { let (_, loc_expr, state) = loc_expr_in_parens_help_help(min_indent).parse(arena, state)?; @@ -97,7 +97,7 @@ fn loc_expr_in_parens_help<'a>(min_indent: u16) -> impl Parser<'a, Loc> } fn loc_expr_in_parens_help_help<'a>( - min_indent: u16, + min_indent: u32, ) -> impl Parser<'a, Loc>, EInParens<'a>> { between!( word1(b'(', EInParens::Open), @@ -114,7 +114,7 @@ fn loc_expr_in_parens_help_help<'a>( ) } -fn loc_expr_in_parens_etc_help<'a>(min_indent: u16) -> impl Parser<'a, Loc>, EExpr<'a>> { +fn loc_expr_in_parens_etc_help<'a>(min_indent: u32) -> impl Parser<'a, Loc>, EExpr<'a>> { move |arena, state: State<'a>| { let parser = loc!(and!( specialize(EExpr::InParens, loc_expr_in_parens_help(min_indent)), @@ -175,7 +175,7 @@ fn record_field_access_chain<'a>() -> impl Parser<'a, Vec<'a, &'a str>, EExpr<'a } } Err((MadeProgress, fail, state)) => Err((MadeProgress, fail, state)), - Err((NoProgress, _, state)) => Err((NoProgress, EExpr::Access(state.pos), state)), + Err((NoProgress, _, state)) => Err((NoProgress, EExpr::Access(state.pos()), state)), } } @@ -189,7 +189,7 @@ fn record_field_access<'a>() -> impl Parser<'a, &'a str, EExpr<'a>> { /// In some contexts we want to parse the `_` as an expression, so it can then be turned into a /// pattern later fn parse_loc_term_or_underscore<'a>( - min_indent: u16, + min_indent: u32, options: ExprParseOptions, arena: &'a Bump, state: State<'a>, @@ -211,7 +211,7 @@ fn parse_loc_term_or_underscore<'a>( } fn parse_loc_term<'a>( - min_indent: u16, + min_indent: u32, options: ExprParseOptions, arena: &'a Bump, state: State<'a>, @@ -233,7 +233,7 @@ fn parse_loc_term<'a>( fn underscore_expression<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> { move |arena: &'a Bump, state: State<'a>| { - let start = state.pos; + let start = state.pos(); let (_, _, next_state) = word1(b'_', EExpr::Underscore).parse(arena, state)?; @@ -249,7 +249,7 @@ fn underscore_expression<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> { } fn loc_possibly_negative_or_negated_term<'a>( - min_indent: u16, + min_indent: u32, options: ExprParseOptions, ) -> impl Parser<'a, Loc>, EExpr<'a>> { one_of![ @@ -280,7 +280,7 @@ fn loc_possibly_negative_or_negated_term<'a>( } fn fail_expr_start_e<'a, T: 'a>() -> impl Parser<'a, T, EExpr<'a>> { - |_arena, state: State<'a>| Err((NoProgress, EExpr::Start(state.pos), state)) + |_arena, state: State<'a>| Err((NoProgress, EExpr::Start(state.pos()), state)) } fn unary_negate<'a>() -> impl Parser<'a, (), EExpr<'a>> { @@ -297,20 +297,19 @@ fn unary_negate<'a>() -> impl Parser<'a, (), EExpr<'a>> { if state.bytes().starts_with(b"-") && !followed_by_whitespace { // the negate is only unary if it is not followed by whitespace - let mut state = state.advance(1); - state.pos.column += 1; + let state = state.advance(1); Ok((MadeProgress, (), state)) } else { // this is not a negated expression - Err((NoProgress, EExpr::UnaryNot(state.pos), state)) + Err((NoProgress, EExpr::UnaryNot(state.pos()), state)) } } } fn parse_expr_start<'a>( - min_indent: u16, + min_indent: u32, options: ExprParseOptions, - start: Position, + start_column: u32, arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Loc>, EExpr<'a>> { @@ -322,16 +321,16 @@ fn parse_expr_start<'a>( )), loc!(specialize(EExpr::Expect, expect_help(min_indent, options))), loc!(specialize(EExpr::Lambda, closure_help(min_indent, options))), - loc!(move |a, s| parse_expr_operator_chain(min_indent, options, start, a, s)), + loc!(move |a, s| parse_expr_operator_chain(min_indent, options, start_column, a, s)), fail_expr_start_e() ] .parse(arena, state) } fn parse_expr_operator_chain<'a>( - min_indent: u16, + min_indent: u32, options: ExprParseOptions, - start: Position, + start_column: u32, arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Expr<'a>, EExpr<'a>> { @@ -339,7 +338,7 @@ fn parse_expr_operator_chain<'a>( loc_possibly_negative_or_negated_term(min_indent, options).parse(arena, state)?; let initial = state.clone(); - let end = state.get_position(); + let end = state.pos(); match space0_e(min_indent, EExpr::Space, EExpr::IndentEnd).parse(arena, state) { Err((_, _, state)) => Ok((MadeProgress, expr.value, state)), @@ -353,7 +352,7 @@ fn parse_expr_operator_chain<'a>( end, }; - parse_expr_end(min_indent, options, start, expr_state, arena, state) + parse_expr_end(min_indent, options, start_column, expr_state, arena, state) } } } @@ -410,7 +409,7 @@ impl<'a> ExprState<'a> { let fail = EExpr::BadOperator(opchar, loc_op.region.start()); Err(fail) - } else if !self.arguments.is_empty() { + } else if !self.expr.value.is_tag() && !self.arguments.is_empty() { let region = Region::across_all(self.arguments.iter().map(|v| &v.region)); Err(argument_error(region, loc_op.region.start())) @@ -512,16 +511,10 @@ fn numeric_negate_expression<'a, T>( ) -> Loc> { debug_assert_eq!(state.bytes().get(0), Some(&b'-')); // for overflow reasons, we must make the unary minus part of the number literal. - let start = expr.region.start(); - let region = Region::new( - Position { - column: start.column - 1, - ..start - }, - expr.region.end(), - ); + let start = state.pos(); + let region = Region::new(start, expr.region.end()); - let new_expr = match &expr.value { + let new_expr = match expr.value { Expr::Num(string) => { let new_string = unsafe { std::str::from_utf8_unchecked(&state.bytes()[..string.len() + 1]) }; @@ -543,7 +536,7 @@ fn numeric_negate_expression<'a, T>( Expr::NonBase10Int { is_negative: !is_negative, string, - base: *base, + base, } } _ => Expr::UnaryOp(arena.alloc(expr), Loc::at(loc_op.region, UnaryOp::Negate)), @@ -732,7 +725,7 @@ fn append_expect_definition<'a>( let def = Def::Expect(arena.alloc(loc_expect_body)); let end = loc_expect_body.region.end(); - let region = Region::between(start, end); + let region = Region::new(start, end); let mut loc_def = Loc::at(region, def); @@ -780,17 +773,17 @@ struct DefState<'a> { fn parse_defs_end<'a>( options: ExprParseOptions, - start: Position, + start_column: u32, mut def_state: DefState<'a>, arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, DefState<'a>, EExpr<'a>> { - let min_indent = start.column; + let min_indent = start_column; let initial = state.clone(); let state = match space0_e(min_indent, EExpr::Space, EExpr::IndentStart).parse(arena, state) { Err((MadeProgress, _, s)) => { - return Err((MadeProgress, EExpr::DefMissingFinalExpr(s.pos), s)); + return Err((MadeProgress, EExpr::DefMissingFinalExpr(s.pos()), s)); } Ok((_, spaces, state)) => { def_state.spaces_after = spaces; @@ -799,7 +792,8 @@ fn parse_defs_end<'a>( Err((NoProgress, _, state)) => state, }; - let start = state.get_position(); + let start = state.pos(); + let column = state.column(); match space0_after_e( crate::pattern::loc_pattern_help(min_indent), @@ -835,7 +829,7 @@ fn parse_defs_end<'a>( loc_def_expr, ); - parse_defs_end(options, start, def_state, arena, state) + parse_defs_end(options, column, def_state, arena, state) } } } @@ -862,7 +856,7 @@ fn parse_defs_end<'a>( loc_def_expr, ); - parse_defs_end(options, start, def_state, arena, state) + parse_defs_end(options, column, def_state, arena, state) } Ok((_, BinOp::HasType, state)) => { let (_, ann_type, state) = specialize( @@ -884,7 +878,7 @@ fn parse_defs_end<'a>( ann_type, ); - parse_defs_end(options, start, def_state, arena, state) + parse_defs_end(options, column, def_state, arena, state) } _ => Ok((MadeProgress, def_state, initial)), @@ -894,14 +888,14 @@ fn parse_defs_end<'a>( fn parse_defs_expr<'a>( options: ExprParseOptions, - start: Position, + start_column: u32, def_state: DefState<'a>, arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Expr<'a>, EExpr<'a>> { - let min_indent = start.column; + let min_indent = start_column; - match parse_defs_end(options, start, def_state, arena, state) { + match parse_defs_end(options, start_column, def_state, arena, state) { Err(bad) => Err(bad), Ok((_, def_state, state)) => { // this is no def, because there is no `=` or `:`; parse as an expr @@ -916,7 +910,7 @@ fn parse_defs_expr<'a>( Err((_, fail, state)) => { return Err(( MadeProgress, - EExpr::DefMissingFinalExpr2(arena.alloc(fail), state.pos), + EExpr::DefMissingFinalExpr2(arena.alloc(fail), state.pos()), state, )); } @@ -933,9 +927,9 @@ fn parse_defs_expr<'a>( } fn parse_expr_operator<'a>( - min_indent: u16, + min_indent: u32, options: ExprParseOptions, - start: Position, + start_column: u32, mut expr_state: ExprState<'a>, loc_op: Loc, arena: &'a Bump, @@ -949,13 +943,13 @@ fn parse_expr_operator<'a>( let op = loc_op.value; let op_start = loc_op.region.start(); let op_end = loc_op.region.end(); - let new_start = state.get_position(); + let new_start = state.pos(); match op { BinOp::Minus if expr_state.end != op_start && op_end == new_start => { // negative terms let (_, negated_expr, state) = parse_loc_term(min_indent, options, arena, state)?; - let new_end = state.get_position(); + let new_end = state.pos(); let arg = numeric_negate_expression( arena, @@ -977,11 +971,11 @@ fn parse_expr_operator<'a>( expr_state.spaces_after = spaces; expr_state.end = new_end; - parse_expr_end(min_indent, options, start, expr_state, arena, state) + parse_expr_end(min_indent, options, start_column, expr_state, arena, state) } BinOp::Assignment => { let expr_region = expr_state.expr.region; - let indented_more = start.column + 1; + let indented_more = start_column + 1; let call = expr_state .validate_assignment_or_backpassing(arena, loc_op, EExpr::ElmStyleFunction) @@ -1022,11 +1016,11 @@ fn parse_expr_operator<'a>( spaces_after: &[], }; - parse_defs_expr(options, start, def_state, arena, state) + parse_defs_expr(options, start_column, def_state, arena, state) } BinOp::Backpassing => { let expr_region = expr_state.expr.region; - let indented_more = start.column + 1; + let indented_more = start_column + 1; let call = expr_state .validate_assignment_or_backpassing(arena, loc_op, |_, pos| { @@ -1076,7 +1070,7 @@ fn parse_expr_operator<'a>( } BinOp::HasType => { let expr_region = expr_state.expr.region; - let indented_more = start.column + 1; + let indented_more = start_column + 1; let (expr, arguments) = expr_state .validate_has_type(arena, loc_op) @@ -1170,12 +1164,12 @@ fn parse_expr_operator<'a>( spaces_after: &[], }; - parse_defs_expr(options, start, def_state, arena, state) + parse_defs_expr(options, start_column, def_state, arena, state) } _ => match loc_possibly_negative_or_negated_term(min_indent, options).parse(arena, state) { Err((MadeProgress, f, s)) => Err((MadeProgress, f, s)), Ok((_, mut new_expr, state)) => { - let new_end = state.get_position(); + let new_end = state.pos(); expr_state.initial = state.clone(); @@ -1210,7 +1204,7 @@ fn parse_expr_operator<'a>( expr_state.spaces_after = spaces; // TODO new start? - parse_expr_end(min_indent, options, start, expr_state, arena, state) + parse_expr_end(min_indent, options, start_column, expr_state, arena, state) } } } @@ -1222,9 +1216,9 @@ fn parse_expr_operator<'a>( } fn parse_expr_end<'a>( - min_indent: u16, + min_indent: u32, options: ExprParseOptions, - start: Position, + start_column: u32, mut expr_state: ExprState<'a>, arena: &'a Bump, state: State<'a>, @@ -1237,7 +1231,7 @@ fn parse_expr_end<'a>( match parser.parse(arena, state.clone()) { Err((MadeProgress, f, s)) => Err((MadeProgress, f, s)), Ok((_, mut arg, state)) => { - let new_end = state.get_position(); + let new_end = state.pos(); // now that we have `function arg1 ... argn`, attach the spaces to the `argn` if !expr_state.spaces_after.is_empty() { @@ -1262,7 +1256,7 @@ fn parse_expr_end<'a>( expr_state.end = new_end; expr_state.spaces_after = new_spaces; - parse_expr_end(min_indent, options, start, expr_state, arena, state) + parse_expr_end(min_indent, options, start_column, expr_state, arena, state) } } } @@ -1275,14 +1269,19 @@ fn parse_expr_end<'a>( expr_state.consume_spaces(arena); expr_state.initial = before_op; parse_expr_operator( - min_indent, options, start, expr_state, loc_op, arena, state, + min_indent, + options, + start_column, + expr_state, + loc_op, + arena, + state, ) } Err((NoProgress, _, mut state)) => { // try multi-backpassing if options.accept_multi_backpassing && state.bytes().starts_with(b",") { state = state.advance(1); - state.pos.column += 1; let (_, mut patterns, state) = specialize_ref( EExpr::Pattern, @@ -1312,7 +1311,7 @@ fn parse_expr_end<'a>( match word2(b'<', b'-', EExpr::BackpassArrow).parse(arena, state) { Err((_, fail, state)) => Err((MadeProgress, fail, state)), Ok((_, _, state)) => { - let min_indent = start.column; + let min_indent = start_column; let parse_body = space0_before_e( move |a, s| parse_loc_expr(min_indent + 1, a, s), @@ -1342,7 +1341,7 @@ fn parse_expr_end<'a>( } } } else if options.check_for_arrow && state.bytes().starts_with(b"->") { - Err((MadeProgress, EExpr::BadOperator("->", state.pos), state)) + Err((MadeProgress, EExpr::BadOperator("->", state.pos()), state)) } else { // roll back space parsing let state = expr_state.initial.clone(); @@ -1356,7 +1355,7 @@ fn parse_expr_end<'a>( } pub fn parse_loc_expr<'a>( - min_indent: u16, + min_indent: u32, arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Loc>, EExpr<'a>> { @@ -1372,7 +1371,7 @@ pub fn parse_loc_expr<'a>( } pub fn parse_loc_expr_no_multi_backpassing<'a>( - min_indent: u16, + min_indent: u32, arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Loc>, EExpr<'a>> { @@ -1388,13 +1387,13 @@ pub fn parse_loc_expr_no_multi_backpassing<'a>( } fn parse_loc_expr_with_options<'a>( - min_indent: u16, + min_indent: u32, options: ExprParseOptions, arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Loc>, EExpr<'a>> { - let start = state.get_position(); - parse_expr_start(min_indent, options, start, arena, state) + let column = state.column(); + parse_expr_start(min_indent, options, column, arena, state) } /// If the given Expr would parse the same way as a valid Pattern, convert it. @@ -1451,8 +1450,8 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result Ok(Pattern::FloatLiteral(string)), - Expr::Num(string) => Ok(Pattern::NumLiteral(string)), + &Expr::Float(string) => Ok(Pattern::FloatLiteral(string)), + &Expr::Num(string) => Ok(Pattern::NumLiteral(string)), Expr::NonBase10Int { string, base, @@ -1531,7 +1530,7 @@ fn assigned_expr_field_to_pattern_help<'a>( }) } -pub fn defs<'a>(min_indent: u16) -> impl Parser<'a, Vec<'a, Loc>>, EExpr<'a>> { +pub fn defs<'a>(min_indent: u32) -> impl Parser<'a, Vec<'a, Loc>>, EExpr<'a>> { move |arena, state: State<'a>| { let def_state = DefState { defs: Vec::new_in(arena), @@ -1541,17 +1540,17 @@ pub fn defs<'a>(min_indent: u16) -> impl Parser<'a, Vec<'a, Loc>>, EExpr let (_, initial_space, state) = space0_e(min_indent, EExpr::Space, EExpr::IndentEnd).parse(arena, state)?; - let start = state.get_position(); + let start_column = state.column(); let options = ExprParseOptions { accept_multi_backpassing: false, check_for_arrow: true, }; - let (_, def_state, state) = parse_defs_end(options, start, def_state, arena, state)?; + let (_, def_state, state) = parse_defs_end(options, start_column, def_state, arena, state)?; let (_, final_space, state) = - space0_e(start.column, EExpr::Space, EExpr::IndentEnd).parse(arena, state)?; + space0_e(start_column, EExpr::Space, EExpr::IndentEnd).parse(arena, state)?; let mut output = Vec::with_capacity_in(def_state.defs.len(), arena); @@ -1585,7 +1584,7 @@ pub fn defs<'a>(min_indent: u16) -> impl Parser<'a, Vec<'a, Loc>>, EExpr // PARSER HELPERS fn closure_help<'a>( - min_indent: u16, + min_indent: u32, options: ExprParseOptions, ) -> impl Parser<'a, Expr<'a>, ELambda<'a>> { map_with_arena!( @@ -1638,7 +1637,7 @@ mod when { /// Parser for when expressions. pub fn expr_help<'a>( - min_indent: u16, + min_indent: u32, options: ExprParseOptions, ) -> impl Parser<'a, Expr<'a>, EWhen<'a>> { then( @@ -1662,7 +1661,7 @@ mod when { return Err(( progress, // TODO maybe pass case_indent here? - EWhen::PatternAlignment(5, state.pos), + EWhen::PatternAlignment(5, state.pos()), state, )); } @@ -1682,7 +1681,7 @@ mod when { } /// Parsing when with indentation. - fn when_with_indent<'a>() -> impl Parser<'a, u16, EWhen<'a>> { + fn when_with_indent<'a>() -> impl Parser<'a, u32, EWhen<'a>> { move |arena, state: State<'a>| { parser::keyword_e(keyword::WHEN, EWhen::When) .parse(arena, state) @@ -1691,7 +1690,7 @@ mod when { } fn branches<'a>( - min_indent: u16, + min_indent: u32, options: ExprParseOptions, ) -> impl Parser<'a, Vec<'a, &'a WhenBranch<'a>>, EWhen<'a>> { move |arena, state: State<'a>| { @@ -1733,7 +1732,7 @@ mod when { let indent = pattern_indent_level - indent_column; Err(( MadeProgress, - EWhen::PatternAlignment(indent, state.pos), + EWhen::PatternAlignment(indent, state.pos()), state, )) } @@ -1778,10 +1777,10 @@ mod when { /// Parsing alternative patterns in when branches. fn branch_alternatives<'a>( - min_indent: u16, + min_indent: u32, options: ExprParseOptions, - pattern_indent_level: Option, - ) -> impl Parser<'a, ((u16, Vec<'a, Loc>>), Option>>), EWhen<'a>> { + pattern_indent_level: Option, + ) -> impl Parser<'a, ((u32, Vec<'a, Loc>>), Option>>), EWhen<'a>> { let options = ExprParseOptions { check_for_arrow: false, ..options @@ -1811,7 +1810,7 @@ mod when { } fn branch_single_alternative<'a>( - min_indent: u16, + min_indent: u32, ) -> impl Parser<'a, Loc>, EWhen<'a>> { move |arena, state| { let (_, spaces, state) = @@ -1841,9 +1840,9 @@ mod when { } fn branch_alternatives_help<'a>( - min_indent: u16, - pattern_indent_level: Option, - ) -> impl Parser<'a, (u16, Vec<'a, Loc>>), EWhen<'a>> { + min_indent: u32, + pattern_indent_level: Option, + ) -> impl Parser<'a, (u32, Vec<'a, Loc>>), EWhen<'a>> { move |arena, state: State<'a>| { let initial = state.clone(); @@ -1853,15 +1852,15 @@ mod when { Err((NoProgress, fail, _)) => Err((NoProgress, fail, initial)), Ok((_progress, spaces, state)) => { match pattern_indent_level { - Some(wanted) if state.pos.column > wanted => { + Some(wanted) if state.column() > wanted => { // this branch is indented too much - Err((NoProgress, EWhen::IndentPattern(state.pos), initial)) + Err((NoProgress, EWhen::IndentPattern(state.pos()), initial)) } - Some(wanted) if state.pos.column < wanted => { - let indent = wanted - state.pos.column; + Some(wanted) if state.column() < wanted => { + let indent = wanted - state.column(); Err(( NoProgress, - EWhen::PatternAlignment(indent, state.pos), + EWhen::PatternAlignment(indent, state.pos()), initial, )) } @@ -1870,7 +1869,7 @@ mod when { min_indent.max(pattern_indent_level.unwrap_or(min_indent)); // the region is not reliable for the indent column in the case of // parentheses around patterns - let pattern_indent_column = state.pos.column; + let pattern_indent_column = state.column(); let parser = sep_by1( word1(b'|', EWhen::Bar), @@ -1907,7 +1906,7 @@ mod when { } /// Parsing the righthandside of a branch in a when conditional. - fn branch_result<'a>(indent: u16) -> impl Parser<'a, Loc>, EWhen<'a>> { + fn branch_result<'a>(indent: u32) -> impl Parser<'a, Loc>, EWhen<'a>> { skip_first!( word2(b'-', b'>', EWhen::Arrow), space0_before_e( @@ -1922,7 +1921,7 @@ mod when { } } -fn if_branch<'a>(min_indent: u16) -> impl Parser<'a, (Loc>, Loc>), EIf<'a>> { +fn if_branch<'a>(min_indent: u32) -> impl Parser<'a, (Loc>, Loc>), EIf<'a>> { move |arena, state| { // NOTE: only parse spaces before the expression let (_, cond, state) = space0_around_ee( @@ -1962,20 +1961,20 @@ fn if_branch<'a>(min_indent: u16) -> impl Parser<'a, (Loc>, Loc( - min_indent: u16, + min_indent: u32, options: ExprParseOptions, ) -> impl Parser<'a, Expr<'a>, EExpect<'a>> { move |arena: &'a Bump, state: State<'a>| { - let start = state.get_position(); + let start_column = state.column(); let (_, _, state) = parser::keyword_e(keyword::EXPECT, EExpect::Expect).parse(arena, state)?; let (_, condition, state) = space0_before_e( specialize_ref(EExpect::Condition, move |arena, state| { - parse_loc_expr_with_options(start.column + 1, options, arena, state) + parse_loc_expr_with_options(start_column + 1, options, arena, state) }), - start.column + 1, + start_column + 1, EExpect::Space, EExpect::IndentCondition, ) @@ -2001,7 +2000,7 @@ fn expect_help<'a>( } fn if_expr_help<'a>( - min_indent: u16, + min_indent: u32, options: ExprParseOptions, ) -> impl Parser<'a, Expr<'a>, EIf<'a>> { move |arena: &'a Bump, state| { @@ -2076,7 +2075,7 @@ fn assign_or_destructure_identifier<'a>() -> impl Parser<'a, Ident<'a>, EExpr<'a } #[allow(dead_code)] -fn with_indent<'a, E, T, P>(parser: P) -> impl Parser<'a, u16, E> +fn with_indent<'a, E, T, P>(parser: P) -> impl Parser<'a, u32, E> where P: Parser<'a, T, E>, E: 'a, @@ -2122,7 +2121,7 @@ fn ident_to_expr<'a>(arena: &'a Bump, src: Ident<'a>) -> Expr<'a> { } } -fn list_literal_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, EList<'a>> { +fn list_literal_help<'a>(min_indent: u32) -> impl Parser<'a, Expr<'a>, EList<'a>> { move |arena, state| { let (_, elements, state) = collection_trailing_sep_e!( word1(b'[', EList::Open), @@ -2148,7 +2147,7 @@ fn list_literal_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, EList<'a> } fn record_field_help<'a>( - min_indent: u16, + min_indent: u32, ) -> impl Parser<'a, AssignedField<'a, Expr<'a>>, ERecord<'a>> { use AssignedField::*; @@ -2212,7 +2211,7 @@ fn record_updateable_identifier<'a>() -> impl Parser<'a, Expr<'a>, ERecord<'a>> } fn record_help<'a>( - min_indent: u16, + min_indent: u32, ) -> impl Parser< 'a, ( @@ -2269,7 +2268,7 @@ fn record_help<'a>( ) } -fn record_literal_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, EExpr<'a>> { +fn record_literal_help<'a>(min_indent: u32) -> impl Parser<'a, Expr<'a>, EExpr<'a>> { then( loc!(specialize(EExpr::Record, record_help(min_indent))), move |arena, state, _, loc_record| { @@ -2377,7 +2376,6 @@ where macro_rules! good { ($op:expr, $width:expr) => {{ - state.pos.column += $width; state = state.advance($width); Ok((MadeProgress, $op, state)) @@ -2386,12 +2384,12 @@ where macro_rules! bad_made_progress { ($op:expr) => {{ - Err((MadeProgress, to_error($op, state.pos), state)) + Err((MadeProgress, to_error($op, state.pos()), state)) }}; } match chomped { - "" => Err((NoProgress, to_expectation(state.pos), state)), + "" => Err((NoProgress, to_expectation(state.pos()), state)), "+" => good!(BinOp::Plus, 1), "-" => good!(BinOp::Minus, 1), "*" => good!(BinOp::Star, 1), @@ -2402,7 +2400,7 @@ where "<" => good!(BinOp::LessThan, 1), "." => { // a `.` makes no progress, so it does not interfere with `.foo` access(or) - Err((NoProgress, to_error(".", state.pos), state)) + Err((NoProgress, to_error(".", state.pos()), state)) } "=" => good!(BinOp::Assignment, 1), ":" => good!(BinOp::HasType, 1), @@ -2417,7 +2415,7 @@ where "%%" => good!(BinOp::DoublePercent, 2), "->" => { // makes no progress, so it does not interfere with `_ if isGood -> ...` - Err((NoProgress, to_error("->", state.pos), state)) + Err((NoProgress, to_error("->", state.pos()), state)) } "<-" => good!(BinOp::Backpassing, 2), _ => bad_made_progress!(chomped), diff --git a/compiler/parse/src/header.rs b/compiler/parse/src/header.rs index b34fcebad8..6688b845fe 100644 --- a/compiler/parse/src/header.rs +++ b/compiler/parse/src/header.rs @@ -1,6 +1,6 @@ use crate::ast::{Collection, CommentOrNewline, Spaced, StrLiteral, TypeAnnotation}; use crate::blankspace::space0_e; -use crate::ident::lowercase_ident; +use crate::ident::{lowercase_ident, UppercaseIdent}; use crate::parser::Progress::*; use crate::parser::{specialize, word1, EPackageEntry, EPackageName, Parser}; use crate::state::State; @@ -8,6 +8,28 @@ use crate::string_literal; use bumpalo::collections::Vec; use roc_region::all::Loc; +#[derive(Debug)] +pub enum HeaderFor<'a> { + App { + to_platform: To<'a>, + }, + Hosted { + generates: UppercaseIdent<'a>, + generates_with: &'a [Loc>], + }, + PkgConfig { + /// usually `pf` + config_shorthand: &'a str, + /// the type scheme of the main function (required by the platform) + /// (currently unused) + #[allow(dead_code)] + platform_main_type: TypedIdent<'a>, + /// provided symbol to host (commonly `mainForHost`) + main_for_host: roc_module::symbol::Symbol, + }, + Interface, +} + #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub enum Version<'a> { Exact(&'a str), @@ -47,6 +69,15 @@ impl<'a> ModuleName<'a> { } } +#[derive(Debug)] +pub enum ModuleNameEnum<'a> { + /// A filename + App(StrLiteral<'a>), + Interface(ModuleName<'a>), + Hosted(ModuleName<'a>), + PkgConfig, +} + #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub struct ExposedName<'a>(&'a str); @@ -81,6 +112,27 @@ pub struct InterfaceHeader<'a> { pub after_imports: &'a [CommentOrNewline<'a>], } +#[derive(Clone, Debug, PartialEq)] +pub struct HostedHeader<'a> { + pub name: Loc>, + pub exposes: Collection<'a, Loc>>>, + pub imports: Collection<'a, Loc>>>, + pub generates: UppercaseIdent<'a>, + pub generates_with: Collection<'a, Loc>>>, + + // Potential comments and newlines - these will typically all be empty. + pub before_header: &'a [CommentOrNewline<'a>], + pub after_hosted_keyword: &'a [CommentOrNewline<'a>], + pub before_exposes: &'a [CommentOrNewline<'a>], + pub after_exposes: &'a [CommentOrNewline<'a>], + pub before_imports: &'a [CommentOrNewline<'a>], + pub after_imports: &'a [CommentOrNewline<'a>], + pub before_generates: &'a [CommentOrNewline<'a>], + pub after_generates: &'a [CommentOrNewline<'a>], + pub before_with: &'a [CommentOrNewline<'a>], + pub after_with: &'a [CommentOrNewline<'a>], +} + #[derive(Copy, Clone, Debug, PartialEq)] pub enum To<'a> { ExistingPackage(&'a str), @@ -93,6 +145,7 @@ pub struct AppHeader<'a> { pub packages: Collection<'a, Loc>>>, pub imports: Collection<'a, Loc>>>, pub provides: Collection<'a, Loc>>>, + pub provides_types: Option>>>>, pub to: Loc>, // Potential comments and newlines - these will typically all be empty. @@ -126,15 +179,9 @@ pub struct PackageHeader<'a> { pub after_imports: &'a [CommentOrNewline<'a>], } -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct PlatformRigid<'a> { - pub rigid: &'a str, - pub alias: &'a str, -} - #[derive(Clone, Debug, PartialEq)] pub struct PlatformRequires<'a> { - pub rigids: Collection<'a, Loc>>>, + pub rigids: Collection<'a, Loc>>>, pub signature: Loc>>, } @@ -146,7 +193,6 @@ pub struct PlatformHeader<'a> { pub packages: Collection<'a, Loc>>>, pub imports: Collection<'a, Loc>>>, pub provides: Collection<'a, Loc>>>, - pub effects: Effects<'a>, // Potential comments and newlines - these will typically all be empty. pub before_header: &'a [CommentOrNewline<'a>], @@ -163,17 +209,6 @@ pub struct PlatformHeader<'a> { pub after_provides: &'a [CommentOrNewline<'a>], } -/// e.g. fx.Effects -#[derive(Clone, Debug, PartialEq)] -pub struct Effects<'a> { - pub spaces_before_effects_keyword: &'a [CommentOrNewline<'a>], - pub spaces_after_effects_keyword: &'a [CommentOrNewline<'a>], - pub spaces_after_type_name: &'a [CommentOrNewline<'a>], - pub effect_shortname: &'a str, - pub effect_type_name: &'a str, - pub entries: Collection<'a, Loc>>>, -} - #[derive(Copy, Clone, Debug, PartialEq)] pub enum ImportsEntry<'a> { /// e.g. `Task` or `Task.{ Task, after }` @@ -250,7 +285,7 @@ pub fn package_entry<'a>() -> impl Parser<'a, Spaced<'a, PackageEntry<'a>>, EPac pub fn package_name<'a>() -> impl Parser<'a, PackageName<'a>, EPackageName<'a>> { move |arena, state: State<'a>| { - let pos = state.pos; + let pos = state.pos(); specialize(EPackageName::BadPath, string_literal::parse()) .parse(arena, state) .and_then(|(progress, text, next_state)| match text { diff --git a/compiler/parse/src/ident.rs b/compiler/parse/src/ident.rs index 5e21301312..a310525f88 100644 --- a/compiler/parse/src/ident.rs +++ b/compiler/parse/src/ident.rs @@ -5,6 +5,29 @@ use bumpalo::collections::vec::Vec; use bumpalo::Bump; use roc_region::all::Position; +/// A global tag, for example. Must start with an uppercase letter +/// and then contain only letters and numbers afterwards - no dots allowed! +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct UppercaseIdent<'a>(&'a str); + +impl<'a> From<&'a str> for UppercaseIdent<'a> { + fn from(string: &'a str) -> Self { + UppercaseIdent(string) + } +} + +impl<'a> From> for &'a str { + fn from(ident: UppercaseIdent<'a>) -> Self { + ident.0 + } +} + +impl<'a> From<&'a UppercaseIdent<'a>> for &'a str { + fn from(ident: &'a UppercaseIdent<'a>) -> Self { + ident.0 + } +} + /// The parser accepts all of these in any position where any one of them could /// appear. This way, canonicalization can give more helpful error messages like /// "you can't redefine this tag!" if you wrote `Foo = ...` or @@ -68,10 +91,7 @@ pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str, ()> { Err((NoProgress, (), state)) } else { let width = ident.len(); - match state.advance_without_indenting_ee(width, |_| ()) { - Ok(state) => Ok((MadeProgress, ident, state)), - Err(bad) => Err(bad), - } + Ok((MadeProgress, ident, state.advance(width))) } } } @@ -80,15 +100,12 @@ pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str, ()> { pub fn tag_name<'a>() -> impl Parser<'a, &'a str, ()> { move |arena, state: State<'a>| { if state.bytes().starts_with(b"@") { - match chomp_private_tag(state.bytes(), state.pos) { + match chomp_private_tag(state.bytes(), state.pos()) { Err(BadIdent::Start(_)) => Err((NoProgress, (), state)), Err(_) => Err((MadeProgress, (), state)), Ok(ident) => { let width = ident.len(); - match state.advance_without_indenting_ee(width, |_| ()) { - Ok(state) => Ok((MadeProgress, ident, state)), - Err(bad) => Err(bad), - } + Ok((MadeProgress, ident, state.advance(width))) } } } else { @@ -97,6 +114,21 @@ pub fn tag_name<'a>() -> impl Parser<'a, &'a str, ()> { } } +/// This could be: +/// +/// * A module name +/// * A type name +/// * A global tag +pub fn uppercase<'a>() -> impl Parser<'a, UppercaseIdent<'a>, ()> { + move |_, state: State<'a>| match chomp_uppercase_part(state.bytes()) { + Err(progress) => Err((progress, (), state)), + Ok(ident) => { + let width = ident.len(); + Ok((MadeProgress, ident.into(), state.advance(width))) + } + } +} + /// This could be: /// /// * A module name @@ -107,10 +139,7 @@ pub fn uppercase_ident<'a>() -> impl Parser<'a, &'a str, ()> { Err(progress) => Err((progress, (), state)), Ok(ident) => { let width = ident.len(); - match state.advance_without_indenting_ee(width, |_| ()) { - Ok(state) => Ok((MadeProgress, ident, state)), - Err(bad) => Err(bad), - } + Ok((MadeProgress, ident, state.advance(width))) } } } @@ -123,10 +152,7 @@ pub fn unqualified_ident<'a>() -> impl Parser<'a, &'a str, ()> { Err((MadeProgress, (), state)) } else { let width = ident.len(); - match state.advance_without_indenting_ee(width, |_| ()) { - Ok(state) => Ok((MadeProgress, ident, state)), - Err(bad) => Err(bad), - } + Ok((MadeProgress, ident, state.advance(width))) } } } @@ -134,9 +160,7 @@ pub fn unqualified_ident<'a>() -> impl Parser<'a, &'a str, ()> { macro_rules! advance_state { ($state:expr, $n:expr) => { - $state.advance_without_indenting_ee($n, |pos| { - BadIdent::Space(crate::parser::BadInputError::LineTooLong, pos) - }) + Ok($state.advance($n)) }; } @@ -150,7 +174,7 @@ pub fn parse_ident<'a>(arena: &'a Bump, state: State<'a>) -> ParseResult<'a, Ide if let Some(first) = parts.first() { for keyword in crate::keyword::KEYWORDS.iter() { if first == keyword { - return Err((NoProgress, EExpr::Start(initial.pos), initial)); + return Err((NoProgress, EExpr::Start(initial.pos()), initial)); } } } @@ -159,7 +183,7 @@ pub fn parse_ident<'a>(arena: &'a Bump, state: State<'a>) -> ParseResult<'a, Ide Ok((progress, ident, state)) } - Err((NoProgress, _, state)) => Err((NoProgress, EExpr::Start(state.pos), state)), + Err((NoProgress, _, state)) => Err((NoProgress, EExpr::Start(state.pos()), state)), Err((MadeProgress, fail, state)) => match fail { BadIdent::Start(pos) => Err((NoProgress, EExpr::Start(pos), state)), BadIdent::Space(e, pos) => Err((NoProgress, EExpr::Space(e, pos), state)), @@ -177,9 +201,7 @@ fn malformed_identifier<'a>( let delta = initial_bytes.len() - state.bytes().len(); let parsed_str = unsafe { std::str::from_utf8_unchecked(&initial_bytes[..chomped + delta]) }; - state = state.advance_without_indenting_ee(chomped, |pos| { - EExpr::Space(crate::parser::BadInputError::LineTooLong, pos) - })?; + state = state.advance(chomped); Ok((MadeProgress, Ident::Malformed(parsed_str, problem), state)) } @@ -276,10 +298,7 @@ fn chomp_accessor(buffer: &[u8], pos: Position) -> Result<&str, BadIdent> { } Err(_) => { // we've already made progress with the initial `.` - Err(BadIdent::StrayDot(Position { - line: pos.line, - column: pos.column + 1, - })) + Err(BadIdent::StrayDot(pos.bump_column(1))) } } } @@ -295,16 +314,13 @@ fn chomp_private_tag(buffer: &[u8], pos: Position) -> Result<&str, BadIdent> { let width = 1 + name.len(); if let Ok(('.', _)) = char::from_utf8_slice_start(&buffer[width..]) { - Err(BadIdent::BadPrivateTag(pos.bump_column(width as u16))) + Err(BadIdent::BadPrivateTag(pos.bump_column(width as u32))) } else { let value = unsafe { std::str::from_utf8_unchecked(&buffer[..width]) }; Ok(value) } } - Err(_) => Err(BadIdent::BadPrivateTag(Position { - line: pos.line, - column: pos.column + 1, - })), + Err(_) => Err(BadIdent::BadPrivateTag(pos.bump_column(1))), } } @@ -312,7 +328,7 @@ fn chomp_identifier_chain<'a>( arena: &'a Bump, buffer: &'a [u8], pos: Position, -) -> Result<(u16, Ident<'a>), (u16, BadIdent)> { +) -> Result<(u32, Ident<'a>), (u32, BadIdent)> { use encode_unicode::CharExt; let first_is_uppercase; @@ -324,7 +340,7 @@ fn chomp_identifier_chain<'a>( Ok(accessor) => { let bytes_parsed = 1 + accessor.len(); - return Ok((bytes_parsed as u16, Ident::AccessorFunction(accessor))); + return Ok((bytes_parsed as u32, Ident::AccessorFunction(accessor))); } Err(fail) => return Err((1, fail)), }, @@ -332,7 +348,7 @@ fn chomp_identifier_chain<'a>( Ok(tagname) => { let bytes_parsed = tagname.len(); - return Ok((bytes_parsed as u16, Ident::PrivateTag(tagname))); + return Ok((bytes_parsed as u32, Ident::PrivateTag(tagname))); } Err(fail) => return Err((1, fail)), }, @@ -387,19 +403,19 @@ fn chomp_identifier_chain<'a>( parts: parts.into_bump_slice(), }; - Ok((chomped as u16, ident)) + Ok((chomped as u32, ident)) } Err(0) if !module_name.is_empty() => Err(( - chomped as u16, - BadIdent::QualifiedTag(pos.bump_column(chomped as u16)), + chomped as u32, + BadIdent::QualifiedTag(pos.bump_column(chomped as u32)), )), Err(1) if parts.is_empty() => Err(( - chomped as u16 + 1, - BadIdent::WeirdDotQualified(pos.bump_column(chomped as u16 + 1)), + chomped as u32 + 1, + BadIdent::WeirdDotQualified(pos.bump_column(chomped as u32 + 1)), )), Err(width) => Err(( - chomped as u16 + width, - BadIdent::WeirdDotAccess(pos.bump_column(chomped as u16 + width)), + chomped as u32 + width, + BadIdent::WeirdDotAccess(pos.bump_column(chomped as u32 + width)), )), } } else if let Ok(('_', _)) = char::from_utf8_slice_start(&buffer[chomped..]) { @@ -407,13 +423,13 @@ fn chomp_identifier_chain<'a>( // but still parse them (and generate a malformed identifier) // to give good error messages for this case Err(( - chomped as u16 + 1, - BadIdent::Underscore(pos.bump_column(chomped as u16 + 1)), + chomped as u32 + 1, + BadIdent::Underscore(pos.bump_column(chomped as u32 + 1)), )) } else if first_is_uppercase { // just one segment, starting with an uppercase letter; that's a global tag let value = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) }; - Ok((chomped as u16, Ident::GlobalTag(value))) + Ok((chomped as u32, Ident::GlobalTag(value))) } else { // just one segment, starting with a lowercase letter; that's a normal identifier let value = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) }; @@ -421,11 +437,11 @@ fn chomp_identifier_chain<'a>( module_name: "", parts: arena.alloc([value]), }; - Ok((chomped as u16, ident)) + Ok((chomped as u32, ident)) } } -fn chomp_module_chain(buffer: &[u8]) -> Result { +fn chomp_module_chain(buffer: &[u8]) -> Result { let mut chomped = 0; while let Some(b'.') = buffer.get(chomped) { @@ -444,7 +460,7 @@ fn chomp_module_chain(buffer: &[u8]) -> Result { if chomped == 0 { Err(NoProgress) } else { - Ok(chomped as u16) + Ok(chomped as u32) } } @@ -452,10 +468,7 @@ pub fn concrete_type<'a>() -> impl Parser<'a, (&'a str, &'a str), ()> { move |_, state: State<'a>| match chomp_concrete_type(state.bytes()) { Err(progress) => Err((progress, (), state)), Ok((module_name, type_name, width)) => { - match state.advance_without_indenting_ee(width, |_| ()) { - Ok(state) => Ok((MadeProgress, (module_name, type_name), state)), - Err(bad) => Err(bad), - } + Ok((MadeProgress, (module_name, type_name), state.advance(width))) } } } @@ -495,7 +508,7 @@ fn chomp_concrete_type(buffer: &[u8]) -> Result<(&str, &str, usize), Progress> { } } -fn chomp_access_chain<'a>(buffer: &'a [u8], parts: &mut Vec<'a, &'a str>) -> Result { +fn chomp_access_chain<'a>(buffer: &'a [u8], parts: &mut Vec<'a, &'a str>) -> Result { let mut chomped = 0; while let Some(b'.') = buffer.get(chomped) { @@ -511,16 +524,16 @@ fn chomp_access_chain<'a>(buffer: &'a [u8], parts: &mut Vec<'a, &'a str>) -> Res chomped += name.len() + 1; } - Err(_) => return Err(chomped as u16 + 1), + Err(_) => return Err(chomped as u32 + 1), }, - None => return Err(chomped as u16 + 1), + None => return Err(chomped as u32 + 1), } } if chomped == 0 { Err(0) } else { - Ok(chomped as u16) + Ok(chomped as u32) } } @@ -528,7 +541,7 @@ fn parse_ident_help<'a>( arena: &'a Bump, mut state: State<'a>, ) -> ParseResult<'a, Ident<'a>, BadIdent> { - match chomp_identifier_chain(arena, state.bytes(), state.pos) { + match chomp_identifier_chain(arena, state.bytes(), state.pos()) { Ok((width, ident)) => { state = advance_state!(state, width as usize)?; Ok((MadeProgress, ident, state)) diff --git a/compiler/parse/src/module.rs b/compiler/parse/src/module.rs index e420c20bb5..e9db8397ce 100644 --- a/compiler/parse/src/module.rs +++ b/compiler/parse/src/module.rs @@ -1,14 +1,15 @@ use crate::ast::{Collection, CommentOrNewline, Def, Module, Spaced}; use crate::blankspace::{space0_around_ee, space0_before_e, space0_e}; use crate::header::{ - package_entry, package_name, AppHeader, Effects, ExposedName, ImportsEntry, InterfaceHeader, - ModuleName, PackageEntry, PlatformHeader, PlatformRequires, PlatformRigid, To, TypedIdent, + package_entry, package_name, AppHeader, ExposedName, HostedHeader, ImportsEntry, + InterfaceHeader, ModuleName, PackageEntry, PlatformHeader, PlatformRequires, To, TypedIdent, }; -use crate::ident::{lowercase_ident, unqualified_ident, uppercase_ident}; +use crate::ident::{self, lowercase_ident, unqualified_ident, uppercase, UppercaseIdent}; use crate::parser::Progress::{self, *}; use crate::parser::{ - backtrackable, specialize, word1, word2, EEffects, EExposes, EHeader, EImports, EPackages, - EProvides, ERequires, ETypedIdent, Parser, SyntaxError, + backtrackable, optional, specialize, specialize_region, word1, EExposes, EGenerates, + EGeneratesWith, EHeader, EImports, EPackages, EProvides, ERequires, ETypedIdent, Parser, + SourceError, SyntaxError, }; use crate::state::State; use crate::string_literal; @@ -21,7 +22,7 @@ fn end_of_file<'a>() -> impl Parser<'a, (), SyntaxError<'a>> { if state.has_reached_end() { Ok((NoProgress, (), state)) } else { - Err((NoProgress, SyntaxError::NotEndOfFile(state.pos), state)) + Err((NoProgress, SyntaxError::NotEndOfFile(state.pos()), state)) } } } @@ -31,7 +32,10 @@ pub fn module_defs<'a>() -> impl Parser<'a, Vec<'a, Loc>>, SyntaxError<' // force that we parse until the end of the input let min_indent = 0; skip_second!( - specialize(|e, _| SyntaxError::Expr(e), crate::expr::defs(min_indent),), + specialize_region( + |e, r| SyntaxError::Expr(e, r.start()), + crate::expr::defs(min_indent), + ), end_of_file() ) } @@ -39,51 +43,62 @@ pub fn module_defs<'a>() -> impl Parser<'a, Vec<'a, Loc>>, SyntaxError<' pub fn parse_header<'a>( arena: &'a bumpalo::Bump, state: State<'a>, -) -> Result<(Module<'a>, State<'a>), EHeader<'a>> { +) -> Result<(Module<'a>, State<'a>), SourceError<'a, EHeader<'a>>> { match header().parse(arena, state) { Ok((_, module, state)) => Ok((module, state)), - Err((_, fail, _)) => Err(fail), + Err((_, fail, state)) => Err(SourceError::new(fail, &state)), } } fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> { use crate::parser::keyword_e; - one_of![ - map!( - and!( - space0_e(0, EHeader::Space, EHeader::IndentStart), - skip_first!(keyword_e("app", EHeader::Start), app_header()) - ), - |(spaces, mut header): (&'a [CommentOrNewline], AppHeader<'a>)| { - header.before_header = spaces; + type Clos<'b> = Box<(dyn FnOnce(&'b [CommentOrNewline]) -> Module<'b> + 'b)>; - Module::App { header } - } + map!( + and!( + space0_e(0, EHeader::Space, EHeader::IndentStart), + one_of![ + map!( + skip_first!(keyword_e("interface", EHeader::Start), interface_header()), + |mut header: InterfaceHeader<'a>| -> Clos<'a> { + Box::new(|spaces| { + header.before_header = spaces; + Module::Interface { header } + }) + } + ), + map!( + skip_first!(keyword_e("app", EHeader::Start), app_header()), + |mut header: AppHeader<'a>| -> Clos<'a> { + Box::new(|spaces| { + header.before_header = spaces; + Module::App { header } + }) + } + ), + map!( + skip_first!(keyword_e("platform", EHeader::Start), platform_header()), + |mut header: PlatformHeader<'a>| -> Clos<'a> { + Box::new(|spaces| { + header.before_header = spaces; + Module::Platform { header } + }) + } + ), + map!( + skip_first!(keyword_e("hosted", EHeader::Start), hosted_header()), + |mut header: HostedHeader<'a>| -> Clos<'a> { + Box::new(|spaces| { + header.before_header = spaces; + Module::Hosted { header } + }) + } + ) + ] ), - map!( - and!( - space0_e(0, EHeader::Space, EHeader::IndentStart), - skip_first!(keyword_e("platform", EHeader::Start), platform_header()) - ), - |(spaces, mut header): (&'a [CommentOrNewline], PlatformHeader<'a>)| { - header.before_header = spaces; - - Module::Platform { header } - } - ), - map!( - and!( - space0_e(0, EHeader::Space, EHeader::IndentStart), - skip_first!(keyword_e("interface", EHeader::Start), interface_header()) - ), - |(spaces, mut header): (&'a [CommentOrNewline], InterfaceHeader<'a>)| { - header.before_header = spaces; - - Module::Interface { header } - } - ) - ] + |(spaces, make_header): (&'a [CommentOrNewline], Clos<'a>)| { make_header(spaces) } + ) } #[inline(always)] @@ -116,6 +131,46 @@ fn interface_header<'a>() -> impl Parser<'a, InterfaceHeader<'a>, EHeader<'a>> { } } +#[inline(always)] +fn hosted_header<'a>() -> impl Parser<'a, HostedHeader<'a>, EHeader<'a>> { + |arena, state| { + let min_indent = 1; + + let (_, after_hosted_keyword, state) = + space0_e(min_indent, EHeader::Space, EHeader::IndentStart).parse(arena, state)?; + let (_, name, state) = loc!(module_name_help(EHeader::ModuleName)).parse(arena, state)?; + + let (_, ((before_exposes, after_exposes), exposes), state) = + specialize(EHeader::Exposes, exposes_values()).parse(arena, state)?; + let (_, ((before_imports, after_imports), imports), state) = + specialize(EHeader::Imports, imports()).parse(arena, state)?; + let (_, ((before_generates, after_generates), generates), state) = + specialize(EHeader::Generates, generates()).parse(arena, state)?; + let (_, ((before_with, after_with), generates_with), state) = + specialize(EHeader::GeneratesWith, generates_with()).parse(arena, state)?; + + let header = HostedHeader { + name, + exposes, + imports, + generates, + generates_with, + before_header: &[] as &[_], + after_hosted_keyword, + before_exposes, + after_exposes, + before_imports, + after_imports, + before_generates, + after_generates, + before_with, + after_with, + }; + + Ok((MadeProgress, header, state)) + } +} + fn chomp_module_name(buffer: &[u8]) -> Result<&str, Progress> { use encode_unicode::CharExt; @@ -167,7 +222,6 @@ fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>, ()> { |_, mut state: State<'a>| match chomp_module_name(state.bytes()) { Ok(name) => { let width = name.len(); - state.pos.column += width as u16; state = state.advance(width); Ok((MadeProgress, ModuleName::new(name), state)) @@ -225,6 +279,7 @@ fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>, EHeader<'a>> { packages, imports, provides: provides.entries, + provides_types: provides.types, to: provides.to, before_header: &[] as &[_], after_app_keyword, @@ -263,11 +318,9 @@ fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, EHeader<'a>> { let (_, ((before_imports, after_imports), imports), state) = specialize(EHeader::Imports, imports()).parse(arena, state)?; - let (_, ((before_provides, after_provides), provides), state) = + let (_, ((before_provides, after_provides), (provides, _provides_type)), state) = specialize(EHeader::Provides, provides_without_to()).parse(arena, state)?; - let (_, effects, state) = specialize(EHeader::Effects, effects()).parse(arena, state)?; - let header = PlatformHeader { name, requires, @@ -275,7 +328,6 @@ fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, EHeader<'a>> { packages: packages.entries, imports, provides, - effects, before_header: &[] as &[_], after_platform_keyword, before_requires, @@ -297,6 +349,7 @@ fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, EHeader<'a>> { #[derive(Debug)] struct ProvidesTo<'a> { entries: Collection<'a, Loc>>>, + types: Option>>>>, to: Loc>, before_provides_keyword: &'a [CommentOrNewline<'a>], @@ -335,11 +388,12 @@ fn provides_to<'a>() -> impl Parser<'a, ProvidesTo<'a>, EProvides<'a>> { ) ), |( - ((before_provides_keyword, after_provides_keyword), entries), + ((before_provides_keyword, after_provides_keyword), (entries, provides_types)), ((before_to_keyword, after_to_keyword), to), )| { ProvidesTo { entries, + types: provides_types, to, before_provides_keyword, after_provides_keyword, @@ -355,7 +409,10 @@ fn provides_without_to<'a>() -> impl Parser< 'a, ( (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), - Collection<'a, Loc>>>, + ( + Collection<'a, Loc>>>, + Option>>>>, + ), ), EProvides<'a>, > { @@ -369,11 +426,46 @@ fn provides_without_to<'a>() -> impl Parser< EProvides::IndentProvides, EProvides::IndentListStart ), + and!( + collection_trailing_sep_e!( + word1(b'[', EProvides::ListStart), + exposes_entry(EProvides::Identifier), + word1(b',', EProvides::ListEnd), + word1(b']', EProvides::ListEnd), + min_indent, + EProvides::Open, + EProvides::Space, + EProvides::IndentListEnd, + Spaced::SpaceBefore + ), + // Optionally + optional(provides_types()) + ) + ) +} + +#[inline(always)] +fn provides_types<'a>( +) -> impl Parser<'a, Collection<'a, Loc>>>, EProvides<'a>> { + let min_indent = 1; + + skip_first!( + // We only support spaces here, not newlines, because this is not intended + // to be the design forever. Someday it will hopefully work like Elm, + // where platform authors can provide functions like Browser.sandbox which + // present an API based on ordinary-looking type variables. + zero_or_more!(word1( + b' ', + // HACK: If this errors, EProvides::Provides is not an accurate reflection + // of what went wrong. However, this is both skipped and zero_or_more, + // so this error should never be visible to anyone in practice! + EProvides::Provides + )), collection_trailing_sep_e!( - word1(b'[', EProvides::ListStart), - exposes_entry(EProvides::Identifier), + word1(b'{', EProvides::ListStart), + provides_type_entry(EProvides::Identifier), word1(b',', EProvides::ListEnd), - word1(b']', EProvides::ListEnd), + word1(b'}', EProvides::ListEnd), min_indent, EProvides::Open, EProvides::Space, @@ -383,6 +475,20 @@ fn provides_without_to<'a>() -> impl Parser< ) } +fn provides_type_entry<'a, F, E>( + to_expectation: F, +) -> impl Parser<'a, Loc>>, E> +where + F: Fn(Position) -> E, + F: Copy, + E: 'a, +{ + loc!(map!( + specialize(|_, pos| to_expectation(pos), ident::uppercase()), + Spaced::Item + )) +} + fn exposes_entry<'a, F, E>( to_expectation: F, ) -> impl Parser<'a, Loc>>, E> @@ -436,11 +542,14 @@ fn platform_requires<'a>() -> impl Parser<'a, PlatformRequires<'a>, ERequires<'a #[inline(always)] fn requires_rigids<'a>( - min_indent: u16, -) -> impl Parser<'a, Collection<'a, Loc>>>, ERequires<'a>> { + min_indent: u32, +) -> impl Parser<'a, Collection<'a, Loc>>>, ERequires<'a>> { collection_trailing_sep_e!( word1(b'{', ERequires::ListStart), - specialize(|_, pos| ERequires::Rigid(pos), loc!(requires_rigid())), + specialize( + |_, pos| ERequires::Rigid(pos), + loc!(map!(ident::uppercase(), Spaced::Item)) + ), word1(b',', ERequires::ListEnd), word1(b'}', ERequires::ListEnd), min_indent, @@ -451,17 +560,6 @@ fn requires_rigids<'a>( ) } -#[inline(always)] -fn requires_rigid<'a>() -> impl Parser<'a, Spaced<'a, PlatformRigid<'a>>, ()> { - map!( - and!( - lowercase_ident(), - skip_first!(word2(b'=', b'>', |_| ()), uppercase_ident()) - ), - |(rigid, alias)| Spaced::Item(PlatformRigid { rigid, alias }) - ) -} - #[inline(always)] fn requires_typed_ident<'a>() -> impl Parser<'a, Loc>>, ERequires<'a>> { skip_first!( @@ -514,7 +612,7 @@ fn exposes_values<'a>() -> impl Parser< } fn spaces_around_keyword<'a, E>( - min_indent: u16, + min_indent: u32, keyword: &'static str, expectation: fn(Position) -> E, space_problem: fn(crate::parser::BadInputError, Position) -> E, @@ -627,6 +725,64 @@ fn packages<'a>() -> impl Parser<'a, Packages<'a>, EPackages<'a>> { ) } +#[inline(always)] +fn generates<'a>() -> impl Parser< + 'a, + ( + (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), + UppercaseIdent<'a>, + ), + EGenerates, +> { + let min_indent = 1; + + and!( + spaces_around_keyword( + min_indent, + "generates", + EGenerates::Generates, + EGenerates::Space, + EGenerates::IndentGenerates, + EGenerates::IndentTypeStart + ), + specialize(|(), pos| EGenerates::Identifier(pos), uppercase()) + ) +} + +#[inline(always)] +fn generates_with<'a>() -> impl Parser< + 'a, + ( + (&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]), + Collection<'a, Loc>>>, + ), + EGeneratesWith, +> { + let min_indent = 1; + + and!( + spaces_around_keyword( + min_indent, + "with", + EGeneratesWith::With, + EGeneratesWith::Space, + EGeneratesWith::IndentWith, + EGeneratesWith::IndentListStart + ), + collection_trailing_sep_e!( + word1(b'[', EGeneratesWith::ListStart), + exposes_entry(EGeneratesWith::Identifier), + word1(b',', EGeneratesWith::ListEnd), + word1(b']', EGeneratesWith::ListEnd), + min_indent, + EGeneratesWith::Open, + EGeneratesWith::Space, + EGeneratesWith::IndentListEnd, + Spaced::SpaceBefore + ) + ) +} + #[inline(always)] fn imports<'a>() -> impl Parser< 'a, @@ -661,63 +817,6 @@ fn imports<'a>() -> impl Parser< ) } -#[inline(always)] -fn effects<'a>() -> impl Parser<'a, Effects<'a>, EEffects<'a>> { - move |arena, state| { - let min_indent = 1; - - let (_, (spaces_before_effects_keyword, spaces_after_effects_keyword), state) = - spaces_around_keyword( - min_indent, - "effects", - EEffects::Effects, - EEffects::Space, - EEffects::IndentEffects, - EEffects::IndentListStart, - ) - .parse(arena, state)?; - - // e.g. `fx.` - let (_, type_shortname, state) = skip_second!( - specialize(|_, pos| EEffects::Shorthand(pos), lowercase_ident()), - word1(b'.', EEffects::ShorthandDot) - ) - .parse(arena, state)?; - - // the type name, e.g. Effects - let (_, (type_name, spaces_after_type_name), state) = and!( - specialize(|_, pos| EEffects::TypeName(pos), uppercase_ident()), - space0_e(min_indent, EEffects::Space, EEffects::IndentListStart) - ) - .parse(arena, state)?; - let (_, entries, state) = collection_trailing_sep_e!( - word1(b'{', EEffects::ListStart), - specialize(EEffects::TypedIdent, loc!(typed_ident())), - word1(b',', EEffects::ListEnd), - word1(b'}', EEffects::ListEnd), - min_indent, - EEffects::Open, - EEffects::Space, - EEffects::IndentListEnd, - Spaced::SpaceBefore - ) - .parse(arena, state)?; - - Ok(( - MadeProgress, - Effects { - spaces_before_effects_keyword, - spaces_after_effects_keyword, - spaces_after_type_name, - effect_shortname: type_shortname, - effect_type_name: type_name, - entries, - }, - state, - )) - } -} - #[inline(always)] fn typed_ident<'a>() -> impl Parser<'a, Spaced<'a, TypedIdent<'a>>, ETypedIdent<'a>> { // e.g. diff --git a/compiler/parse/src/number_literal.rs b/compiler/parse/src/number_literal.rs index 38d503ccbb..a4e8624684 100644 --- a/compiler/parse/src/number_literal.rs +++ b/compiler/parse/src/number_literal.rs @@ -67,9 +67,7 @@ fn chomp_number_base<'a>( let string = unsafe { std::str::from_utf8_unchecked(&bytes[..chomped]) }; - let new = state.advance_without_indenting_ee(chomped + 2 + is_negative as usize, |_| { - ENumber::LineTooLong - })?; + let new = state.advance(chomped + 2 + is_negative as usize); Ok(( Progress::MadeProgress, @@ -102,8 +100,7 @@ fn chomp_number_dec<'a>( let string = unsafe { std::str::from_utf8_unchecked(&state.bytes()[0..chomped + is_negative as usize]) }; - let new = state - .advance_without_indenting_ee(chomped + is_negative as usize, |_| ENumber::LineTooLong)?; + let new = state.advance(chomped + is_negative as usize); Ok(( Progress::MadeProgress, diff --git a/compiler/parse/src/parser.rs b/compiler/parse/src/parser.rs index c6a299ffa1..25471207a8 100644 --- a/compiler/parse/src/parser.rs +++ b/compiler/parse/src/parser.rs @@ -48,8 +48,6 @@ impl Progress { pub enum SyntaxError<'a> { Unexpected(Region), OutdentedTooFar, - ConditionFailed, - LineTooLong(u32 /* which line was too long */), TooManyLines, Eof(Region), InvalidPattern, @@ -60,7 +58,7 @@ pub enum SyntaxError<'a> { Todo, Type(EType<'a>), Pattern(EPattern<'a>), - Expr(EExpr<'a>), + Expr(EExpr<'a>, Position), Header(EHeader<'a>), Space(BadInputError), NotEndOfFile(Position), @@ -73,7 +71,8 @@ pub enum EHeader<'a> { Imports(EImports, Position), Requires(ERequires<'a>, Position), Packages(EPackages<'a>, Position), - Effects(EEffects<'a>, Position), + Generates(EGenerates, Position), + GeneratesWith(EGeneratesWith, Position), Space(BadInputError, Position), Start(Position), @@ -167,22 +166,6 @@ pub enum EPackageEntry<'a> { Space(BadInputError, Position), } -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum EEffects<'a> { - Space(BadInputError, Position), - Effects(Position), - Open(Position), - IndentEffects(Position), - ListStart(Position), - ListEnd(Position), - IndentListStart(Position), - IndentListEnd(Position), - TypedIdent(ETypedIdent<'a>, Position), - ShorthandDot(Position), - Shorthand(Position), - TypeName(Position), -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum EImports { Open(Position), @@ -204,44 +187,89 @@ pub enum EImports { SetEnd(Position), } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum EGenerates { + Open(Position), + Generates(Position), + IndentGenerates(Position), + Identifier(Position), + Space(BadInputError, Position), + IndentTypeStart(Position), + IndentTypeEnd(Position), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum EGeneratesWith { + Open(Position), + With(Position), + IndentWith(Position), + IndentListStart(Position), + IndentListEnd(Position), + ListStart(Position), + ListEnd(Position), + Identifier(Position), + Space(BadInputError, Position), +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BadInputError { HasTab, /// - LineTooLong, TooManyLines, /// /// BadUtf8, } -pub fn bad_input_to_syntax_error<'a>(bad_input: BadInputError, pos: Position) -> SyntaxError<'a> { +pub fn bad_input_to_syntax_error<'a>(bad_input: BadInputError) -> SyntaxError<'a> { use crate::parser::BadInputError::*; match bad_input { HasTab => SyntaxError::NotYetImplemented("call error on tabs".to_string()), - LineTooLong => SyntaxError::LineTooLong(pos.line), TooManyLines => SyntaxError::TooManyLines, BadUtf8 => SyntaxError::BadUtf8, } } -impl<'a> SyntaxError<'a> { - pub fn into_parse_problem( - self, - filename: std::path::PathBuf, - prefix: &'a str, - bytes: &'a [u8], - ) -> ParseProblem<'a, SyntaxError<'a>> { - ParseProblem { - pos: Position::default(), +impl<'a, T> SourceError<'a, T> { + pub fn new(problem: T, state: &State<'a>) -> Self { + Self { + problem, + bytes: state.original_bytes(), + } + } + + pub fn map_problem(self, f: impl FnOnce(T) -> E) -> SourceError<'a, E> { + SourceError { + problem: f(self.problem), + bytes: self.bytes, + } + } + + pub fn into_file_error(self, filename: std::path::PathBuf) -> FileError<'a, T> { + FileError { problem: self, filename, - bytes, - prefix, } } } +impl<'a> SyntaxError<'a> { + pub fn into_source_error(self, state: &State<'a>) -> SourceError<'a, SyntaxError<'a>> { + SourceError { + problem: self, + bytes: state.original_bytes(), + } + } + + pub fn into_file_error( + self, + filename: std::path::PathBuf, + state: &State<'a>, + ) -> FileError<'a, SyntaxError<'a>> { + self.into_source_error(state).into_file_error(filename) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum EExpr<'a> { Start(Position), @@ -293,7 +321,6 @@ pub enum EExpr<'a> { #[derive(Debug, Clone, PartialEq, Eq)] pub enum ENumber { End, - LineTooLong, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -397,7 +424,7 @@ pub enum EWhen<'a> { IndentArrow(Position), IndentBranch(Position), IndentIfGuard(Position), - PatternAlignment(u16, Position), + PatternAlignment(u32, Position), } #[derive(Debug, Clone, PartialEq, Eq)] @@ -562,13 +589,15 @@ pub enum ETypeInlineAlias { } #[derive(Debug)] -pub struct ParseProblem<'a, T> { - pub pos: Position, +pub struct SourceError<'a, T> { pub problem: T, - pub filename: std::path::PathBuf, pub bytes: &'a [u8], - /// prefix is usually the header (for parse problems in the body), or empty - pub prefix: &'a str, +} + +#[derive(Debug)] +pub struct FileError<'a, T> { + pub problem: SourceError<'a, T>, + pub filename: std::path::PathBuf, } pub trait Parser<'a, Output, Error> { @@ -723,23 +752,21 @@ where let width = keyword.len(); if !state.bytes().starts_with(keyword.as_bytes()) { - return Err((NoProgress, if_error(state.pos), state)); + return Err((NoProgress, if_error(state.pos()), state)); } // the next character should not be an identifier character // to prevent treating `whence` or `iffy` as keywords match state.bytes().get(width) { Some(next) if *next == b' ' || *next == b'#' || *next == b'\n' => { - state.pos.column += width as u16; state = state.advance(width); Ok((MadeProgress, (), state)) } None => { - state.pos.column += width as u16; state = state.advance(width); Ok((MadeProgress, (), state)) } - Some(_) => Err((NoProgress, if_error(state.pos), state)), + Some(_) => Err((NoProgress, if_error(state.pos()), state)), } } } @@ -964,7 +991,7 @@ where return Err((MadeProgress, fail, state)); } Err((NoProgress, _fail, state)) => { - return Err((NoProgress, to_element_error(state.pos), state)); + return Err((NoProgress, to_element_error(state.pos()), state)); } } } @@ -989,7 +1016,7 @@ where Err((MadeProgress, fail, state)) => Err((MadeProgress, fail, state)), Err((NoProgress, _fail, state)) => { - Err((NoProgress, to_element_error(state.pos), state)) + Err((NoProgress, to_element_error(state.pos()), state)) } } } @@ -1039,11 +1066,11 @@ macro_rules! loc { move |arena, state: $crate::state::State<'a>| { use roc_region::all::{Loc, Region}; - let start = state.pos; + let start = state.pos(); match $parser.parse(arena, state) { Ok((progress, value, state)) => { - let end = state.pos; + let end = state.pos(); let region = Region::new(start, end); Ok((progress, Loc { region, value }, state)) @@ -1245,7 +1272,7 @@ macro_rules! one_of_with_error { match $p1.parse(arena, state) { valid @ Ok(_) => valid, Err((MadeProgress, fail, state)) => Err((MadeProgress, fail, state )), - Err((NoProgress, _, state)) => Err((MadeProgress, $toerror(state.pos), state)), + Err((NoProgress, _, state)) => Err((MadeProgress, $toerror(state.pos()), state)), } } }; @@ -1263,7 +1290,23 @@ where { move |a, s| match parser.parse(a, s) { Ok(t) => Ok(t), - Err((p, error, s)) => Err((p, map_error(error, s.pos), s)), + Err((p, error, s)) => Err((p, map_error(error, s.pos()), s)), + } +} + +/// Like `specialize`, except the error function receives a Region representing the begin/end of the error +pub fn specialize_region<'a, F, P, T, X, Y>(map_error: F, parser: P) -> impl Parser<'a, T, Y> +where + F: Fn(X, Region) -> Y, + P: Parser<'a, T, X>, + Y: 'a, +{ + move |a, s: State<'a>| { + let start = s.pos(); + match parser.parse(a, s) { + Ok(t) => Ok(t), + Err((p, error, s)) => Err((p, map_error(error, Region::new(start, s.pos())), s)), + } } } @@ -1276,7 +1319,7 @@ where { move |a, s| match parser.parse(a, s) { Ok(t) => Ok(t), - Err((p, error, s)) => Err((p, map_error(a.alloc(error), s.pos), s)), + Err((p, error, s)) => Err((p, map_error(a.alloc(error), s.pos()), s)), } } @@ -1289,11 +1332,10 @@ where move |_arena: &'a Bump, state: State<'a>| match state.bytes().get(0) { Some(x) if *x == word => { - let mut state = state.advance(1); - state.pos.column += 1; + let state = state.advance(1); Ok((MadeProgress, (), state)) } - _ => Err((NoProgress, to_error(state.pos), state)), + _ => Err((NoProgress, to_error(state.pos()), state)), } } @@ -1309,16 +1351,15 @@ where move |_arena: &'a Bump, state: State<'a>| { if state.bytes().starts_with(&needle) { - let mut state = state.advance(2); - state.pos.column += 2; + let state = state.advance(2); Ok((MadeProgress, (), state)) } else { - Err((NoProgress, to_error(state.pos), state)) + Err((NoProgress, to_error(state.pos()), state)) } } } -pub fn check_indent<'a, TE, E>(min_indent: u16, to_problem: TE) -> impl Parser<'a, (), E> +pub fn check_indent<'a, TE, E>(min_indent: u32, to_problem: TE) -> impl Parser<'a, (), E> where TE: Fn(Position) -> E, E: 'a, @@ -1326,7 +1367,7 @@ where move |_arena, state: State<'a>| { dbg!(state.indent_column, min_indent); if state.indent_column < min_indent { - Err((NoProgress, to_problem(state.pos), state)) + Err((NoProgress, to_problem(state.pos()), state)) } else { Ok((NoProgress, (), state)) } diff --git a/compiler/parse/src/pattern.rs b/compiler/parse/src/pattern.rs index 27eda3caeb..c76d115fc9 100644 --- a/compiler/parse/src/pattern.rs +++ b/compiler/parse/src/pattern.rs @@ -24,14 +24,14 @@ pub enum PatternType { WhenBranch, } -pub fn loc_closure_param<'a>(min_indent: u16) -> impl Parser<'a, Loc>, EPattern<'a>> { +pub fn loc_closure_param<'a>(min_indent: u32) -> impl Parser<'a, Loc>, EPattern<'a>> { move |arena, state| parse_closure_param(arena, state, min_indent) } fn parse_closure_param<'a>( arena: &'a Bump, state: State<'a>, - min_indent: u16, + min_indent: u32, ) -> ParseResult<'a, Loc>, EPattern<'a>> { one_of!( // An ident is the most common param, e.g. \foo -> ... @@ -50,7 +50,7 @@ fn parse_closure_param<'a>( .parse(arena, state) } -pub fn loc_pattern_help<'a>(min_indent: u16) -> impl Parser<'a, Loc>, EPattern<'a>> { +pub fn loc_pattern_help<'a>(min_indent: u32) -> impl Parser<'a, Loc>, EPattern<'a>> { one_of!( specialize(EPattern::PInParens, loc_pattern_in_parens_help(min_indent)), loc!(underscore_pattern_help()), @@ -65,12 +65,12 @@ pub fn loc_pattern_help<'a>(min_indent: u16) -> impl Parser<'a, Loc> } fn loc_tag_pattern_args_help<'a>( - min_indent: u16, + min_indent: u32, ) -> impl Parser<'a, Vec<'a, Loc>>, EPattern<'a>> { zero_or_more!(loc_tag_pattern_arg(min_indent)) } -fn loc_tag_pattern_arg<'a>(min_indent: u16) -> impl Parser<'a, Loc>, EPattern<'a>> { +fn loc_tag_pattern_arg<'a>(min_indent: u32) -> impl Parser<'a, Loc>, EPattern<'a>> { // Don't parse operators, because they have a higher precedence than function application. // If we encounter one, we're done parsing function args! move |arena, state| { @@ -95,7 +95,7 @@ fn loc_tag_pattern_arg<'a>(min_indent: u16) -> impl Parser<'a, Loc>, } fn loc_parse_tag_pattern_arg<'a>( - min_indent: u16, + min_indent: u32, arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Loc>, EPattern<'a>> { @@ -115,7 +115,7 @@ fn loc_parse_tag_pattern_arg<'a>( } fn loc_pattern_in_parens_help<'a>( - min_indent: u16, + min_indent: u32, ) -> impl Parser<'a, Loc>, PInParens<'a>> { between!( word1(b'(', PInParens::Open), @@ -162,7 +162,7 @@ fn string_pattern_help<'a>() -> impl Parser<'a, Pattern<'a>, EPattern<'a>> { } fn loc_ident_pattern_help<'a>( - min_indent: u16, + min_indent: u32, can_have_arguments: bool, ) -> impl Parser<'a, Loc>, EPattern<'a>> { move |arena: &'a Bump, state: State<'a>| { @@ -232,7 +232,7 @@ fn loc_ident_pattern_help<'a>( if crate::keyword::KEYWORDS.contains(&parts[0]) { Err(( NoProgress, - EPattern::End(original_state.pos), + EPattern::End(original_state.pos()), original_state, )) } else if module_name.is_empty() && parts.len() == 1 { @@ -304,13 +304,13 @@ fn lowercase_ident_pattern<'a>( arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, &'a str, EPattern<'a>> { - let pos = state.pos; + let pos = state.pos(); specialize(move |_, _| EPattern::End(pos), lowercase_ident()).parse(arena, state) } #[inline(always)] -fn record_pattern_help<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>, PRecord<'a>> { +fn record_pattern_help<'a>(min_indent: u32) -> impl Parser<'a, Pattern<'a>, PRecord<'a>> { move |arena, state| { let (_, fields, state) = collection_trailing_sep_e!( // word1_check_indent!(b'{', PRecord::Open, min_indent, PRecord::IndentOpen), @@ -333,13 +333,13 @@ fn record_pattern_help<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>, PRec } } -fn record_pattern_field<'a>(min_indent: u16) -> impl Parser<'a, Loc>, PRecord<'a>> { +fn record_pattern_field<'a>(min_indent: u32) -> impl Parser<'a, Loc>, PRecord<'a>> { use crate::parser::Either::*; move |arena, state: State<'a>| { // You must have a field name, e.g. "email" // using the initial pos is important for error reporting - let pos = state.pos; + let pos = state.pos(); let (progress, loc_label, state) = loc!(specialize( move |_, _| PRecord::Field(pos), lowercase_ident() diff --git a/compiler/parse/src/state.rs b/compiler/parse/src/state.rs index 7d244bb99d..b21bccc122 100644 --- a/compiler/parse/src/state.rs +++ b/compiler/parse/src/state.rs @@ -1,6 +1,3 @@ -use crate::parser::Progress::*; -use crate::parser::{BadInputError, Progress}; -use bumpalo::Bump; use roc_region::all::{Position, Region}; use std::fmt; @@ -8,112 +5,74 @@ use std::fmt; #[derive(Clone)] pub struct State<'a> { /// The raw input bytes from the file. - bytes: &'a [u8], + /// Beware: original_bytes[0] always points the the start of the file. + /// Use bytes()[0] to access the current byte the parser is inspecting + original_bytes: &'a [u8], - /// Current position within the input (line/column) - pub pos: Position, + /// Offset in original_bytes that the parser is currently inspecting + offset: usize, + + /// Position of the start of the current line + line_start: Position, /// Current indentation level, in columns /// (so no indent is col 1 - this saves an arithmetic operation.) - pub indent_column: u16, + pub indent_column: u32, } impl<'a> State<'a> { pub fn new(bytes: &'a [u8]) -> State<'a> { State { - bytes, - pos: Position::default(), + original_bytes: bytes, + offset: 0, + line_start: Position::zero(), indent_column: 0, } } - pub fn bytes(&self) -> &'a [u8] { - self.bytes + pub fn original_bytes(&self) -> &'a [u8] { + self.original_bytes + } + + pub(crate) fn bytes(&self) -> &'a [u8] { + &self.original_bytes[self.offset..] + } + + pub fn column(&self) -> u32 { + self.pos().offset - self.line_start.offset } #[must_use] - pub fn advance(&self, offset: usize) -> State<'a> { + pub(crate) fn advance(&self, offset: usize) -> State<'a> { let mut state = self.clone(); - state.bytes = &state.bytes[offset..]; + state.offset += offset; + state + } + + #[must_use] + pub(crate) fn advance_newline(&self) -> State<'a> { + let mut state = self.clone(); + state.offset += 1; + state.line_start = state.pos(); state } /// Returns the current position - // TODO: replace this with just accessing the field - pub const fn get_position(&self) -> Position { - self.pos + pub const fn pos(&self) -> Position { + Position::new(self.offset as u32) } /// Returns whether the parser has reached the end of the input pub const fn has_reached_end(&self) -> bool { - self.bytes.is_empty() - } - - /// Use advance_spaces to advance with indenting. - /// This assumes we are *not* advancing with spaces, or at least that - /// any spaces on the line were preceded by non-spaces - which would mean - /// they weren't eligible to indent anyway. - pub fn advance_without_indenting_e( - self, - quantity: usize, - to_error: TE, - ) -> Result - where - TE: Fn(BadInputError, Position) -> E, - { - self.advance_without_indenting_ee(quantity, |p| to_error(BadInputError::LineTooLong, p)) - } - - pub fn advance_without_indenting_ee( - self, - quantity: usize, - to_error: TE, - ) -> Result - where - TE: Fn(Position) -> E, - { - match (self.pos.column as usize).checked_add(quantity) { - Some(column_usize) if column_usize <= u16::MAX as usize => { - Ok(State { - bytes: &self.bytes[quantity..], - pos: Position { - line: self.pos.line, - column: column_usize as u16, - }, - // Once we hit a nonspace character, we are no longer indenting. - ..self - }) - } - _ => Err((NoProgress, to_error(self.pos), self)), - } + self.offset == self.original_bytes.len() } /// Returns a Region corresponding to the current state, but /// with the the end column advanced by the given amount. This is /// useful when parsing something "manually" (using input.chars()) /// and thus wanting a Region while not having access to loc(). - pub fn len_region(&self, length: u16) -> Region { - Region::new( - self.pos, - Position { - line: self.pos.line, - column: self - .pos - .column - .checked_add(length) - .unwrap_or_else(|| panic!("len_region overflowed")), - }, - ) - } - - /// Return a failing ParseResult for the given FailReason - pub fn fail( - self, - _arena: &'a Bump, - progress: Progress, - reason: X, - ) -> Result<(Progress, T, Self), (Progress, X, Self)> { - Err((progress, reason, self)) + pub fn len_region(&self, length: u32) -> Region { + Region::new(self.pos(), self.pos().bump_column(length)) } } @@ -121,16 +80,12 @@ impl<'a> fmt::Debug for State<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "State {{")?; - match std::str::from_utf8(self.bytes) { + match std::str::from_utf8(self.bytes()) { Ok(string) => write!(f, "\n\tbytes: [utf8] {:?}", string)?, - Err(_) => write!(f, "\n\tbytes: [invalid utf8] {:?}", self.bytes)?, + Err(_) => write!(f, "\n\tbytes: [invalid utf8] {:?}", self.bytes())?, } - write!( - f, - "\n\t(line, col): ({}, {}),", - self.pos.line, self.pos.column - )?; + write!(f, "\n\t(offset): {:?},", self.pos())?; write!(f, "\n\tindent_column: {}", self.indent_column)?; write!(f, "\n}}") } diff --git a/compiler/parse/src/string_literal.rs b/compiler/parse/src/string_literal.rs index dfeb4e42da..cf2b4b5674 100644 --- a/compiler/parse/src/string_literal.rs +++ b/compiler/parse/src/string_literal.rs @@ -17,24 +17,21 @@ fn ascii_hex_digits<'a>() -> impl Parser<'a, &'a str, EString<'a>> { buf.push(byte as char); } else if buf.is_empty() { // We didn't find any hex digits! - return Err((NoProgress, EString::CodePtEnd(state.pos), state)); + return Err((NoProgress, EString::CodePtEnd(state.pos()), state)); } else { - let state = state.advance_without_indenting_ee(buf.len(), |pos| { - EString::Space(BadInputError::LineTooLong, pos) - })?; + let state = state.advance(buf.len()); return Ok((MadeProgress, buf.into_bump_str(), state)); } } - Err((NoProgress, EString::CodePtEnd(state.pos), state)) + Err((NoProgress, EString::CodePtEnd(state.pos()), state)) } } macro_rules! advance_state { ($state:expr, $n:expr) => { - $state - .advance_without_indenting_ee($n, |pos| EString::Space(BadInputError::LineTooLong, pos)) + Ok($state.advance($n)) }; } @@ -56,7 +53,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> { bytes = state.bytes()[1..].iter(); state = advance_state!(state, 1)?; } else { - return Err((NoProgress, EString::Open(state.pos), state)); + return Err((NoProgress, EString::Open(state.pos()), state)); } // At the parsing stage we keep the entire raw string, because the formatter @@ -100,7 +97,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> { Err(_) => { return Err(( MadeProgress, - EString::Space(BadInputError::BadUtf8, state.pos), + EString::Space(BadInputError::BadUtf8, state.pos()), state, )); } @@ -192,7 +189,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> { // all remaining chars. This will mask all other errors, but // it should make it easiest to debug; the file will be a giant // error starting from where the open quote appeared. - return Err((MadeProgress, EString::EndlessSingle(state.pos), state)); + return Err((MadeProgress, EString::EndlessSingle(state.pos()), state)); } } b'\\' => { @@ -281,7 +278,7 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> { // Invalid escape! A backslash must be followed // by either an open paren or else one of the // escapable characters (\n, \t, \", \\, etc) - return Err((MadeProgress, EString::UnknownEscape(state.pos), state)); + return Err((MadeProgress, EString::UnknownEscape(state.pos()), state)); } } } @@ -295,9 +292,9 @@ pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> { Err(( MadeProgress, if is_multiline { - EString::EndlessMulti(state.pos) + EString::EndlessMulti(state.pos()) } else { - EString::EndlessSingle(state.pos) + EString::EndlessSingle(state.pos()) }, state, )) diff --git a/compiler/parse/src/test_helpers.rs b/compiler/parse/src/test_helpers.rs index 6f3a94b4d4..77c165dde9 100644 --- a/compiler/parse/src/test_helpers.rs +++ b/compiler/parse/src/test_helpers.rs @@ -2,29 +2,33 @@ use crate::ast; use crate::module::module_defs; // use crate::module::module_defs; use crate::parser::Parser; +use crate::parser::SourceError; use crate::parser::SyntaxError; use crate::state::State; use bumpalo::collections::Vec as BumpVec; use bumpalo::Bump; use roc_region::all::Loc; +use roc_region::all::Position; pub fn parse_expr_with<'a>( arena: &'a Bump, input: &'a str, ) -> Result, SyntaxError<'a>> { - parse_loc_with(arena, input).map(|loc_expr| loc_expr.value) + parse_loc_with(arena, input) + .map(|loc_expr| loc_expr.value) + .map_err(|e| e.problem) } #[allow(dead_code)] pub fn parse_loc_with<'a>( arena: &'a Bump, input: &'a str, -) -> Result>, SyntaxError<'a>> { +) -> Result>, SourceError<'a, SyntaxError<'a>>> { let state = State::new(input.trim().as_bytes()); - match crate::expr::test_parse_expr(0, arena, state) { + match crate::expr::test_parse_expr(0, arena, state.clone()) { Ok(loc_expr) => Ok(loc_expr), - Err(fail) => Err(SyntaxError::Expr(fail)), + Err(fail) => Err(SyntaxError::Expr(fail, Position::default()).into_source_error(&state)), } } diff --git a/compiler/parse/src/type_annotation.rs b/compiler/parse/src/type_annotation.rs index 27434dbd6d..c73aebab6c 100644 --- a/compiler/parse/src/type_annotation.rs +++ b/compiler/parse/src/type_annotation.rs @@ -12,14 +12,14 @@ use bumpalo::Bump; use roc_region::all::{Loc, Position, Region}; pub fn located_help<'a>( - min_indent: u16, + min_indent: u32, is_trailing_comma_valid: bool, ) -> impl Parser<'a, Loc>, EType<'a>> { expression(min_indent, is_trailing_comma_valid) } #[inline(always)] -fn tag_union_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, ETypeTagUnion<'a>> { +fn tag_union_type<'a>(min_indent: u32) -> impl Parser<'a, TypeAnnotation<'a>, ETypeTagUnion<'a>> { move |arena, state| { let (_, tags, state) = collection_trailing_sep_e!( word1(b'[', ETypeTagUnion::Open), @@ -69,7 +69,7 @@ fn check_type_alias( let name_start = annot.region.start(); let name_region = - Region::between(name_start, name_start.bump_column(tag_name.len() as u16)); + Region::between(name_start, name_start.bump_column(tag_name.len() as u32)); let header = AliasHeader { name: Loc::at(name_region, tag_name), @@ -85,7 +85,7 @@ fn check_type_alias( } } -fn parse_type_alias_after_as<'a>(min_indent: u16) -> impl Parser<'a, AliasHeader<'a>, EType<'a>> { +fn parse_type_alias_after_as<'a>(min_indent: u32) -> impl Parser<'a, AliasHeader<'a>, EType<'a>> { move |arena, state| { space0_before_e( term(min_indent), @@ -101,10 +101,10 @@ fn parse_type_alias_after_as<'a>(min_indent: u16) -> impl Parser<'a, AliasHeader } fn fail_type_start<'a, T: 'a>() -> impl Parser<'a, T, EType<'a>> { - |_arena, state: State<'a>| Err((NoProgress, EType::TStart(state.pos), state)) + |_arena, state: State<'a>| Err((NoProgress, EType::TStart(state.pos()), state)) } -fn term<'a>(min_indent: u16) -> impl Parser<'a, Loc>, EType<'a>> { +fn term<'a>(min_indent: u32) -> impl Parser<'a, Loc>, EType<'a>> { map_with_arena!( and!( one_of!( @@ -165,7 +165,7 @@ fn loc_inferred<'a>() -> impl Parser<'a, Loc>, EType<'a>> { }) } -fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Loc>, EType<'a>> { +fn loc_applied_arg<'a>(min_indent: u32) -> impl Parser<'a, Loc>, EType<'a>> { use crate::ast::Spaceable; map_with_arena!( @@ -193,7 +193,7 @@ fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Loc( - min_indent: u16, + min_indent: u32, ) -> impl Parser<'a, Loc>, ETypeInParens<'a>> { between!( word1(b'(', ETypeInParens::Open), @@ -210,7 +210,7 @@ fn loc_type_in_parens<'a>( } #[inline(always)] -fn tag_type<'a>(min_indent: u16) -> impl Parser<'a, Tag<'a>, ETypeTagUnion<'a>> { +fn tag_type<'a>(min_indent: u32) -> impl Parser<'a, Tag<'a>, ETypeTagUnion<'a>> { move |arena, state: State<'a>| { let (_, name, state) = loc!(parse_tag_name(ETypeTagUnion::End)).parse(arena, state)?; @@ -240,12 +240,12 @@ where { move |arena, state: State<'a>| match crate::ident::tag_name().parse(arena, state) { Ok(good) => Ok(good), - Err((progress, _, state)) => Err((progress, to_problem(state.pos), state)), + Err((progress, _, state)) => Err((progress, to_problem(state.pos()), state)), } } fn record_type_field<'a>( - min_indent: u16, + min_indent: u32, ) -> impl Parser<'a, AssignedField<'a, TypeAnnotation<'a>>, ETypeRecord<'a>> { use crate::ident::lowercase_ident; use crate::parser::Either::*; @@ -254,7 +254,7 @@ fn record_type_field<'a>( (move |arena, state: State<'a>| { // You must have a field name, e.g. "email" // using the initial pos is important for error reporting - let pos = state.pos; + let pos = state.pos(); let (progress, loc_label, state) = loc!(specialize( move |_, _| ETypeRecord::Field(pos), lowercase_ident() @@ -323,7 +323,7 @@ fn record_type_field<'a>( } #[inline(always)] -fn record_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, ETypeRecord<'a>> { +fn record_type<'a>(min_indent: u32) -> impl Parser<'a, TypeAnnotation<'a>, ETypeRecord<'a>> { use crate::type_annotation::TypeAnnotation::*; (move |arena, state| { @@ -352,7 +352,7 @@ fn record_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, EType .trace("type_annotation:record_type") } -fn applied_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, EType<'a>> { +fn applied_type<'a>(min_indent: u32) -> impl Parser<'a, TypeAnnotation<'a>, EType<'a>> { map!( and!( specialize(EType::TApply, parse_concrete_type), @@ -379,13 +379,13 @@ fn applied_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, ETyp } fn loc_applied_args_e<'a>( - min_indent: u16, + min_indent: u32, ) -> impl Parser<'a, Vec<'a, Loc>>, EType<'a>> { zero_or_more!(loc_applied_arg(min_indent)) } fn expression<'a>( - min_indent: u16, + min_indent: u32, is_trailing_comma_valid: bool, ) -> impl Parser<'a, Loc>, EType<'a>> { (move |arena, state: State<'a>| { @@ -410,7 +410,7 @@ fn expression<'a>( ), |_, state: State<'a>| Err(( NoProgress, - EType::TFunctionArgument(state.pos), + EType::TFunctionArgument(state.pos()), state )) ] @@ -503,7 +503,7 @@ fn parse_concrete_type<'a>( Ok((MadeProgress, answer, state)) } - Err((NoProgress, _, state)) => Err((NoProgress, ETypeApply::End(state.pos), state)), + Err((NoProgress, _, state)) => Err((NoProgress, ETypeApply::End(state.pos()), state)), Err((MadeProgress, _, mut state)) => { // we made some progress, but ultimately failed. // that means a malformed type name @@ -512,9 +512,7 @@ fn parse_concrete_type<'a>( let parsed_str = unsafe { std::str::from_utf8_unchecked(&initial_bytes[..chomped + delta]) }; - state = state.advance_without_indenting_ee(chomped, |pos| { - ETypeApply::Space(crate::parser::BadInputError::LineTooLong, pos) - })?; + state = state.advance(chomped); Ok((MadeProgress, TypeAnnotation::Malformed(parsed_str), state)) } @@ -531,6 +529,6 @@ fn parse_type_variable<'a>( Ok((MadeProgress, answer, state)) } - Err((progress, _, state)) => Err((progress, EType::TBadTypeVariable(state.pos), state)), + Err((progress, _, state)) => Err((progress, EType::TBadTypeVariable(state.pos()), state)), } } diff --git a/compiler/parse/tests/snapshots/fail/type_argument_no_arrow.expr.result-ast b/compiler/parse/tests/snapshots/fail/type_argument_no_arrow.expr.result-ast index bff7c1a23a..ded80c1d02 100644 --- a/compiler/parse/tests/snapshots/fail/type_argument_no_arrow.expr.result-ast +++ b/compiler/parse/tests/snapshots/fail/type_argument_no_arrow.expr.result-ast @@ -1 +1 @@ -Expr(Type(TIndentEnd(0:12), 0:4)) \ No newline at end of file +Expr(Type(TIndentEnd(@12), @4), @0) \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/fail/type_double_comma.expr.result-ast b/compiler/parse/tests/snapshots/fail/type_double_comma.expr.result-ast index 15c6461fec..41d89171c2 100644 --- a/compiler/parse/tests/snapshots/fail/type_double_comma.expr.result-ast +++ b/compiler/parse/tests/snapshots/fail/type_double_comma.expr.result-ast @@ -1 +1 @@ -Expr(Type(TFunctionArgument(0:8), 0:4)) \ No newline at end of file +Expr(Type(TFunctionArgument(@8), @4), @0) \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/add_var_with_spaces.expr.result-ast b/compiler/parse/tests/snapshots/pass/add_var_with_spaces.expr.result-ast index 5d07b7897e..cd27503529 100644 --- a/compiler/parse/tests/snapshots/pass/add_var_with_spaces.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/add_var_with_spaces.expr.result-ast @@ -1,14 +1,14 @@ BinOps( [ ( - |L 0-0, C 0-1| Var { + @0-1 Var { module_name: "", ident: "x", }, - |L 0-0, C 2-3| Plus, + @2-3 Plus, ), ], - |L 0-0, C 4-5| Num( + @4-5 Num( "2", ), ) diff --git a/compiler/parse/tests/snapshots/pass/add_with_spaces.expr.result-ast b/compiler/parse/tests/snapshots/pass/add_with_spaces.expr.result-ast index b3ba87f0bf..4d75339647 100644 --- a/compiler/parse/tests/snapshots/pass/add_with_spaces.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/add_with_spaces.expr.result-ast @@ -1,13 +1,13 @@ BinOps( [ ( - |L 0-0, C 0-1| Num( + @0-1 Num( "1", ), - |L 0-0, C 3-4| Plus, + @3-4 Plus, ), ], - |L 0-0, C 7-8| Num( + @7-8 Num( "2", ), ) diff --git a/compiler/parse/tests/snapshots/pass/annotated_record_destructure.expr.result-ast b/compiler/parse/tests/snapshots/pass/annotated_record_destructure.expr.result-ast index d6960ede3c..3b4a561069 100644 --- a/compiler/parse/tests/snapshots/pass/annotated_record_destructure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/annotated_record_destructure.expr.result-ast @@ -1,47 +1,47 @@ Defs( [ - |L 1-1, C 0-34| AnnotatedBody { - ann_pattern: |L 0-0, C 0-8| RecordDestructure( + @15-49 AnnotatedBody { + ann_pattern: @0-8 RecordDestructure( [ - |L 0-0, C 2-3| Identifier( + @2-3 Identifier( "x", ), - |L 0-0, C 5-7| Identifier( + @5-7 Identifier( "y", ), ], ), - ann_type: |L 0-0, C 11-14| Apply( + ann_type: @11-14 Apply( "", "Foo", [], ), comment: None, - body_pattern: |L 1-1, C 0-8| RecordDestructure( + body_pattern: @15-23 RecordDestructure( [ - |L 1-1, C 2-3| Identifier( + @17-18 Identifier( "x", ), - |L 1-1, C 5-6| Identifier( + @20-21 Identifier( "y", ), ], ), - body_expr: |L 1-1, C 11-34| Record( + body_expr: @26-49 Record( [ - |L 1-1, C 13-22| RequiredValue( - |L 1-1, C 13-14| "x", + @28-37 RequiredValue( + @28-29 "x", [], - |L 1-1, C 17-22| Str( + @32-37 Str( PlainLine( "foo", ), ), ), - |L 1-1, C 24-32| RequiredValue( - |L 1-1, C 24-25| "y", + @39-47 RequiredValue( + @39-40 "y", [], - |L 1-1, C 28-32| Float( + @43-47 Float( "3.14", ), ), @@ -49,7 +49,7 @@ Defs( ), }, ], - |L 3-3, C 0-1| SpaceBefore( + @51-52 SpaceBefore( Var { module_name: "", ident: "x", diff --git a/compiler/parse/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast b/compiler/parse/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast index cf1dd8c0a9..178aca2667 100644 --- a/compiler/parse/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast @@ -1,23 +1,23 @@ Defs( [ - |L 1-1, C 0-20| AnnotatedBody { - ann_pattern: |L 0-0, C 0-8| Apply( - |L 0-0, C 0-6| GlobalTag( + @26-46 AnnotatedBody { + ann_pattern: @0-8 Apply( + @0-6 GlobalTag( "UserId", ), [ - |L 0-0, C 7-8| Identifier( + @7-8 Identifier( "x", ), ], ), - ann_type: |L 0-0, C 11-25| TagUnion { + ann_type: @11-25 TagUnion { ext: None, tags: [ - |L 0-0, C 13-23| Global { - name: |L 0-0, C 13-19| "UserId", + @13-23 Global { + name: @13-19 "UserId", args: [ - |L 0-0, C 20-23| Apply( + @20-23 Apply( "", "I64", [], @@ -27,22 +27,22 @@ Defs( ], }, comment: None, - body_pattern: |L 1-1, C 0-8| Apply( - |L 1-1, C 0-6| GlobalTag( + body_pattern: @26-34 Apply( + @26-32 GlobalTag( "UserId", ), [ - |L 1-1, C 7-8| Identifier( + @33-34 Identifier( "x", ), ], ), - body_expr: |L 1-1, C 11-20| Apply( - |L 1-1, C 11-17| GlobalTag( + body_expr: @37-46 Apply( + @37-43 GlobalTag( "UserId", ), [ - |L 1-1, C 18-20| Num( + @44-46 Num( "42", ), ], @@ -50,7 +50,7 @@ Defs( ), }, ], - |L 3-3, C 0-1| SpaceBefore( + @48-49 SpaceBefore( Var { module_name: "", ident: "x", diff --git a/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.result-ast index 8e6245cb9f..f939664412 100644 --- a/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.result-ast @@ -1,12 +1,12 @@ Apply( - |L 0-0, C 0-4| GlobalTag( + @0-4 GlobalTag( "Whee", ), [ - |L 0-0, C 5-7| Num( + @5-7 Num( "12", ), - |L 0-0, C 8-10| Num( + @8-10 Num( "34", ), ], diff --git a/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.result-ast index 14cf85eed0..483292bdcf 100644 --- a/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.result-ast @@ -1,14 +1,14 @@ Apply( - |L 0-0, C 0-4| GlobalTag( + @0-4 GlobalTag( "Whee", ), [ - |L 0-0, C 6-8| ParensAround( + @6-8 ParensAround( Num( "12", ), ), - |L 0-0, C 11-13| ParensAround( + @11-13 ParensAround( Num( "34", ), diff --git a/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.result-ast index 47719397a0..185e681f5b 100644 --- a/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.result-ast @@ -1,12 +1,12 @@ Apply( - |L 0-0, C 0-5| PrivateTag( + @0-5 PrivateTag( "@Whee", ), [ - |L 0-0, C 6-8| Num( + @6-8 Num( "12", ), - |L 0-0, C 9-11| Num( + @9-11 Num( "34", ), ], diff --git a/compiler/parse/tests/snapshots/pass/apply_three_args.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_three_args.expr.result-ast index 45d7902727..bdf0bf721a 100644 --- a/compiler/parse/tests/snapshots/pass/apply_three_args.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_three_args.expr.result-ast @@ -1,18 +1,18 @@ Apply( - |L 0-0, C 0-1| Var { + @0-1 Var { module_name: "", ident: "a", }, [ - |L 0-0, C 2-3| Var { + @2-3 Var { module_name: "", ident: "b", }, - |L 0-0, C 4-5| Var { + @4-5 Var { module_name: "", ident: "c", }, - |L 0-0, C 6-7| Var { + @6-7 Var { module_name: "", ident: "d", }, diff --git a/compiler/parse/tests/snapshots/pass/apply_two_args.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_two_args.expr.result-ast index a4e39d6fd5..882b37c09d 100644 --- a/compiler/parse/tests/snapshots/pass/apply_two_args.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_two_args.expr.result-ast @@ -1,13 +1,13 @@ Apply( - |L 0-0, C 0-4| Var { + @0-4 Var { module_name: "", ident: "whee", }, [ - |L 0-0, C 6-8| Num( + @6-8 Num( "12", ), - |L 0-0, C 10-12| Num( + @10-12 Num( "34", ), ], diff --git a/compiler/parse/tests/snapshots/pass/apply_unary_negation.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_unary_negation.expr.result-ast index 54e180b592..d74301a614 100644 --- a/compiler/parse/tests/snapshots/pass/apply_unary_negation.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_unary_negation.expr.result-ast @@ -1,16 +1,16 @@ Apply( - |L 0-0, C 0-5| UnaryOp( - |L 0-0, C 1-5| Var { + @0-5 UnaryOp( + @1-5 Var { module_name: "", ident: "whee", }, - |L 0-0, C 0-1| Negate, + @0-1 Negate, ), [ - |L 0-0, C 7-9| Num( + @7-9 Num( "12", ), - |L 0-0, C 10-13| Var { + @10-13 Var { module_name: "", ident: "foo", }, diff --git a/compiler/parse/tests/snapshots/pass/apply_unary_not.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_unary_not.expr.result-ast index 86eee8cafa..7a50a7af86 100644 --- a/compiler/parse/tests/snapshots/pass/apply_unary_not.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_unary_not.expr.result-ast @@ -1,16 +1,16 @@ Apply( - |L 0-0, C 0-5| UnaryOp( - |L 0-0, C 1-5| Var { + @0-5 UnaryOp( + @1-5 Var { module_name: "", ident: "whee", }, - |L 0-0, C 0-1| Not, + @0-1 Not, ), [ - |L 0-0, C 7-9| Num( + @7-9 Num( "12", ), - |L 0-0, C 10-13| Var { + @10-13 Var { module_name: "", ident: "foo", }, diff --git a/compiler/parse/tests/snapshots/pass/basic_apply.expr.result-ast b/compiler/parse/tests/snapshots/pass/basic_apply.expr.result-ast index 6d906548f6..ae21012711 100644 --- a/compiler/parse/tests/snapshots/pass/basic_apply.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/basic_apply.expr.result-ast @@ -1,10 +1,10 @@ Apply( - |L 0-0, C 0-4| Var { + @0-4 Var { module_name: "", ident: "whee", }, [ - |L 0-0, C 5-6| Num( + @5-6 Num( "1", ), ], diff --git a/compiler/parse/tests/snapshots/pass/basic_docs.expr.result-ast b/compiler/parse/tests/snapshots/pass/basic_docs.expr.result-ast index 3e891ee430..8f016cf433 100644 --- a/compiler/parse/tests/snapshots/pass/basic_docs.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/basic_docs.expr.result-ast @@ -1,16 +1,16 @@ SpaceBefore( Defs( [ - |L 6-6, C 0-5| Body( - |L 6-6, C 0-1| Identifier( + @107-112 Body( + @107-108 Identifier( "x", ), - |L 6-6, C 4-5| Num( + @111-112 Num( "5", ), ), ], - |L 8-8, C 0-2| SpaceBefore( + @114-116 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/closure_with_underscores.expr.result-ast b/compiler/parse/tests/snapshots/pass/closure_with_underscores.expr.result-ast index 61ea2b2405..83a2f401e4 100644 --- a/compiler/parse/tests/snapshots/pass/closure_with_underscores.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/closure_with_underscores.expr.result-ast @@ -1,13 +1,13 @@ Closure( [ - |L 0-0, C 1-2| Underscore( + @1-2 Underscore( "", ), - |L 0-0, C 4-9| Underscore( + @4-9 Underscore( "name", ), ], - |L 0-0, C 13-15| Num( + @13-15 Num( "42", ), ) diff --git a/compiler/parse/tests/snapshots/pass/comment_after_op.expr.result-ast b/compiler/parse/tests/snapshots/pass/comment_after_op.expr.result-ast index 34d2472071..4930aa1a55 100644 --- a/compiler/parse/tests/snapshots/pass/comment_after_op.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/comment_after_op.expr.result-ast @@ -1,13 +1,13 @@ BinOps( [ ( - |L 0-0, C 0-2| Num( + @0-2 Num( "12", ), - |L 0-0, C 4-5| Star, + @4-5 Star, ), ], - |L 1-1, C 1-3| SpaceBefore( + @15-17 SpaceBefore( Num( "92", ), diff --git a/compiler/parse/tests/snapshots/pass/comment_before_op.expr.result-ast b/compiler/parse/tests/snapshots/pass/comment_before_op.expr.result-ast index af6abd380a..8d2a5f0b63 100644 --- a/compiler/parse/tests/snapshots/pass/comment_before_op.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/comment_before_op.expr.result-ast @@ -1,7 +1,7 @@ BinOps( [ ( - |L 0-0, C 0-1| SpaceAfter( + @0-1 SpaceAfter( Num( "3", ), @@ -11,10 +11,10 @@ BinOps( ), ], ), - |L 1-1, C 0-1| Plus, + @11-12 Plus, ), ], - |L 1-1, C 2-3| Num( + @13-14 Num( "4", ), ) diff --git a/compiler/parse/tests/snapshots/pass/comment_with_non_ascii.expr.result-ast b/compiler/parse/tests/snapshots/pass/comment_with_non_ascii.expr.result-ast index ded9ef9ea0..5bf526ed0e 100644 --- a/compiler/parse/tests/snapshots/pass/comment_with_non_ascii.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/comment_with_non_ascii.expr.result-ast @@ -1,7 +1,7 @@ BinOps( [ ( - |L 0-0, C 0-1| SpaceAfter( + @0-1 SpaceAfter( Num( "3", ), @@ -11,10 +11,10 @@ BinOps( ), ], ), - |L 1-1, C 0-1| Plus, + @12-13 Plus, ), ], - |L 1-1, C 2-3| Num( + @14-15 Num( "4", ), ) diff --git a/compiler/parse/tests/snapshots/pass/destructure_tag_assignment.expr.result-ast b/compiler/parse/tests/snapshots/pass/destructure_tag_assignment.expr.result-ast new file mode 100644 index 0000000000..be7a2a54bf --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/destructure_tag_assignment.expr.result-ast @@ -0,0 +1,38 @@ +Defs( + [ + @0-36 Body( + @0-5 Apply( + @0-5 GlobalTag( + "Email", + ), + [ + @6-9 Identifier( + "str", + ), + ], + ), + @12-36 Apply( + @12-17 GlobalTag( + "Email", + ), + [ + @18-36 Str( + PlainLine( + "blah@example.com", + ), + ), + ], + Space, + ), + ), + ], + @37-40 SpaceBefore( + Var { + module_name: "", + ident: "str", + }, + [ + Newline, + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/destructure_tag_assignment.expr.roc b/compiler/parse/tests/snapshots/pass/destructure_tag_assignment.expr.roc new file mode 100644 index 0000000000..e97a842431 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/destructure_tag_assignment.expr.roc @@ -0,0 +1,2 @@ +Email str = Email "blah@example.com" +str diff --git a/compiler/parse/tests/snapshots/pass/empty_app_header.header.result-ast b/compiler/parse/tests/snapshots/pass/empty_app_header.header.result-ast index 74bf278e99..d0ba5ec7b0 100644 --- a/compiler/parse/tests/snapshots/pass/empty_app_header.header.result-ast +++ b/compiler/parse/tests/snapshots/pass/empty_app_header.header.result-ast @@ -1,12 +1,13 @@ App { header: AppHeader { - name: |L 0-0, C 4-14| PlainLine( + name: @4-14 PlainLine( "test-app", ), packages: [], imports: [], provides: [], - to: |L 0-0, C 53-57| ExistingPackage( + provides_types: None, + to: @53-57 ExistingPackage( "blah", ), before_header: [], diff --git a/compiler/parse/tests/snapshots/pass/empty_hosted_header.header.result-ast b/compiler/parse/tests/snapshots/pass/empty_hosted_header.header.result-ast new file mode 100644 index 0000000000..8cf0174c92 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/empty_hosted_header.header.result-ast @@ -0,0 +1,23 @@ +Hosted { + header: HostedHeader { + name: @7-10 ModuleName( + "Foo", + ), + exposes: [], + imports: [], + generates: UppercaseIdent( + "Bar", + ), + generates_with: [], + before_header: [], + after_hosted_keyword: [], + before_exposes: [], + after_exposes: [], + before_imports: [], + after_imports: [], + before_generates: [], + after_generates: [], + before_with: [], + after_with: [], + }, +} diff --git a/compiler/parse/tests/snapshots/pass/empty_hosted_header.header.roc b/compiler/parse/tests/snapshots/pass/empty_hosted_header.header.roc new file mode 100644 index 0000000000..e43d69f4a1 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/empty_hosted_header.header.roc @@ -0,0 +1 @@ +hosted Foo exposes [] imports [] generates Bar with [] diff --git a/compiler/parse/tests/snapshots/pass/empty_interface_header.header.result-ast b/compiler/parse/tests/snapshots/pass/empty_interface_header.header.result-ast index 0e71559779..1bd388d31d 100644 --- a/compiler/parse/tests/snapshots/pass/empty_interface_header.header.result-ast +++ b/compiler/parse/tests/snapshots/pass/empty_interface_header.header.result-ast @@ -1,6 +1,6 @@ Interface { header: InterfaceHeader { - name: |L 0-0, C 10-13| ModuleName( + name: @10-13 ModuleName( "Foo", ), exposes: [], diff --git a/compiler/parse/tests/snapshots/pass/empty_platform_header.header.result-ast b/compiler/parse/tests/snapshots/pass/empty_platform_header.header.result-ast index cb2368db17..736414d3a8 100644 --- a/compiler/parse/tests/snapshots/pass/empty_platform_header.header.result-ast +++ b/compiler/parse/tests/snapshots/pass/empty_platform_header.header.result-ast @@ -1,14 +1,14 @@ Platform { header: PlatformHeader { - name: |L 0-0, C 9-25| PackageName( + name: @9-25 PackageName( "rtfeldman/blah", ), requires: PlatformRequires { rigids: [], - signature: |L 0-0, C 40-49| TypedIdent { - ident: |L 0-0, C 40-44| "main", + signature: @40-49 TypedIdent { + ident: @40-44 "main", spaces_before_colon: [], - ann: |L 0-0, C 47-49| Record { + ann: @47-49 Record { fields: [], ext: None, }, @@ -18,14 +18,6 @@ Platform { packages: [], imports: [], provides: [], - effects: Effects { - spaces_before_effects_keyword: [], - spaces_after_effects_keyword: [], - spaces_after_type_name: [], - effect_shortname: "fx", - effect_type_name: "Blah", - entries: [], - }, before_header: [], after_platform_keyword: [], before_requires: [], diff --git a/compiler/parse/tests/snapshots/pass/empty_platform_header.header.roc b/compiler/parse/tests/snapshots/pass/empty_platform_header.header.roc index ce0e4468a5..04d71d738d 100644 --- a/compiler/parse/tests/snapshots/pass/empty_platform_header.header.roc +++ b/compiler/parse/tests/snapshots/pass/empty_platform_header.header.roc @@ -1 +1 @@ -platform "rtfeldman/blah" requires {} { main : {} } exposes [] packages {} imports [] provides [] effects fx.Blah {} \ No newline at end of file +platform "rtfeldman/blah" requires {} { main : {} } exposes [] packages {} imports [] provides [] diff --git a/compiler/parse/tests/snapshots/pass/equals.expr.result-ast b/compiler/parse/tests/snapshots/pass/equals.expr.result-ast index e69ba3d3da..76f314ce4f 100644 --- a/compiler/parse/tests/snapshots/pass/equals.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/equals.expr.result-ast @@ -1,14 +1,14 @@ BinOps( [ ( - |L 0-0, C 0-1| Var { + @0-1 Var { module_name: "", ident: "x", }, - |L 0-0, C 1-3| Equals, + @1-3 Equals, ), ], - |L 0-0, C 3-4| Var { + @3-4 Var { module_name: "", ident: "y", }, diff --git a/compiler/parse/tests/snapshots/pass/equals_with_spaces.expr.result-ast b/compiler/parse/tests/snapshots/pass/equals_with_spaces.expr.result-ast index 10e4d471e7..420f432778 100644 --- a/compiler/parse/tests/snapshots/pass/equals_with_spaces.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/equals_with_spaces.expr.result-ast @@ -1,14 +1,14 @@ BinOps( [ ( - |L 0-0, C 0-1| Var { + @0-1 Var { module_name: "", ident: "x", }, - |L 0-0, C 2-4| Equals, + @2-4 Equals, ), ], - |L 0-0, C 5-6| Var { + @5-6 Var { module_name: "", ident: "y", }, diff --git a/compiler/parse/tests/snapshots/pass/expect.expr.result-ast b/compiler/parse/tests/snapshots/pass/expect.expr.result-ast index 06d5710e1a..fb56585387 100644 --- a/compiler/parse/tests/snapshots/pass/expect.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/expect.expr.result-ast @@ -1,18 +1,18 @@ Expect( - |L 0-0, C 7-13| BinOps( + @7-13 BinOps( [ ( - |L 0-0, C 7-8| Num( + @7-8 Num( "1", ), - |L 0-0, C 9-11| Equals, + @9-11 Equals, ), ], - |L 0-0, C 12-13| Num( + @12-13 Num( "1", ), ), - |L 2-2, C 0-1| SpaceBefore( + @15-16 SpaceBefore( Num( "4", ), diff --git a/compiler/parse/tests/snapshots/pass/full_app_header.header.result-ast b/compiler/parse/tests/snapshots/pass/full_app_header.header.result-ast index 1d0739ba94..76545b43e9 100644 --- a/compiler/parse/tests/snapshots/pass/full_app_header.header.result-ast +++ b/compiler/parse/tests/snapshots/pass/full_app_header.header.result-ast @@ -1,19 +1,19 @@ App { header: AppHeader { - name: |L 0-0, C 4-15| PlainLine( + name: @4-15 PlainLine( "quicksort", ), packages: [ - |L 1-1, C 15-31| PackageEntry { + @31-47 PackageEntry { shorthand: "pf", spaces_after_shorthand: [], - package_name: |L 1-1, C 19-31| PackageName( + package_name: @35-47 PackageName( "./platform", ), }, ], imports: [ - |L 2-2, C 14-25| Package( + @64-75 Package( "foo", ModuleName( "Bar.Baz", @@ -22,11 +22,12 @@ App { ), ], provides: [ - |L 3-3, C 15-24| ExposedName( + @93-102 ExposedName( "quicksort", ), ], - to: |L 3-3, C 30-32| ExistingPackage( + provides_types: None, + to: @108-110 ExistingPackage( "pf", ), before_header: [], diff --git a/compiler/parse/tests/snapshots/pass/full_app_header_trailing_commas.header.result-ast b/compiler/parse/tests/snapshots/pass/full_app_header_trailing_commas.header.result-ast index 543965a8c4..8840febceb 100644 --- a/compiler/parse/tests/snapshots/pass/full_app_header_trailing_commas.header.result-ast +++ b/compiler/parse/tests/snapshots/pass/full_app_header_trailing_commas.header.result-ast @@ -1,26 +1,26 @@ App { header: AppHeader { - name: |L 0-0, C 4-15| PlainLine( + name: @4-15 PlainLine( "quicksort", ), packages: [ - |L 1-1, C 15-31| PackageEntry { + @31-47 PackageEntry { shorthand: "pf", spaces_after_shorthand: [], - package_name: |L 1-1, C 19-31| PackageName( + package_name: @35-47 PackageName( "./platform", ), }, ], imports: [ - |L 2-6, C 14-5| Package( + @65-141 Package( "foo", ModuleName( "Bar", ), Collection { items: [ - |L 3-3, C 8-11| SpaceBefore( + @83-86 SpaceBefore( ExposedName( "Baz", ), @@ -28,7 +28,7 @@ App { Newline, ], ), - |L 4-4, C 8-16| SpaceBefore( + @96-104 SpaceBefore( ExposedName( "FortyTwo", ), @@ -47,11 +47,12 @@ App { ), ], provides: [ - |L 7-7, C 15-24| ExposedName( + @159-168 ExposedName( "quicksort", ), ], - to: |L 7-7, C 31-33| ExistingPackage( + provides_types: None, + to: @175-177 ExistingPackage( "pf", ), before_header: [], diff --git a/compiler/parse/tests/snapshots/pass/function_effect_types.header.result-ast b/compiler/parse/tests/snapshots/pass/function_effect_types.header.result-ast index 9db9377a6f..9075843166 100644 --- a/compiler/parse/tests/snapshots/pass/function_effect_types.header.result-ast +++ b/compiler/parse/tests/snapshots/pass/function_effect_types.header.result-ast @@ -1,22 +1,22 @@ Platform { header: PlatformHeader { - name: |L 0-0, C 9-23| PackageName( + name: @9-23 PackageName( "examples/cli", ), requires: PlatformRequires { rigids: [], - signature: |L 1-1, C 17-34| TypedIdent { - ident: |L 1-1, C 17-21| "main", + signature: @41-58 TypedIdent { + ident: @41-45 "main", spaces_before_colon: [], - ann: |L 1-1, C 24-34| Apply( + ann: @48-58 Apply( "", "Task", [ - |L 1-1, C 29-31| Record { + @53-55 Record { fields: [], ext: None, }, - |L 1-1, C 32-34| TagUnion { + @56-58 TagUnion { ext: None, tags: [], }, @@ -27,121 +27,22 @@ Platform { exposes: [], packages: [], imports: [ - |L 4-4, C 14-27| Module( + @119-132 Module( ModuleName( "Task", ), [ - |L 4-4, C 21-25| ExposedName( + @126-130 ExposedName( "Task", ), ], ), ], provides: [ - |L 5-5, C 15-26| ExposedName( + @150-161 ExposedName( "mainForHost", ), ], - effects: Effects { - spaces_before_effects_keyword: [ - Newline, - ], - spaces_after_effects_keyword: [], - spaces_after_type_name: [ - Newline, - ], - effect_shortname: "fx", - effect_type_name: "Effect", - entries: [ - |L 8-8, C 12-32| SpaceBefore( - TypedIdent { - ident: |L 8-8, C 12-19| "getLine", - spaces_before_colon: [], - ann: |L 8-8, C 22-32| Apply( - "", - "Effect", - [ - |L 8-8, C 29-32| Apply( - "", - "Str", - [], - ), - ], - ), - }, - [ - Newline, - ], - ), - |L 9-9, C 12-38| SpaceBefore( - TypedIdent { - ident: |L 9-9, C 12-19| "putLine", - spaces_before_colon: [], - ann: |L 9-9, C 29-38| Function( - [ - |L 9-9, C 22-25| Apply( - "", - "Str", - [], - ), - ], - |L 9-9, C 29-38| Apply( - "", - "Effect", - [ - |L 9-9, C 36-38| Record { - fields: [], - ext: None, - }, - ], - ), - ), - }, - [ - Newline, - ], - ), - |L 10-10, C 12-48| SpaceBefore( - SpaceAfter( - TypedIdent { - ident: |L 10-10, C 12-24| "twoArguments", - spaces_before_colon: [], - ann: |L 10-10, C 39-48| Function( - [ - |L 10-10, C 27-30| Apply( - "", - "Int", - [], - ), - |L 10-10, C 32-35| Apply( - "", - "Int", - [], - ), - ], - |L 10-10, C 39-48| Apply( - "", - "Effect", - [ - |L 10-10, C 46-48| Record { - fields: [], - ext: None, - }, - ], - ), - ), - }, - [ - Newline, - ], - ), - [ - Newline, - ], - ), - ], - }, before_header: [], after_platform_keyword: [], before_requires: [ diff --git a/compiler/parse/tests/snapshots/pass/function_effect_types.header.roc b/compiler/parse/tests/snapshots/pass/function_effect_types.header.roc index 1135057391..8142e6a4fe 100644 --- a/compiler/parse/tests/snapshots/pass/function_effect_types.header.roc +++ b/compiler/parse/tests/snapshots/pass/function_effect_types.header.roc @@ -4,9 +4,3 @@ platform "examples/cli" packages {} imports [ Task.{ Task } ] provides [ mainForHost ] - effects fx.Effect - { - getLine : Effect Str, - putLine : Str -> Effect {}, - twoArguments : Int, Int -> Effect {} - } diff --git a/compiler/parse/tests/snapshots/pass/if_def.expr.result-ast b/compiler/parse/tests/snapshots/pass/if_def.expr.result-ast index 08f0ebe98f..08284e3121 100644 --- a/compiler/parse/tests/snapshots/pass/if_def.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/if_def.expr.result-ast @@ -1,15 +1,15 @@ Defs( [ - |L 0-0, C 0-6| Body( - |L 0-0, C 0-4| Identifier( + @0-6 Body( + @0-4 Identifier( "iffy", ), - |L 0-0, C 5-6| Num( + @5-6 Num( "5", ), ), ], - |L 2-2, C 0-2| SpaceBefore( + @8-10 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/interface_with_newline.header.result-ast b/compiler/parse/tests/snapshots/pass/interface_with_newline.header.result-ast new file mode 100644 index 0000000000..fe67388046 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/interface_with_newline.header.result-ast @@ -0,0 +1,15 @@ +Interface { + header: InterfaceHeader { + name: @10-11 ModuleName( + "T", + ), + exposes: [], + imports: [], + before_header: [], + after_interface_keyword: [], + before_exposes: [], + after_exposes: [], + before_imports: [], + after_imports: [], + }, +} diff --git a/compiler/parse/tests/snapshots/pass/interface_with_newline.header.roc b/compiler/parse/tests/snapshots/pass/interface_with_newline.header.roc new file mode 100644 index 0000000000..696cdada15 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/interface_with_newline.header.roc @@ -0,0 +1,2 @@ + +interface T exposes [] imports [] diff --git a/compiler/parse/tests/snapshots/pass/malformed_ident_due_to_underscore.expr.result-ast b/compiler/parse/tests/snapshots/pass/malformed_ident_due_to_underscore.expr.result-ast index c393e3defa..b776a10113 100644 --- a/compiler/parse/tests/snapshots/pass/malformed_ident_due_to_underscore.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/malformed_ident_due_to_underscore.expr.result-ast @@ -1,13 +1,13 @@ Closure( [ - |L 0-0, C 1-11| MalformedIdent( + @1-11 MalformedIdent( "the_answer", Underscore( - 0:5, + @5, ), ), ], - |L 0-0, C 15-17| Num( + @15-17 Num( "42", ), ) diff --git a/compiler/parse/tests/snapshots/pass/malformed_pattern_field_access.expr.result-ast b/compiler/parse/tests/snapshots/pass/malformed_pattern_field_access.expr.result-ast index 038dab88da..526bf8a5cd 100644 --- a/compiler/parse/tests/snapshots/pass/malformed_pattern_field_access.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/malformed_pattern_field_access.expr.result-ast @@ -1,12 +1,12 @@ When( - |L 0-0, C 5-6| Var { + @5-6 Var { module_name: "", ident: "x", }, [ WhenBranch { patterns: [ - |L 1-1, C 4-11| SpaceBefore( + @14-21 SpaceBefore( Malformed( "bar.and", ), @@ -15,14 +15,14 @@ When( ], ), ], - value: |L 1-1, C 15-16| Num( + value: @25-26 Num( "1", ), guard: None, }, WhenBranch { patterns: [ - |L 2-2, C 4-5| SpaceBefore( + @31-32 SpaceBefore( Underscore( "", ), @@ -31,7 +31,7 @@ When( ], ), ], - value: |L 2-2, C 9-10| Num( + value: @36-37 Num( "4", ), guard: None, diff --git a/compiler/parse/tests/snapshots/pass/malformed_pattern_module_name.expr.result-ast b/compiler/parse/tests/snapshots/pass/malformed_pattern_module_name.expr.result-ast index 65b4c3c94f..fa23b429e9 100644 --- a/compiler/parse/tests/snapshots/pass/malformed_pattern_module_name.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/malformed_pattern_module_name.expr.result-ast @@ -1,12 +1,12 @@ When( - |L 0-0, C 5-6| Var { + @5-6 Var { module_name: "", ident: "x", }, [ WhenBranch { patterns: [ - |L 1-1, C 4-11| SpaceBefore( + @14-21 SpaceBefore( Malformed( "Foo.and", ), @@ -15,14 +15,14 @@ When( ], ), ], - value: |L 1-1, C 15-16| Num( + value: @25-26 Num( "1", ), guard: None, }, WhenBranch { patterns: [ - |L 2-2, C 4-5| SpaceBefore( + @31-32 SpaceBefore( Underscore( "", ), @@ -31,7 +31,7 @@ When( ], ), ], - value: |L 2-2, C 9-10| Num( + value: @36-37 Num( "4", ), guard: None, diff --git a/compiler/parse/tests/snapshots/pass/minimal_app_header.header.result-ast b/compiler/parse/tests/snapshots/pass/minimal_app_header.header.result-ast index ba70384b58..225de00938 100644 --- a/compiler/parse/tests/snapshots/pass/minimal_app_header.header.result-ast +++ b/compiler/parse/tests/snapshots/pass/minimal_app_header.header.result-ast @@ -1,12 +1,13 @@ App { header: AppHeader { - name: |L 0-0, C 4-14| PlainLine( + name: @4-14 PlainLine( "test-app", ), packages: [], imports: [], provides: [], - to: |L 0-0, C 30-38| NewPackage( + provides_types: None, + to: @30-38 NewPackage( PackageName( "./blah", ), diff --git a/compiler/parse/tests/snapshots/pass/minus_twelve_minus_five.expr.result-ast b/compiler/parse/tests/snapshots/pass/minus_twelve_minus_five.expr.result-ast index 2540766a37..1d8f6c4e68 100644 --- a/compiler/parse/tests/snapshots/pass/minus_twelve_minus_five.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/minus_twelve_minus_five.expr.result-ast @@ -1,13 +1,13 @@ BinOps( [ ( - |L 0-0, C 0-3| Num( + @0-3 Num( "-12", ), - |L 0-0, C 3-4| Minus, + @3-4 Minus, ), ], - |L 0-0, C 4-5| Num( + @4-5 Num( "5", ), ) diff --git a/compiler/parse/tests/snapshots/pass/mixed_docs.expr.result-ast b/compiler/parse/tests/snapshots/pass/mixed_docs.expr.result-ast index eb8ed8b3ea..190e21ca40 100644 --- a/compiler/parse/tests/snapshots/pass/mixed_docs.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/mixed_docs.expr.result-ast @@ -1,16 +1,16 @@ SpaceBefore( Defs( [ - |L 4-4, C 0-5| Body( - |L 4-4, C 0-1| Identifier( + @113-118 Body( + @113-114 Identifier( "x", ), - |L 4-4, C 4-5| Num( + @117-118 Num( "5", ), ), ], - |L 6-6, C 0-2| SpaceBefore( + @120-122 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/module_def_newline.module.result-ast b/compiler/parse/tests/snapshots/pass/module_def_newline.module.result-ast index 1a9e243989..11a0ded5c2 100644 --- a/compiler/parse/tests/snapshots/pass/module_def_newline.module.result-ast +++ b/compiler/parse/tests/snapshots/pass/module_def_newline.module.result-ast @@ -1,23 +1,23 @@ [ - |L 0-3, C 0-5| SpaceAfter( + @0-24 SpaceAfter( SpaceBefore( Body( - |L 0-0, C 0-4| Identifier( + @0-4 Identifier( "main", ), - |L 1-3, C 4-5| SpaceBefore( + @11-24 SpaceBefore( Defs( [ - |L 1-1, C 4-10| Body( - |L 1-1, C 4-5| Identifier( + @11-17 Body( + @11-12 Identifier( "i", ), - |L 1-1, C 8-10| Num( + @15-17 Num( "64", ), ), ], - |L 3-3, C 4-5| SpaceBefore( + @23-24 SpaceBefore( Var { module_name: "", ident: "i", diff --git a/compiler/parse/tests/snapshots/pass/multi_backpassing.expr.result-ast b/compiler/parse/tests/snapshots/pass/multi_backpassing.expr.result-ast index d077cef709..3db758a0e5 100644 --- a/compiler/parse/tests/snapshots/pass/multi_backpassing.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/multi_backpassing.expr.result-ast @@ -1,39 +1,39 @@ Backpassing( [ - |L 0-0, C 0-1| Identifier( + @0-1 Identifier( "x", ), - |L 0-0, C 3-4| Identifier( + @3-4 Identifier( "y", ), ], - |L 0-0, C 8-23| Apply( - |L 0-0, C 8-17| Var { + @8-23 Apply( + @8-17 Var { module_name: "List", ident: "map2", }, [ - |L 0-0, C 18-20| List( + @18-20 List( [], ), - |L 0-0, C 21-23| List( + @21-23 List( [], ), ], Space, ), - |L 2-2, C 0-5| SpaceBefore( + @25-30 SpaceBefore( BinOps( [ ( - |L 2-2, C 0-1| Var { + @25-26 Var { module_name: "", ident: "x", }, - |L 2-2, C 2-3| Plus, + @27-28 Plus, ), ], - |L 2-2, C 4-5| Var { + @29-30 Var { module_name: "", ident: "y", }, diff --git a/compiler/parse/tests/snapshots/pass/multiline_type_signature.expr.result-ast b/compiler/parse/tests/snapshots/pass/multiline_type_signature.expr.result-ast index c6e1c801ac..8946c66262 100644 --- a/compiler/parse/tests/snapshots/pass/multiline_type_signature.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/multiline_type_signature.expr.result-ast @@ -1,10 +1,10 @@ Defs( [ - |L 0-1, C 0-6| Annotation( - |L 0-0, C 0-1| Identifier( + @0-10 Annotation( + @0-1 Identifier( "f", ), - |L 1-1, C 4-6| SpaceBefore( + @8-10 SpaceBefore( Record { fields: [], ext: None, @@ -15,7 +15,7 @@ Defs( ), ), ], - |L 3-3, C 0-2| SpaceBefore( + @12-14 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/multiline_type_signature_with_comment.expr.result-ast b/compiler/parse/tests/snapshots/pass/multiline_type_signature_with_comment.expr.result-ast index 8ba2bc2b26..8c30fd95c0 100644 --- a/compiler/parse/tests/snapshots/pass/multiline_type_signature_with_comment.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/multiline_type_signature_with_comment.expr.result-ast @@ -1,10 +1,10 @@ Defs( [ - |L 0-1, C 0-6| Annotation( - |L 0-0, C 0-1| Identifier( + @0-19 Annotation( + @0-1 Identifier( "f", ), - |L 1-1, C 4-6| SpaceBefore( + @17-19 SpaceBefore( Record { fields: [], ext: None, @@ -17,7 +17,7 @@ Defs( ), ), ], - |L 3-3, C 0-2| SpaceBefore( + @21-23 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/multiple_operators.expr.result-ast b/compiler/parse/tests/snapshots/pass/multiple_operators.expr.result-ast index f5e1463092..c5582c91c6 100644 --- a/compiler/parse/tests/snapshots/pass/multiple_operators.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/multiple_operators.expr.result-ast @@ -1,19 +1,19 @@ BinOps( [ ( - |L 0-0, C 0-2| Num( + @0-2 Num( "31", ), - |L 0-0, C 2-3| Star, + @2-3 Star, ), ( - |L 0-0, C 3-5| Num( + @3-5 Num( "42", ), - |L 0-0, C 5-6| Plus, + @5-6 Plus, ), ], - |L 0-0, C 6-9| Num( + @6-9 Num( "534", ), ) diff --git a/compiler/parse/tests/snapshots/pass/neg_inf_float.expr.result-ast b/compiler/parse/tests/snapshots/pass/neg_inf_float.expr.result-ast index 617306a026..8ef03b6192 100644 --- a/compiler/parse/tests/snapshots/pass/neg_inf_float.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/neg_inf_float.expr.result-ast @@ -1,7 +1,7 @@ UnaryOp( - |L 0-0, C 1-4| Var { + @1-4 Var { module_name: "", ident: "inf", }, - |L 0-0, C 0-1| Negate, + @0-1 Negate, ) diff --git a/compiler/parse/tests/snapshots/pass/nested_def_annotation.module.result-ast b/compiler/parse/tests/snapshots/pass/nested_def_annotation.module.result-ast index 0c597f8d28..33722ca531 100644 --- a/compiler/parse/tests/snapshots/pass/nested_def_annotation.module.result-ast +++ b/compiler/parse/tests/snapshots/pass/nested_def_annotation.module.result-ast @@ -1,57 +1,57 @@ [ - |L 0-5, C 0-20| SpaceAfter( + @0-115 SpaceAfter( SpaceBefore( Body( - |L 0-0, C 0-4| Identifier( + @0-4 Identifier( "main", ), - |L 1-5, C 4-20| SpaceBefore( + @11-115 SpaceBefore( Defs( [ - |L 2-3, C 4-20| AnnotatedBody { - ann_pattern: |L 1-1, C 4-16| Identifier( + @43-93 AnnotatedBody { + ann_pattern: @11-23 Identifier( "wrappedNotEq", ), - ann_type: |L 1-1, C 27-31| Function( + ann_type: @34-38 Function( [ - |L 1-1, C 19-20| BoundVariable( + @26-27 BoundVariable( "a", ), - |L 1-1, C 22-23| BoundVariable( + @29-30 BoundVariable( "a", ), ], - |L 1-1, C 27-31| Apply( + @34-38 Apply( "", "Bool", [], ), ), comment: None, - body_pattern: |L 2-2, C 4-16| Identifier( + body_pattern: @43-55 Identifier( "wrappedNotEq", ), - body_expr: |L 2-3, C 19-20| Closure( + body_expr: @58-93 Closure( [ - |L 2-2, C 20-24| Identifier( + @59-63 Identifier( "num1", ), - |L 2-2, C 26-30| Identifier( + @65-69 Identifier( "num2", ), ], - |L 3-3, C 8-20| SpaceBefore( + @81-93 SpaceBefore( BinOps( [ ( - |L 3-3, C 8-12| Var { + @81-85 Var { module_name: "", ident: "num1", }, - |L 3-3, C 13-15| NotEquals, + @86-88 NotEquals, ), ], - |L 3-3, C 16-20| Var { + @89-93 Var { module_name: "", ident: "num2", }, @@ -63,17 +63,17 @@ ), }, ], - |L 5-5, C 4-20| SpaceBefore( + @99-115 SpaceBefore( Apply( - |L 5-5, C 4-16| Var { + @99-111 Var { module_name: "", ident: "wrappedNotEq", }, [ - |L 5-5, C 17-18| Num( + @112-113 Num( "2", ), - |L 5-5, C 19-20| Num( + @114-115 Num( "3", ), ], diff --git a/compiler/parse/tests/snapshots/pass/nested_if.expr.result-ast b/compiler/parse/tests/snapshots/pass/nested_if.expr.result-ast index 9a2e97e67c..c4542c48eb 100644 --- a/compiler/parse/tests/snapshots/pass/nested_if.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/nested_if.expr.result-ast @@ -1,11 +1,11 @@ If( [ ( - |L 0-0, C 3-5| Var { + @3-5 Var { module_name: "", ident: "t1", }, - |L 1-1, C 2-3| SpaceBefore( + @13-14 SpaceBefore( SpaceAfter( Num( "1", @@ -20,11 +20,11 @@ If( ), ), ( - |L 2-2, C 8-10| Var { + @23-25 Var { module_name: "", ident: "t2", }, - |L 3-3, C 2-3| SpaceBefore( + @33-34 SpaceBefore( SpaceAfter( Num( "2", @@ -39,7 +39,7 @@ If( ), ), ], - |L 5-5, C 2-3| SpaceBefore( + @42-43 SpaceBefore( Num( "3", ), diff --git a/compiler/parse/tests/snapshots/pass/nested_module.header.result-ast b/compiler/parse/tests/snapshots/pass/nested_module.header.result-ast index 532089580b..50f7b410ed 100644 --- a/compiler/parse/tests/snapshots/pass/nested_module.header.result-ast +++ b/compiler/parse/tests/snapshots/pass/nested_module.header.result-ast @@ -1,6 +1,6 @@ Interface { header: InterfaceHeader { - name: |L 0-0, C 10-21| ModuleName( + name: @10-21 ModuleName( "Foo.Bar.Baz", ), exposes: [], diff --git a/compiler/parse/tests/snapshots/pass/newline_after_equals.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_after_equals.expr.result-ast index 70cc7456f6..3cdf6755b6 100644 --- a/compiler/parse/tests/snapshots/pass/newline_after_equals.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_after_equals.expr.result-ast @@ -1,10 +1,10 @@ Defs( [ - |L 0-1, C 0-5| Body( - |L 0-0, C 0-1| Identifier( + @0-9 Body( + @0-1 Identifier( "x", ), - |L 1-1, C 4-5| SpaceBefore( + @8-9 SpaceBefore( Num( "5", ), @@ -14,7 +14,7 @@ Defs( ), ), ], - |L 3-3, C 0-2| SpaceBefore( + @11-13 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/newline_after_mul.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_after_mul.expr.result-ast index 977121c8ea..ee6690e728 100644 --- a/compiler/parse/tests/snapshots/pass/newline_after_mul.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_after_mul.expr.result-ast @@ -1,13 +1,13 @@ BinOps( [ ( - |L 0-0, C 0-1| Num( + @0-1 Num( "3", ), - |L 0-0, C 3-4| Star, + @3-4 Star, ), ], - |L 1-1, C 2-3| SpaceBefore( + @7-8 SpaceBefore( Num( "4", ), diff --git a/compiler/parse/tests/snapshots/pass/newline_after_sub.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_after_sub.expr.result-ast index f6ac47b32a..444199db82 100644 --- a/compiler/parse/tests/snapshots/pass/newline_after_sub.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_after_sub.expr.result-ast @@ -1,13 +1,13 @@ BinOps( [ ( - |L 0-0, C 0-1| Num( + @0-1 Num( "3", ), - |L 0-0, C 3-4| Minus, + @3-4 Minus, ), ], - |L 1-1, C 2-3| SpaceBefore( + @7-8 SpaceBefore( Num( "4", ), diff --git a/compiler/parse/tests/snapshots/pass/newline_and_spaces_before_less_than.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_and_spaces_before_less_than.expr.result-ast index 78bbc9865e..cbc30d8ba0 100644 --- a/compiler/parse/tests/snapshots/pass/newline_and_spaces_before_less_than.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_and_spaces_before_less_than.expr.result-ast @@ -1,13 +1,13 @@ Defs( [ - |L 0-1, C 0-7| Body( - |L 0-0, C 0-1| Identifier( + @0-13 Body( + @0-1 Identifier( "x", ), - |L 0-1, C 4-7| BinOps( + @4-13 BinOps( [ ( - |L 0-0, C 4-5| SpaceAfter( + @4-5 SpaceAfter( Num( "1", ), @@ -15,16 +15,16 @@ Defs( Newline, ], ), - |L 1-1, C 4-5| LessThan, + @10-11 LessThan, ), ], - |L 1-1, C 6-7| Num( + @12-13 Num( "2", ), ), ), ], - |L 3-3, C 0-2| SpaceBefore( + @15-17 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/newline_before_add.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_before_add.expr.result-ast index 73c9b6b40d..57dac2f56f 100644 --- a/compiler/parse/tests/snapshots/pass/newline_before_add.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_before_add.expr.result-ast @@ -1,7 +1,7 @@ BinOps( [ ( - |L 0-0, C 0-1| SpaceAfter( + @0-1 SpaceAfter( Num( "3", ), @@ -9,10 +9,10 @@ BinOps( Newline, ], ), - |L 1-1, C 0-1| Plus, + @2-3 Plus, ), ], - |L 1-1, C 2-3| Num( + @4-5 Num( "4", ), ) diff --git a/compiler/parse/tests/snapshots/pass/newline_before_add.expr.roc b/compiler/parse/tests/snapshots/pass/newline_before_add.expr.roc index 2aef041bd9..73999f176e 100644 --- a/compiler/parse/tests/snapshots/pass/newline_before_add.expr.roc +++ b/compiler/parse/tests/snapshots/pass/newline_before_add.expr.roc @@ -1,2 +1,2 @@ -3 +3 + 4 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.result-ast index 4b8d5725c4..c01422cca9 100644 --- a/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.result-ast @@ -1,7 +1,7 @@ BinOps( [ ( - |L 0-0, C 0-1| SpaceAfter( + @0-1 SpaceAfter( Num( "3", ), @@ -9,10 +9,10 @@ BinOps( Newline, ], ), - |L 1-1, C 0-1| Minus, + @2-3 Minus, ), ], - |L 1-1, C 2-3| Num( + @4-5 Num( "4", ), ) diff --git a/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.roc b/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.roc index 19ab26bdea..dfab8da599 100644 --- a/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.roc +++ b/compiler/parse/tests/snapshots/pass/newline_before_sub.expr.roc @@ -1,2 +1,2 @@ -3 +3 - 4 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/newline_singleton_list.expr.result-ast b/compiler/parse/tests/snapshots/pass/newline_singleton_list.expr.result-ast index 15f065505b..77f172790b 100644 --- a/compiler/parse/tests/snapshots/pass/newline_singleton_list.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/newline_singleton_list.expr.result-ast @@ -1,6 +1,6 @@ List( [ - |L 1-1, C 0-1| SpaceBefore( + @2-3 SpaceBefore( SpaceAfter( Num( "1", diff --git a/compiler/parse/tests/snapshots/pass/nonempty_hosted_header.header.result-ast b/compiler/parse/tests/snapshots/pass/nonempty_hosted_header.header.result-ast new file mode 100644 index 0000000000..c5b245f35f --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/nonempty_hosted_header.header.result-ast @@ -0,0 +1,130 @@ +Hosted { + header: HostedHeader { + name: @7-10 ModuleName( + "Foo", + ), + exposes: Collection { + items: [ + @45-50 SpaceBefore( + ExposedName( + "Stuff", + ), + [ + Newline, + ], + ), + @64-70 SpaceBefore( + ExposedName( + "Things", + ), + [ + Newline, + ], + ), + @84-97 SpaceBefore( + ExposedName( + "somethingElse", + ), + [ + Newline, + ], + ), + ], + final_comments: [ + Newline, + ], + }, + imports: Collection { + items: [ + @143-147 SpaceBefore( + Module( + ModuleName( + "Blah", + ), + [], + ), + [ + Newline, + ], + ), + @161-182 SpaceBefore( + Module( + ModuleName( + "Baz", + ), + [ + @167-172 ExposedName( + "stuff", + ), + @174-180 ExposedName( + "things", + ), + ], + ), + [ + Newline, + ], + ), + ], + final_comments: [ + Newline, + ], + }, + generates: UppercaseIdent( + "Bar", + ), + generates_with: Collection { + items: [ + @239-242 SpaceBefore( + ExposedName( + "map", + ), + [ + Newline, + ], + ), + @256-261 SpaceBefore( + ExposedName( + "after", + ), + [ + Newline, + ], + ), + @275-279 SpaceBefore( + ExposedName( + "loop", + ), + [ + Newline, + ], + ), + ], + final_comments: [ + Newline, + ], + }, + before_header: [], + after_hosted_keyword: [], + before_exposes: [ + Newline, + ], + after_exposes: [ + Newline, + ], + before_imports: [ + Newline, + ], + after_imports: [ + Newline, + ], + before_generates: [ + Newline, + ], + after_generates: [], + before_with: [], + after_with: [ + Newline, + ], + }, +} diff --git a/compiler/parse/tests/snapshots/pass/nonempty_hosted_header.header.roc b/compiler/parse/tests/snapshots/pass/nonempty_hosted_header.header.roc new file mode 100644 index 0000000000..2f417ee54b --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/nonempty_hosted_header.header.roc @@ -0,0 +1,18 @@ +hosted Foo + exposes + [ + Stuff, + Things, + somethingElse, + ] + imports + [ + Blah, + Baz.{ stuff, things }, + ] + generates Bar with + [ + map, + after, + loop, + ] diff --git a/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.result-ast b/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.result-ast index 318ec551ac..38b5d6f691 100644 --- a/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.result-ast +++ b/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.result-ast @@ -1,19 +1,18 @@ Platform { header: PlatformHeader { - name: |L 0-0, C 9-21| PackageName( + name: @9-21 PackageName( "foo/barbaz", ), requires: PlatformRequires { rigids: [ - |L 1-1, C 14-26| PlatformRigid { - rigid: "model", - alias: "Model", - }, + @36-41 UppercaseIdent( + "Model", + ), ], - signature: |L 1-1, C 30-39| TypedIdent { - ident: |L 1-1, C 30-34| "main", + signature: @45-54 TypedIdent { + ident: @45-49 "main", spaces_before_colon: [], - ann: |L 1-1, C 37-39| Record { + ann: @52-54 Record { fields: [], ext: None, }, @@ -21,30 +20,20 @@ Platform { }, exposes: [], packages: [ - |L 3-3, C 15-27| PackageEntry { + @87-99 PackageEntry { shorthand: "foo", spaces_after_shorthand: [], - package_name: |L 3-3, C 20-27| PackageName( + package_name: @92-99 PackageName( "./foo", ), }, ], imports: [], provides: [ - |L 5-5, C 15-26| ExposedName( + @132-143 ExposedName( "mainForHost", ), ], - effects: Effects { - spaces_before_effects_keyword: [ - Newline, - ], - spaces_after_effects_keyword: [], - spaces_after_type_name: [], - effect_shortname: "fx", - effect_type_name: "Effect", - entries: [], - }, before_header: [], after_platform_keyword: [], before_requires: [ diff --git a/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.roc b/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.roc index c38245cdc6..789dae3055 100644 --- a/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.roc +++ b/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.roc @@ -1,7 +1,6 @@ platform "foo/barbaz" - requires {model=>Model} { main : {} } + requires {Model} { main : {} } exposes [] packages { foo: "./foo" } imports [] provides [ mainForHost ] - effects fx.Effect {} diff --git a/compiler/parse/tests/snapshots/pass/not_docs.expr.result-ast b/compiler/parse/tests/snapshots/pass/not_docs.expr.result-ast index 0a5c470bc0..e9a5247b17 100644 --- a/compiler/parse/tests/snapshots/pass/not_docs.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/not_docs.expr.result-ast @@ -1,16 +1,16 @@ SpaceBefore( Defs( [ - |L 4-4, C 0-5| Body( - |L 4-4, C 0-1| Identifier( + @46-51 Body( + @46-47 Identifier( "x", ), - |L 4-4, C 4-5| Num( + @50-51 Num( "5", ), ), ], - |L 6-6, C 0-2| SpaceBefore( + @53-55 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/number_literal_suffixes.expr.result-ast b/compiler/parse/tests/snapshots/pass/number_literal_suffixes.expr.result-ast new file mode 100644 index 0000000000..8ee611e8f7 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/number_literal_suffixes.expr.result-ast @@ -0,0 +1,451 @@ +Record( + Collection { + items: [ + @4-15 SpaceBefore( + RequiredValue( + @4-6 "u8", + [], + @10-15 Num( + "123u8", + ), + ), + [ + Newline, + ], + ), + @19-31 SpaceBefore( + RequiredValue( + @19-22 "u16", + [], + @25-31 Num( + "123u16", + ), + ), + [ + Newline, + ], + ), + @35-47 SpaceBefore( + RequiredValue( + @35-38 "u32", + [], + @41-47 Num( + "123u32", + ), + ), + [ + Newline, + ], + ), + @51-63 SpaceBefore( + RequiredValue( + @51-54 "u64", + [], + @57-63 Num( + "123u64", + ), + ), + [ + Newline, + ], + ), + @67-80 SpaceBefore( + RequiredValue( + @67-71 "u128", + [], + @73-80 Num( + "123u128", + ), + ), + [ + Newline, + ], + ), + @84-95 SpaceBefore( + RequiredValue( + @84-86 "i8", + [], + @90-95 Num( + "123i8", + ), + ), + [ + Newline, + ], + ), + @99-111 SpaceBefore( + RequiredValue( + @99-102 "i16", + [], + @105-111 Num( + "123i16", + ), + ), + [ + Newline, + ], + ), + @115-127 SpaceBefore( + RequiredValue( + @115-118 "i32", + [], + @121-127 Num( + "123i32", + ), + ), + [ + Newline, + ], + ), + @131-143 SpaceBefore( + RequiredValue( + @131-134 "i64", + [], + @137-143 Num( + "123i64", + ), + ), + [ + Newline, + ], + ), + @147-160 SpaceBefore( + RequiredValue( + @147-151 "i128", + [], + @153-160 Num( + "123i128", + ), + ), + [ + Newline, + ], + ), + @164-176 SpaceBefore( + RequiredValue( + @164-167 "nat", + [], + @170-176 Num( + "123nat", + ), + ), + [ + Newline, + ], + ), + @180-192 SpaceBefore( + RequiredValue( + @180-183 "dec", + [], + @186-192 Num( + "123dec", + ), + ), + [ + Newline, + ], + ), + @196-211 SpaceBefore( + RequiredValue( + @196-201 "u8Neg", + [], + @205-211 Num( + "-123u8", + ), + ), + [ + Newline, + ], + ), + @215-231 SpaceBefore( + RequiredValue( + @215-221 "u16Neg", + [], + @224-231 Num( + "-123u16", + ), + ), + [ + Newline, + ], + ), + @235-251 SpaceBefore( + RequiredValue( + @235-241 "u32Neg", + [], + @244-251 Num( + "-123u32", + ), + ), + [ + Newline, + ], + ), + @255-271 SpaceBefore( + RequiredValue( + @255-261 "u64Neg", + [], + @264-271 Num( + "-123u64", + ), + ), + [ + Newline, + ], + ), + @275-292 SpaceBefore( + RequiredValue( + @275-282 "u128Neg", + [], + @284-292 Num( + "-123u128", + ), + ), + [ + Newline, + ], + ), + @296-311 SpaceBefore( + RequiredValue( + @296-301 "i8Neg", + [], + @305-311 Num( + "-123i8", + ), + ), + [ + Newline, + ], + ), + @315-331 SpaceBefore( + RequiredValue( + @315-321 "i16Neg", + [], + @324-331 Num( + "-123i16", + ), + ), + [ + Newline, + ], + ), + @335-351 SpaceBefore( + RequiredValue( + @335-341 "i32Neg", + [], + @344-351 Num( + "-123i32", + ), + ), + [ + Newline, + ], + ), + @355-371 SpaceBefore( + RequiredValue( + @355-361 "i64Neg", + [], + @364-371 Num( + "-123i64", + ), + ), + [ + Newline, + ], + ), + @375-392 SpaceBefore( + RequiredValue( + @375-382 "i128Neg", + [], + @384-392 Num( + "-123i128", + ), + ), + [ + Newline, + ], + ), + @396-412 SpaceBefore( + RequiredValue( + @396-402 "natNeg", + [], + @405-412 Num( + "-123nat", + ), + ), + [ + Newline, + ], + ), + @416-432 SpaceBefore( + RequiredValue( + @416-422 "decNeg", + [], + @425-432 Num( + "-123dec", + ), + ), + [ + Newline, + ], + ), + @436-452 SpaceBefore( + RequiredValue( + @436-441 "u8Bin", + [], + @445-452 NonBase10Int { + string: "101u8", + base: Binary, + is_negative: false, + }, + ), + [ + Newline, + ], + ), + @456-473 SpaceBefore( + RequiredValue( + @456-462 "u16Bin", + [], + @465-473 NonBase10Int { + string: "101u16", + base: Binary, + is_negative: false, + }, + ), + [ + Newline, + ], + ), + @477-494 SpaceBefore( + RequiredValue( + @477-483 "u32Bin", + [], + @486-494 NonBase10Int { + string: "101u32", + base: Binary, + is_negative: false, + }, + ), + [ + Newline, + ], + ), + @498-515 SpaceBefore( + RequiredValue( + @498-504 "u64Bin", + [], + @507-515 NonBase10Int { + string: "101u64", + base: Binary, + is_negative: false, + }, + ), + [ + Newline, + ], + ), + @519-537 SpaceBefore( + RequiredValue( + @519-526 "u128Bin", + [], + @528-537 NonBase10Int { + string: "101u128", + base: Binary, + is_negative: false, + }, + ), + [ + Newline, + ], + ), + @541-557 SpaceBefore( + RequiredValue( + @541-546 "i8Bin", + [], + @550-557 NonBase10Int { + string: "101i8", + base: Binary, + is_negative: false, + }, + ), + [ + Newline, + ], + ), + @561-578 SpaceBefore( + RequiredValue( + @561-567 "i16Bin", + [], + @570-578 NonBase10Int { + string: "101i16", + base: Binary, + is_negative: false, + }, + ), + [ + Newline, + ], + ), + @582-599 SpaceBefore( + RequiredValue( + @582-588 "i32Bin", + [], + @591-599 NonBase10Int { + string: "101i32", + base: Binary, + is_negative: false, + }, + ), + [ + Newline, + ], + ), + @603-620 SpaceBefore( + RequiredValue( + @603-609 "i64Bin", + [], + @612-620 NonBase10Int { + string: "101i64", + base: Binary, + is_negative: false, + }, + ), + [ + Newline, + ], + ), + @624-642 SpaceBefore( + RequiredValue( + @624-631 "i128Bin", + [], + @633-642 NonBase10Int { + string: "101i128", + base: Binary, + is_negative: false, + }, + ), + [ + Newline, + ], + ), + @646-663 SpaceBefore( + RequiredValue( + @646-652 "natBin", + [], + @655-663 NonBase10Int { + string: "101nat", + base: Binary, + is_negative: false, + }, + ), + [ + Newline, + ], + ), + ], + final_comments: [ + Newline, + ], + }, +) diff --git a/compiler/parse/tests/snapshots/pass/number_literal_suffixes.expr.roc b/compiler/parse/tests/snapshots/pass/number_literal_suffixes.expr.roc new file mode 100644 index 0000000000..e76387bf3c --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/number_literal_suffixes.expr.roc @@ -0,0 +1,37 @@ +{ + u8: 123u8, + u16: 123u16, + u32: 123u32, + u64: 123u64, + u128: 123u128, + i8: 123i8, + i16: 123i16, + i32: 123i32, + i64: 123i64, + i128: 123i128, + nat: 123nat, + dec: 123dec, + u8Neg: -123u8, + u16Neg: -123u16, + u32Neg: -123u32, + u64Neg: -123u64, + u128Neg: -123u128, + i8Neg: -123i8, + i16Neg: -123i16, + i32Neg: -123i32, + i64Neg: -123i64, + i128Neg: -123i128, + natNeg: -123nat, + decNeg: -123dec, + u8Bin: 0b101u8, + u16Bin: 0b101u16, + u32Bin: 0b101u32, + u64Bin: 0b101u64, + u128Bin: 0b101u128, + i8Bin: 0b101i8, + i16Bin: 0b101i16, + i32Bin: 0b101i32, + i64Bin: 0b101i64, + i128Bin: 0b101i128, + natBin: 0b101nat, +} diff --git a/compiler/parse/tests/snapshots/pass/one_backpassing.expr.result-ast b/compiler/parse/tests/snapshots/pass/one_backpassing.expr.result-ast index 257e698479..405df4abf8 100644 --- a/compiler/parse/tests/snapshots/pass/one_backpassing.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/one_backpassing.expr.result-ast @@ -1,24 +1,24 @@ SpaceBefore( Backpassing( [ - |L 1-1, C 0-1| Identifier( + @18-19 Identifier( "x", ), ], - |L 1-1, C 5-14| ParensAround( + @23-32 ParensAround( Closure( [ - |L 1-1, C 7-8| Identifier( + @25-26 Identifier( "y", ), ], - |L 1-1, C 12-13| Var { + @30-31 Var { module_name: "", ident: "y", }, ), ), - |L 3-3, C 0-1| SpaceBefore( + @34-35 SpaceBefore( Var { module_name: "", ident: "x", diff --git a/compiler/parse/tests/snapshots/pass/one_def.expr.result-ast b/compiler/parse/tests/snapshots/pass/one_def.expr.result-ast index daa047957c..7caa61c752 100644 --- a/compiler/parse/tests/snapshots/pass/one_def.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/one_def.expr.result-ast @@ -1,16 +1,16 @@ SpaceBefore( Defs( [ - |L 1-1, C 0-3| Body( - |L 1-1, C 0-1| Identifier( + @18-21 Body( + @18-19 Identifier( "x", ), - |L 1-1, C 2-3| Num( + @20-21 Num( "5", ), ), ], - |L 3-3, C 0-2| SpaceBefore( + @23-25 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/one_minus_two.expr.result-ast b/compiler/parse/tests/snapshots/pass/one_minus_two.expr.result-ast index 77fd849f59..4dc9b56640 100644 --- a/compiler/parse/tests/snapshots/pass/one_minus_two.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/one_minus_two.expr.result-ast @@ -1,13 +1,13 @@ BinOps( [ ( - |L 0-0, C 0-1| Num( + @0-1 Num( "1", ), - |L 0-0, C 1-2| Minus, + @1-2 Minus, ), ], - |L 0-0, C 2-3| Num( + @2-3 Num( "2", ), ) diff --git a/compiler/parse/tests/snapshots/pass/one_plus_two.expr.result-ast b/compiler/parse/tests/snapshots/pass/one_plus_two.expr.result-ast index 7e45139380..e6bcba4260 100644 --- a/compiler/parse/tests/snapshots/pass/one_plus_two.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/one_plus_two.expr.result-ast @@ -1,13 +1,13 @@ BinOps( [ ( - |L 0-0, C 0-1| Num( + @0-1 Num( "1", ), - |L 0-0, C 1-2| Plus, + @1-2 Plus, ), ], - |L 0-0, C 2-3| Num( + @2-3 Num( "2", ), ) diff --git a/compiler/parse/tests/snapshots/pass/one_spaced_def.expr.result-ast b/compiler/parse/tests/snapshots/pass/one_spaced_def.expr.result-ast index 001962d745..a1fab8b814 100644 --- a/compiler/parse/tests/snapshots/pass/one_spaced_def.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/one_spaced_def.expr.result-ast @@ -1,16 +1,16 @@ SpaceBefore( Defs( [ - |L 1-1, C 0-5| Body( - |L 1-1, C 0-1| Identifier( + @18-23 Body( + @18-19 Identifier( "x", ), - |L 1-1, C 4-5| Num( + @22-23 Num( "5", ), ), ], - |L 3-3, C 0-2| SpaceBefore( + @25-27 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.result-ast b/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.result-ast index f35a0b6c44..28005999cb 100644 --- a/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.result-ast @@ -1,7 +1,7 @@ BinOps( [ ( - |L 0-0, C 0-1| SpaceAfter( + @0-1 SpaceAfter( Num( "3", ), @@ -9,10 +9,10 @@ BinOps( Newline, ], ), - |L 1-1, C 0-1| Plus, + @2-3 Plus, ), ], - |L 3-3, C 2-3| SpaceBefore( + @7-8 SpaceBefore( Num( "4", ), diff --git a/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.roc b/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.roc index 518b92423c..e2985d8af2 100644 --- a/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.roc +++ b/compiler/parse/tests/snapshots/pass/ops_with_newlines.expr.roc @@ -1,4 +1,4 @@ -3 -+ +3 ++ 4 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/packed_singleton_list.expr.result-ast b/compiler/parse/tests/snapshots/pass/packed_singleton_list.expr.result-ast index 2548a5221e..859505b0f3 100644 --- a/compiler/parse/tests/snapshots/pass/packed_singleton_list.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/packed_singleton_list.expr.result-ast @@ -1,6 +1,6 @@ List( [ - |L 0-0, C 1-2| Num( + @1-2 Num( "1", ), ], diff --git a/compiler/parse/tests/snapshots/pass/parenthetical_apply.expr.result-ast b/compiler/parse/tests/snapshots/pass/parenthetical_apply.expr.result-ast index ea1031f76d..c8000965eb 100644 --- a/compiler/parse/tests/snapshots/pass/parenthetical_apply.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/parenthetical_apply.expr.result-ast @@ -1,12 +1,12 @@ Apply( - |L 0-0, C 1-5| ParensAround( + @1-5 ParensAround( Var { module_name: "", ident: "whee", }, ), [ - |L 0-0, C 7-8| Num( + @7-8 Num( "1", ), ], diff --git a/compiler/parse/tests/snapshots/pass/parse_alias.expr.result-ast b/compiler/parse/tests/snapshots/pass/parse_alias.expr.result-ast index f0006fe0da..3deb39c07f 100644 --- a/compiler/parse/tests/snapshots/pass/parse_alias.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/parse_alias.expr.result-ast @@ -1,32 +1,32 @@ Defs( [ - |L 0-0, C 0-26| Alias { + @0-26 Alias { header: AliasHeader { - name: |L 0-0, C 0-4| "Blah", + name: @0-4 "Blah", vars: [ - |L 0-0, C 5-6| Identifier( + @5-6 Identifier( "a", ), - |L 0-0, C 7-8| Identifier( + @7-8 Identifier( "b", ), ], }, - ann: |L 0-0, C 11-26| Apply( + ann: @11-26 Apply( "Foo.Bar", "Baz", [ - |L 0-0, C 23-24| BoundVariable( + @23-24 BoundVariable( "x", ), - |L 0-0, C 25-26| BoundVariable( + @25-26 BoundVariable( "y", ), ], ), }, ], - |L 2-2, C 0-2| SpaceBefore( + @28-30 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/parse_as_ann.expr.result-ast b/compiler/parse/tests/snapshots/pass/parse_as_ann.expr.result-ast index edefb5bf4f..f99bd7c8fb 100644 --- a/compiler/parse/tests/snapshots/pass/parse_as_ann.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/parse_as_ann.expr.result-ast @@ -1,30 +1,30 @@ Defs( [ - |L 0-0, C 0-33| Annotation( - |L 0-0, C 0-3| Identifier( + @0-33 Annotation( + @0-3 Identifier( "foo", ), - |L 0-0, C 6-33| As( - |L 0-0, C 6-21| Apply( + @6-33 As( + @6-21 Apply( "Foo.Bar", "Baz", [ - |L 0-0, C 18-19| BoundVariable( + @18-19 BoundVariable( "x", ), - |L 0-0, C 20-21| BoundVariable( + @20-21 BoundVariable( "y", ), ], ), [], AliasHeader { - name: |L 0-0, C 25-29| "Blah", + name: @25-29 "Blah", vars: [ - |L 0-0, C 30-31| Identifier( + @30-31 Identifier( "a", ), - |L 0-0, C 32-33| Identifier( + @32-33 Identifier( "b", ), ], @@ -32,7 +32,7 @@ Defs( ), ), ], - |L 2-2, C 0-2| SpaceBefore( + @35-37 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast b/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast index 962f52971a..d45bbdf656 100644 --- a/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast @@ -1,16 +1,16 @@ When( - |L 0-0, C 5-22| Apply( - |L 0-0, C 5-11| GlobalTag( + @5-22 Apply( + @5-11 GlobalTag( "Delmin", ), [ - |L 0-0, C 13-19| ParensAround( + @13-19 ParensAround( Apply( - |L 0-0, C 13-16| GlobalTag( + @13-16 GlobalTag( "Del", ), [ - |L 0-0, C 17-19| Var { + @17-19 Var { module_name: "", ident: "rx", }, @@ -18,7 +18,7 @@ When( Space, ), ), - |L 0-0, C 21-22| Num( + @21-22 Num( "0", ), ], @@ -27,23 +27,23 @@ When( [ WhenBranch { patterns: [ - |L 1-1, C 4-22| SpaceBefore( + @30-48 SpaceBefore( Apply( - |L 1-1, C 4-10| GlobalTag( + @30-36 GlobalTag( "Delmin", ), [ - |L 1-1, C 12-18| Apply( - |L 1-1, C 12-15| GlobalTag( + @38-44 Apply( + @38-41 GlobalTag( "Del", ), [ - |L 1-1, C 16-18| Identifier( + @42-44 Identifier( "ry", ), ], ), - |L 1-1, C 21-22| Underscore( + @47-48 Underscore( "", ), ], @@ -53,21 +53,21 @@ When( ], ), ], - value: |L 1-1, C 26-47| Apply( - |L 1-1, C 26-30| GlobalTag( + value: @52-73 Apply( + @52-56 GlobalTag( "Node", ), [ - |L 1-1, C 31-36| GlobalTag( + @57-62 GlobalTag( "Black", ), - |L 1-1, C 37-38| Num( + @63-64 Num( "0", ), - |L 1-1, C 39-44| GlobalTag( + @65-70 GlobalTag( "False", ), - |L 1-1, C 45-47| Var { + @71-73 Var { module_name: "", ident: "ry", }, diff --git a/compiler/parse/tests/snapshots/pass/private_qualified_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/private_qualified_tag.expr.result-ast index b66c7b77c0..b770fe6077 100644 --- a/compiler/parse/tests/snapshots/pass/private_qualified_tag.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/private_qualified_tag.expr.result-ast @@ -1,6 +1,6 @@ MalformedIdent( "@One.Two.Whee", BadPrivateTag( - 0:4, + @4, ), ) diff --git a/compiler/parse/tests/snapshots/pass/provides_type.header.result-ast b/compiler/parse/tests/snapshots/pass/provides_type.header.result-ast new file mode 100644 index 0000000000..1991b3bd8f --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/provides_type.header.result-ast @@ -0,0 +1,59 @@ +App { + header: AppHeader { + name: @4-10 PlainLine( + "test", + ), + packages: [ + @26-42 PackageEntry { + shorthand: "pf", + spaces_after_shorthand: [], + package_name: @30-42 PackageName( + "./platform", + ), + }, + ], + imports: [ + @59-70 Package( + "foo", + ModuleName( + "Bar.Baz", + ), + [], + ), + ], + provides: [ + @88-97 ExposedName( + "quicksort", + ), + ], + provides_types: Some( + [ + @102-107 UppercaseIdent( + "Flags", + ), + @109-114 UppercaseIdent( + "Model", + ), + ], + ), + to: @121-123 ExistingPackage( + "pf", + ), + before_header: [], + after_app_keyword: [], + before_packages: [ + Newline, + ], + after_packages: [], + before_imports: [ + Newline, + ], + after_imports: [], + before_provides: [ + Newline, + ], + after_provides: [], + before_to: [], + after_to: [], + }, +} diff --git a/compiler/parse/tests/snapshots/pass/provides_type.header.roc b/compiler/parse/tests/snapshots/pass/provides_type.header.roc new file mode 100644 index 0000000000..c49764ac4a --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/provides_type.header.roc @@ -0,0 +1,4 @@ +app "test" + packages { pf: "./platform" } + imports [ foo.Bar.Baz ] + provides [ quicksort ] { Flags, Model, } to pf diff --git a/compiler/parse/tests/snapshots/pass/qualified_global_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/qualified_global_tag.expr.result-ast index 531d2fb377..44c9a08d3d 100644 --- a/compiler/parse/tests/snapshots/pass/qualified_global_tag.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/qualified_global_tag.expr.result-ast @@ -1,6 +1,6 @@ MalformedIdent( "One.Two.Whee", QualifiedTag( - 0:12, + @12, ), ) diff --git a/compiler/parse/tests/snapshots/pass/record_destructure_def.expr.result-ast b/compiler/parse/tests/snapshots/pass/record_destructure_def.expr.result-ast index db061e68f7..498e786bb0 100644 --- a/compiler/parse/tests/snapshots/pass/record_destructure_def.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/record_destructure_def.expr.result-ast @@ -1,27 +1,27 @@ SpaceBefore( Defs( [ - |L 1-1, C 0-12| Body( - |L 1-1, C 0-8| RecordDestructure( + @18-30 Body( + @18-26 RecordDestructure( [ - |L 1-1, C 2-3| Identifier( + @20-21 Identifier( "x", ), - |L 1-1, C 5-7| Identifier( + @23-25 Identifier( "y", ), ], ), - |L 1-1, C 11-12| Num( + @29-30 Num( "5", ), ), - |L 2-2, C 0-5| SpaceBefore( + @31-36 SpaceBefore( Body( - |L 2-2, C 0-1| Identifier( + @31-32 Identifier( "y", ), - |L 2-2, C 4-5| Num( + @35-36 Num( "6", ), ), @@ -30,7 +30,7 @@ SpaceBefore( ], ), ], - |L 4-4, C 0-2| SpaceBefore( + @38-40 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/record_func_type_decl.expr.result-ast b/compiler/parse/tests/snapshots/pass/record_func_type_decl.expr.result-ast index 5c2599233c..6ae895c6e0 100644 --- a/compiler/parse/tests/snapshots/pass/record_func_type_decl.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/record_func_type_decl.expr.result-ast @@ -1,21 +1,21 @@ Defs( [ - |L 0-6, C 0-5| Annotation( - |L 0-0, C 0-1| Identifier( + @0-122 Annotation( + @0-1 Identifier( "f", ), - |L 1-6, C 4-5| SpaceBefore( + @8-122 SpaceBefore( Record { fields: [ - |L 2-2, C 8-28| SpaceBefore( + @18-38 SpaceBefore( RequiredValue( - |L 2-2, C 8-15| "getLine", + @18-25 "getLine", [], - |L 2-2, C 18-28| Apply( + @28-38 Apply( "", "Effect", [ - |L 2-2, C 25-28| Apply( + @35-38 Apply( "", "Str", [], @@ -27,23 +27,23 @@ Defs( Newline, ], ), - |L 3-3, C 8-35| SpaceBefore( + @48-75 SpaceBefore( RequiredValue( - |L 3-3, C 8-15| "putLine", + @48-55 "putLine", [], - |L 3-3, C 25-35| Function( + @65-75 Function( [ - |L 3-3, C 18-21| Apply( + @58-61 Apply( "", "Str", [], ), ], - |L 3-3, C 25-35| Apply( + @65-75 Apply( "", "Effect", [ - |L 3-3, C 32-35| Apply( + @72-75 Apply( "", "Int", [], @@ -56,11 +56,11 @@ Defs( Newline, ], ), - |L 4-4, C 8-17| SpaceBefore( + @85-94 SpaceBefore( RequiredValue( - |L 4-4, C 8-12| "text", + @85-89 "text", [], - |L 4-4, C 14-17| Apply( + @91-94 Apply( "", "Str", [], @@ -70,16 +70,16 @@ Defs( Newline, ], ), - |L 5-5, C 8-20| SpaceBefore( + @104-116 SpaceBefore( SpaceAfter( RequiredValue( - |L 5-5, C 8-13| "value", + @104-109 "value", [], - |L 5-5, C 15-20| Apply( + @111-116 Apply( "", "Int", [ - |L 5-5, C 19-20| Wildcard, + @115-116 Wildcard, ], ), ), @@ -100,7 +100,7 @@ Defs( ), ), ], - |L 8-8, C 0-2| SpaceBefore( + @124-126 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/record_update.expr.result-ast b/compiler/parse/tests/snapshots/pass/record_update.expr.result-ast index 7ee1e6b7b0..8666e0151f 100644 --- a/compiler/parse/tests/snapshots/pass/record_update.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/record_update.expr.result-ast @@ -1,20 +1,20 @@ RecordUpdate { - update: |L 0-0, C 2-13| Var { + update: @2-13 Var { module_name: "Foo.Bar", ident: "baz", }, fields: [ - |L 0-0, C 16-20| RequiredValue( - |L 0-0, C 16-17| "x", + @16-20 RequiredValue( + @16-17 "x", [], - |L 0-0, C 19-20| Num( + @19-20 Num( "5", ), ), - |L 0-0, C 22-26| RequiredValue( - |L 0-0, C 22-23| "y", + @22-26 RequiredValue( + @22-23 "y", [], - |L 0-0, C 25-26| Num( + @25-26 Num( "0", ), ), diff --git a/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast b/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast index b4f4133f3b..1776ed2ff8 100644 --- a/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast @@ -1,28 +1,28 @@ Record( [ - |L 0-0, C 1-26| RequiredValue( - |L 0-0, C 1-2| "x", + @1-26 RequiredValue( + @1-2 "x", [], - |L 0-0, C 5-26| If( + @5-26 If( [ ( - |L 0-0, C 8-12| GlobalTag( + @8-12 GlobalTag( "True", ), - |L 0-0, C 18-19| Num( + @18-19 Num( "1", ), ), ], - |L 0-0, C 25-26| Num( + @25-26 Num( "2", ), ), ), - |L 0-0, C 28-32| RequiredValue( - |L 0-0, C 28-29| "y", + @28-32 RequiredValue( + @28-29 "y", [], - |L 0-0, C 31-32| Num( + @31-32 Num( "3", ), ), diff --git a/compiler/parse/tests/snapshots/pass/requires_type.header.result-ast b/compiler/parse/tests/snapshots/pass/requires_type.header.result-ast new file mode 100644 index 0000000000..35b2f64345 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/requires_type.header.result-ast @@ -0,0 +1,67 @@ +Platform { + header: PlatformHeader { + name: @9-21 PackageName( + "test/types", + ), + requires: PlatformRequires { + rigids: [ + @37-42 UppercaseIdent( + "Flags", + ), + @44-49 UppercaseIdent( + "Model", + ), + ], + signature: @55-77 TypedIdent { + ident: @55-59 "main", + spaces_before_colon: [], + ann: @62-77 Apply( + "", + "App", + [ + @66-71 Apply( + "", + "Flags", + [], + ), + @72-77 Apply( + "", + "Model", + [], + ), + ], + ), + }, + }, + exposes: [], + packages: [], + imports: [], + provides: [ + @141-152 ExposedName( + "mainForHost", + ), + ], + before_header: [], + after_platform_keyword: [], + before_requires: [ + Newline, + ], + after_requires: [], + before_exposes: [ + Newline, + ], + after_exposes: [], + before_packages: [ + Newline, + ], + after_packages: [], + before_imports: [ + Newline, + ], + after_imports: [], + before_provides: [ + Newline, + ], + after_provides: [], + }, +} diff --git a/compiler/parse/tests/snapshots/pass/requires_type.header.roc b/compiler/parse/tests/snapshots/pass/requires_type.header.roc new file mode 100644 index 0000000000..1755f5a066 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/requires_type.header.roc @@ -0,0 +1,9 @@ +platform "test/types" + requires { Flags, Model, } { main : App Flags Model } + exposes [] + packages {} + imports [] + provides [ mainForHost ] + +mainForHost : App Flags Model +mainForHost = main diff --git a/compiler/parse/tests/snapshots/pass/single_arg_closure.expr.result-ast b/compiler/parse/tests/snapshots/pass/single_arg_closure.expr.result-ast index ca8f338bbd..1c5fbbaa84 100644 --- a/compiler/parse/tests/snapshots/pass/single_arg_closure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/single_arg_closure.expr.result-ast @@ -1,10 +1,10 @@ Closure( [ - |L 0-0, C 1-2| Identifier( + @1-2 Identifier( "a", ), ], - |L 0-0, C 6-8| Num( + @6-8 Num( "42", ), ) diff --git a/compiler/parse/tests/snapshots/pass/single_underscore_closure.expr.result-ast b/compiler/parse/tests/snapshots/pass/single_underscore_closure.expr.result-ast index c3657bfca5..d04c130ed2 100644 --- a/compiler/parse/tests/snapshots/pass/single_underscore_closure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/single_underscore_closure.expr.result-ast @@ -1,10 +1,10 @@ Closure( [ - |L 0-0, C 1-2| Underscore( + @1-2 Underscore( "", ), ], - |L 0-0, C 6-8| Num( + @6-8 Num( "42", ), ) diff --git a/compiler/parse/tests/snapshots/pass/space_only_after_minus.expr.result-ast b/compiler/parse/tests/snapshots/pass/space_only_after_minus.expr.result-ast index 7ba8b91f84..45c8618b1c 100644 --- a/compiler/parse/tests/snapshots/pass/space_only_after_minus.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/space_only_after_minus.expr.result-ast @@ -1,14 +1,14 @@ BinOps( [ ( - |L 0-0, C 0-1| Var { + @0-1 Var { module_name: "", ident: "x", }, - |L 0-0, C 1-2| Minus, + @1-2 Minus, ), ], - |L 0-0, C 3-4| Var { + @3-4 Var { module_name: "", ident: "y", }, diff --git a/compiler/parse/tests/snapshots/pass/spaced_singleton_list.expr.result-ast b/compiler/parse/tests/snapshots/pass/spaced_singleton_list.expr.result-ast index 38acc0b739..231c1c41b1 100644 --- a/compiler/parse/tests/snapshots/pass/spaced_singleton_list.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/spaced_singleton_list.expr.result-ast @@ -1,6 +1,6 @@ List( [ - |L 0-0, C 2-3| Num( + @2-3 Num( "1", ), ], diff --git a/compiler/parse/tests/snapshots/pass/standalone_module_defs.module.result-ast b/compiler/parse/tests/snapshots/pass/standalone_module_defs.module.result-ast index df2876e5a3..e03334f801 100644 --- a/compiler/parse/tests/snapshots/pass/standalone_module_defs.module.result-ast +++ b/compiler/parse/tests/snapshots/pass/standalone_module_defs.module.result-ast @@ -1,10 +1,10 @@ [ - |L 1-1, C 0-7| SpaceBefore( + @12-19 SpaceBefore( Body( - |L 1-1, C 0-3| Identifier( + @12-15 Identifier( "foo", ), - |L 1-1, C 6-7| Num( + @18-19 Num( "1", ), ), @@ -14,12 +14,12 @@ ), ], ), - |L 4-4, C 0-10| SpaceBefore( + @33-43 SpaceBefore( Body( - |L 4-4, C 0-3| Identifier( + @33-36 Identifier( "bar", ), - |L 4-4, C 6-10| Str( + @39-43 Str( PlainLine( "hi", ), @@ -33,13 +33,13 @@ ), ], ), - |L 5-5, C 0-13| SpaceAfter( + @44-57 SpaceAfter( SpaceBefore( Body( - |L 5-5, C 0-3| Identifier( + @44-47 Identifier( "baz", ), - |L 5-5, C 6-13| Str( + @50-57 Str( PlainLine( "stuff", ), diff --git a/compiler/parse/tests/snapshots/pass/sub_var_with_spaces.expr.result-ast b/compiler/parse/tests/snapshots/pass/sub_var_with_spaces.expr.result-ast index a0930ee9cf..2e4b2c87d2 100644 --- a/compiler/parse/tests/snapshots/pass/sub_var_with_spaces.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/sub_var_with_spaces.expr.result-ast @@ -1,14 +1,14 @@ BinOps( [ ( - |L 0-0, C 0-1| Var { + @0-1 Var { module_name: "", ident: "x", }, - |L 0-0, C 2-3| Minus, + @2-3 Minus, ), ], - |L 0-0, C 4-5| Num( + @4-5 Num( "2", ), ) diff --git a/compiler/parse/tests/snapshots/pass/sub_with_spaces.expr.result-ast b/compiler/parse/tests/snapshots/pass/sub_with_spaces.expr.result-ast index f68168b626..849e5609fd 100644 --- a/compiler/parse/tests/snapshots/pass/sub_with_spaces.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/sub_with_spaces.expr.result-ast @@ -1,13 +1,13 @@ BinOps( [ ( - |L 0-0, C 0-1| Num( + @0-1 Num( "1", ), - |L 0-0, C 3-4| Minus, + @3-4 Minus, ), ], - |L 0-0, C 7-8| Num( + @7-8 Num( "2", ), ) diff --git a/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast b/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast index 921fe3c6ad..93bc481021 100644 --- a/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast @@ -1,10 +1,10 @@ Closure( [ - |L 0-0, C 1-6| GlobalTag( + @1-6 GlobalTag( "Thing", ), ], - |L 0-0, C 10-12| Num( + @10-12 Num( "42", ), ) diff --git a/compiler/parse/tests/snapshots/pass/ten_times_eleven.expr.result-ast b/compiler/parse/tests/snapshots/pass/ten_times_eleven.expr.result-ast index 431a12166e..6b4d587eb8 100644 --- a/compiler/parse/tests/snapshots/pass/ten_times_eleven.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/ten_times_eleven.expr.result-ast @@ -1,13 +1,13 @@ BinOps( [ ( - |L 0-0, C 0-2| Num( + @0-2 Num( "10", ), - |L 0-0, C 2-3| Star, + @2-3 Star, ), ], - |L 0-0, C 3-5| Num( + @3-5 Num( "11", ), ) diff --git a/compiler/parse/tests/snapshots/pass/three_arg_closure.expr.result-ast b/compiler/parse/tests/snapshots/pass/three_arg_closure.expr.result-ast index e40979f575..72f726931a 100644 --- a/compiler/parse/tests/snapshots/pass/three_arg_closure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/three_arg_closure.expr.result-ast @@ -1,16 +1,16 @@ Closure( [ - |L 0-0, C 1-2| Identifier( + @1-2 Identifier( "a", ), - |L 0-0, C 4-5| Identifier( + @4-5 Identifier( "b", ), - |L 0-0, C 7-8| Identifier( + @7-8 Identifier( "c", ), ], - |L 0-0, C 12-14| Num( + @12-14 Num( "42", ), ) diff --git a/compiler/parse/tests/snapshots/pass/two_arg_closure.expr.result-ast b/compiler/parse/tests/snapshots/pass/two_arg_closure.expr.result-ast index cba1cb7d83..84d9721826 100644 --- a/compiler/parse/tests/snapshots/pass/two_arg_closure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/two_arg_closure.expr.result-ast @@ -1,13 +1,13 @@ Closure( [ - |L 0-0, C 1-2| Identifier( + @1-2 Identifier( "a", ), - |L 0-0, C 4-5| Identifier( + @4-5 Identifier( "b", ), ], - |L 0-0, C 9-11| Num( + @9-11 Num( "42", ), ) diff --git a/compiler/parse/tests/snapshots/pass/two_backpassing.expr.result-ast b/compiler/parse/tests/snapshots/pass/two_backpassing.expr.result-ast index d1fefd06d4..4792ed7351 100644 --- a/compiler/parse/tests/snapshots/pass/two_backpassing.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/two_backpassing.expr.result-ast @@ -1,34 +1,34 @@ SpaceBefore( Backpassing( [ - |L 1-1, C 0-1| Identifier( + @18-19 Identifier( "x", ), ], - |L 1-1, C 5-14| ParensAround( + @23-32 ParensAround( Closure( [ - |L 1-1, C 7-8| Identifier( + @25-26 Identifier( "y", ), ], - |L 1-1, C 12-13| Var { + @30-31 Var { module_name: "", ident: "y", }, ), ), - |L 2-4, C 0-1| SpaceBefore( + @33-43 SpaceBefore( Backpassing( [ - |L 2-2, C 0-1| Identifier( + @33-34 Identifier( "z", ), ], - |L 2-2, C 5-7| Record( + @38-40 Record( [], ), - |L 4-4, C 0-1| SpaceBefore( + @42-43 SpaceBefore( Var { module_name: "", ident: "x", diff --git a/compiler/parse/tests/snapshots/pass/two_backpassing.expr.roc b/compiler/parse/tests/snapshots/pass/two_backpassing.expr.roc index f36a289c6c..051228bc5e 100644 --- a/compiler/parse/tests/snapshots/pass/two_backpassing.expr.roc +++ b/compiler/parse/tests/snapshots/pass/two_backpassing.expr.roc @@ -1,5 +1,5 @@ # leading comment x <- (\y -> y) -z <- {} +z <- {} x diff --git a/compiler/parse/tests/snapshots/pass/two_branch_when.expr.result-ast b/compiler/parse/tests/snapshots/pass/two_branch_when.expr.result-ast index bfda25dc05..7cab905c51 100644 --- a/compiler/parse/tests/snapshots/pass/two_branch_when.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/two_branch_when.expr.result-ast @@ -1,12 +1,12 @@ When( - |L 0-0, C 5-6| Var { + @5-6 Var { module_name: "", ident: "x", }, [ WhenBranch { patterns: [ - |L 1-1, C 1-3| SpaceBefore( + @11-13 SpaceBefore( StrLiteral( PlainLine( "", @@ -17,14 +17,14 @@ When( ], ), ], - value: |L 1-1, C 7-8| Num( + value: @17-18 Num( "1", ), guard: None, }, WhenBranch { patterns: [ - |L 2-2, C 1-7| SpaceBefore( + @20-26 SpaceBefore( StrLiteral( PlainLine( "mise", @@ -35,7 +35,7 @@ When( ], ), ], - value: |L 2-2, C 11-12| Num( + value: @30-31 Num( "2", ), guard: None, diff --git a/compiler/parse/tests/snapshots/pass/two_spaced_def.expr.result-ast b/compiler/parse/tests/snapshots/pass/two_spaced_def.expr.result-ast index 4b7817ea4c..7ae7c0abd2 100644 --- a/compiler/parse/tests/snapshots/pass/two_spaced_def.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/two_spaced_def.expr.result-ast @@ -1,20 +1,20 @@ SpaceBefore( Defs( [ - |L 1-1, C 0-5| Body( - |L 1-1, C 0-1| Identifier( + @18-23 Body( + @18-19 Identifier( "x", ), - |L 1-1, C 4-5| Num( + @22-23 Num( "5", ), ), - |L 2-2, C 0-5| SpaceBefore( + @24-29 SpaceBefore( Body( - |L 2-2, C 0-1| Identifier( + @24-25 Identifier( "y", ), - |L 2-2, C 4-5| Num( + @28-29 Num( "6", ), ), @@ -23,7 +23,7 @@ SpaceBefore( ], ), ], - |L 4-4, C 0-2| SpaceBefore( + @31-33 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast b/compiler/parse/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast index 3408a90669..ca8a45f681 100644 --- a/compiler/parse/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast @@ -1,33 +1,33 @@ Defs( [ - |L 0-0, C 0-30| Annotation( - |L 0-0, C 0-7| Identifier( + @0-30 Annotation( + @0-7 Identifier( "doStuff", ), - |L 0-0, C 20-30| Function( + @20-30 Function( [ - |L 0-0, C 10-16| Apply( + @10-16 Apply( "", "UserId", [], ), ], - |L 0-0, C 20-30| Apply( + @20-30 Apply( "", "Task", [ - |L 0-0, C 25-28| Apply( + @25-28 Apply( "", "Str", [], ), - |L 0-0, C 29-30| Inferred, + @29-30 Inferred, ], ), ), ), ], - |L 1-1, C 0-2| SpaceBefore( + @31-33 SpaceBefore( Num( "42", ), diff --git a/compiler/parse/tests/snapshots/pass/unary_negation.expr.result-ast b/compiler/parse/tests/snapshots/pass/unary_negation.expr.result-ast index 0b69481d27..ded94596fe 100644 --- a/compiler/parse/tests/snapshots/pass/unary_negation.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/unary_negation.expr.result-ast @@ -1,7 +1,7 @@ UnaryOp( - |L 0-0, C 1-4| Var { + @1-4 Var { module_name: "", ident: "foo", }, - |L 0-0, C 0-1| Negate, + @0-1 Negate, ) diff --git a/compiler/parse/tests/snapshots/pass/unary_negation_access.expr.result-ast b/compiler/parse/tests/snapshots/pass/unary_negation_access.expr.result-ast index bac391b5bb..8a32af8c8a 100644 --- a/compiler/parse/tests/snapshots/pass/unary_negation_access.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/unary_negation_access.expr.result-ast @@ -1,10 +1,10 @@ UnaryOp( - |L 0-0, C 1-11| Access( + @1-11 Access( Var { module_name: "", ident: "rec1", }, "field", ), - |L 0-0, C 0-1| Negate, + @0-1 Negate, ) diff --git a/compiler/parse/tests/snapshots/pass/unary_negation_arg.expr.result-ast b/compiler/parse/tests/snapshots/pass/unary_negation_arg.expr.result-ast index b584acfb8b..f42c07d6fd 100644 --- a/compiler/parse/tests/snapshots/pass/unary_negation_arg.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/unary_negation_arg.expr.result-ast @@ -1,18 +1,18 @@ Apply( - |L 0-0, C 0-4| Var { + @0-4 Var { module_name: "", ident: "whee", }, [ - |L 0-0, C 6-8| Num( + @6-8 Num( "12", ), - |L 0-0, C 9-13| UnaryOp( - |L 0-0, C 10-13| Var { + @9-13 UnaryOp( + @10-13 Var { module_name: "", ident: "foo", }, - |L 0-0, C 9-10| Negate, + @9-10 Negate, ), ], Space, diff --git a/compiler/parse/tests/snapshots/pass/unary_negation_with_parens.expr.result-ast b/compiler/parse/tests/snapshots/pass/unary_negation_with_parens.expr.result-ast index 995295897e..acb507d2b2 100644 --- a/compiler/parse/tests/snapshots/pass/unary_negation_with_parens.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/unary_negation_with_parens.expr.result-ast @@ -1,15 +1,15 @@ UnaryOp( - |L 0-0, C 2-14| ParensAround( + @2-14 ParensAround( Apply( - |L 0-0, C 2-6| Var { + @2-6 Var { module_name: "", ident: "whee", }, [ - |L 0-0, C 8-10| Num( + @8-10 Num( "12", ), - |L 0-0, C 11-14| Var { + @11-14 Var { module_name: "", ident: "foo", }, @@ -17,5 +17,5 @@ UnaryOp( Space, ), ), - |L 0-0, C 0-1| Negate, + @0-1 Negate, ) diff --git a/compiler/parse/tests/snapshots/pass/unary_not.expr.result-ast b/compiler/parse/tests/snapshots/pass/unary_not.expr.result-ast index 1264450798..87170ac08a 100644 --- a/compiler/parse/tests/snapshots/pass/unary_not.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/unary_not.expr.result-ast @@ -1,7 +1,7 @@ UnaryOp( - |L 0-0, C 1-5| Var { + @1-5 Var { module_name: "", ident: "blah", }, - |L 0-0, C 0-1| Not, + @0-1 Not, ) diff --git a/compiler/parse/tests/snapshots/pass/unary_not_with_parens.expr.result-ast b/compiler/parse/tests/snapshots/pass/unary_not_with_parens.expr.result-ast index 4d744df18b..b1f93ec376 100644 --- a/compiler/parse/tests/snapshots/pass/unary_not_with_parens.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/unary_not_with_parens.expr.result-ast @@ -1,15 +1,15 @@ UnaryOp( - |L 0-0, C 2-14| ParensAround( + @2-14 ParensAround( Apply( - |L 0-0, C 2-6| Var { + @2-6 Var { module_name: "", ident: "whee", }, [ - |L 0-0, C 8-10| Num( + @8-10 Num( "12", ), - |L 0-0, C 11-14| Var { + @11-14 Var { module_name: "", ident: "foo", }, @@ -17,5 +17,5 @@ UnaryOp( Space, ), ), - |L 0-0, C 0-1| Not, + @0-1 Not, ) diff --git a/compiler/parse/tests/snapshots/pass/underscore_backpassing.expr.result-ast b/compiler/parse/tests/snapshots/pass/underscore_backpassing.expr.result-ast index afb4402f55..744f023e4f 100644 --- a/compiler/parse/tests/snapshots/pass/underscore_backpassing.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/underscore_backpassing.expr.result-ast @@ -1,24 +1,24 @@ SpaceBefore( Backpassing( [ - |L 1-1, C 0-1| Underscore( + @18-19 Underscore( "", ), ], - |L 1-1, C 5-14| ParensAround( + @23-32 ParensAround( Closure( [ - |L 1-1, C 7-8| Identifier( + @25-26 Identifier( "y", ), ], - |L 1-1, C 12-13| Var { + @30-31 Var { module_name: "", ident: "y", }, ), ), - |L 3-3, C 0-1| SpaceBefore( + @34-35 SpaceBefore( Num( "4", ), diff --git a/compiler/parse/tests/snapshots/pass/var_minus_two.expr.result-ast b/compiler/parse/tests/snapshots/pass/var_minus_two.expr.result-ast index f3eedcea65..499046f993 100644 --- a/compiler/parse/tests/snapshots/pass/var_minus_two.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/var_minus_two.expr.result-ast @@ -1,14 +1,14 @@ BinOps( [ ( - |L 0-0, C 0-1| Var { + @0-1 Var { module_name: "", ident: "x", }, - |L 0-0, C 1-2| Minus, + @1-2 Minus, ), ], - |L 0-0, C 2-3| Num( + @2-3 Num( "2", ), ) diff --git a/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast index 83f3424244..e0f8d41ece 100644 --- a/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast @@ -1,12 +1,12 @@ When( - |L 0-0, C 5-6| Var { + @5-6 Var { module_name: "", ident: "x", }, [ WhenBranch { patterns: [ - |L 1-1, C 4-5| SpaceBefore( + @14-15 SpaceBefore( Underscore( "", ), @@ -15,7 +15,7 @@ When( ], ), ], - value: |L 2-2, C 8-9| SpaceBefore( + value: @27-28 SpaceBefore( Num( "1", ), @@ -27,7 +27,7 @@ When( }, WhenBranch { patterns: [ - |L 4-4, C 4-5| SpaceBefore( + @34-35 SpaceBefore( Underscore( "", ), @@ -37,7 +37,7 @@ When( ], ), ], - value: |L 5-5, C 8-9| SpaceBefore( + value: @47-48 SpaceBefore( Num( "2", ), @@ -49,7 +49,7 @@ When( }, WhenBranch { patterns: [ - |L 7-7, C 4-6| SpaceBefore( + @54-56 SpaceBefore( GlobalTag( "Ok", ), @@ -59,7 +59,7 @@ When( ], ), ], - value: |L 8-8, C 8-9| SpaceBefore( + value: @68-69 SpaceBefore( Num( "3", ), diff --git a/compiler/parse/tests/snapshots/pass/when_if_guard.expr.roc b/compiler/parse/tests/snapshots/pass/when_if_guard.expr.roc index 392b1fe99f..e696ac51ae 100644 --- a/compiler/parse/tests/snapshots/pass/when_if_guard.expr.roc +++ b/compiler/parse/tests/snapshots/pass/when_if_guard.expr.roc @@ -1,9 +1,9 @@ when x is - _ -> + _ -> 1 - _ -> + _ -> 2 - Ok -> + Ok -> 3 diff --git a/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast index 324e03f7ce..9192d1f1b6 100644 --- a/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast @@ -1,13 +1,13 @@ ParensAround( When( - |L 0-0, C 6-7| Var { + @6-7 Var { module_name: "", ident: "x", }, [ WhenBranch { patterns: [ - |L 1-1, C 4-6| SpaceBefore( + @15-17 SpaceBefore( GlobalTag( "Ok", ), @@ -16,7 +16,7 @@ ParensAround( ], ), ], - value: |L 2-2, C 8-9| SpaceBefore( + value: @29-30 SpaceBefore( Num( "3", ), diff --git a/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast index b7fcacfb2d..6f74fb5e4d 100644 --- a/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast @@ -1,14 +1,14 @@ ParensAround( SpaceAfter( When( - |L 0-0, C 6-7| Var { + @6-7 Var { module_name: "", ident: "x", }, [ WhenBranch { patterns: [ - |L 1-1, C 4-6| SpaceBefore( + @15-17 SpaceBefore( GlobalTag( "Ok", ), @@ -17,7 +17,7 @@ ParensAround( ], ), ], - value: |L 1-1, C 10-11| Num( + value: @21-22 Num( "3", ), guard: None, diff --git a/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.roc b/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.roc index 21bd43313f..a599c446fc 100644 --- a/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.roc +++ b/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.roc @@ -1,3 +1,3 @@ (when x is - Ok -> 3 + Ok -> 3 ) diff --git a/compiler/parse/tests/snapshots/pass/when_with_alternative_patterns.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_alternative_patterns.expr.result-ast index 46a12593ae..62dd86481e 100644 --- a/compiler/parse/tests/snapshots/pass/when_with_alternative_patterns.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_with_alternative_patterns.expr.result-ast @@ -1,12 +1,12 @@ When( - |L 0-0, C 5-6| Var { + @5-6 Var { module_name: "", ident: "x", }, [ WhenBranch { patterns: [ - |L 1-1, C 1-7| SpaceBefore( + @11-17 SpaceBefore( StrLiteral( PlainLine( "blah", @@ -16,20 +16,20 @@ When( Newline, ], ), - |L 1-1, C 10-16| StrLiteral( + @20-26 StrLiteral( PlainLine( "blop", ), ), ], - value: |L 1-1, C 20-21| Num( + value: @30-31 Num( "1", ), guard: None, }, WhenBranch { patterns: [ - |L 2-2, C 1-6| SpaceBefore( + @33-38 SpaceBefore( StrLiteral( PlainLine( "foo", @@ -39,7 +39,7 @@ When( Newline, ], ), - |L 3-3, C 2-7| SpaceBefore( + @43-48 SpaceBefore( StrLiteral( PlainLine( "bar", @@ -50,7 +50,7 @@ When( ], ), ], - value: |L 3-3, C 11-12| Num( + value: @52-53 Num( "2", ), guard: None, diff --git a/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.result-ast index 6438516e8d..1822dfa962 100644 --- a/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.result-ast @@ -1,12 +1,12 @@ When( - |L 0-0, C 5-6| Var { + @5-6 Var { module_name: "", ident: "x", }, [ WhenBranch { patterns: [ - |L 1-1, C 4-5| SpaceBefore( + @14-15 SpaceBefore( NumLiteral( "1", ), @@ -15,13 +15,13 @@ When( ], ), ], - value: |L 1-2, C 9-6| Apply( - |L 1-1, C 9-16| Var { + value: @19-33 Apply( + @19-26 Var { module_name: "Num", ident: "neg", }, [ - |L 2-2, C 5-6| SpaceBefore( + @32-33 SpaceBefore( Num( "2", ), @@ -36,7 +36,7 @@ When( }, WhenBranch { patterns: [ - |L 3-3, C 4-5| SpaceBefore( + @38-39 SpaceBefore( Underscore( "", ), @@ -45,7 +45,7 @@ When( ], ), ], - value: |L 3-3, C 9-10| Num( + value: @43-44 Num( "4", ), guard: None, diff --git a/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.roc b/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.roc index fd5da76dec..647ce0d14a 100644 --- a/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.roc +++ b/compiler/parse/tests/snapshots/pass/when_with_function_application.expr.roc @@ -1,4 +1,4 @@ when x is 1 -> Num.neg - 2 + 2 _ -> 4 diff --git a/compiler/parse/tests/snapshots/pass/when_with_negative_numbers.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_negative_numbers.expr.result-ast index b1c09c7e0c..8c6a5e4e61 100644 --- a/compiler/parse/tests/snapshots/pass/when_with_negative_numbers.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_with_negative_numbers.expr.result-ast @@ -1,12 +1,12 @@ When( - |L 0-0, C 5-6| Var { + @5-6 Var { module_name: "", ident: "x", }, [ WhenBranch { patterns: [ - |L 1-1, C 1-2| SpaceBefore( + @11-12 SpaceBefore( NumLiteral( "1", ), @@ -15,14 +15,14 @@ When( ], ), ], - value: |L 1-1, C 6-7| Num( + value: @16-17 Num( "2", ), guard: None, }, WhenBranch { patterns: [ - |L 2-2, C 1-3| SpaceBefore( + @19-21 SpaceBefore( NumLiteral( "-3", ), @@ -31,7 +31,7 @@ When( ], ), ], - value: |L 2-2, C 7-8| Num( + value: @25-26 Num( "4", ), guard: None, diff --git a/compiler/parse/tests/snapshots/pass/when_with_numbers.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_numbers.expr.result-ast index e1ed86ded5..8866aef903 100644 --- a/compiler/parse/tests/snapshots/pass/when_with_numbers.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_with_numbers.expr.result-ast @@ -1,12 +1,12 @@ When( - |L 0-0, C 5-6| Var { + @5-6 Var { module_name: "", ident: "x", }, [ WhenBranch { patterns: [ - |L 1-1, C 1-2| SpaceBefore( + @11-12 SpaceBefore( NumLiteral( "1", ), @@ -15,14 +15,14 @@ When( ], ), ], - value: |L 1-1, C 6-7| Num( + value: @16-17 Num( "2", ), guard: None, }, WhenBranch { patterns: [ - |L 2-2, C 1-2| SpaceBefore( + @19-20 SpaceBefore( NumLiteral( "3", ), @@ -31,7 +31,7 @@ When( ], ), ], - value: |L 2-2, C 6-7| Num( + value: @24-25 Num( "4", ), guard: None, diff --git a/compiler/parse/tests/snapshots/pass/when_with_records.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_with_records.expr.result-ast index 174cc8abb2..cfb5251e17 100644 --- a/compiler/parse/tests/snapshots/pass/when_with_records.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_with_records.expr.result-ast @@ -1,15 +1,15 @@ When( - |L 0-0, C 5-6| Var { + @5-6 Var { module_name: "", ident: "x", }, [ WhenBranch { patterns: [ - |L 1-1, C 1-6| SpaceBefore( + @11-16 SpaceBefore( RecordDestructure( [ - |L 1-1, C 3-4| Identifier( + @13-14 Identifier( "y", ), ], @@ -19,20 +19,20 @@ When( ], ), ], - value: |L 1-1, C 10-11| Num( + value: @20-21 Num( "2", ), guard: None, }, WhenBranch { patterns: [ - |L 2-2, C 1-9| SpaceBefore( + @23-31 SpaceBefore( RecordDestructure( [ - |L 2-2, C 3-4| Identifier( + @25-26 Identifier( "z", ), - |L 2-2, C 6-7| Identifier( + @28-29 Identifier( "w", ), ], @@ -42,7 +42,7 @@ When( ], ), ], - value: |L 2-2, C 13-14| Num( + value: @35-36 Num( "4", ), guard: None, diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index 3dc061201e..b42d3edca6 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -130,8 +130,11 @@ mod test_parse { pass/comment_before_op.expr, pass/comment_inside_empty_list.expr, pass/comment_with_non_ascii.expr, + pass/destructure_tag_assignment.expr, pass/empty_app_header.header, pass/empty_interface_header.header, + pass/empty_hosted_header.header, + pass/nonempty_hosted_header.header, pass/empty_list.expr, pass/empty_platform_header.header, pass/empty_record.expr, @@ -147,6 +150,7 @@ mod test_parse { pass/highest_int.expr, pass/if_def.expr, pass/int_with_underscore.expr, + pass/interface_with_newline.header, pass/lowest_float.expr, pass/lowest_int.expr, pass/malformed_ident_due_to_underscore.expr, @@ -178,6 +182,7 @@ mod test_parse { pass/newline_singleton_list.expr, pass/nonempty_platform_header.header, pass/not_docs.expr, + pass/number_literal_suffixes.expr, pass/one_backpassing.expr, pass/one_char_string.expr, pass/one_def.expr, @@ -197,6 +202,7 @@ mod test_parse { pass/positive_float.expr, pass/positive_int.expr, pass/private_qualified_tag.expr, + pass/provides_type.header, pass/qualified_field.expr, pass/qualified_global_tag.expr, pass/qualified_var.expr, @@ -204,6 +210,7 @@ mod test_parse { pass/record_func_type_decl.expr, pass/record_update.expr, pass/record_with_if.expr, + pass/requires_type.header, pass/single_arg_closure.expr, pass/single_underscore_closure.expr, pass/space_only_after_minus.expr, @@ -263,8 +270,14 @@ mod test_parse { let result = func(&input); let actual_result = if should_pass { + eprintln!("The source code for this test did not successfully parse!\n"); + result.unwrap() } else { + eprintln!( + "The source code for this test successfully parsed, but it was not expected to!\n" + ); + result.unwrap_err() }; @@ -365,7 +378,7 @@ mod test_parse { assert_segments(r#""Hi, \u(123)!""#, |arena| { bumpalo::vec![in arena; Plaintext("Hi, "), - Unicode(Loc::new(0, 0, 8, 11, "123")), + Unicode(Loc::new(8, 11, "123")), Plaintext("!") ] }); @@ -375,7 +388,7 @@ mod test_parse { fn unicode_escape_in_front() { assert_segments(r#""\u(1234) is a unicode char""#, |arena| { bumpalo::vec![in arena; - Unicode(Loc::new(0, 0, 4, 8, "1234")), + Unicode(Loc::new(4, 8, "1234")), Plaintext(" is a unicode char") ] }); @@ -386,7 +399,7 @@ mod test_parse { assert_segments(r#""this is unicode: \u(1)""#, |arena| { bumpalo::vec![in arena; Plaintext("this is unicode: "), - Unicode(Loc::new(0, 0, 21, 22, "1")) + Unicode(Loc::new(21, 22, "1")) ] }); } @@ -395,11 +408,11 @@ mod test_parse { fn unicode_escape_multiple() { assert_segments(r#""\u(a1) this is \u(2Bcd) unicode \u(ef97)""#, |arena| { bumpalo::vec![in arena; - Unicode(Loc::new(0, 0, 4, 6, "a1")), + Unicode(Loc::new(4, 6, "a1")), Plaintext(" this is "), - Unicode(Loc::new(0, 0, 19, 23, "2Bcd")), + Unicode(Loc::new(19, 23, "2Bcd")), Plaintext(" unicode "), - Unicode(Loc::new(0, 0, 36, 40, "ef97")) + Unicode(Loc::new(36, 40, "ef97")) ] }); } @@ -416,7 +429,7 @@ mod test_parse { bumpalo::vec![in arena; Plaintext("Hi, "), - Interpolated(Loc::new(0, 0, 7, 11, expr)), + Interpolated(Loc::new(7, 11, expr)), Plaintext("!") ] }); @@ -431,7 +444,7 @@ mod test_parse { }); bumpalo::vec![in arena; - Interpolated(Loc::new(0, 0, 3, 7, expr)), + Interpolated(Loc::new(3, 7, expr)), Plaintext(", hi!") ] }); @@ -447,7 +460,7 @@ mod test_parse { bumpalo::vec![in arena; Plaintext("Hello "), - Interpolated(Loc::new(0, 0, 9, 13, expr)) + Interpolated(Loc::new(9, 13, expr)) ] }); } @@ -467,9 +480,9 @@ mod test_parse { bumpalo::vec![in arena; Plaintext("Hi, "), - Interpolated(Loc::new(0, 0, 7, 11, expr1)), + Interpolated(Loc::new(7, 11, expr1)), Plaintext("! How is "), - Interpolated(Loc::new(0, 0, 23, 30, expr2)), + Interpolated(Loc::new(23, 30, expr2)), Plaintext(" going?") ] }); @@ -480,23 +493,6 @@ mod test_parse { assert_parsing_fails("", SyntaxError::Eof(Region::zero())); } - #[test] - fn first_line_too_long() { - let max_line_length = u16::MAX as usize; - - // the string literal "ZZZZZZZZZ" but with way more Zs - let too_long_str_body: String = (1..max_line_length) - .into_iter() - .map(|_| "Z".to_string()) - .collect(); - let too_long_str = format!("\"{}\"", too_long_str_body); - - // Make sure it's longer than our maximum line length - assert_eq!(too_long_str.len(), max_line_length + 1); - - assert_parsing_fails(&too_long_str, SyntaxError::LineTooLong(0)); - } - #[quickcheck] fn all_i64_values_parse(num: i64) { assert_parses_to(num.to_string().as_str(), Num(num.to_string().as_str())); diff --git a/compiler/problem/src/can.rs b/compiler/problem/src/can.rs index d9e910becb..a4eeddebe7 100644 --- a/compiler/problem/src/can.rs +++ b/compiler/problem/src/can.rs @@ -25,6 +25,7 @@ pub enum Problem { UnusedDef(Symbol, Region), UnusedImport(ModuleId, Region), ExposedButNotDefined(Symbol), + UnknownGeneratesWith(Loc), /// First symbol is the name of the closure with that argument /// Second symbol is the name of the argument that is unused UnusedArgument(Symbol, Symbol, Region), @@ -78,6 +79,11 @@ pub enum Problem { InvalidInterpolation(Region), InvalidHexadecimal(Region), InvalidUnicodeCodePt(Region), + NestedDatatype { + alias: Symbol, + def_region: Region, + differing_recursion_region: Region, + }, } #[derive(Clone, Debug, PartialEq)] @@ -102,6 +108,18 @@ pub enum IntErrorKind { Overflow, /// Integer is too small to store in target integer type. Underflow, + /// This is an integer, but it has a float numeric suffix. + FloatSuffix, + /// The integer literal overflows the width of the suffix associated with it. + OverflowsSuffix { + suffix_type: &'static str, + max_value: u128, + }, + /// The integer literal underflows the width of the suffix associated with it. + UnderflowsSuffix { + suffix_type: &'static str, + min_value: i128, + }, } /// Enum to store the various types of errors that can cause parsing a float to fail. @@ -113,6 +131,8 @@ pub enum FloatErrorKind { NegativeInfinity, /// the literal is too large for f64 PositiveInfinity, + /// This is a float, but it has an integer numeric suffix. + IntSuffix, } #[derive(Clone, Debug, PartialEq)] diff --git a/compiler/region/Cargo.toml b/compiler/region/Cargo.toml index 4ca1e3d4e3..ecea4be54a 100644 --- a/compiler/region/Cargo.toml +++ b/compiler/region/Cargo.toml @@ -4,3 +4,6 @@ version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" edition = "2018" + +[dependencies] +static_assertions = "1.1.0" diff --git a/compiler/region/src/all.rs b/compiler/region/src/all.rs index a84bbe785b..1481e4a735 100644 --- a/compiler/region/src/all.rs +++ b/compiler/region/src/all.rs @@ -2,58 +2,34 @@ use std::fmt::{self, Debug}; #[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Default)] pub struct Region { - start_line: u32, - end_line: u32, - start_col: u16, - end_col: u16, + start: Position, + end: Position, } impl Region { pub const fn zero() -> Self { Region { - start_line: 0, - end_line: 0, - start_col: 0, - end_col: 0, + start: Position::zero(), + end: Position::zero(), } } pub const fn new(start: Position, end: Position) -> Self { - Self { - start_line: start.line, - end_line: end.line, - start_col: start.column, - end_col: end.column, - } + Self { start, end } } pub fn contains(&self, other: &Self) -> bool { - use std::cmp::Ordering::*; - match self.start_line.cmp(&other.start_line) { - Greater => false, - Equal => match self.end_line.cmp(&other.end_line) { - Less => false, - Equal => self.start_col <= other.start_col && self.end_col >= other.end_col, - Greater => self.start_col >= other.start_col, - }, - Less => match self.end_line.cmp(&other.end_line) { - Less => false, - Equal => self.end_col >= other.end_col, - Greater => true, - }, - } + self.start <= other.start && self.end >= other.end } pub fn is_empty(&self) -> bool { - self.end_line == self.start_line && self.start_col == self.end_col + self.start == self.end } pub fn span_across(start: &Region, end: &Region) -> Self { Region { - start_line: start.start_line, - end_line: end.end_line, - start_col: start.start_col, - end_col: end.end_col, + start: start.start, + end: end.end, } } @@ -76,68 +52,212 @@ impl Region { } } - pub fn lines_between(&self, other: &Region) -> u32 { - if self.end_line <= other.start_line { - other.start_line - self.end_line - } else if self.start_line >= other.end_line { - self.start_line - other.end_line + pub const fn from_pos(pos: Position) -> Self { + Region { + start: pos, + end: pos.bump_column(1), + } + } + + pub const fn start(&self) -> Position { + self.start + } + + pub const fn end(&self) -> Position { + self.end + } + + pub const fn between(start: Position, end: Position) -> Self { + Self::new(start, end) + } +} + +// Region is used all over the place. Avoid increasing its size! +static_assertions::assert_eq_size!([u8; 8], Region); + +impl fmt::Debug for Region { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.start == Position::zero() && self.end == Position::zero() { + // In tests, it's super common to set all Located values to 0. + // Also in tests, we don't want to bother printing the locations + // because it makes failed assertions much harder to read. + write!(f, "…") + } else { + write!(f, "@{}-{}", self.start.offset, self.end.offset,) + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct Position { + pub offset: u32, +} + +impl Position { + pub const fn zero() -> Position { + Position { offset: 0 } + } + + pub const fn new(offset: u32) -> Position { + Position { offset } + } + + #[must_use] + pub const fn bump_column(self, count: u32) -> Self { + Self { + offset: self.offset + count as u32, + } + } + + #[must_use] + pub fn bump_invisible(self, count: u32) -> Self { + Self { + offset: self.offset + count as u32, + } + } + + #[must_use] + pub fn bump_newline(self) -> Self { + Self { + offset: self.offset + 1, + } + } + + #[must_use] + pub const fn sub(self, count: u32) -> Self { + Self { + offset: self.offset - count as u32, + } + } +} + +impl Debug for Position { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "@{}", self.offset) + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Default)] +pub struct LineColumn { + pub line: u32, + pub column: u32, +} + +impl LineColumn { + pub const fn zero() -> Self { + LineColumn { line: 0, column: 0 } + } + + #[must_use] + pub const fn bump_column(self, count: u32) -> Self { + Self { + line: self.line, + column: self.column + count, + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Default)] +pub struct LineColumnRegion { + pub start: LineColumn, + pub end: LineColumn, +} + +impl LineColumnRegion { + pub const fn new(start: LineColumn, end: LineColumn) -> Self { + LineColumnRegion { start, end } + } + + pub const fn zero() -> Self { + LineColumnRegion { + start: LineColumn::zero(), + end: LineColumn::zero(), + } + } + + pub fn contains(&self, other: &Self) -> bool { + use std::cmp::Ordering::*; + match self.start.line.cmp(&other.start.line) { + Greater => false, + Equal => match self.end.line.cmp(&other.end.line) { + Less => false, + Equal => { + self.start.column <= other.start.column && self.end.column >= other.end.column + } + Greater => self.start.column >= other.start.column, + }, + Less => match self.end.line.cmp(&other.end.line) { + Less => false, + Equal => self.end.column >= other.end.column, + Greater => true, + }, + } + } + + pub const fn from_pos(pos: LineColumn) -> Self { + Self { + start: pos, + end: pos.bump_column(1), + } + } + + pub fn is_empty(&self) -> bool { + self.end.line == self.start.line && self.start.column == self.end.column + } + + pub fn span_across(start: &LineColumnRegion, end: &LineColumnRegion) -> Self { + LineColumnRegion { + start: start.start, + end: end.end, + } + } + + pub fn across_all<'a, I>(regions: I) -> Self + where + I: IntoIterator, + { + let mut it = regions.into_iter(); + + if let Some(first) = it.next() { + let mut result = *first; + + for r in it { + result = Self::span_across(&result, r); + } + + result + } else { + Self::zero() + } + } + + pub fn lines_between(&self, other: &LineColumnRegion) -> u32 { + if self.end.line <= other.start.line { + other.start.line - self.end.line + } else if self.start.line >= other.end.line { + self.start.line - other.end.line } else { // intersection 0 } } - pub const fn from_pos(pos: Position) -> Self { - Region { - start_col: pos.column, - start_line: pos.line, - end_col: pos.column + 1, - end_line: pos.line, - } + pub const fn start(&self) -> LineColumn { + self.start } - pub const fn from_rows_cols( - start_line: u32, - start_col: u16, - end_line: u32, - end_col: u16, - ) -> Self { - Region { - start_line, - end_line, - start_col, - end_col, - } - } - - pub const fn start(&self) -> Position { - Position { - line: self.start_line, - column: self.start_col, - } - } - - pub const fn end(&self) -> Position { - Position { - line: self.end_line, - column: self.end_col, - } - } - - pub const fn between(start: Position, end: Position) -> Self { - Self::from_rows_cols(start.line, start.column, end.line, end.column) + pub const fn end(&self) -> LineColumn { + self.end } } -#[test] -fn region_size() { - // Region is used all over the place. Avoid increasing its size! - assert_eq!(std::mem::size_of::(), 12); -} - -impl fmt::Debug for Region { +impl fmt::Debug for LineColumnRegion { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if self.start_line == 0 && self.start_col == 0 && self.end_line == 0 && self.end_col == 0 { + if self.start.line == 0 + && self.start.column == 0 + && self.end.line == 0 + && self.end.column == 0 + { // In tests, it's super common to set all Located values to 0. // Also in tests, we don't want to bother printing the locations // because it makes failed assertions much harder to read. @@ -146,33 +266,12 @@ impl fmt::Debug for Region { write!( f, "|L {}-{}, C {}-{}|", - self.start_line, self.end_line, self.start_col, self.end_col, + self.start.line, self.end.line, self.start.column, self.end.column, ) } } } -#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Default)] -pub struct Position { - pub line: u32, - pub column: u16, -} - -impl Position { - pub fn bump_column(self, count: u16) -> Self { - Self { - line: self.line, - column: self.column + count, - } - } -} - -impl Debug for Position { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}:{}", self.line, self.column) - } -} - #[derive(Clone, Eq, Copy, PartialEq, PartialOrd, Ord, Hash)] pub struct Loc { pub region: Region, @@ -180,13 +279,8 @@ pub struct Loc { } impl Loc { - pub fn new(start_line: u32, end_line: u32, start_col: u16, end_col: u16, value: T) -> Loc { - let region = Region { - start_line, - end_line, - start_col, - end_col, - }; + pub fn new(start: u32, end: u32, value: T) -> Loc { + let region = Region::new(Position::new(start), Position::new(end)); Loc { region, value } } @@ -226,11 +320,7 @@ where fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let region = self.region; - if region.start_line == 0 - && region.start_col == 0 - && region.end_line == 0 - && region.end_col == 0 - { + if region.start == Position::zero() && region.end == Position::zero() { // In tests, it's super common to set all Located values to 0. // Also in tests, we don't want to bother printing the locations // because it makes failed assertions much harder to read. @@ -242,3 +332,103 @@ where } } } + +pub struct LineInfo { + line_offsets: Vec, +} + +impl LineInfo { + pub fn new(src: &str) -> LineInfo { + let mut line_offsets = vec![0]; + line_offsets.extend(src.match_indices('\n').map(|(offset, _)| offset as u32 + 1)); + LineInfo { line_offsets } + } + + pub fn convert_offset(&self, offset: u32) -> LineColumn { + let search = self.line_offsets.binary_search(&offset); + let line = match search { + Ok(i) => i, + Err(i) => i - 1, + }; + let column = offset - self.line_offsets[line]; + LineColumn { + line: line as u32, + column: column as u32, + } + } + + pub fn convert_pos(&self, pos: Position) -> LineColumn { + self.convert_offset(pos.offset) + } + + pub fn convert_region(&self, region: Region) -> LineColumnRegion { + LineColumnRegion { + start: self.convert_pos(region.start()), + end: self.convert_pos(region.end()), + } + } +} + +#[test] +fn test_line_info() { + fn char_at_line<'a>(lines: &[&'a str], line_column: LineColumn) -> &'a str { + let line = line_column.line as usize; + let line_text = if line < lines.len() { lines[line] } else { "" }; + let column = line_column.column as usize; + if column == line_text.len() { + "\n" + } else { + &line_text[column..column + 1] + } + } + + fn check_correctness(lines: &[&str]) { + let mut input = String::new(); + for (i, line) in lines.iter().enumerate() { + if i > 0 { + input.push('\n'); + } + input.push_str(line); + } + let info = LineInfo::new(&input); + + let mut last: Option = None; + + for offset in 0..=input.len() { + let expected = if offset < input.len() { + &input[offset..offset + 1] + } else { + "\n" // HACK! pretend there's an extra newline on the end, strictly so we can do the comparison + }; + println!( + "checking {:?} {:?}, expecting {:?}", + input, offset, expected + ); + let line_column = info.convert_offset(offset as u32); + assert!( + Some(line_column) > last, + "{:?} > {:?}", + Some(line_column), + last + ); + assert_eq!(expected, char_at_line(lines, line_column)); + last = Some(line_column); + } + + assert_eq!( + info.convert_offset(input.len() as u32), + LineColumn { + line: lines.len().saturating_sub(1) as u32, + column: lines.last().map(|l| l.len()).unwrap_or(0) as u32, + } + ) + } + + check_correctness(&["", "abc", "def", "", "gi"]); + + check_correctness(&[]); + + check_correctness(&["a"]); + + check_correctness(&["", ""]); +} diff --git a/compiler/roc_target/Cargo.toml b/compiler/roc_target/Cargo.toml new file mode 100644 index 0000000000..28211dbdc9 --- /dev/null +++ b/compiler/roc_target/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "roc_target" +version = "0.1.0" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +edition = "2018" + +[dependencies] +target-lexicon = "0.12.2" diff --git a/compiler/roc_target/src/lib.rs b/compiler/roc_target/src/lib.rs new file mode 100644 index 0000000000..68269a3849 --- /dev/null +++ b/compiler/roc_target/src/lib.rs @@ -0,0 +1,74 @@ +#![warn(clippy::dbg_macro)] +// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. +#![allow(clippy::large_enum_variant)] + +#[derive(Debug, Clone, Copy)] +pub struct TargetInfo { + pub architecture: Architecture, +} + +impl TargetInfo { + pub const fn ptr_width(&self) -> PtrWidth { + self.architecture.ptr_width() + } + + pub const fn default_x86_64() -> Self { + TargetInfo { + architecture: Architecture::X86_64, + } + } + + pub const fn default_wasm32() -> Self { + TargetInfo { + architecture: Architecture::Wasm32, + } + } +} + +impl From<&target_lexicon::Triple> for TargetInfo { + fn from(triple: &target_lexicon::Triple) -> Self { + let architecture = Architecture::from(triple.architecture); + + Self { architecture } + } +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy)] +pub enum PtrWidth { + Bytes4 = 4, + Bytes8 = 8, +} + +#[derive(Debug, Clone, Copy)] +pub enum Architecture { + X86_64, + X86_32, + Aarch64, + Arm, + Wasm32, +} + +impl Architecture { + pub const fn ptr_width(&self) -> PtrWidth { + use Architecture::*; + + match self { + X86_64 | Aarch64 | Arm => PtrWidth::Bytes8, + X86_32 | Wasm32 => PtrWidth::Bytes4, + } + } +} + +impl From for Architecture { + fn from(target: target_lexicon::Architecture) -> Self { + match target { + target_lexicon::Architecture::X86_64 => Architecture::X86_64, + target_lexicon::Architecture::X86_32(_) => Architecture::X86_32, + target_lexicon::Architecture::Aarch64(_) => Architecture::Aarch64, + target_lexicon::Architecture::Arm(_) => Architecture::Arm, + target_lexicon::Architecture::Wasm32 => Architecture::Wasm32, + _ => unreachable!("unsupported architecture"), + } + } +} diff --git a/compiler/solve/Cargo.toml b/compiler/solve/Cargo.toml index 89da067e95..ebb9e4a615 100644 --- a/compiler/solve/Cargo.toml +++ b/compiler/solve/Cargo.toml @@ -21,6 +21,7 @@ roc_builtins = { path = "../builtins" } roc_problem = { path = "../problem" } roc_parse = { path = "../parse" } roc_solve = { path = "../solve" } +roc_target = { path = "../roc_target" } pretty_assertions = "1.0.0" indoc = "1.0.3" tempfile = "3.2.0" diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 00b2c19042..8c2baee3e5 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -206,7 +206,7 @@ fn solve( 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); @@ -241,7 +241,7 @@ fn solve( let actual = type_to_var(subs, rank, pools, cached_aliases, source); let target = *target; - match unify(subs, actual, target, Mode::Eq) { + match unify(subs, actual, target, Mode::EQ) { Success(vars) => { introduce(subs, rank, pools, &vars); @@ -254,7 +254,7 @@ fn solve( state } - BadType(vars, _problem) => { + BadType(vars, _) => { introduce(subs, rank, pools, &vars); // ERROR NOT REPORTED @@ -295,7 +295,7 @@ fn solve( cached_aliases, 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); @@ -362,8 +362,8 @@ fn solve( ); let mode = match constraint { - Present(_, _) => Mode::Present, - _ => Mode::Eq, + Present(_, _) => Mode::PRESENT, + _ => Mode::EQ, }; match unify(subs, actual, expected, mode) { @@ -659,7 +659,7 @@ fn solve( ); let includes = type_to_var(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); @@ -771,7 +771,14 @@ fn type_to_variable<'a>( match typ { Variable(var) => *var, - Apply(symbol, arguments) => { + RangedNumber(typ, vars) => { + let ty_var = type_to_variable(subs, rank, pools, arena, typ); + let vars = VariableSubsSlice::insert_into_subs(subs, vars.iter().copied()); + let content = Content::RangedNumber(ty_var, vars); + + register(subs, rank, pools, content) + } + Apply(symbol, arguments, _) => { let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); for (target_index, var_index) in (new_arguments.indices()).zip(arguments) { let var = type_to_variable(subs, rank, pools, arena, var_index); @@ -1591,6 +1598,8 @@ fn adjust_rank_content( rank } + + RangedNumber(typ, _) => adjust_rank(subs, young_mark, visit_mark, group_rank, *typ), } } @@ -1726,6 +1735,11 @@ fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, initial: Variable) { stack.push(var); } + &RangedNumber(typ, vars) => { + stack.push(typ); + + stack.extend(var_slice!(vars)); + } } } @@ -1982,6 +1996,23 @@ fn deep_copy_var_help( copy } + + RangedNumber(typ, range_vars) => { + let new_type_var = deep_copy_var_help(subs, max_rank, pools, visited, typ); + + let new_vars = SubsSlice::reserve_into_subs(subs, range_vars.len()); + for (target_index, var_index) in (new_vars.indices()).zip(range_vars) { + let var = subs[var_index]; + let copy_var = deep_copy_var_help(subs, max_rank, pools, visited, var); + subs.variables[target_index] = copy_var; + } + + let new_content = RangedNumber(new_type_var, new_vars); + + subs.set(copy, make_descriptor(new_content)); + + copy + } } } diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index 6a55b884a9..d8c5a38d6e 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -63,7 +63,7 @@ mod solve_expr { &stdlib, dir.path(), exposed_types, - 8, + roc_target::TargetInfo::default_x86_64(), builtin_defs_map, ); @@ -1327,7 +1327,7 @@ mod solve_expr { \Foo -> 42 "# ), - "[ Foo ]* -> Num *", + "[ Foo ] -> Num *", ); } @@ -1339,7 +1339,7 @@ mod solve_expr { \@Foo -> 42 "# ), - "[ @Foo ]* -> Num *", + "[ @Foo ] -> Num *", ); } @@ -1419,7 +1419,7 @@ mod solve_expr { \Foo x -> Foo x "# ), - "[ Foo a ]* -> [ Foo a ]*", + "[ Foo a ] -> [ Foo a ]*", ); } @@ -1431,7 +1431,7 @@ mod solve_expr { \Foo x _ -> Foo x "y" "# ), - "[ Foo a * ]* -> [ Foo a Str ]*", + "[ Foo a * ] -> [ Foo a Str ]*", ); } @@ -3341,6 +3341,18 @@ mod solve_expr { ); } + #[test] + fn min_i128() { + infer_eq_without_problem( + indoc!( + r#" + Num.minI128 + "# + ), + "I128", + ); + } + #[test] fn max_i128() { infer_eq_without_problem( @@ -3353,6 +3365,102 @@ mod solve_expr { ); } + #[test] + fn min_i64() { + infer_eq_without_problem( + indoc!( + r#" + Num.minI64 + "# + ), + "I64", + ); + } + + #[test] + fn max_i64() { + infer_eq_without_problem( + indoc!( + r#" + Num.maxI64 + "# + ), + "I64", + ); + } + + #[test] + fn min_u64() { + infer_eq_without_problem( + indoc!( + r#" + Num.minU64 + "# + ), + "U64", + ); + } + + #[test] + fn max_u64() { + infer_eq_without_problem( + indoc!( + r#" + Num.maxU64 + "# + ), + "U64", + ); + } + + #[test] + fn min_i32() { + infer_eq_without_problem( + indoc!( + r#" + Num.minI32 + "# + ), + "I32", + ); + } + + #[test] + fn max_i32() { + infer_eq_without_problem( + indoc!( + r#" + Num.maxI32 + "# + ), + "I32", + ); + } + + #[test] + fn min_u32() { + infer_eq_without_problem( + indoc!( + r#" + Num.minU32 + "# + ), + "U32", + ); + } + + #[test] + fn max_u32() { + infer_eq_without_problem( + indoc!( + r#" + Num.maxU32 + "# + ), + "U32", + ); + } + #[test] fn reconstruct_path() { infer_eq_without_problem( @@ -4909,4 +5017,183 @@ mod solve_expr { "{ x : [ Blue, Red ], y ? Num a }* -> Num a", ) } + + #[test] + // Issue #2299 + fn infer_union_argument_position() { + infer_eq_without_problem( + indoc!( + r#" + \UserId id -> id + 1 + "# + ), + "[ UserId (Num a) ] -> Num a", + ) + } + + #[test] + fn infer_union_def_position() { + infer_eq_without_problem( + indoc!( + r#" + \email -> + Email str = email + Str.isEmpty str + "# + ), + "[ Email Str ] -> Bool", + ) + } + + #[test] + fn numeric_literal_suffixes() { + infer_eq_without_problem( + indoc!( + r#" + { + u8: 123u8, + u16: 123u16, + u32: 123u32, + u64: 123u64, + u128: 123u128, + + i8: 123i8, + i16: 123i16, + i32: 123i32, + i64: 123i64, + i128: 123i128, + + nat: 123nat, + + bu8: 0b11u8, + bu16: 0b11u16, + bu32: 0b11u32, + bu64: 0b11u64, + bu128: 0b11u128, + + bi8: 0b11i8, + bi16: 0b11i16, + bi32: 0b11i32, + bi64: 0b11i64, + bi128: 0b11i128, + + bnat: 0b11nat, + + dec: 123.0dec, + f32: 123.0f32, + f64: 123.0f64, + + fdec: 123dec, + ff32: 123f32, + ff64: 123f64, + } + "# + ), + r#"{ bi128 : I128, bi16 : I16, bi32 : I32, bi64 : I64, bi8 : I8, bnat : Nat, bu128 : U128, bu16 : U16, bu32 : U32, bu64 : U64, bu8 : U8, dec : Dec, f32 : F32, f64 : F64, fdec : Dec, ff32 : F32, ff64 : F64, i128 : I128, i16 : I16, i32 : I32, i64 : I64, i8 : I8, nat : Nat, u128 : U128, u16 : U16, u32 : U32, u64 : U64, u8 : U8 }"#, + ) + } + + #[test] + fn numeric_literal_suffixes_in_pattern() { + infer_eq_without_problem( + indoc!( + r#" + { + u8: (\n -> + when n is + 123u8 -> n), + u16: (\n -> + when n is + 123u16 -> n), + u32: (\n -> + when n is + 123u32 -> n), + u64: (\n -> + when n is + 123u64 -> n), + u128: (\n -> + when n is + 123u128 -> n), + + i8: (\n -> + when n is + 123i8 -> n), + i16: (\n -> + when n is + 123i16 -> n), + i32: (\n -> + when n is + 123i32 -> n), + i64: (\n -> + when n is + 123i64 -> n), + i128: (\n -> + when n is + 123i128 -> n), + + nat: (\n -> + when n is + 123nat -> n), + + bu8: (\n -> + when n is + 0b11u8 -> n), + bu16: (\n -> + when n is + 0b11u16 -> n), + bu32: (\n -> + when n is + 0b11u32 -> n), + bu64: (\n -> + when n is + 0b11u64 -> n), + bu128: (\n -> + when n is + 0b11u128 -> n), + + bi8: (\n -> + when n is + 0b11i8 -> n), + bi16: (\n -> + when n is + 0b11i16 -> n), + bi32: (\n -> + when n is + 0b11i32 -> n), + bi64: (\n -> + when n is + 0b11i64 -> n), + bi128: (\n -> + when n is + 0b11i128 -> n), + + bnat: (\n -> + when n is + 0b11nat -> n), + + dec: (\n -> + when n is + 123.0dec -> n), + f32: (\n -> + when n is + 123.0f32 -> n), + f64: (\n -> + when n is + 123.0f64 -> n), + + fdec: (\n -> + when n is + 123dec -> n), + ff32: (\n -> + when n is + 123f32 -> n), + ff64: (\n -> + when n is + 123f64 -> n), + } + "# + ), + r#"{ bi128 : I128 -> I128, bi16 : I16 -> I16, bi32 : I32 -> I32, bi64 : I64 -> I64, bi8 : I8 -> I8, bnat : Nat -> Nat, bu128 : U128 -> U128, bu16 : U16 -> U16, bu32 : U32 -> U32, bu64 : U64 -> U64, bu8 : U8 -> U8, dec : Dec -> Dec, f32 : F32 -> F32, f64 : F64 -> F64, fdec : Dec -> Dec, ff32 : F32 -> F32, ff64 : F64 -> F64, i128 : I128 -> I128, i16 : I16 -> I16, i32 : I32 -> I32, i64 : I64 -> I64, i8 : I8 -> I8, nat : Nat -> Nat, u128 : U128 -> U128, u16 : U16 -> U16, u32 : U32 -> U32, u64 : U64 -> U64, u8 : U8 -> U8 }"#, + ) + } } diff --git a/compiler/str/README.md b/compiler/str/README.md index 04af2f5e9f..be3a82df20 100644 --- a/compiler/str/README.md +++ b/compiler/str/README.md @@ -59,7 +59,7 @@ In some cases, the compiler can detect that no reference counting is necessary. The fact that the reference count may or may not be present could creat a tricky situation for some `List` operations. -For example, should `List.get 0` return the first 16B of the heap-allocated bytes, or the second 16B? If there's a reference count in the first 16B, it should return the second 16B. If there's no refcount, it should return the first 16B. +For example, should `List.get 0` return the first 16B of the heap-allocated bytes, or the second 16B? If there's a reference count in the first 16B, it should return the second 16B. If there's no refcount, it should return the first 16B. To solve this, the pointer in the List struct *always* points to the first element in the list. That means to access the reference count, it does negative pointer arithmetic to get the address at 16B *preceding* the memory address it has stored in its pointer field. diff --git a/compiler/test_gen/Cargo.toml b/compiler/test_gen/Cargo.toml index bd6ddfd5d1..607b14b7ff 100644 --- a/compiler/test_gen/Cargo.toml +++ b/compiler/test_gen/Cargo.toml @@ -9,6 +9,9 @@ edition = "2018" name = "test_gen" path = "src/tests.rs" +[build-dependencies] +roc_builtins = { path = "../builtins" } + [dev-dependencies] roc_gen_llvm = { path = "../gen_llvm" } roc_gen_dev = { path = "../gen_dev" } @@ -28,6 +31,7 @@ roc_load = { path = "../load" } roc_can = { path = "../can" } roc_parse = { path = "../parse" } roc_build = { path = "../build" } +roc_target = { path = "../roc_target" } roc_std = { path = "../../roc_std" } bumpalo = { version = "3.8.0", features = ["collections"] } either = "1.6.1" diff --git a/compiler/test_gen/build.rs b/compiler/test_gen/build.rs index 5f775d6400..ea60706621 100644 --- a/compiler/test_gen/build.rs +++ b/compiler/test_gen/build.rs @@ -1,3 +1,4 @@ +use roc_builtins::bitcode; use std::env; use std::ffi::OsStr; use std::path::Path; @@ -5,8 +6,6 @@ use std::process::Command; const PLATFORM_FILENAME: &str = "wasm_test_platform"; const OUT_DIR_VAR: &str = "TEST_GEN_OUT"; -const LIBC_PATH_VAR: &str = "TEST_GEN_WASM_LIBC_PATH"; -const COMPILER_RT_PATH_VAR: &str = "TEST_GEN_WASM_COMPILER_RT_PATH"; fn main() { println!("cargo:rerun-if-changed=build.rs"); @@ -20,8 +19,7 @@ fn build_wasm() { println!("cargo:rustc-env={}={}", OUT_DIR_VAR, out_dir); - build_wasm_test_platform(&out_dir); - build_wasm_libc(&out_dir); + build_wasm_platform_and_builtins(&out_dir); } fn zig_executable() -> String { @@ -31,52 +29,29 @@ fn zig_executable() -> String { } } -fn build_wasm_test_platform(out_dir: &str) { +/// Create an all-in-one object file: platform + builtins + libc +fn build_wasm_platform_and_builtins(out_dir: &str) { println!("cargo:rerun-if-changed=src/helpers/{}.c", PLATFORM_FILENAME); - run_command( - Path::new("."), - &zig_executable(), - [ - "build-obj", - "-target", - "wasm32-wasi", - "-lc", - &format!("src/helpers/{}.c", PLATFORM_FILENAME), - &format!("-femit-bin={}/{}.o", out_dir, PLATFORM_FILENAME), - ], - ); -} + // See discussion with Luuk de Gram (Zig contributor) + // https://github.com/rtfeldman/roc/pull/2181#pullrequestreview-839608063 + // This builds a library file that exports everything. It has no linker data but we don't need that. + let args = [ + "build-lib", + "-target", + "wasm32-wasi", + "-lc", + "-dynamic", // -dynamic ensures libc code goes into the binary + bitcode::BUILTINS_WASM32_OBJ_PATH, + &format!("src/helpers/{}.c", PLATFORM_FILENAME), + &format!("-femit-bin={}/{}.o", out_dir, PLATFORM_FILENAME), + ]; -fn build_wasm_libc(out_dir: &str) { - let source_path = "src/helpers/dummy_libc_program.c"; - println!("cargo:rerun-if-changed={}", source_path); - let cwd = Path::new("."); - let zig_cache_dir = format!("{}/zig-cache-wasm32", out_dir); + let zig = zig_executable(); - run_command( - cwd, - &zig_executable(), - [ - "build-exe", // must be an executable or it won't compile libc - "-target", - "wasm32-wasi", - "-lc", - source_path, - "-femit-bin=/dev/null", - "--global-cache-dir", - &zig_cache_dir, - ], - ); + // println!("{} {}", zig, args.join(" ")); - let libc_path = run_command(cwd, "find", [&zig_cache_dir, "-name", "libc.a"]); - let compiler_rt_path = run_command(cwd, "find", [&zig_cache_dir, "-name", "compiler_rt.o"]); - - println!("cargo:rustc-env={}={}", LIBC_PATH_VAR, libc_path); - println!( - "cargo:rustc-env={}={}", - COMPILER_RT_PATH_VAR, compiler_rt_path - ); + run_command(Path::new("."), &zig, args); } fn feature_is_enabled(feature_name: &str) -> bool { diff --git a/compiler/test_gen/src/gen_compare.rs b/compiler/test_gen/src/gen_compare.rs index 65fe0a252e..edaf68dbb8 100644 --- a/compiler/test_gen/src/gen_compare.rs +++ b/compiler/test_gen/src/gen_compare.rs @@ -498,6 +498,48 @@ fn eq_rosetree() { ); } +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn eq_different_rosetrees() { + // Requires two different equality procedures for `List (Rose I64)` and `List (Rose Str)` + // even though both appear in the mono Layout as `List(RecursivePointer)` + assert_evals_to!( + indoc!( + r#" + Rose a : [ Rose a (List (Rose a)) ] + + a1 : Rose I64 + a1 = Rose 999 [] + a2 : Rose I64 + a2 = Rose 0 [a1] + + b1 : Rose I64 + b1 = Rose 999 [] + b2 : Rose I64 + b2 = Rose 0 [b1] + + ab = a2 == b2 + + c1 : Rose Str + c1 = Rose "hello" [] + c2 : Rose Str + c2 = Rose "" [c1] + + d1 : Rose Str + d1 = Rose "hello" [] + d2 : Rose Str + d2 = Rose "" [d1] + + cd = c2 == d2 + + ab && cd + "# + ), + true, + bool + ); +} + #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[ignore] @@ -509,10 +551,10 @@ fn rosetree_with_tag() { r#" Rose a : [ Rose (Result (List (Rose a)) I64) ] - x : Rose I64 + x : Rose I64 x = (Rose (Ok [])) - y : Rose I64 + y : Rose I64 y = (Rose (Ok [])) x == y @@ -595,10 +637,10 @@ fn compare_recursive_union_same_content() { r#" Expr : [ Add Expr Expr, Mul Expr Expr, Val1 I64, Val2 I64 ] - v1 : Expr + v1 : Expr v1 = Val1 42 - v2 : Expr + v2 : Expr v2 = Val2 42 v1 == v2 @@ -617,10 +659,10 @@ fn compare_nullable_recursive_union_same_content() { r#" Expr : [ Add Expr Expr, Mul Expr Expr, Val1 I64, Val2 I64, Empty ] - v1 : Expr + v1 : Expr v1 = Val1 42 - v2 : Expr + v2 : Expr v2 = Val2 42 v1 == v2 diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index 0005f295b4..585013cc54 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -1319,7 +1319,7 @@ fn list_reverse_empty_list() { #[test] #[cfg(any(feature = "gen-llvm"))] -fn foobarbaz() { +fn list_concat() { assert_evals_to!( indoc!( r#" @@ -1903,98 +1903,6 @@ fn gen_swap() { ); } -// #[test] -#[cfg(any(feature = "gen-llvm"))] -// fn gen_partition() { -// assert_evals_to!( -// indoc!( -// r#" -// swap : I64, I64, List a -> List a -// swap = \i, j, list -> -// when Pair (List.get list i) (List.get list j) is -// Pair (Ok atI) (Ok atJ) -> -// list -// |> List.set i atJ -// |> List.set j atI -// -// _ -> -// [] -// partition : I64, I64, List (Num a) -> [ Pair I64 (List (Num a)) ] -// partition = \low, high, initialList -> -// when List.get initialList high is -// Ok pivot -> -// when partitionHelp (low - 1) low initialList high pivot is -// Pair newI newList -> -// Pair (newI + 1) (swap (newI + 1) high newList) -// -// Err _ -> -// Pair (low - 1) initialList -// -// -// partitionHelp : I64, I64, List (Num a), I64, I64 -> [ Pair I64 (List (Num a)) ] -// partitionHelp = \i, j, list, high, pivot -> -// if j < high then -// when List.get list j is -// Ok value -> -// if value <= pivot then -// partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot -// else -// partitionHelp i (j + 1) list high pivot -// -// Err _ -> -// Pair i list -// else -// Pair i list -// -// # when partition 0 0 [ 1,2,3,4,5 ] is -// # Pair list _ -> list -// [ 1,3 ] -// "# -// ), -// RocList::from_slice(&[2, 1]), -// RocList -// ); -// } - -// #[test] -#[cfg(any(feature = "gen-llvm"))] -// fn gen_partition() { -// assert_evals_to!( -// indoc!( -// r#" -// swap : I64, I64, List a -> List a -// swap = \i, j, list -> -// when Pair (List.get list i) (List.get list j) is -// Pair (Ok atI) (Ok atJ) -> -// list -// |> List.set i atJ -// |> List.set j atI -// -// _ -> -// [] -// partition : I64, I64, List (Num a) -> [ Pair I64 (List (Num a)) ] -// partition = \low, high, initialList -> -// when List.get initialList high is -// Ok pivot -> -// when partitionHelp (low - 1) low initialList high pivot is -// Pair newI newList -> -// Pair (newI + 1) (swap (newI + 1) high newList) -// -// Err _ -> -// Pair (low - 1) initialList -// -// -// partitionHelp : I64, I64, List (Num a), I64, I64 -> [ Pair I64 (List (Num a)) ] -// -// # when partition 0 0 [ 1,2,3,4,5 ] is -// # Pair list _ -> list -// [ 1,3 ] -// "# -// ), -// RocList::from_slice(&[2, 1]), -// RocList -// ); -// } #[test] #[cfg(any(feature = "gen-llvm"))] fn gen_quicksort() { @@ -2014,7 +1922,7 @@ fn gen_quicksort() { when partition low high list is Pair partitionIndex partitioned -> partitioned - |> quicksortHelp low (partitionIndex - 1) + |> quicksortHelp low (Num.subSaturated partitionIndex 1) |> quicksortHelp (partitionIndex + 1) high else list @@ -2035,12 +1943,12 @@ fn gen_quicksort() { partition = \low, high, initialList -> when List.get initialList high is Ok pivot -> - when partitionHelp (low - 1) low initialList high pivot is + when partitionHelp low low initialList high pivot is Pair newI newList -> - Pair (newI + 1) (swap (newI + 1) high newList) + Pair newI (swap newI high newList) Err _ -> - Pair (low - 1) initialList + Pair low initialList partitionHelp : Nat, Nat, List (Num a), Nat, (Num a) -> [ Pair Nat (List (Num a)) ] @@ -2049,7 +1957,7 @@ fn gen_quicksort() { when List.get list j is Ok value -> if value <= pivot then - partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot + partitionHelp (i + 1) (j + 1) (swap i j list) high pivot else partitionHelp i (j + 1) list high pivot @@ -2069,7 +1977,7 @@ fn gen_quicksort() { #[test] #[cfg(any(feature = "gen-llvm"))] -fn foobar2() { +fn quicksort() { with_larger_debug_stack(|| { assert_evals_to!( indoc!( @@ -2085,7 +1993,7 @@ fn foobar2() { when partition low high list is Pair partitionIndex partitioned -> partitioned - |> quicksortHelp low (partitionIndex - 1) + |> quicksortHelp low (Num.subSaturated partitionIndex 1) |> quicksortHelp (partitionIndex + 1) high else list @@ -2106,12 +2014,12 @@ fn foobar2() { partition = \low, high, initialList -> when List.get initialList high is Ok pivot -> - when partitionHelp (low - 1) low initialList high pivot is + when partitionHelp low low initialList high pivot is Pair newI newList -> - Pair (newI + 1) (swap (newI + 1) high newList) + Pair newI (swap newI high newList) Err _ -> - Pair (low - 1) initialList + Pair low initialList partitionHelp : Nat, Nat, List (Num a), Nat, Num a -> [ Pair Nat (List (Num a)) ] @@ -2121,7 +2029,7 @@ fn foobar2() { when List.get list j is Ok value -> if value <= pivot then - partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot + partitionHelp (i + 1) (j + 1) (swap i j list) high pivot else partitionHelp i (j + 1) list high pivot @@ -2143,7 +2051,7 @@ fn foobar2() { #[test] #[cfg(any(feature = "gen-llvm"))] -fn foobar() { +fn quicksort_singleton() { with_larger_debug_stack(|| { assert_evals_to!( indoc!( @@ -2159,7 +2067,7 @@ fn foobar() { when partition low high list is Pair partitionIndex partitioned -> partitioned - |> quicksortHelp low (partitionIndex - 1) + |> quicksortHelp low (Num.subSaturated partitionIndex 1) |> quicksortHelp (partitionIndex + 1) high else list @@ -2180,12 +2088,12 @@ fn foobar() { partition = \low, high, initialList -> when List.get initialList high is Ok pivot -> - when partitionHelp (low - 1) low initialList high pivot is + when partitionHelp low low initialList high pivot is Pair newI newList -> - Pair (newI + 1) (swap (newI + 1) high newList) + Pair newI (swap newI high newList) Err _ -> - Pair (low - 1) initialList + Pair low initialList partitionHelp : Nat, Nat, List (Num a), Nat, Num a -> [ Pair Nat (List (Num a)) ] @@ -2194,7 +2102,7 @@ fn foobar() { when List.get list j is Ok value -> if value <= pivot then - partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot + partitionHelp (i + 1) (j + 1) (swap i j list) high pivot else partitionHelp i (j + 1) list high pivot @@ -2473,7 +2381,7 @@ fn cleanup_because_exception() { five : I64 five = 5 - five + Num.maxInt + 3 + (Num.intCast (List.len x)) + five + Num.maxI64 + 3 + (Num.intCast (List.len x)) "# ), 9, @@ -2513,6 +2421,28 @@ fn list_sort_with() { ); } +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn list_sort_asc() { + assert_evals_to!("List.sortAsc []", RocList::from_slice(&[]), RocList); + assert_evals_to!( + "List.sortAsc [ 4,3,2,1 ]", + RocList::from_slice(&[1, 2, 3, 4]), + RocList + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn list_sort_desc() { + assert_evals_to!("List.sortDesc []", RocList::from_slice(&[]), RocList); + assert_evals_to!( + "List.sortDesc [ 1,2,3,4 ]", + RocList::from_slice(&[4, 3, 2, 1]), + RocList + ); +} + #[test] #[cfg(any(feature = "gen-llvm"))] fn list_any() { @@ -2723,3 +2653,22 @@ fn list_find_empty_layout() { i64 ); } + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn monomorphized_lists() { + assert_evals_to!( + indoc!( + r#" + l = [1, 2, 3] + + f : List U8, List U16 -> Nat + f = \_, _ -> 18 + + f l l + "# + ), + 18, + u64 + ) +} diff --git a/compiler/test_gen/src/gen_num.rs b/compiler/test_gen/src/gen_num.rs index c9ec3b7560..fd7009328e 100644 --- a/compiler/test_gen/src/gen_num.rs +++ b/compiler/test_gen/src/gen_num.rs @@ -549,8 +549,8 @@ fn i64_abs() { assert_evals_to!("Num.abs 1", 1, i64); assert_evals_to!("Num.abs 9_000_000_000_000", 9_000_000_000_000, i64); assert_evals_to!("Num.abs -9_000_000_000_000", 9_000_000_000_000, i64); - assert_evals_to!("Num.abs Num.maxInt", i64::MAX, i64); - assert_evals_to!("Num.abs (Num.minInt + 1)", -(i64::MIN + 1), i64); + assert_evals_to!("Num.abs Num.maxI64", i64::MAX, i64); + assert_evals_to!("Num.abs (Num.minI64 + 1)", -(i64::MIN + 1), i64); } #[test] @@ -562,7 +562,7 @@ fn abs_min_int_overflow() { assert_evals_to!( indoc!( r#" - Num.abs Num.minInt + Num.abs Num.minI64 "# ), 0, @@ -1231,7 +1231,7 @@ fn tail_call_elimination() { #[cfg(any(feature = "gen-dev"))] fn int_negate_dev() { // TODO - // dev backend yet to have `Num.maxInt` or `Num.minInt`. + // dev backend yet to have `Num.maxI64` or `Num.minI64`. // add the "gen-dev" feature to the test below after implementing them both. assert_evals_to!("Num.neg 123", -123, i64); assert_evals_to!("Num.neg -123", 123, i64); @@ -1242,8 +1242,8 @@ fn int_negate_dev() { #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn int_negate() { assert_evals_to!("Num.neg 123", -123, i64); - assert_evals_to!("Num.neg Num.maxInt", -i64::MAX, i64); - assert_evals_to!("Num.neg (Num.minInt + 1)", i64::MAX, i64); + assert_evals_to!("Num.neg Num.maxI64", -i64::MAX, i64); + assert_evals_to!("Num.neg (Num.minI64 + 1)", i64::MAX, i64); } #[test] @@ -1255,7 +1255,7 @@ fn neg_min_int_overflow() { assert_evals_to!( indoc!( r#" - Num.neg Num.minInt + Num.neg Num.minI64 "# ), 0, @@ -1543,34 +1543,6 @@ fn float_add_overflow() { ); } -#[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] -fn num_max_int() { - assert_evals_to!( - indoc!( - r#" - Num.maxInt - "# - ), - i64::MAX, - i64 - ); -} - -#[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] -fn num_min_int() { - assert_evals_to!( - indoc!( - r#" - Num.minInt - "# - ), - i64::MIN, - i64 - ); -} - #[test] #[cfg(any(feature = "gen-llvm"))] #[should_panic(expected = r#"Roc failed with message: "integer subtraction overflowed!"#)] @@ -1633,7 +1605,7 @@ fn int_sub_checked() { assert_evals_to!( indoc!( r#" - when Num.subChecked Num.minInt 1 is + when Num.subChecked Num.minI64 1 is Err Overflow -> -1 Ok v -> v "# @@ -1737,7 +1709,7 @@ fn int_mul_wrap() { assert_evals_to!( indoc!( r#" - Num.mulWrap Num.maxInt 2 + Num.mulWrap Num.maxI64 2 "# ), -2, @@ -1763,7 +1735,7 @@ fn int_mul_checked() { assert_evals_to!( indoc!( r#" - when Num.mulChecked Num.maxInt 2 is + when Num.mulChecked Num.maxI64 2 is Err Overflow -> -1 Ok v -> v "# @@ -1829,6 +1801,20 @@ fn shift_right_zf_by() { assert_evals_to!("Num.shiftRightBy 3 0b0000_1100u8", 0b0000_0011, i64); } +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn min_i128() { + assert_evals_to!( + indoc!( + r#" + Num.minI128 + "# + ), + i128::MIN, + i128 + ); +} + #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn max_i128() { @@ -1843,6 +1829,230 @@ fn max_i128() { ); } +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn min_i64() { + assert_evals_to!( + indoc!( + r#" + Num.minI64 + "# + ), + i64::MIN, + i64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn max_i64() { + assert_evals_to!( + indoc!( + r#" + Num.maxI64 + "# + ), + i64::MAX, + i64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn min_u64() { + assert_evals_to!( + indoc!( + r#" + Num.minU64 + "# + ), + u64::MIN, + u64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn max_u64() { + assert_evals_to!( + indoc!( + r#" + Num.maxU64 + "# + ), + u64::MAX, + u64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn min_i32() { + assert_evals_to!( + indoc!( + r#" + Num.minI32 + "# + ), + i32::MIN, + i32 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn max_i32() { + assert_evals_to!( + indoc!( + r#" + Num.maxI32 + "# + ), + i32::MAX, + i32 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn min_u32() { + assert_evals_to!( + indoc!( + r#" + Num.minU32 + "# + ), + u32::MIN, + u32 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn max_u32() { + assert_evals_to!( + indoc!( + r#" + Num.maxU32 + "# + ), + u32::MAX, + u32 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn min_i16() { + assert_evals_to!( + indoc!( + r#" + Num.minI16 + "# + ), + i16::MIN, + i16 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn max_i16() { + assert_evals_to!( + indoc!( + r#" + Num.maxI16 + "# + ), + i16::MAX, + i16 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn min_u16() { + assert_evals_to!( + indoc!( + r#" + Num.minU16 + "# + ), + u16::MIN, + u16 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn max_u16() { + assert_evals_to!( + indoc!( + r#" + Num.maxU16 + "# + ), + u16::MAX, + u16 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn min_i8() { + assert_evals_to!( + indoc!( + r#" + Num.minI8 + "# + ), + i8::MIN, + i8 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn max_i8() { + assert_evals_to!( + indoc!( + r#" + Num.maxI8 + "# + ), + i8::MAX, + i8 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn min_u8() { + assert_evals_to!( + indoc!( + r#" + Num.minU8 + "# + ), + u8::MIN, + u8 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn max_u8() { + assert_evals_to!( + indoc!( + r#" + Num.maxU8 + "# + ), + u8::MAX, + u8 + ); +} + #[test] #[cfg(any(feature = "gen-llvm"))] fn is_multiple_of() { @@ -2089,15 +2299,194 @@ fn num_to_str() { let max = format!("{}", i64::MAX); assert_evals_to!( - r#"Num.toStr Num.maxInt"#, + r#"Num.toStr Num.maxI64"#, RocStr::from_slice(max.as_bytes()), RocStr ); let min = format!("{}", i64::MIN); assert_evals_to!( - r#"Num.toStr Num.minInt"#, + r#"Num.toStr Num.minI64"#, RocStr::from_slice(min.as_bytes()), RocStr ); } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn u8_addition_greater_than_i8() { + assert_evals_to!( + indoc!( + r#" + x : U8 + x = 100 + y : U8 + y = 100 + x + y + "# + ), + 200, + u8 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn u8_sub_greater_than_i8() { + assert_evals_to!( + indoc!( + r#" + x : U8 + x = 255 + y : U8 + y = 55 + x - y + "# + ), + 200, + u8 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn u8_mul_greater_than_i8() { + assert_evals_to!( + indoc!( + r#" + x : U8 + x = 40 + y : U8 + y = 5 + x * y + "# + ), + 200, + u8 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn add_saturated() { + assert_evals_to!( + indoc!( + r#" + x : U8 + x = 200 + y : U8 + y = 200 + Num.addSaturated x y + "# + ), + 255, + u8 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn sub_saturated() { + assert_evals_to!( + indoc!( + r#" + x : U8 + x = 10 + y : U8 + y = 20 + Num.subSaturated x y + "# + ), + 0, + u8 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn monomorphized_ints() { + assert_evals_to!( + indoc!( + r#" + x = 100 + + f : U8, U32 -> Nat + f = \_, _ -> 18 + + f x x + "# + ), + 18, + u64 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn monomorphized_floats() { + assert_evals_to!( + indoc!( + r#" + x = 100.0 + + f : F32, F64 -> Nat + f = \_, _ -> 18 + + f x x + "# + ), + 18, + u64 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn monomorphized_ints_names_dont_conflict() { + assert_evals_to!( + indoc!( + r#" + f : U8 -> Nat + f = \_ -> 9 + x = + n = 100 + f n + + y = + n = 100 + f n + + x + y + "# + ), + 18, + u64 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn monomorphized_ints_aliased() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + main = + y = 100 + w1 = y + w2 = y + + f1 : U8, U32 -> U8 + f1 = \_, _ -> 1 + + f2 : U32, U8 -> U8 + f2 = \_, _ -> 2 + + f1 w1 w2 + f2 w1 w2 + "# + ), + 3, + u8 + ) +} diff --git a/compiler/test_gen/src/gen_primitives.rs b/compiler/test_gen/src/gen_primitives.rs index f8443f283e..2f97c23753 100644 --- a/compiler/test_gen/src/gen_primitives.rs +++ b/compiler/test_gen/src/gen_primitives.rs @@ -1,6 +1,8 @@ #[cfg(feature = "gen-llvm")] use crate::helpers::llvm::assert_evals_to; #[cfg(feature = "gen-llvm")] +use crate::helpers::llvm::assert_expect_failed; +#[cfg(feature = "gen-llvm")] use crate::helpers::llvm::assert_llvm_evals_to; #[cfg(feature = "gen-llvm")] use crate::helpers::llvm::assert_non_opt_evals_to; @@ -8,6 +10,8 @@ use crate::helpers::llvm::assert_non_opt_evals_to; #[cfg(feature = "gen-dev")] use crate::helpers::dev::assert_evals_to; // #[cfg(feature = "gen-dev")] +// use crate::helpers::dev::assert_expect_failed; +// #[cfg(feature = "gen-dev")] // use crate::helpers::dev::assert_evals_to as assert_llvm_evals_to; // #[cfg(feature = "gen-dev")] // use crate::helpers::dev::assert_evals_to as assert_non_opt_evals_to; @@ -15,6 +19,8 @@ use crate::helpers::dev::assert_evals_to; #[cfg(feature = "gen-wasm")] use crate::helpers::wasm::assert_evals_to; // #[cfg(feature = "gen-wasm")] +// use crate::helpers::dev::assert_expect_failed; +// #[cfg(feature = "gen-wasm")] // use crate::helpers::wasm::assert_evals_to as assert_llvm_evals_to; // #[cfg(feature = "gen-wasm")] // use crate::helpers::wasm::assert_evals_to as assert_non_opt_evals_to; @@ -1972,7 +1978,7 @@ fn hof_conditional() { #[test] #[cfg(any(feature = "gen-llvm"))] #[should_panic( - expected = "Roc failed with message: \"Shadowing { original_region: |L 3-3, C 4-5|, shadow: |L 6-6, C 8-9| Ident" + expected = "Roc failed with message: \"Shadowing { original_region: @57-58, shadow: @90-91 Ident" )] fn pattern_shadowing() { assert_evals_to!( @@ -1989,26 +1995,6 @@ fn pattern_shadowing() { ); } -#[test] -#[cfg(any(feature = "gen-llvm"))] -#[should_panic(expected = "TODO non-exhaustive pattern")] -fn non_exhaustive_pattern_let() { - assert_evals_to!( - indoc!( - r#" - x : Result (Int a) (Float b) - x = Ok 4 - - (Ok y) = x - - y - "# - ), - 0, - i64 - ); -} - #[test] #[cfg(any(feature = "gen-llvm"))] #[ignore] @@ -2468,9 +2454,7 @@ fn backpassing_result() { #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] -#[should_panic( - expected = "Shadowing { original_region: |L 3-3, C 4-5|, shadow: |L 5-5, C 6-7| Ident" -)] +#[should_panic(expected = "Shadowing { original_region: @57-58, shadow: @74-75 Ident")] fn function_malformed_pattern() { assert_evals_to!( indoc!( @@ -2507,9 +2491,9 @@ fn call_invalid_layout() { #[test] #[cfg(any(feature = "gen-llvm"))] -#[should_panic(expected = "An expectation failed!")] +#[should_panic(expected = "Failed with 1 failures. Failures: ")] fn expect_fail() { - assert_evals_to!( + assert_expect_failed!( indoc!( r#" expect 1 == 2 @@ -3158,3 +3142,91 @@ fn alias_defined_out_of_order() { RocStr ); } + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn recursively_build_effect() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + greeting = + hi = "Hello" + name = "World" + + "\(hi), \(name)!" + + main = + when nestHelp 4 is + _ -> greeting + + nestHelp : I64 -> XEffect {} + nestHelp = \m -> + when m is + 0 -> + always {} + + _ -> + always {} |> after \_ -> nestHelp (m - 1) + + + XEffect a : [ @XEffect ({} -> a) ] + + always : a -> XEffect a + always = \x -> @XEffect (\{} -> x) + + after : XEffect a, (a -> XEffect b) -> XEffect b + after = \(@XEffect e), toB -> + @XEffect \{} -> + when toB (e {}) is + @XEffect e2 -> + e2 {} + "# + ), + RocStr::from_slice(b"Hello, World!"), + RocStr + ); +} + +#[test] +#[ignore = "TODO; currently generates bad code because `a` isn't specialized inside the closure."] +#[cfg(any(feature = "gen-llvm"))] +fn polymophic_expression_captured_inside_closure() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + asU8 : U8 -> U8 + asU8 = \_ -> 30 + + main = + a = 15 + f = \{} -> + asU8 a + + f {} + "# + ), + 30, + u8 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn issue_2322() { + assert_evals_to!( + indoc!( + r#" + double = \x -> x * 2 + doubleBind = \x -> (\_ -> double x) + doubleThree = doubleBind 3 + doubleThree {} + "# + ), + 6, + i64 + ) +} diff --git a/compiler/test_gen/src/gen_records.rs b/compiler/test_gen/src/gen_records.rs index a6266b9a0c..5377da5afc 100644 --- a/compiler/test_gen/src/gen_records.rs +++ b/compiler/test_gen/src/gen_records.rs @@ -587,9 +587,7 @@ fn optional_field_function_use_default() { #[test] #[cfg(any(feature = "gen-llvm"))] -#[ignore] fn optional_field_function_no_use_default() { - // blocked on https://github.com/rtfeldman/roc/issues/786 assert_evals_to!( indoc!( r#" @@ -608,9 +606,7 @@ fn optional_field_function_no_use_default() { #[test] #[cfg(any(feature = "gen-llvm"))] -#[ignore] fn optional_field_function_no_use_default_nested() { - // blocked on https://github.com/rtfeldman/roc/issues/786 assert_evals_to!( indoc!( r#" @@ -1001,7 +997,7 @@ fn both_have_unique_fields() { b = { x: 42, z: 44 } f : { x : I64 }a, { x : I64 }b -> I64 - f = \{ x: x1}, { x: x2 } -> x1 + x2 + f = \{ x: x1}, { x: x2 } -> x1 + x2 f a b "# diff --git a/compiler/test_gen/src/gen_refcount.rs b/compiler/test_gen/src/gen_refcount.rs new file mode 100644 index 0000000000..e16cad765b --- /dev/null +++ b/compiler/test_gen/src/gen_refcount.rs @@ -0,0 +1,439 @@ +#[cfg(feature = "gen-wasm")] +use crate::helpers::wasm::assert_refcounts; + +#[allow(unused_imports)] +use indoc::indoc; + +#[allow(unused_imports)] +use roc_std::{RocList, RocStr}; + +// A "good enough" representation of a pointer for these tests, because +// we ignore the return value. As long as it's the right stack size, it's fine. +#[allow(dead_code)] +type Pointer = usize; + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn str_inc() { + assert_refcounts!( + indoc!( + r#" + s = Str.concat "A long enough string " "to be heap-allocated" + + [s, s, s] + "# + ), + RocList, + &[ + 3, // s + 1 // result + ] + ); +} + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn str_dealloc() { + assert_refcounts!( + indoc!( + r#" + s = Str.concat "A long enough string " "to be heap-allocated" + + Str.isEmpty s + "# + ), + bool, + &[0] + ); +} + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn list_int_inc() { + assert_refcounts!( + indoc!( + r#" + list = [0x111, 0x222, 0x333] + [list, list, list] + "# + ), + RocList>, + &[ + // TODO be smarter about coalescing polymorphic list values + 1, // list0 + 1, // list1 + 1, // list2 + 1 // result + ] + ); +} + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn list_int_dealloc() { + assert_refcounts!( + indoc!( + r#" + list = [0x111, 0x222, 0x333] + List.len [list, list, list] + "# + ), + usize, + &[ + // TODO be smarter about coalescing polymorphic list values + 0, // list0 + 0, // list1 + 0, // list2 + 0 // result + ] + ); +} + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn list_str_inc() { + assert_refcounts!( + indoc!( + r#" + s = Str.concat "A long enough string " "to be heap-allocated" + list = [s, s, s] + [list, list] + "# + ), + RocList>, + &[ + 6, // s + 2, // list + 1 // result + ] + ); +} + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn list_str_dealloc() { + assert_refcounts!( + indoc!( + r#" + s = Str.concat "A long enough string " "to be heap-allocated" + list = [s, s, s] + List.len [list, list] + "# + ), + usize, + &[ + 0, // s + 0, // list + 0 // result + ] + ); +} + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn struct_inc() { + assert_refcounts!( + indoc!( + r#" + s = Str.concat "A long enough string " "to be heap-allocated" + r1 : { a: I64, b: Str, c: Str } + r1 = { a: 123, b: s, c: s } + { y: r1, z: r1 } + "# + ), + [(i64, RocStr, RocStr); 2], + &[4] // s + ); +} + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn struct_dealloc() { + assert_refcounts!( + indoc!( + r#" + s = Str.concat "A long enough string " "to be heap-allocated" + r1 : { a: I64, b: Str, c: Str } + r1 = { a: 123, b: s, c: s } + r2 = { x: 456, y: r1, z: r1 } + r2.x + "# + ), + i64, + &[0] // s + ); +} + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn union_nonrecursive_inc() { + type TwoStr = (RocStr, RocStr, i64); + + assert_refcounts!( + indoc!( + r#" + TwoOrNone a: [ Two a a, None ] + + s = Str.concat "A long enough string " "to be heap-allocated" + + two : TwoOrNone Str + two = Two s s + + four : TwoOrNone (TwoOrNone Str) + four = Two two two + + four + "# + ), + (TwoStr, TwoStr, i64), + &[4] + ); +} + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn union_nonrecursive_dec() { + assert_refcounts!( + indoc!( + r#" + TwoOrNone a: [ Two a a, None ] + + s = Str.concat "A long enough string " "to be heap-allocated" + + two : TwoOrNone Str + two = Two s s + + when two is + Two x _ -> x + None -> "" + "# + ), + RocStr, + &[1] // s + ); +} + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn union_recursive_inc() { + assert_refcounts!( + indoc!( + r#" + Expr : [ Sym Str, Add Expr Expr ] + + s = Str.concat "heap_allocated" "_symbol_name" + + x : Expr + x = Sym s + + e : Expr + e = Add x x + + Pair e e + "# + ), + (Pointer, Pointer), + &[ + 4, // s + 4, // sym + 2, // e + ] + ); +} + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn union_recursive_dec() { + assert_refcounts!( + indoc!( + r#" + Expr : [ Sym Str, Add Expr Expr ] + + s = Str.concat "heap_allocated" "_symbol_name" + + x : Expr + x = Sym s + + e : Expr + e = Add x x + + when e is + Add y _ -> y + Sym _ -> e + "# + ), + Pointer, + &[ + 1, // s + 1, // sym + 0 // e + ] + ); +} + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn refcount_different_rosetrees_inc() { + // Requires two different Inc procedures for `List (Rose I64)` and `List (Rose Str)` + // even though both appear in the mono Layout as `List(RecursivePointer)` + assert_refcounts!( + indoc!( + r#" + Rose a : [ Rose a (List (Rose a)) ] + + s = Str.concat "A long enough string " "to be heap-allocated" + + i1 : Rose I64 + i1 = Rose 999 [] + + s1 : Rose Str + s1 = Rose s [] + + i2 : Rose I64 + i2 = Rose 0 [i1, i1, i1] + + s2 : Rose Str + s2 = Rose "" [s1, s1] + + Tuple i2 s2 + "# + ), + (Pointer, Pointer), + &[ + 2, // s + 3, // i1 + 2, // s1 + 1, // [i1, i1] + 1, // i2 + 1, // [s1, s1] + 1 // s2 + ] + ); +} + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn refcount_different_rosetrees_dec() { + // Requires two different Dec procedures for `List (Rose I64)` and `List (Rose Str)` + // even though both appear in the mono Layout as `List(RecursivePointer)` + assert_refcounts!( + indoc!( + r#" + Rose a : [ Rose a (List (Rose a)) ] + + s = Str.concat "A long enough string " "to be heap-allocated" + + i1 : Rose I64 + i1 = Rose 999 [] + + s1 : Rose Str + s1 = Rose s [] + + i2 : Rose I64 + i2 = Rose 0 [i1, i1] + + s2 : Rose Str + s2 = Rose "" [s1, s1] + + when (Tuple i2 s2) is + Tuple (Rose x _) _ -> x + "# + ), + i64, + &[ + 0, // s + 0, // i1 + 0, // s1 + 0, // [i1, i1] + 0, // i2 + 0, // [s1, s1] + 0, // s2 + ] + ); +} + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn union_linked_list_inc() { + assert_refcounts!( + indoc!( + r#" + LinkedList a : [ Nil, Cons a (LinkedList a) ] + + s = Str.concat "A long enough string " "to be heap-allocated" + + linked : LinkedList Str + linked = Cons s (Cons s (Cons s Nil)) + + Tuple linked linked + "# + ), + (Pointer, Pointer), + &[ + 6, // s + 2, // Cons + 2, // Cons + 2, // Cons + ] + ); +} + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn union_linked_list_dec() { + assert_refcounts!( + indoc!( + r#" + LinkedList a : [ Nil, Cons a (LinkedList a) ] + + s = Str.concat "A long enough string " "to be heap-allocated" + + linked : LinkedList Str + linked = Cons s (Cons s (Cons s Nil)) + + when linked is + Cons x _ -> x + Nil -> "" + "# + ), + RocStr, + &[ + 1, // s + 0, // Cons + 0, // Cons + 0, // Cons + ] + ); +} + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn union_linked_list_long_dec() { + assert_refcounts!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + LinkedList a : [ Nil, Cons a (LinkedList a) ] + + prependOnes = \n, tail -> + if n == 0 then + tail + else + prependOnes (n-1) (Cons 1 tail) + + main = + n = 1_000 + + linked : LinkedList I64 + linked = prependOnes n Nil + + when linked is + Cons x _ -> x + Nil -> -1 + "# + ), + i64, + &[0; 1_000] + ); +} diff --git a/compiler/test_gen/src/gen_result.rs b/compiler/test_gen/src/gen_result.rs index cf466665ca..d919bf89ef 100644 --- a/compiler/test_gen/src/gen_result.rs +++ b/compiler/test_gen/src/gen_result.rs @@ -34,7 +34,7 @@ fn with_default() { indoc!( r#" result : Result I64 {} - result = Err {} + result = Err {} Result.withDefault result 0 "# @@ -66,7 +66,7 @@ fn result_map() { indoc!( r#" result : Result I64 {} - result = Err {} + result = Err {} result |> Result.map (\x -> x + 1) @@ -230,7 +230,7 @@ fn roc_result_ok() { indoc!( r#" result : Result I64 {} - result = Ok 42 + result = Ok 42 result "# @@ -246,7 +246,7 @@ fn roc_result_err() { assert_evals_to!( indoc!( r#" - result : Result I64 Str + result : Result I64 Str result = Err "foo" result diff --git a/compiler/test_gen/src/gen_set.rs b/compiler/test_gen/src/gen_set.rs index b1607176f6..77503c105e 100644 --- a/compiler/test_gen/src/gen_set.rs +++ b/compiler/test_gen/src/gen_set.rs @@ -121,7 +121,7 @@ fn union() { set1 = Set.fromList [1,2] set2 : Set I64 - set2 = Set.fromList [1,3,4] + set2 = Set.fromList [1,3,4] Set.union set1 set2 |> Set.toList @@ -142,7 +142,7 @@ fn difference() { set1 = Set.fromList [1,2] set2 : Set I64 - set2 = Set.fromList [1,3,4] + set2 = Set.fromList [1,3,4] Set.difference set1 set2 |> Set.toList @@ -163,7 +163,7 @@ fn intersection() { set1 = Set.fromList [1,2] set2 : Set I64 - set2 = Set.fromList [1,3,4] + set2 = Set.fromList [1,3,4] Set.intersection set1 set2 |> Set.toList diff --git a/compiler/test_gen/src/gen_str.rs b/compiler/test_gen/src/gen_str.rs index 4e5a74ecfd..2792b86fd7 100644 --- a/compiler/test_gen/src/gen_str.rs +++ b/compiler/test_gen/src/gen_str.rs @@ -36,7 +36,7 @@ fn str_split_empty_delimiter() { Str.countGraphemes str _ -> - -1 + 1729 "# ), @@ -66,7 +66,7 @@ fn str_split_bigger_delimiter_small_str() { Str.countGraphemes str _ -> - -1 + 1729 "# ), diff --git a/compiler/test_gen/src/gen_tags.rs b/compiler/test_gen/src/gen_tags.rs index b4dc57e4dd..89251ac981 100644 --- a/compiler/test_gen/src/gen_tags.rs +++ b/compiler/test_gen/src/gen_tags.rs @@ -23,9 +23,9 @@ fn width_and_alignment_u8_u8() { let layout = Layout::Union(UnionLayout::NonRecursive(&tt)); - let ptr_width = 8; - assert_eq!(layout.alignment_bytes(ptr_width), 1); - assert_eq!(layout.stack_size(ptr_width), 2); + let target_info = roc_target::TargetInfo::default_x86_64(); + assert_eq!(layout.alignment_bytes(target_info), 1); + assert_eq!(layout.stack_size(target_info), 2); } #[test] @@ -1243,3 +1243,234 @@ fn tag_must_be_its_own_type() { i64 ); } + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn recursive_tag_union_into_flat_tag_union() { + // Comprehensive test for correctness in cli/tests/repl_eval + assert_evals_to!( + indoc!( + r#" + Item : [ Shallow [ L Str, R Str ], Deep Item ] + i : Item + i = Deep (Shallow (R "woo")) + i + "# + ), + 0, + usize, + |_| 0 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn monomorphized_tag() { + assert_evals_to!( + indoc!( + r#" + b = False + f : Bool, [True, False, Idk] -> U8 + f = \_, _ -> 18 + f b b + "# + ), + 18, + u8 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn monomorphized_applied_tag() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + main = + a = A "abc" + f = \x -> + when x is + A y -> y + B y -> y + f a + "# + ), + RocStr::from_slice(b"abc"), + RocStr + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn monomorphized_tag_with_polymorphic_arg() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + main = + a = A + wrap = Wrapped a + + useWrap1 : [Wrapped [A], Other] -> U8 + useWrap1 = + \w -> when w is + Wrapped A -> 2 + Other -> 3 + + useWrap2 : [Wrapped [A, B]] -> U8 + useWrap2 = + \w -> when w is + Wrapped A -> 5 + Wrapped B -> 7 + + useWrap1 wrap * useWrap2 wrap + "# + ), + 10, + u8 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn monomorphized_tag_with_polymorphic_arg_and_monomorphic_arg() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + main = + mono : U8 + mono = 15 + poly = A + wrap = Wrapped poly mono + + useWrap1 : [Wrapped [A] U8, Other] -> U8 + useWrap1 = + \w -> when w is + Wrapped A n -> n + Other -> 0 + + useWrap2 : [Wrapped [A, B] U8] -> U8 + useWrap2 = + \w -> when w is + Wrapped A n -> n + Wrapped B _ -> 0 + + useWrap1 wrap * useWrap2 wrap + "# + ), + 225, + u8 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn issue_2365_monomorphize_tag_with_non_empty_ext_var() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + Single a : [A, B, C]a + Compound a : Single [D, E, F]a + + single : {} -> Single * + single = \{} -> C + + compound : {} -> Compound * + compound = \{} -> single {} + + main = compound {} + "# + ), + 2, // C + u8 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn issue_2365_monomorphize_tag_with_non_empty_ext_var_wrapped() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + Single a : [A, B, C]a + Compound a : Single [D, E, F]a + + single : {} -> Result Str (Single *) + single = \{} -> Err C + + compound : {} -> Result Str (Compound *) + compound = \{} -> + when single {} is + Ok s -> Ok s + Err e -> Err e + + main = compound {} + "# + ), + 2, // C + u8 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn issue_2365_monomorphize_tag_with_non_empty_ext_var_wrapped_nested() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + Single a : [A, B, C]a + Compound a : Single [D, E, F]a + + main = + single : {} -> Result Str (Single *) + single = \{} -> Err C + + compound : {} -> Result Str (Compound *) + compound = \{} -> + when single {} is + Ok s -> Ok s + Err e -> Err e + + compound {} + "# + ), + 2, // C + u8 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn issue_2445() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + none : [ None, Update a ] + none = None + + press : [ None, Update U8 ] + press = none + + main = + when press is + None -> 15 + Update _ -> 25 + "# + ), + 15, + i64 + ); +} diff --git a/compiler/test_gen/src/helpers/debug-wasm-test.html b/compiler/test_gen/src/helpers/debug-wasm-test.html new file mode 100644 index 0000000000..caf1b01901 --- /dev/null +++ b/compiler/test_gen/src/helpers/debug-wasm-test.html @@ -0,0 +1,369 @@ + + + + + +
+

Debug Wasm tests in the browser!

+

+ You can step through the generated code instruction-by-instruction, and + examine memory contents +

+

Steps

+
    +
  • + In gen_wasm/src/lib.rs, set + DEBUG_LOG_SETTINGS.keep_test_binary = true +
  • +
  • Run cargo test-gen-wasm -- my_test --nocapture
  • +
  • + Look for the path written to the console for + final.wasm and select it in the file picker below +
  • +
  • + Open the browser DevTools
    + Control+Shift+I or Command+Option+I or F12 +
  • +
  • + Click one of the buttons below, depending on what kind of test it is. +
    + + Only one of them will work. The other will probably crash or + something. + +
  • +
  • + The debugger should pause just before entering the first Wasm call. + Step into a couple of Wasm calls until you reach your test code in + $#UserApp_main_1 +
  • +
  • + Chrome DevTools now has a Memory Inspector panel! In the debugger, + find Module -> memories -> $memory. Right click and + select "Reveal in Memory Inspector" +
  • +
+ +
+
+ + +
+
+
+ + +
+
+
+ + + diff --git a/compiler/test_gen/src/helpers/dev.rs b/compiler/test_gen/src/helpers/dev.rs index 522dc6ed51..d17c326161 100644 --- a/compiler/test_gen/src/helpers/dev.rs +++ b/compiler/test_gen/src/helpers/dev.rs @@ -3,10 +3,11 @@ use roc_build::link::{link, LinkType}; use roc_builtins::bitcode; use roc_can::builtins::builtin_defs_map; use roc_collections::all::MutMap; +use roc_region::all::LineInfo; use tempfile::tempdir; #[allow(unused_imports)] -use roc_mono::ir::PRETTY_PRINT_IR_SYMBOLS; +use roc_mono::ir::pretty_print_ir_symbols; #[allow(dead_code)] fn promote_expr_to_module(src: &str) -> String { @@ -56,7 +57,7 @@ pub fn helper( &stdlib, src_dir, exposed_types, - 8, + roc_target::TargetInfo::default_x86_64(), builtin_defs_map, ); @@ -75,7 +76,7 @@ pub fn helper( // while you're working on the dev backend! { // println!("=========== Procedures =========="); - // if PRETTY_PRINT_IR_SYMBOLS { + // if pretty_print_ir_symbols() { // println!(""); // for proc in procedures.values() { // println!("{}", proc.to_pretty(200)); @@ -94,7 +95,7 @@ pub fn helper( // println!("=================================\n"); } - debug_assert_eq!(exposed_to_host.len(), 1); + debug_assert_eq!(exposed_to_host.values.len(), 1); let main_fn_symbol = loaded.entry_point.symbol; let main_fn_layout = loaded.entry_point.layout; @@ -124,6 +125,7 @@ pub fn helper( continue; } + let line_info = LineInfo::new(&src); let src_lines: Vec<&str> = src.split('\n').collect(); let palette = DEFAULT_PALETTE; @@ -139,7 +141,7 @@ pub fn helper( continue; } _ => { - let report = can_problem(&alloc, module_path.clone(), problem); + let report = can_problem(&alloc, &line_info, module_path.clone(), problem); let mut buf = String::new(); report.render_color_terminal(&mut buf, &alloc, &palette); @@ -150,7 +152,7 @@ pub fn helper( } for problem in type_problems { - if let Some(report) = type_problem(&alloc, module_path.clone(), problem) { + if let Some(report) = type_problem(&alloc, &line_info, module_path.clone(), problem) { let mut buf = String::new(); report.render_color_terminal(&mut buf, &alloc, &palette); @@ -160,7 +162,7 @@ pub fn helper( } for problem in mono_problems { - let report = mono_problem(&alloc, module_path.clone(), problem); + let report = mono_problem(&alloc, &line_info, module_path.clone(), problem); let mut buf = String::new(); report.render_color_terminal(&mut buf, &alloc, &palette); @@ -177,7 +179,7 @@ pub fn helper( let env = roc_gen_dev::Env { arena, module_id, - exposed_to_host: exposed_to_host.keys().copied().collect(), + exposed_to_host: exposed_to_host.values.keys().copied().collect(), lazy_literals, generate_allocators: true, // Needed for testing, since we don't have a platform }; @@ -255,5 +257,25 @@ macro_rules! assert_evals_to { }; } +#[allow(unused_macros)] +macro_rules! assert_expect_failed { + ($src:expr, $expected:expr, $ty:ty, $failures:expr) => {{ + use bumpalo::Bump; + use roc_gen_dev::run_jit_function_raw; + let stdlib = roc_builtins::std::standard_stdlib(); + + let arena = Bump::new(); + let (main_fn_name, errors, lib) = + $crate::helpers::dev::helper(&arena, $src, stdlib, true, true); + + let transform = |success| { + let expected = $expected; + assert_eq!(&success, &expected); + }; + run_jit_function_raw!(lib, main_fn_name, $ty, transform, errors); + }}; +} + #[allow(unused_imports)] pub(crate) use assert_evals_to; +pub(crate) use assert_expect_failed; diff --git a/compiler/test_gen/src/helpers/dummy_libc_program.c b/compiler/test_gen/src/helpers/dummy_libc_program.c deleted file mode 100644 index 0d358ef2a7..0000000000 --- a/compiler/test_gen/src/helpers/dummy_libc_program.c +++ /dev/null @@ -1,7 +0,0 @@ -#include - -void main() { - printf("Hello, I am a C program and I use libc.\n"); - printf("If you compile me, you'll compile libc too. That's handy for cross-compilation including Wasm.\n"); - printf("Use `zig build-exe` with `--global-cache-dir my/build/directory` to put libc.a where you want it.\n"); -} diff --git a/compiler/test_gen/src/helpers/from_wasm32_memory.rs b/compiler/test_gen/src/helpers/from_wasmer_memory.rs similarity index 58% rename from compiler/test_gen/src/helpers/from_wasm32_memory.rs rename to compiler/test_gen/src/helpers/from_wasmer_memory.rs index b082e0f28e..99f368f87f 100644 --- a/compiler/test_gen/src/helpers/from_wasm32_memory.rs +++ b/compiler/test_gen/src/helpers/from_wasmer_memory.rs @@ -1,22 +1,12 @@ +use roc_gen_wasm::wasm32_sized::Wasm32Sized; use roc_std::{RocDec, RocList, RocOrder, RocStr}; -pub trait FromWasm32Memory: 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)) - }; - +pub trait FromWasmerMemory: Wasm32Sized { fn decode(memory: &wasmer::Memory, offset: u32) -> Self; } macro_rules! from_wasm_memory_primitive_decode { ($type_name:ident) => { - const SIZE_OF_WASM: usize = core::mem::size_of::<$type_name>(); - const ALIGN_OF_WASM: usize = core::mem::align_of::<$type_name>(); - fn decode(memory: &wasmer::Memory, offset: u32) -> Self { use core::mem::MaybeUninit; @@ -41,7 +31,7 @@ macro_rules! from_wasm_memory_primitive_decode { macro_rules! from_wasm_memory_primitive { ($($type_name:ident ,)+) => { $( - impl FromWasm32Memory for $type_name { + impl FromWasmerMemory for $type_name { from_wasm_memory_primitive_decode!($type_name); } )* @@ -52,19 +42,13 @@ from_wasm_memory_primitive!( u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, f32, f64, bool, RocDec, RocOrder, ); -impl FromWasm32Memory for () { - const SIZE_OF_WASM: usize = 0; - const ALIGN_OF_WASM: usize = 0; - +impl FromWasmerMemory for () { fn decode(_: &wasmer::Memory, _: u32) -> Self {} } -impl FromWasm32Memory for RocStr { - const SIZE_OF_WASM: usize = 8; - const ALIGN_OF_WASM: usize = 4; - +impl FromWasmerMemory for RocStr { fn decode(memory: &wasmer::Memory, offset: u32) -> Self { - let bytes = ::decode(memory, offset); + let bytes = ::decode(memory, offset); let length = (bytes >> 32) as u32; let elements = bytes as u32; @@ -89,12 +73,9 @@ impl FromWasm32Memory for RocStr { } } -impl FromWasm32Memory for RocList { - const SIZE_OF_WASM: usize = 8; - const ALIGN_OF_WASM: usize = 4; - +impl FromWasmerMemory for RocList { fn decode(memory: &wasmer::Memory, offset: u32) -> Self { - let bytes = ::decode(memory, offset); + let bytes = ::decode(memory, offset); let length = (bytes >> 32) as u32; let elements = bytes as u32; @@ -102,9 +83,9 @@ impl FromWasm32Memory for RocList { let mut items = Vec::with_capacity(length as usize); for i in 0..length { - let item = ::decode( + let item = ::decode( memory, - elements + i * ::SIZE_OF_WASM as u32, + elements + i * ::SIZE_OF_WASM as u32, ); items.push(item); } @@ -113,14 +94,11 @@ impl FromWasm32Memory for RocList { } } -impl FromWasm32Memory for &'_ T { - const SIZE_OF_WASM: usize = 4; - const ALIGN_OF_WASM: usize = 4; - +impl FromWasmerMemory for &'_ T { fn decode(memory: &wasmer::Memory, offset: u32) -> Self { - let elements = ::decode(memory, offset); + let elements = ::decode(memory, offset); - let actual = ::decode(memory, elements); + let actual = ::decode(memory, elements); let b = Box::new(actual); @@ -128,13 +106,10 @@ impl FromWasm32Memory for &'_ T { } } -impl FromWasm32Memory for [T; N] { - const SIZE_OF_WASM: usize = N * T::SIZE_OF_WASM; - const ALIGN_OF_WASM: usize = T::ALIGN_OF_WASM; - +impl FromWasmerMemory for [T; N] { fn decode(memory: &wasmer::Memory, offset: u32) -> Self { let ptr: wasmer::WasmPtr = wasmer::WasmPtr::new(offset); - let width = ::SIZE_OF_WASM as u32 * N as u32; + let width = ::SIZE_OF_WASM as u32 * N as u32; let foobar = (ptr.deref(memory, 0, width)).unwrap(); let wasm_slice: &[T; N] = unsafe { &*(foobar as *const _ as *const [T; N]) }; @@ -142,49 +117,28 @@ impl FromWasm32Memory for [T; N] { } } -impl FromWasm32Memory for usize { - const SIZE_OF_WASM: usize = 4; - const ALIGN_OF_WASM: usize = 4; - +impl FromWasmerMemory for usize { fn decode(memory: &wasmer::Memory, offset: u32) -> Self { - ::decode(memory, offset) as usize + ::decode(memory, offset) as usize } } -impl FromWasm32Memory 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 FromWasmerMemory for (T, U) { fn decode(memory: &wasmer::Memory, offset: u32) -> Self { debug_assert!( T::ALIGN_OF_WASM >= U::ALIGN_OF_WASM, "this function does not handle alignment" ); - let t = ::decode(memory, offset); + let t = ::decode(memory, offset); - let u = ::decode(memory, offset + T::ACTUAL_WIDTH as u32); + let u = ::decode(memory, offset + T::ACTUAL_WIDTH as u32); (t, u) } } -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) -} - -impl FromWasm32Memory 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); - +impl FromWasmerMemory for (T, U, V) { fn decode(memory: &wasmer::Memory, offset: u32) -> Self { debug_assert!( T::ALIGN_OF_WASM >= U::ALIGN_OF_WASM, @@ -196,11 +150,11 @@ impl FromWasm32Me "this function does not handle alignment" ); - let t = ::decode(memory, offset); + let t = ::decode(memory, offset); - let u = ::decode(memory, offset + T::ACTUAL_WIDTH as u32); + let u = ::decode(memory, offset + T::ACTUAL_WIDTH as u32); - let v = ::decode( + let v = ::decode( memory, offset + T::ACTUAL_WIDTH as u32 + U::ACTUAL_WIDTH as u32, ); diff --git a/compiler/test_gen/src/helpers/llvm.rs b/compiler/test_gen/src/helpers/llvm.rs index d9bdafef43..d9af1a821d 100644 --- a/compiler/test_gen/src/helpers/llvm.rs +++ b/compiler/test_gen/src/helpers/llvm.rs @@ -1,4 +1,4 @@ -use crate::helpers::from_wasm32_memory::FromWasm32Memory; +use crate::helpers::from_wasmer_memory::FromWasmerMemory; use inkwell::module::Module; use libloading::Library; use roc_build::link::module_to_dylib; @@ -9,6 +9,7 @@ use roc_collections::all::{MutMap, MutSet}; use roc_gen_llvm::llvm::externs::add_default_roc_externs; use roc_module::symbol::Symbol; use roc_mono::ir::OptLevel; +use roc_region::all::LineInfo; use roc_types::subs::VarStore; use target_lexicon::Triple; @@ -41,6 +42,8 @@ fn create_llvm_module<'a>( ) -> (&'static str, String, &'a Module<'a>) { use std::path::{Path, PathBuf}; + let target_info = roc_target::TargetInfo::from(target); + let filename = PathBuf::from("Test.roc"); let src_dir = Path::new("fake/test/path"); @@ -55,8 +58,6 @@ fn create_llvm_module<'a>( module_src = &temp; } - let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; - let exposed_types = MutMap::default(); let loaded = roc_load::file::load_and_monomorphize_from_str( arena, @@ -65,7 +66,7 @@ fn create_llvm_module<'a>( stdlib, src_dir, exposed_types, - ptr_bytes, + target_info, test_builtin_defs, ); @@ -105,6 +106,7 @@ fn create_llvm_module<'a>( continue; } + let line_info = LineInfo::new(&src); let src_lines: Vec<&str> = src.split('\n').collect(); let palette = DEFAULT_PALETTE; @@ -121,7 +123,7 @@ fn create_llvm_module<'a>( | RuntimeError(_) | UnsupportedPattern(_, _) | ExposedButNotDefined(_) => { - let report = can_problem(&alloc, module_path.clone(), problem); + let report = can_problem(&alloc, &line_info, module_path.clone(), problem); let mut buf = String::new(); report.render_color_terminal(&mut buf, &alloc, &palette); @@ -130,7 +132,7 @@ fn create_llvm_module<'a>( lines.push(buf); } _ => { - let report = can_problem(&alloc, module_path.clone(), problem); + let report = can_problem(&alloc, &line_info, module_path.clone(), problem); let mut buf = String::new(); report.render_color_terminal(&mut buf, &alloc, &palette); @@ -141,7 +143,7 @@ fn create_llvm_module<'a>( } for problem in type_problems { - if let Some(report) = type_problem(&alloc, module_path.clone(), problem) { + if let Some(report) = type_problem(&alloc, &line_info, module_path.clone(), problem) { let mut buf = String::new(); report.render_color_terminal(&mut buf, &alloc, &palette); @@ -151,7 +153,7 @@ fn create_llvm_module<'a>( } for problem in mono_problems { - let report = mono_problem(&alloc, module_path.clone(), problem); + let report = mono_problem(&alloc, &line_info, module_path.clone(), problem); let mut buf = String::new(); report.render_color_terminal(&mut buf, &alloc, &palette); @@ -190,7 +192,11 @@ fn create_llvm_module<'a>( for function in FunctionIterator::from_module(module) { let name = function.get_name().to_str().unwrap(); if name.starts_with("roc_builtins") { - function.set_linkage(Linkage::Internal); + if name.starts_with("roc_builtins.expect") { + function.set_linkage(Linkage::External); + } else { + function.set_linkage(Linkage::Internal); + } } if name.starts_with("roc_builtins.dict") { @@ -211,7 +217,7 @@ fn create_llvm_module<'a>( context, interns, module, - ptr_bytes, + target_info, is_gen_test, // important! we don't want any procedures to get the C calling convention exposed_to_host: MutSet::default(), @@ -462,7 +468,7 @@ fn fake_wasm_main_function(_: u32, _: u32) -> u32 { #[allow(dead_code)] pub fn assert_wasm_evals_to_help(src: &str, ignore_problems: bool) -> Result where - T: FromWasm32Memory, + T: FromWasmerMemory, { let arena = bumpalo::Bump::new(); let context = inkwell::context::Context::create(); @@ -496,7 +502,7 @@ where _ => panic!(), }; - let output = ::decode( + let output = ::decode( memory, // skip the RocCallResult tag id address as u32 + 8, @@ -599,6 +605,46 @@ macro_rules! assert_evals_to { }; } +#[allow(unused_macros)] +macro_rules! assert_expect_failed { + ($src:expr, $expected:expr, $ty:ty) => { + use bumpalo::Bump; + use inkwell::context::Context; + use roc_gen_llvm::run_jit_function; + + let arena = Bump::new(); + let context = Context::create(); + + // NOTE the stdlib must be in the arena; just taking a reference will segfault + let stdlib = arena.alloc(roc_builtins::std::standard_stdlib()); + + let is_gen_test = true; + let (main_fn_name, errors, lib) = + $crate::helpers::llvm::helper(&arena, $src, stdlib, is_gen_test, false, &context); + + let transform = |success| { + let expected = $expected; + assert_eq!(&success, &expected, "LLVM test failed"); + }; + + run_jit_function!(lib, main_fn_name, $ty, transform, errors) + }; + + ($src:expr, $expected:expr, $ty:ty) => { + $crate::helpers::llvm::assert_llvm_evals_to!( + $src, + $expected, + $ty, + $crate::helpers::llvm::identity, + false + ); + }; + + ($src:expr, $expected:expr, $ty:ty, $transform:expr) => { + $crate::helpers::llvm::assert_llvm_evals_to!($src, $expected, $ty, $transform, false); + }; +} + macro_rules! expect_runtime_error_panic { ($src:expr) => {{ #[cfg(feature = "wasm-cli-run")] @@ -649,6 +695,8 @@ macro_rules! assert_non_opt_evals_to { #[allow(unused_imports)] pub(crate) use assert_evals_to; #[allow(unused_imports)] +pub(crate) use assert_expect_failed; +#[allow(unused_imports)] pub(crate) use assert_llvm_evals_to; #[allow(unused_imports)] pub(crate) use assert_non_opt_evals_to; diff --git a/compiler/test_gen/src/helpers/mod.rs b/compiler/test_gen/src/helpers/mod.rs index 6d2c6dfcd4..10e3c70e8b 100644 --- a/compiler/test_gen/src/helpers/mod.rs +++ b/compiler/test_gen/src/helpers/mod.rs @@ -2,14 +2,13 @@ extern crate bumpalo; #[cfg(feature = "gen-dev")] pub mod dev; -pub mod from_wasm32_memory; +pub mod from_wasmer_memory; #[cfg(feature = "gen-llvm")] pub mod llvm; #[cfg(feature = "gen-wasm")] pub mod wasm; -#[cfg(feature = "gen-wasm")] -pub mod wasm32_test_result; +#[allow(dead_code)] pub fn zig_executable() -> String { match std::env::var("ROC_ZIG") { Ok(path) => path, diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index 70f6e3e987..a0fb311283 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -1,26 +1,23 @@ +use core::cell::Cell; +use roc_gen_wasm::wasm_module::{Export, ExportType}; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use std::marker::PhantomData; -use tempfile::{tempdir, TempDir}; -use wasmer::Memory; +use std::path::{Path, PathBuf}; +use wasmer::{Memory, WasmPtr}; -use crate::helpers::from_wasm32_memory::FromWasm32Memory; -use crate::helpers::wasm32_test_result::Wasm32TestResult; -use roc_builtins::bitcode; +use crate::helpers::from_wasmer_memory::FromWasmerMemory; use roc_can::builtins::builtin_defs_map; use roc_collections::all::{MutMap, MutSet}; +use roc_gen_wasm::wasm32_result::Wasm32Result; use roc_gen_wasm::{DEBUG_LOG_SETTINGS, MEMORY_NAME}; // Should manually match build.rs const PLATFORM_FILENAME: &str = "wasm_test_platform"; const OUT_DIR_VAR: &str = "TEST_GEN_OUT"; -const LIBC_PATH_VAR: &str = "TEST_GEN_WASM_LIBC_PATH"; -const COMPILER_RT_PATH_VAR: &str = "TEST_GEN_WASM_COMPILER_RT_PATH"; - -#[allow(unused_imports)] -use roc_mono::ir::PRETTY_PRINT_IR_SYMBOLS; const TEST_WRAPPER_NAME: &str = "test_wrapper"; +const INIT_REFCOUNT_NAME: &str = "init_refcount_test"; fn promote_expr_to_module(src: &str) -> String { let mut buffer = String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n"); @@ -36,14 +33,44 @@ fn promote_expr_to_module(src: &str) -> String { } #[allow(dead_code)] -pub fn helper_wasm<'a, T: Wasm32TestResult>( +pub fn compile_and_load<'a, T: Wasm32Result>( arena: &'a bumpalo::Bump, src: &str, stdlib: &'a roc_builtins::std::StdLib, - _result_type_dummy: PhantomData, + _test_wrapper_type_info: PhantomData, ) -> wasmer::Instance { - use std::path::{Path, PathBuf}; + let platform_bytes = load_platform_and_builtins(); + let compiled_bytes = + compile_roc_to_wasm_bytes(arena, stdlib, &platform_bytes, src, _test_wrapper_type_info); + + if DEBUG_LOG_SETTINGS.keep_test_binary { + let build_dir_hash = src_hash(src); + save_wasm_file(&compiled_bytes, build_dir_hash) + }; + + load_bytes_into_runtime(compiled_bytes) +} + +fn load_platform_and_builtins() -> std::vec::Vec { + let out_dir = std::env::var(OUT_DIR_VAR).unwrap(); + let platform_path = Path::new(&out_dir).join([PLATFORM_FILENAME, "o"].join(".")); + std::fs::read(&platform_path).unwrap() +} + +fn src_hash(src: &str) -> u64 { + let mut hash_state = DefaultHasher::new(); + src.hash(&mut hash_state); + hash_state.finish() +} + +fn compile_roc_to_wasm_bytes<'a, T: Wasm32Result>( + arena: &'a bumpalo::Bump, + stdlib: &'a roc_builtins::std::StdLib, + preload_bytes: &[u8], + src: &str, + _test_wrapper_type_info: PhantomData, +) -> Vec { let filename = PathBuf::from("Test.roc"); let src_dir = Path::new("fake/test/path"); @@ -59,7 +86,6 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( } let exposed_types = MutMap::default(); - let ptr_bytes = 4; let loaded = roc_load::file::load_and_monomorphize_from_str( arena, filename, @@ -67,7 +93,7 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( stdlib, src_dir, exposed_types, - ptr_bytes, + roc_target::TargetInfo::default_wasm32(), builtin_defs_map, ); @@ -82,32 +108,13 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( .. } = loaded; - // You can comment and uncomment this block out to get more useful information - // while you're working on the wasm backend! - { - // println!("=========== Procedures =========="); - // if PRETTY_PRINT_IR_SYMBOLS { - // println!(""); - // for proc in procedures.values() { - // println!("{}", proc.to_pretty(200)); - // } - // } else { - // println!("{:?}", procedures.values()); - // } - // println!("=================================\n"); + debug_assert_eq!(exposed_to_host.values.len(), 1); - // println!("=========== Interns =========="); - // println!("{:?}", interns); - // println!("=================================\n"); - - // println!("=========== Exposed =========="); - // println!("{:?}", exposed_to_host); - // println!("=================================\n"); - } - - debug_assert_eq!(exposed_to_host.len(), 1); - - let exposed_to_host = exposed_to_host.keys().copied().collect::>(); + let exposed_to_host = exposed_to_host + .values + .keys() + .copied() + .collect::>(); let env = roc_gen_wasm::Env { arena, @@ -115,95 +122,50 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( exposed_to_host, }; - let (mut wasm_module, main_fn_index) = - roc_gen_wasm::build_module_help(&env, &mut interns, procedures).unwrap(); + let (mut module, called_preload_fns, main_fn_index) = + roc_gen_wasm::build_module_without_wrapper(&env, &mut interns, preload_bytes, procedures); - T::insert_test_wrapper(arena, &mut wasm_module, TEST_WRAPPER_NAME, main_fn_index); + T::insert_wrapper(arena, &mut module, TEST_WRAPPER_NAME, main_fn_index); - let mut module_bytes = std::vec::Vec::with_capacity(4096); - wasm_module.serialize_mut(&mut module_bytes); + // Export the initialiser function for refcount tests + let init_refcount_bytes = INIT_REFCOUNT_NAME.as_bytes(); + let init_refcount_idx = module.names.functions[init_refcount_bytes]; + module.export.append(Export { + name: arena.alloc_slice_copy(init_refcount_bytes), + ty: ExportType::Func, + index: init_refcount_idx, + }); - // now, do wasmer stuff + module.remove_dead_preloads(env.arena, called_preload_fns); - use wasmer::{Instance, Module, Store}; + let mut app_module_bytes = std::vec::Vec::with_capacity(module.size()); + module.serialize(&mut app_module_bytes); + + app_module_bytes +} + +fn save_wasm_file(app_module_bytes: &[u8], build_dir_hash: u64) { + let debug_dir_str = format!("/tmp/roc/gen_wasm/{:016x}", build_dir_hash); + let debug_dir_path = Path::new(&debug_dir_str); + let final_wasm_file = debug_dir_path.join("final.wasm"); + + std::fs::create_dir_all(debug_dir_path).unwrap(); + std::fs::write(&final_wasm_file, app_module_bytes).unwrap(); + + println!( + "Debug command:\n\twasm-objdump -dx {}", + final_wasm_file.to_str().unwrap() + ); +} + +fn load_bytes_into_runtime(bytes: Vec) -> wasmer::Instance { + use wasmer::{Module, Store}; + use wasmer_wasi::WasiState; let store = Store::default(); - - let wasmer_module = { - let tmp_dir: TempDir; // directory for normal test runs, deleted when dropped - let debug_dir: String; // persistent directory for debugging - - let wasm_build_dir: &Path = if DEBUG_LOG_SETTINGS.keep_test_binary { - // Directory name based on a hash of the Roc source - let mut hash_state = DefaultHasher::new(); - src.hash(&mut hash_state); - let src_hash = hash_state.finish(); - debug_dir = format!("/tmp/roc/gen_wasm/{:016x}", src_hash); - std::fs::create_dir_all(&debug_dir).unwrap(); - println!( - "Debug commands:\n\twasm-objdump -dx {}/app.o\n\twasm-objdump -dx {}/final.wasm", - &debug_dir, &debug_dir, - ); - Path::new(&debug_dir) - } else { - tmp_dir = tempdir().unwrap(); - tmp_dir.path() - }; - - let final_wasm_file = wasm_build_dir.join("final.wasm"); - let app_o_file = wasm_build_dir.join("app.o"); - let test_out_dir = std::env::var(OUT_DIR_VAR).unwrap(); - let test_platform_o = format!("{}/{}.o", test_out_dir, PLATFORM_FILENAME); - let libc_a_file = std::env::var(LIBC_PATH_VAR).unwrap(); - let compiler_rt_o_file = std::env::var(COMPILER_RT_PATH_VAR).unwrap(); - - // write the module to a file so the linker can access it - std::fs::write(&app_o_file, &module_bytes).unwrap(); - - let args = &[ - "wasm-ld", - // input files - app_o_file.to_str().unwrap(), - bitcode::BUILTINS_WASM32_OBJ_PATH, - &test_platform_o, - &libc_a_file, - &compiler_rt_o_file, - // output - "-o", - final_wasm_file.to_str().unwrap(), - // we don't define `_start` - "--no-entry", - // If you only specify test_wrapper, it will stop at the call to UserApp_main_1 - // But if you specify both exports, you get all the dependencies. - // - // It seems that it will not write out an export you didn't explicitly specify, - // even if it's a dependency of another export! - // In our case we always export main and test_wrapper so that's OK. - "--export", - "test_wrapper", - "--export", - "#UserApp_main_1", - ]; - - let linker_output = std::process::Command::new(&crate::helpers::zig_executable()) - .args(args) - .output() - .unwrap(); - - if !linker_output.status.success() { - print!("\nLINKER FAILED\n"); - for arg in args { - print!("{} ", arg); - } - println!("\n{}", std::str::from_utf8(&linker_output.stdout).unwrap()); - println!("{}", std::str::from_utf8(&linker_output.stderr).unwrap()); - } - - Module::from_file(&store, &final_wasm_file).unwrap() - }; + let wasmer_module = Module::new(&store, &bytes).unwrap(); // First, we create the `WasiEnv` - use wasmer_wasi::WasiState; let mut wasi_env = WasiState::new("hello").finalize().unwrap(); // Then, we get the import object related to our WASI @@ -212,20 +174,20 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( .import_object(&wasmer_module) .unwrap_or_else(|_| wasmer::imports!()); - Instance::new(&wasmer_module, &import_object).unwrap() + wasmer::Instance::new(&wasmer_module, &import_object).unwrap() } #[allow(dead_code)] pub fn assert_wasm_evals_to_help(src: &str, phantom: PhantomData) -> Result where - T: FromWasm32Memory + Wasm32TestResult, + T: FromWasmerMemory + Wasm32Result, { let arena = bumpalo::Bump::new(); // NOTE the stdlib must be in the arena; just taking a reference will segfault let stdlib = arena.alloc(roc_builtins::std::standard_stdlib()); - let instance = crate::helpers::wasm::helper_wasm(&arena, src, stdlib, phantom); + let instance = crate::helpers::wasm::compile_and_load(&arena, src, stdlib, phantom); let memory = instance.exports.get_memory(MEMORY_NAME).unwrap(); @@ -249,13 +211,79 @@ where // Manually provide address and size based on printf in wasm_test_platform.c crate::helpers::wasm::debug_memory_hex(memory, 0x11440, 24); } - let output = ::decode(memory, address as u32); + let output = ::decode(memory, address as u32); Ok(output) } } } +#[allow(dead_code)] +pub fn assert_wasm_refcounts_help( + src: &str, + phantom: PhantomData, + num_refcounts: usize, +) -> Result, String> +where + T: FromWasmerMemory + Wasm32Result, +{ + let arena = bumpalo::Bump::new(); + + // NOTE the stdlib must be in the arena; just taking a reference will segfault + let stdlib = arena.alloc(roc_builtins::std::standard_stdlib()); + + let instance = crate::helpers::wasm::compile_and_load(&arena, src, stdlib, phantom); + + let memory = instance.exports.get_memory(MEMORY_NAME).unwrap(); + + let expected_len = num_refcounts as i32; + let init_refcount_test = instance.exports.get_function(INIT_REFCOUNT_NAME).unwrap(); + let init_result = init_refcount_test.call(&[wasmer::Value::I32(expected_len)]); + let refcount_vector_addr = match init_result { + Err(e) => return Err(format!("{:?}", e)), + Ok(result) => match result[0] { + wasmer::Value::I32(a) => a, + _ => panic!(), + }, + }; + + // Run the test + let test_wrapper = instance.exports.get_function(TEST_WRAPPER_NAME).unwrap(); + match test_wrapper.call(&[]) { + Err(e) => return Err(format!("{:?}", e)), + Ok(_) => {} + } + + // Check we got the right number of refcounts + let refcount_vector_len: WasmPtr = WasmPtr::new(refcount_vector_addr as u32); + let actual_len = refcount_vector_len.deref(memory).unwrap().get(); + if actual_len != expected_len { + panic!("Expected {} refcounts but got {}", expected_len, actual_len); + } + + // Read the actual refcount values + let refcount_ptr_array: WasmPtr, wasmer::Array> = + WasmPtr::new(4 + refcount_vector_addr as u32); + let refcount_ptrs: &[Cell>] = refcount_ptr_array + .deref(memory, 0, num_refcounts as u32) + .unwrap(); + + let mut refcounts = Vec::with_capacity(num_refcounts); + for i in 0..num_refcounts { + let rc_ptr = refcount_ptrs[i].get(); + let rc = if rc_ptr.offset() == 0 { + // RC pointer has been set to null, which means the value has been freed. + // In tests, we simply represent this as zero refcount. + 0 + } else { + let rc_encoded = rc_ptr.deref(memory).unwrap().get(); + (rc_encoded - i32::MIN + 1) as u32 + }; + refcounts.push(rc); + } + Ok(refcounts) +} + /// Print out hex bytes of the test result, and a few words on either side /// Can be handy for debugging misalignment issues etc. pub fn debug_memory_hex(memory: &Memory, address: i32, size: usize) { @@ -330,7 +358,29 @@ pub fn identity(value: T) -> T { value } +#[allow(unused_macros)] +macro_rules! assert_refcounts { + // We need the result type to generate the test_wrapper, even though we ignore the value! + // We can't just call `main` with no args, because some tests return structs, via pointer arg! + // Also we need to know how much stack space to reserve for the struct. + ($src: expr, $ty: ty, $expected_refcounts: expr) => {{ + let phantom = std::marker::PhantomData; + let num_refcounts = $expected_refcounts.len(); + let result = + $crate::helpers::wasm::assert_wasm_refcounts_help::<$ty>($src, phantom, num_refcounts); + match result { + Err(msg) => panic!("{:?}", msg), + Ok(actual_refcounts) => { + assert_eq!(&actual_refcounts, $expected_refcounts) + } + } + }}; +} + #[allow(unused_imports)] pub(crate) use assert_evals_to; #[allow(unused_imports)] pub(crate) use assert_wasm_evals_to; + +#[allow(unused_imports)] +pub(crate) use assert_refcounts; diff --git a/compiler/test_gen/src/helpers/wasm32_test_result.rs b/compiler/test_gen/src/helpers/wasm32_test_result.rs deleted file mode 100644 index 498c086dde..0000000000 --- a/compiler/test_gen/src/helpers/wasm32_test_result.rs +++ /dev/null @@ -1,289 +0,0 @@ -use bumpalo::collections::Vec; - -use crate::helpers::from_wasm32_memory::FromWasm32Memory; -use roc_gen_wasm::wasm_module::{ - linking::SymInfo, linking::WasmObjectSymbol, Align, CodeBuilder, Export, ExportType, LocalId, - Signature, ValueType, WasmModule, -}; -use roc_std::{RocDec, RocList, RocOrder, RocStr}; - -pub trait Wasm32TestResult { - fn insert_test_wrapper<'a>( - arena: &'a bumpalo::Bump, - module: &mut WasmModule<'a>, - wrapper_name: &str, - main_function_index: u32, - ) { - let index = 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.entries.push(Export { - name: wrapper_name.to_string(), - ty: ExportType::Func, - index, - }); - - let symbol_table = module.linking.symbol_table_mut(); - symbol_table.push(SymInfo::Function(WasmObjectSymbol::Defined { - flags: 0, - index, - name: wrapper_name.to_string(), - })); - - 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); -} - -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_test_result_primitive { - ($type_name: ident, $store_instruction: ident, $align: expr) => { - impl Wasm32TestResult 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_test_result_stack_memory { - ($type_name: ident) => { - impl Wasm32TestResult 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_test_result_primitive!(bool, i32_store8, Align::Bytes1); -wasm_test_result_primitive!(RocOrder, i32_store8, Align::Bytes1); - -wasm_test_result_primitive!(u8, i32_store8, Align::Bytes1); -wasm_test_result_primitive!(i8, i32_store8, Align::Bytes1); -wasm_test_result_primitive!(u16, i32_store16, Align::Bytes2); -wasm_test_result_primitive!(i16, i32_store16, Align::Bytes2); -wasm_test_result_primitive!(u32, i32_store, Align::Bytes4); -wasm_test_result_primitive!(i32, i32_store, Align::Bytes4); -wasm_test_result_primitive!(u64, i64_store, Align::Bytes8); -wasm_test_result_primitive!(i64, i64_store, Align::Bytes8); -wasm_test_result_primitive!(usize, i32_store, Align::Bytes4); - -wasm_test_result_primitive!(f32, f32_store, Align::Bytes8); -wasm_test_result_primitive!(f64, f64_store, Align::Bytes8); - -wasm_test_result_stack_memory!(u128); -wasm_test_result_stack_memory!(i128); -wasm_test_result_stack_memory!(RocDec); -wasm_test_result_stack_memory!(RocStr); - -impl Wasm32TestResult for RocList { - fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { - build_wrapper_body_stack_memory(code_builder, main_function_index, 12) - } -} - -impl Wasm32TestResult for &'_ T { - build_wrapper_body_primitive!(i32_store, Align::Bytes4); -} - -impl Wasm32TestResult for [T; N] -where - T: Wasm32TestResult + FromWasm32Memory, -{ - 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 Wasm32TestResult 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 Wasm32TestResult for (T, U) -where - T: Wasm32TestResult + FromWasm32Memory, - U: Wasm32TestResult + FromWasm32Memory, -{ - 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 Wasm32TestResult for (T, U, V) -where - T: Wasm32TestResult + FromWasm32Memory, - U: Wasm32TestResult + FromWasm32Memory, - V: Wasm32TestResult + FromWasm32Memory, -{ - 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, - ) - } -} - -impl Wasm32TestResult for (T, U, V, W) -where - T: Wasm32TestResult + FromWasm32Memory, - U: Wasm32TestResult + FromWasm32Memory, - V: Wasm32TestResult + FromWasm32Memory, - W: Wasm32TestResult + FromWasm32Memory, -{ - 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 + W::ACTUAL_WIDTH, - ) - } -} - -impl Wasm32TestResult for (T, U, V, W, X) -where - T: Wasm32TestResult + FromWasm32Memory, - U: Wasm32TestResult + FromWasm32Memory, - V: Wasm32TestResult + FromWasm32Memory, - W: Wasm32TestResult + FromWasm32Memory, - X: Wasm32TestResult + FromWasm32Memory, -{ - 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 + W::ACTUAL_WIDTH + X::ACTUAL_WIDTH, - ) - } -} - -impl Wasm32TestResult for (T, U, V, W, X, Y) -where - T: Wasm32TestResult + FromWasm32Memory, - U: Wasm32TestResult + FromWasm32Memory, - V: Wasm32TestResult + FromWasm32Memory, - W: Wasm32TestResult + FromWasm32Memory, - X: Wasm32TestResult + FromWasm32Memory, - Y: Wasm32TestResult + FromWasm32Memory, -{ - 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 - + W::ACTUAL_WIDTH - + X::ACTUAL_WIDTH - + Y::ACTUAL_WIDTH, - ) - } -} - -impl Wasm32TestResult for (T, U, V, W, X, Y, Z) -where - T: Wasm32TestResult + FromWasm32Memory, - U: Wasm32TestResult + FromWasm32Memory, - V: Wasm32TestResult + FromWasm32Memory, - W: Wasm32TestResult + FromWasm32Memory, - X: Wasm32TestResult + FromWasm32Memory, - Y: Wasm32TestResult + FromWasm32Memory, - Z: Wasm32TestResult + FromWasm32Memory, -{ - 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 - + W::ACTUAL_WIDTH - + X::ACTUAL_WIDTH - + Y::ACTUAL_WIDTH - + Z::ACTUAL_WIDTH, - ) - } -} - -impl Wasm32TestResult for (T, U, V, W, X, Y, Z, A) -where - T: Wasm32TestResult + FromWasm32Memory, - U: Wasm32TestResult + FromWasm32Memory, - V: Wasm32TestResult + FromWasm32Memory, - W: Wasm32TestResult + FromWasm32Memory, - X: Wasm32TestResult + FromWasm32Memory, - Y: Wasm32TestResult + FromWasm32Memory, - Z: Wasm32TestResult + FromWasm32Memory, - A: Wasm32TestResult + FromWasm32Memory, -{ - 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 - + W::ACTUAL_WIDTH - + X::ACTUAL_WIDTH - + Y::ACTUAL_WIDTH - + Z::ACTUAL_WIDTH - + A::ACTUAL_WIDTH, - ) - } -} diff --git a/compiler/test_gen/src/helpers/wasm_test_platform.c b/compiler/test_gen/src/helpers/wasm_test_platform.c index 5f466c707e..b01543d0de 100644 --- a/compiler/test_gen/src/helpers/wasm_test_platform.c +++ b/compiler/test_gen/src/helpers/wasm_test_platform.c @@ -1,14 +1,71 @@ #include -// If any printf is included for compilation, even if unused, test runs take 50% longer -#define DEBUG 0 +// Makes test runs take 50% longer, due to linking +#define ENABLE_PRINTF 0 + +typedef struct +{ + size_t length; + size_t *elements[]; // flexible array member +} Vector; + +// Globals for refcount testing +Vector *rc_pointers; +size_t rc_pointers_capacity; + +// The rust test passes us the max number of allocations it expects to make, +// and we tell it where we're going to write the refcount pointers. +// It won't actually read that memory until later, when the test is done. +Vector *init_refcount_test(size_t capacity) +{ + rc_pointers_capacity = capacity; + + rc_pointers = malloc((1 + capacity) * sizeof(size_t *)); + rc_pointers->length = 0; + for (size_t i = 0; i < capacity; ++i) + rc_pointers->elements[i] = NULL; + + return rc_pointers; +} + +#if ENABLE_PRINTF +#define ASSERT(condition, format, ...) \ + if (!(condition)) \ + { \ + printf("ASSERT FAILED: " #format "\n", __VA_ARGS__); \ + abort(); \ + } +#else +#define ASSERT(condition, format, ...) \ + if (!(condition)) \ + abort(); +#endif + +size_t *alloc_ptr_to_rc_ptr(void *ptr, unsigned int alignment) +{ + size_t alloc_addr = (size_t)ptr; + size_t rc_addr = alloc_addr + alignment - sizeof(size_t); + return (size_t *)rc_addr; +} //-------------------------- void *roc_alloc(size_t size, unsigned int alignment) { void *allocated = malloc(size); -#if DEBUG + + if (rc_pointers) + { + ASSERT(alignment >= sizeof(size_t), "alignment %zd != %zd", alignment, sizeof(size_t)); + size_t num_alloc = rc_pointers->length + 1; + ASSERT(num_alloc <= rc_pointers_capacity, "Too many allocations %zd > %zd", num_alloc, rc_pointers_capacity); + + size_t *rc_ptr = alloc_ptr_to_rc_ptr(allocated, alignment); + rc_pointers->elements[rc_pointers->length] = rc_ptr; + rc_pointers->length++; + } + +#if ENABLE_PRINTF if (!allocated) { fprintf(stderr, "roc_alloc failed\n"); @@ -16,7 +73,7 @@ void *roc_alloc(size_t size, unsigned int alignment) } else { - printf("roc_alloc allocated %d bytes at %p\n", size, allocated); + printf("roc_alloc allocated %d bytes with alignment %d at %p\n", size, alignment, allocated); } #endif return allocated; @@ -27,6 +84,10 @@ void *roc_alloc(size_t size, unsigned int alignment) void *roc_realloc(void *ptr, size_t new_size, size_t old_size, unsigned int alignment) { +#if ENABLE_PRINTF + printf("roc_realloc reallocated %p from %d to %d with alignment %zd\n", + ptr, old_size, new_size, alignment); +#endif return realloc(ptr, new_size); } @@ -34,6 +95,27 @@ void *roc_realloc(void *ptr, size_t new_size, size_t old_size, void roc_dealloc(void *ptr, unsigned int alignment) { + if (rc_pointers) + { + // Null out the entry in the test array to indicate that it was freed + // Then even if malloc reuses the space, everything still works + size_t *rc_ptr = alloc_ptr_to_rc_ptr(ptr, alignment); + int i = 0; + for (; i < rc_pointers->length; ++i) + { + if (rc_pointers->elements[i] == rc_ptr) + { + rc_pointers->elements[i] = NULL; + break; + } + } + int was_found = i < rc_pointers->length; + ASSERT(was_found, "RC pointer not found %p", rc_ptr); + } + +#if ENABLE_PRINTF + printf("roc_dealloc deallocated %p with alignment %zd\n", ptr, alignment); +#endif free(ptr); } @@ -41,12 +123,12 @@ void roc_dealloc(void *ptr, unsigned int alignment) void roc_panic(void *ptr, unsigned int alignment) { -#if DEBUG +#if ENABLE_PRINTF char *msg = (char *)ptr; fprintf(stderr, "Application crashed with message\n\n %s\n\nShutting down\n", msg); #endif - exit(1); + abort(); } //-------------------------- diff --git a/compiler/test_gen/src/tests.rs b/compiler/test_gen/src/tests.rs index 7e324f608b..41923811a5 100644 --- a/compiler/test_gen/src/tests.rs +++ b/compiler/test_gen/src/tests.rs @@ -10,6 +10,7 @@ pub mod gen_list; pub mod gen_num; pub mod gen_primitives; pub mod gen_records; +pub mod gen_refcount; pub mod gen_result; pub mod gen_set; pub mod gen_str; @@ -23,6 +24,11 @@ pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { libc::malloc(size) } +#[no_mangle] +pub unsafe fn roc_memcpy(dest: *mut c_void, src: *const c_void, bytes: usize) -> *mut c_void { + libc::memcpy(dest, src, bytes) +} + #[no_mangle] pub unsafe fn roc_realloc( c_ptr: *mut c_void, diff --git a/compiler/test_gen/src/wasm_str.rs b/compiler/test_gen/src/wasm_str.rs index cf4b1c1957..69a88fd2b1 100644 --- a/compiler/test_gen/src/wasm_str.rs +++ b/compiler/test_gen/src/wasm_str.rs @@ -1121,3 +1121,215 @@ fn str_trim_right_small_to_small_shared() { (RocStr, RocStr) ); } + +#[test] +fn str_to_nat() { + assert_evals_to!( + indoc!( + r#" + when Str.toNat "1" is + Ok n -> n + Err _ -> 0 + "# + ), + 1, + usize + ); +} + +#[test] +fn str_to_i128() { + assert_evals_to!( + indoc!( + r#" + when Str.toI128 "1" is + Ok n -> n + Err _ -> 0 + "# + ), + 1, + i128 + ); +} + +#[test] +fn str_to_u128() { + assert_evals_to!( + indoc!( + r#" + when Str.toU128 "1" is + Ok n -> n + Err _ -> 0 + "# + ), + 1, + u128 + ); +} + +#[test] +fn str_to_i64() { + assert_evals_to!( + indoc!( + r#" + when Str.toI64 "1" is + Ok n -> n + Err _ -> 0 + "# + ), + 1, + i64 + ); +} + +#[test] +fn str_to_u64() { + assert_evals_to!( + indoc!( + r#" + when Str.toU64 "1" is + Ok n -> n + Err _ -> 0 + "# + ), + 1, + u64 + ); +} + +#[test] +fn str_to_i32() { + assert_evals_to!( + indoc!( + r#" + when Str.toI32 "1" is + Ok n -> n + Err _ -> 0 + "# + ), + 1, + i32 + ); +} + +#[test] +fn str_to_u32() { + assert_evals_to!( + indoc!( + r#" + when Str.toU32 "1" is + Ok n -> n + Err _ -> 0 + "# + ), + 1, + u32 + ); +} + +#[test] +fn str_to_i16() { + assert_evals_to!( + indoc!( + r#" + when Str.toI16 "1" is + Ok n -> n + Err _ -> 0 + "# + ), + 1, + i16 + ); +} + +#[test] +fn str_to_u16() { + assert_evals_to!( + indoc!( + r#" + when Str.toU16 "1" is + Ok n -> n + Err _ -> 0 + "# + ), + 1, + u16 + ); +} + +#[test] +fn str_to_i8() { + assert_evals_to!( + indoc!( + r#" + when Str.toI8 "1" is + Ok n -> n + Err _ -> 0 + "# + ), + 1, + i8 + ); +} + +#[test] +fn str_to_u8() { + assert_evals_to!( + indoc!( + r#" + when Str.toU8 "1" is + Ok n -> n + Err _ -> 0 + "# + ), + 1, + u8 + ); +} + +#[test] +fn str_to_f64() { + assert_evals_to!( + indoc!( + r#" + when Str.toF64 "1.0" is + Ok n -> n + Err _ -> 0 + "# + ), + 1.0, + f64 + ); +} + +#[test] +fn str_to_f32() { + assert_evals_to!( + indoc!( + r#" + when Str.toF32 "1.0" is + Ok n -> n + Err _ -> 0 + "# + ), + 1.0, + f32 + ); +} + +#[test] +fn str_to_dec() { + use roc_std::RocDec; + + assert_evals_to!( + indoc!( + r#" + when Str.toDec "1.0" is + Ok n -> n + Err _ -> 0 + "# + ), + RocDec::from_str("1.0").unwrap(), + RocDec + ); +} diff --git a/compiler/test_mono/Cargo.toml b/compiler/test_mono/Cargo.toml index d599b1b64c..2f56668cb1 100644 --- a/compiler/test_mono/Cargo.toml +++ b/compiler/test_mono/Cargo.toml @@ -16,6 +16,7 @@ roc_builtins = { path = "../builtins" } roc_load = { path = "../load" } roc_can = { path = "../can" } roc_mono = { path = "../mono" } +roc_target = { path = "../roc_target" } test_mono_macros = { path = "../test_mono_macros" } pretty_assertions = "1.0.0" bumpalo = { version = "3.8.0", features = ["collections"] } diff --git a/compiler/test_mono/generated/alias_variable.txt b/compiler/test_mono/generated/alias_variable.txt index 30f430e2d7..657376d73a 100644 --- a/compiler/test_mono/generated/alias_variable.txt +++ b/compiler/test_mono/generated/alias_variable.txt @@ -1,4 +1,3 @@ procedure Test.0 (): - let Test.1 = 5i64; - let Test.3 = 3i64; + let Test.3 : Builtin(Int(I64)) = 3i64; ret Test.3; diff --git a/compiler/test_mono/generated/alias_variable_and_return_it.txt b/compiler/test_mono/generated/alias_variable_and_return_it.txt index 755d390183..9e5824a185 100644 --- a/compiler/test_mono/generated/alias_variable_and_return_it.txt +++ b/compiler/test_mono/generated/alias_variable_and_return_it.txt @@ -1,3 +1,3 @@ procedure Test.0 (): - let Test.1 = 5i64; - ret Test.1; + let Test.2 : Builtin(Int(I64)) = 5i64; + ret Test.2; diff --git a/compiler/test_mono/generated/aliased_polymorphic_closure.txt b/compiler/test_mono/generated/aliased_polymorphic_closure.txt new file mode 100644 index 0000000000..bf1eb3d95d --- /dev/null +++ b/compiler/test_mono/generated/aliased_polymorphic_closure.txt @@ -0,0 +1,17 @@ +procedure Test.2 (Test.6, #Attr.12): + let Test.1 : Builtin(Int(U8)) = StructAtIndex 0 #Attr.12; + let Test.11 : LambdaSet(LambdaSet { set: [( Test.4, [Builtin(Int(U8))])], representation: Struct([Builtin(Int(U8))]) }) = Struct {Test.1}; + ret Test.11; + +procedure Test.4 (Test.5, #Attr.12): + let Test.1 : Builtin(Int(U8)) = StructAtIndex 0 #Attr.12; + ret Test.1; + +procedure Test.0 (): + let Test.1 : Builtin(Int(U8)) = 1i64; + let Test.8 : Struct([]) = Struct {}; + let Test.10 : Struct([]) = Struct {}; + let Test.14 : LambdaSet(LambdaSet { set: [( Test.2, [Builtin(Int(U8))])], representation: Struct([Builtin(Int(U8))]) }) = Struct {Test.1}; + let Test.9 : LambdaSet(LambdaSet { set: [( Test.4, [Builtin(Int(U8))])], representation: Struct([Builtin(Int(U8))]) }) = CallByName Test.2 Test.10 Test.14; + let Test.7 : Builtin(Int(U8)) = CallByName Test.4 Test.8 Test.9; + ret Test.7; diff --git a/compiler/test_mono/generated/branch_store_variable.txt b/compiler/test_mono/generated/branch_store_variable.txt index 59f4d90045..567b83bd1f 100644 --- a/compiler/test_mono/generated/branch_store_variable.txt +++ b/compiler/test_mono/generated/branch_store_variable.txt @@ -1,9 +1,9 @@ procedure Test.0 (): - let Test.2 = 0i64; - let Test.5 = 1i64; - let Test.6 = lowlevel Eq Test.5 Test.2; + let Test.2 : Builtin(Int(I64)) = 0i64; + let Test.5 : Builtin(Int(I64)) = 1i64; + let Test.6 : Builtin(Bool) = lowlevel Eq Test.5 Test.2; if Test.6 then - let Test.3 = 12i64; + let Test.3 : Builtin(Int(I64)) = 12i64; ret Test.3; else ret Test.2; diff --git a/compiler/test_mono/generated/closure_in_list.txt b/compiler/test_mono/generated/closure_in_list.txt index 41c1e43828..0347966c3e 100644 --- a/compiler/test_mono/generated/closure_in_list.txt +++ b/compiler/test_mono/generated/closure_in_list.txt @@ -1,20 +1,21 @@ procedure List.7 (#Attr.2): - let Test.7 = lowlevel ListLen #Attr.2; - ret Test.7; + let Test.8 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; + ret Test.8; procedure Test.1 (Test.5): - let Test.2 = 41i64; - let Test.11 = Struct {Test.2}; - let Test.10 = Array [Test.11]; - ret Test.10; + let Test.2 : Builtin(Int(I64)) = 41i64; + let Test.12 : LambdaSet(LambdaSet { set: [( Test.3, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }) = Struct {Test.2}; + let Test.11 : Builtin(List(LambdaSet(LambdaSet { set: [( Test.3, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }))) = Array [Test.12]; + ret Test.11; -procedure Test.3 (Test.9, #Attr.12): - let Test.2 = StructAtIndex 0 #Attr.12; +procedure Test.3 (Test.10, #Attr.12): + let Test.2 : Builtin(Int(I64)) = StructAtIndex 0 #Attr.12; + let Test.2 : Builtin(Int(I64)) = 41i64; ret Test.2; procedure Test.0 (): - let Test.8 = Struct {}; - let Test.4 = CallByName Test.1 Test.8; - let Test.6 = CallByName List.7 Test.4; - dec Test.4; + let Test.9 : Struct([]) = Struct {}; + let Test.7 : Builtin(List(LambdaSet(LambdaSet { set: [( Test.3, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }))) = CallByName Test.1 Test.9; + let Test.6 : Builtin(Int(U64)) = CallByName List.7 Test.7; + dec Test.7; ret Test.6; diff --git a/compiler/test_mono/generated/dict.txt b/compiler/test_mono/generated/dict.txt index 42fa1a4676..ad2fbf5701 100644 --- a/compiler/test_mono/generated/dict.txt +++ b/compiler/test_mono/generated/dict.txt @@ -1,13 +1,13 @@ procedure Dict.2 (): - let Test.4 = lowlevel DictEmpty ; + let Test.4 : Builtin(Dict(Union(NonRecursive([])), Union(NonRecursive([])))) = lowlevel DictEmpty ; ret Test.4; procedure Dict.8 (#Attr.2): - let Test.3 = lowlevel DictSize #Attr.2; + let Test.3 : Builtin(Int(U64)) = lowlevel DictSize #Attr.2; dec #Attr.2; ret Test.3; procedure Test.0 (): - let Test.2 = CallByName Dict.2; - let Test.1 = CallByName Dict.8 Test.2; + let Test.2 : Builtin(Dict(Union(NonRecursive([])), Union(NonRecursive([])))) = CallByName Dict.2; + let Test.1 : Builtin(Int(U64)) = CallByName Dict.8 Test.2; ret Test.1; diff --git a/compiler/test_mono/generated/empty_list_of_function_type.txt b/compiler/test_mono/generated/empty_list_of_function_type.txt index 89aa3d309b..44c1c42994 100644 --- a/compiler/test_mono/generated/empty_list_of_function_type.txt +++ b/compiler/test_mono/generated/empty_list_of_function_type.txt @@ -1,43 +1,42 @@ procedure List.3 (#Attr.2, #Attr.3): - let Test.20 = lowlevel ListLen #Attr.2; - let Test.17 = lowlevel NumLt #Attr.3 Test.20; + let Test.20 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; + let Test.17 : Builtin(Bool) = lowlevel NumLt #Attr.3 Test.20; if Test.17 then - let Test.19 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - let Test.18 = Ok Test.19; + let Test.19 : LambdaSet(LambdaSet { set: [( Test.2, [])], representation: Struct([]) }) = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + let Test.18 : Union(NonRecursive([[Struct([])], [LambdaSet(LambdaSet { set: [( Test.2, [])], representation: Struct([]) })]])) = Ok Test.19; ret Test.18; else - let Test.16 = Struct {}; - let Test.15 = Err Test.16; + let Test.16 : Struct([]) = Struct {}; + let Test.15 : Union(NonRecursive([[Struct([])], [LambdaSet(LambdaSet { set: [( Test.2, [])], representation: Struct([]) })]])) = Err Test.16; ret Test.15; procedure Test.2 (Test.6): - let Test.24 = "bar"; + let Test.24 : Builtin(Str) = "bar"; ret Test.24; procedure Test.0 (): - let Test.1 = Array []; joinpoint Test.22 Test.3: - let Test.14 = 0i64; - let Test.7 = CallByName List.3 Test.3 Test.14; + let Test.14 : Builtin(Int(U64)) = 0i64; + let Test.7 : Union(NonRecursive([[Struct([])], [LambdaSet(LambdaSet { set: [( Test.2, [])], representation: Struct([]) })]])) = CallByName List.3 Test.3 Test.14; dec Test.3; - let Test.11 = 1i64; - let Test.12 = GetTagId Test.7; - let Test.13 = lowlevel Eq Test.11 Test.12; + let Test.11 : Builtin(Int(U8)) = 1i64; + let Test.12 : Builtin(Int(U8)) = GetTagId Test.7; + let Test.13 : Builtin(Bool) = lowlevel Eq Test.11 Test.12; if Test.13 then - let Test.5 = UnionAtIndex (Id 1) (Index 0) Test.7; - let Test.9 = "foo"; - let Test.8 = CallByName Test.2 Test.9; + let Test.5 : LambdaSet(LambdaSet { set: [( Test.2, [])], representation: Struct([]) }) = UnionAtIndex (Id 1) (Index 0) Test.7; + let Test.9 : Builtin(Str) = "foo"; + let Test.8 : Builtin(Str) = CallByName Test.2 Test.9; dec Test.9; ret Test.8; else - let Test.10 = "bad!"; + let Test.10 : Builtin(Str) = "bad!"; ret Test.10; in - let Test.25 = false; + let Test.25 : Builtin(Bool) = false; if Test.25 then + let Test.1 : Builtin(List(LambdaSet(LambdaSet { set: [( Test.2, [])], representation: Struct([]) }))) = Array []; jump Test.22 Test.1; else - dec Test.1; - let Test.23 = Struct {}; - let Test.21 = Array [Test.23]; + let Test.23 : LambdaSet(LambdaSet { set: [( Test.2, [])], representation: Struct([]) }) = Struct {}; + let Test.21 : Builtin(List(LambdaSet(LambdaSet { set: [( Test.2, [])], representation: Struct([]) }))) = Array [Test.23]; jump Test.22 Test.21; diff --git a/compiler/test_mono/generated/factorial.txt b/compiler/test_mono/generated/factorial.txt index 8abf602d63..d0c12615da 100644 --- a/compiler/test_mono/generated/factorial.txt +++ b/compiler/test_mono/generated/factorial.txt @@ -1,27 +1,27 @@ -procedure Num.25 (#Attr.2, #Attr.3): - let Test.14 = lowlevel NumSub #Attr.2 #Attr.3; +procedure Num.23 (#Attr.2, #Attr.3): + let Test.14 : Builtin(Int(I64)) = lowlevel NumSub #Attr.2 #Attr.3; ret Test.14; -procedure Num.26 (#Attr.2, #Attr.3): - let Test.12 = lowlevel NumMul #Attr.2 #Attr.3; +procedure Num.24 (#Attr.2, #Attr.3): + let Test.12 : Builtin(Int(I64)) = lowlevel NumMul #Attr.2 #Attr.3; ret Test.12; procedure Test.1 (Test.17, Test.18): joinpoint Test.7 Test.2 Test.3: - let Test.15 = 0i64; - let Test.16 = lowlevel Eq Test.15 Test.2; + let Test.15 : Builtin(Int(I64)) = 0i64; + let Test.16 : Builtin(Bool) = lowlevel Eq Test.15 Test.2; if Test.16 then ret Test.3; else - let Test.13 = 1i64; - let Test.10 = CallByName Num.25 Test.2 Test.13; - let Test.11 = CallByName Num.26 Test.2 Test.3; + let Test.13 : Builtin(Int(I64)) = 1i64; + let Test.10 : Builtin(Int(I64)) = CallByName Num.23 Test.2 Test.13; + let Test.11 : Builtin(Int(I64)) = CallByName Num.24 Test.2 Test.3; jump Test.7 Test.10 Test.11; in jump Test.7 Test.17 Test.18; procedure Test.0 (): - let Test.5 = 10i64; - let Test.6 = 1i64; - let Test.4 = CallByName Test.1 Test.5 Test.6; + let Test.5 : Builtin(Int(I64)) = 10i64; + let Test.6 : Builtin(Int(I64)) = 1i64; + let Test.4 : Builtin(Int(I64)) = CallByName Test.1 Test.5 Test.6; ret Test.4; diff --git a/compiler/test_mono/generated/fst.txt b/compiler/test_mono/generated/fst.txt index fe81173aa5..f85b59b85b 100644 --- a/compiler/test_mono/generated/fst.txt +++ b/compiler/test_mono/generated/fst.txt @@ -3,9 +3,9 @@ procedure Test.1 (Test.2, Test.3): ret Test.2; procedure Test.0 (): - let Test.5 = Array [1i64, 2i64, 3i64]; - let Test.6 = Array [3i64, 2i64, 1i64]; - let Test.4 = CallByName Test.1 Test.5 Test.6; + let Test.5 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64, 3i64]; + let Test.6 : Builtin(List(Builtin(Int(I64)))) = Array [3i64, 2i64, 1i64]; + let Test.4 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.1 Test.5 Test.6; dec Test.6; dec Test.5; ret Test.4; diff --git a/compiler/test_mono/generated/guard_pattern_true.txt b/compiler/test_mono/generated/guard_pattern_true.txt index c0ee2f046d..b93ad5f177 100644 --- a/compiler/test_mono/generated/guard_pattern_true.txt +++ b/compiler/test_mono/generated/guard_pattern_true.txt @@ -1,25 +1,25 @@ procedure Test.1 (Test.3): - let Test.6 = 2i64; + let Test.6 : Builtin(Int(I64)) = 2i64; joinpoint Test.11: - let Test.10 = 0i64; + let Test.10 : Builtin(Int(I64)) = 0i64; ret Test.10; in - let Test.13 = 2i64; - let Test.14 = lowlevel Eq Test.13 Test.6; + let Test.13 : Builtin(Int(I64)) = 2i64; + let Test.14 : Builtin(Bool) = lowlevel Eq Test.13 Test.6; if Test.14 then joinpoint Test.8 Test.12: if Test.12 then - let Test.7 = 42i64; + let Test.7 : Builtin(Int(I64)) = 42i64; ret Test.7; else jump Test.11; in - let Test.9 = false; + let Test.9 : Builtin(Bool) = false; jump Test.8 Test.9; else jump Test.11; procedure Test.0 (): - let Test.5 = Struct {}; - let Test.4 = CallByName Test.1 Test.5; + let Test.5 : Struct([]) = Struct {}; + let Test.4 : Builtin(Int(I64)) = CallByName Test.1 Test.5; ret Test.4; diff --git a/compiler/test_mono/generated/has_none.txt b/compiler/test_mono/generated/has_none.txt index b6e7dba474..b97fc22ad0 100644 --- a/compiler/test_mono/generated/has_none.txt +++ b/compiler/test_mono/generated/has_none.txt @@ -1,30 +1,30 @@ procedure Test.3 (Test.29): joinpoint Test.13 Test.4: - let Test.23 = 1i64; - let Test.24 = GetTagId Test.4; - let Test.25 = lowlevel Eq Test.23 Test.24; + let Test.23 : Builtin(Bool) = 1i64; + let Test.24 : Builtin(Bool) = GetTagId Test.4; + let Test.25 : Builtin(Bool) = lowlevel Eq Test.23 Test.24; if Test.25 then - let Test.14 = false; + let Test.14 : Builtin(Bool) = false; ret Test.14; else - let Test.19 = UnionAtIndex (Id 0) (Index 0) Test.4; - let Test.20 = 1i64; - let Test.21 = GetTagId Test.19; - let Test.22 = lowlevel Eq Test.20 Test.21; + let Test.19 : Union(NonRecursive([[Builtin(Int(I64))], []])) = UnionAtIndex (Id 0) (Index 0) Test.4; + let Test.20 : Builtin(Int(U8)) = 1i64; + let Test.21 : Builtin(Int(U8)) = GetTagId Test.19; + let Test.22 : Builtin(Bool) = lowlevel Eq Test.20 Test.21; if Test.22 then - let Test.15 = true; + let Test.15 : Builtin(Bool) = true; ret Test.15; else - let Test.7 = UnionAtIndex (Id 0) (Index 1) Test.4; + let Test.7 : Union(NullableUnwrapped { nullable_id: true, other_fields: [Union(NonRecursive([[Builtin(Int(I64))], []])), RecursivePointer] }) = UnionAtIndex (Id 0) (Index 1) Test.4; jump Test.13 Test.7; in jump Test.13 Test.29; procedure Test.0 (): - let Test.28 = 3i64; - let Test.26 = Just Test.28; - let Test.27 = Nil ; - let Test.12 = Cons Test.26 Test.27; - let Test.11 = CallByName Test.3 Test.12; + let Test.28 : Builtin(Int(I64)) = 3i64; + let Test.26 : Union(NonRecursive([[Builtin(Int(I64))], []])) = Just Test.28; + let Test.27 : Union(NullableUnwrapped { nullable_id: true, other_fields: [Union(NonRecursive([[Builtin(Int(I64))], []])), RecursivePointer] }) = Nil ; + let Test.12 : Union(NullableUnwrapped { nullable_id: true, other_fields: [Union(NonRecursive([[Builtin(Int(I64))], []])), RecursivePointer] }) = Cons Test.26 Test.27; + let Test.11 : Builtin(Bool) = CallByName Test.3 Test.12; dec Test.12; ret Test.11; diff --git a/compiler/test_mono/generated/if_guard_bind_variable_false.txt b/compiler/test_mono/generated/if_guard_bind_variable_false.txt index 1bb0f10960..d5a0da7ccc 100644 --- a/compiler/test_mono/generated/if_guard_bind_variable_false.txt +++ b/compiler/test_mono/generated/if_guard_bind_variable_false.txt @@ -1,22 +1,22 @@ procedure Bool.7 (#Attr.2, #Attr.3): - let Test.11 = lowlevel Eq #Attr.2 #Attr.3; + let Test.11 : Builtin(Bool) = lowlevel Eq #Attr.2 #Attr.3; ret Test.11; procedure Test.1 (Test.3): - let Test.6 = 10i64; + let Test.6 : Builtin(Int(I64)) = 10i64; joinpoint Test.8 Test.13: if Test.13 then - let Test.7 = 0i64; + let Test.7 : Builtin(Int(I64)) = 0i64; ret Test.7; else - let Test.12 = 42i64; + let Test.12 : Builtin(Int(I64)) = 42i64; ret Test.12; in - let Test.10 = 5i64; - let Test.9 = CallByName Bool.7 Test.6 Test.10; + let Test.10 : Builtin(Int(I64)) = 5i64; + let Test.9 : Builtin(Bool) = CallByName Bool.7 Test.6 Test.10; jump Test.8 Test.9; procedure Test.0 (): - let Test.5 = Struct {}; - let Test.4 = CallByName Test.1 Test.5; + let Test.5 : Struct([]) = Struct {}; + let Test.4 : Builtin(Int(I64)) = CallByName Test.1 Test.5; ret Test.4; diff --git a/compiler/test_mono/generated/if_multi_branch.txt b/compiler/test_mono/generated/if_multi_branch.txt index c4953af3c3..778549a457 100644 --- a/compiler/test_mono/generated/if_multi_branch.txt +++ b/compiler/test_mono/generated/if_multi_branch.txt @@ -1,13 +1,13 @@ procedure Test.0 (): - let Test.6 = true; + let Test.6 : Builtin(Bool) = true; if Test.6 then - let Test.7 = 1i64; + let Test.7 : Builtin(Int(I64)) = 1i64; ret Test.7; else - let Test.4 = false; + let Test.4 : Builtin(Bool) = false; if Test.4 then - let Test.5 = 2i64; + let Test.5 : Builtin(Int(I64)) = 2i64; ret Test.5; else - let Test.3 = 3i64; + let Test.3 : Builtin(Int(I64)) = 3i64; ret Test.3; diff --git a/compiler/test_mono/generated/ir_assignment.txt b/compiler/test_mono/generated/ir_assignment.txt index 755d390183..ff5f614740 100644 --- a/compiler/test_mono/generated/ir_assignment.txt +++ b/compiler/test_mono/generated/ir_assignment.txt @@ -1,3 +1,3 @@ procedure Test.0 (): - let Test.1 = 5i64; + let Test.1 : Builtin(Int(I64)) = 5i64; ret Test.1; diff --git a/compiler/test_mono/generated/ir_int_add.txt b/compiler/test_mono/generated/ir_int_add.txt index 0da2caed32..e4ef7ac607 100644 --- a/compiler/test_mono/generated/ir_int_add.txt +++ b/compiler/test_mono/generated/ir_int_add.txt @@ -1,19 +1,19 @@ procedure List.7 (#Attr.2): - let Test.6 = lowlevel ListLen #Attr.2; - ret Test.6; + let Test.7 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; + ret Test.7; -procedure Num.24 (#Attr.2, #Attr.3): - let Test.5 = lowlevel NumAdd #Attr.2 #Attr.3; +procedure Num.22 (#Attr.2, #Attr.3): + let Test.5 : Builtin(Int(U64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.5; procedure Test.0 (): - let Test.1 = Array [1i64, 2i64]; - let Test.9 = 5i64; - let Test.10 = 4i64; - let Test.7 = CallByName Num.24 Test.9 Test.10; - let Test.8 = 3i64; - let Test.3 = CallByName Num.24 Test.7 Test.8; - let Test.4 = CallByName List.7 Test.1; - dec Test.1; - let Test.2 = CallByName Num.24 Test.3 Test.4; + let Test.10 : Builtin(Int(U64)) = 5i64; + let Test.11 : Builtin(Int(U64)) = 4i64; + let Test.8 : Builtin(Int(U64)) = CallByName Num.22 Test.10 Test.11; + let Test.9 : Builtin(Int(U64)) = 3i64; + let Test.3 : Builtin(Int(U64)) = CallByName Num.22 Test.8 Test.9; + let Test.6 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64]; + let Test.4 : Builtin(Int(U64)) = CallByName List.7 Test.6; + dec Test.6; + let Test.2 : Builtin(Int(U64)) = CallByName Num.22 Test.3 Test.4; ret Test.2; diff --git a/compiler/test_mono/generated/ir_int_literal.txt b/compiler/test_mono/generated/ir_int_literal.txt index 755d390183..ff5f614740 100644 --- a/compiler/test_mono/generated/ir_int_literal.txt +++ b/compiler/test_mono/generated/ir_int_literal.txt @@ -1,3 +1,3 @@ procedure Test.0 (): - let Test.1 = 5i64; + let Test.1 : Builtin(Int(I64)) = 5i64; ret Test.1; diff --git a/compiler/test_mono/generated/ir_plus.txt b/compiler/test_mono/generated/ir_plus.txt index 1fa959e684..b0c0e1a060 100644 --- a/compiler/test_mono/generated/ir_plus.txt +++ b/compiler/test_mono/generated/ir_plus.txt @@ -1,9 +1,9 @@ -procedure Num.24 (#Attr.2, #Attr.3): - let Test.4 = lowlevel NumAdd #Attr.2 #Attr.3; +procedure Num.22 (#Attr.2, #Attr.3): + let Test.4 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.4; procedure Test.0 (): - let Test.2 = 1i64; - let Test.3 = 2i64; - let Test.1 = CallByName Num.24 Test.2 Test.3; + let Test.2 : Builtin(Int(I64)) = 1i64; + let Test.3 : Builtin(Int(I64)) = 2i64; + let Test.1 : Builtin(Int(I64)) = CallByName Num.22 Test.2 Test.3; ret Test.1; diff --git a/compiler/test_mono/generated/ir_round.txt b/compiler/test_mono/generated/ir_round.txt index 5448d22900..7f16bbcd71 100644 --- a/compiler/test_mono/generated/ir_round.txt +++ b/compiler/test_mono/generated/ir_round.txt @@ -1,8 +1,8 @@ -procedure Num.47 (#Attr.2): - let Test.3 = lowlevel NumRound #Attr.2; +procedure Num.45 (#Attr.2): + let Test.3 : Builtin(Int(I64)) = lowlevel NumRound #Attr.2; ret Test.3; procedure Test.0 (): - let Test.2 = 3.6f64; - let Test.1 = CallByName Num.47 Test.2; + let Test.2 : Builtin(Float(F64)) = 3.6f64; + let Test.1 : Builtin(Int(I64)) = CallByName Num.45 Test.2; ret Test.1; diff --git a/compiler/test_mono/generated/ir_two_defs.txt b/compiler/test_mono/generated/ir_two_defs.txt index 04a64113c5..b39fc79319 100644 --- a/compiler/test_mono/generated/ir_two_defs.txt +++ b/compiler/test_mono/generated/ir_two_defs.txt @@ -1,9 +1,9 @@ -procedure Num.24 (#Attr.2, #Attr.3): - let Test.4 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.4; +procedure Num.22 (#Attr.2, #Attr.3): + let Test.6 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; + ret Test.6; procedure Test.0 (): - let Test.1 = 3i64; - let Test.2 = 4i64; - let Test.3 = CallByName Num.24 Test.1 Test.2; + let Test.4 : Builtin(Int(I64)) = 3i64; + let Test.5 : Builtin(Int(I64)) = 4i64; + let Test.3 : Builtin(Int(I64)) = CallByName Num.22 Test.4 Test.5; ret Test.3; diff --git a/compiler/test_mono/generated/ir_when_idiv.txt b/compiler/test_mono/generated/ir_when_idiv.txt index a7463bbad2..6b60e0547c 100644 --- a/compiler/test_mono/generated/ir_when_idiv.txt +++ b/compiler/test_mono/generated/ir_when_idiv.txt @@ -1,25 +1,25 @@ -procedure Num.42 (#Attr.2, #Attr.3): - let Test.15 = 0i64; - let Test.12 = lowlevel NotEq #Attr.3 Test.15; +procedure Num.40 (#Attr.2, #Attr.3): + let Test.15 : Builtin(Int(I64)) = 0i64; + let Test.12 : Builtin(Bool) = lowlevel NotEq #Attr.3 Test.15; if Test.12 then - let Test.14 = lowlevel NumDivUnchecked #Attr.2 #Attr.3; - let Test.13 = Ok Test.14; + let Test.14 : Builtin(Int(I64)) = lowlevel NumDivUnchecked #Attr.2 #Attr.3; + let Test.13 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = Ok Test.14; ret Test.13; else - let Test.11 = Struct {}; - let Test.10 = Err Test.11; + let Test.11 : Struct([]) = Struct {}; + let Test.10 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = Err Test.11; ret Test.10; procedure Test.0 (): - let Test.8 = 1000i64; - let Test.9 = 10i64; - let Test.2 = CallByName Num.42 Test.8 Test.9; - let Test.5 = 1i64; - let Test.6 = GetTagId Test.2; - let Test.7 = lowlevel Eq Test.5 Test.6; + let Test.8 : Builtin(Int(I64)) = 1000i64; + let Test.9 : Builtin(Int(I64)) = 10i64; + let Test.2 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = CallByName Num.40 Test.8 Test.9; + let Test.5 : Builtin(Int(U8)) = 1i64; + let Test.6 : Builtin(Int(U8)) = GetTagId Test.2; + let Test.7 : Builtin(Bool) = lowlevel Eq Test.5 Test.6; if Test.7 then - let Test.1 = UnionAtIndex (Id 1) (Index 0) Test.2; + let Test.1 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) Test.2; ret Test.1; else - let Test.4 = -1i64; + let Test.4 : Builtin(Int(I64)) = -1i64; ret Test.4; diff --git a/compiler/test_mono/generated/ir_when_just.txt b/compiler/test_mono/generated/ir_when_just.txt index 65d6052dd9..a2ab04d249 100644 --- a/compiler/test_mono/generated/ir_when_just.txt +++ b/compiler/test_mono/generated/ir_when_just.txt @@ -1,18 +1,18 @@ -procedure Num.24 (#Attr.2, #Attr.3): - let Test.6 = lowlevel NumAdd #Attr.2 #Attr.3; +procedure Num.22 (#Attr.2, #Attr.3): + let Test.6 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.6; procedure Test.0 (): - let Test.11 = 41i64; - let Test.1 = Just Test.11; - let Test.8 = 0i64; - let Test.9 = GetTagId Test.1; - let Test.10 = lowlevel Eq Test.8 Test.9; + let Test.11 : Builtin(Int(I64)) = 41i64; + let Test.1 : Union(NonRecursive([[Builtin(Int(I64))], []])) = Just Test.11; + let Test.8 : Builtin(Int(U8)) = 0i64; + let Test.9 : Builtin(Int(U8)) = GetTagId Test.1; + let Test.10 : Builtin(Bool) = lowlevel Eq Test.8 Test.9; if Test.10 then - let Test.3 = UnionAtIndex (Id 0) (Index 0) Test.1; - let Test.5 = 1i64; - let Test.4 = CallByName Num.24 Test.3 Test.5; + let Test.3 : Builtin(Int(I64)) = UnionAtIndex (Id 0) (Index 0) Test.1; + let Test.5 : Builtin(Int(I64)) = 1i64; + let Test.4 : Builtin(Int(I64)) = CallByName Num.22 Test.3 Test.5; ret Test.4; else - let Test.7 = 1i64; + let Test.7 : Builtin(Int(I64)) = 1i64; ret Test.7; diff --git a/compiler/test_mono/generated/ir_when_maybe.txt b/compiler/test_mono/generated/ir_when_maybe.txt index 1cc90f1f16..348604c968 100644 --- a/compiler/test_mono/generated/ir_when_maybe.txt +++ b/compiler/test_mono/generated/ir_when_maybe.txt @@ -1,12 +1,12 @@ procedure Test.0 (): - let Test.9 = 3i64; - let Test.3 = Just Test.9; - let Test.6 = 0i64; - let Test.7 = GetTagId Test.3; - let Test.8 = lowlevel Eq Test.6 Test.7; + let Test.9 : Builtin(Int(I64)) = 3i64; + let Test.3 : Union(NonRecursive([[Builtin(Int(I64))], []])) = Just Test.9; + let Test.6 : Builtin(Int(U8)) = 0i64; + let Test.7 : Builtin(Int(U8)) = GetTagId Test.3; + let Test.8 : Builtin(Bool) = lowlevel Eq Test.6 Test.7; if Test.8 then - let Test.2 = UnionAtIndex (Id 0) (Index 0) Test.3; + let Test.2 : Builtin(Int(I64)) = UnionAtIndex (Id 0) (Index 0) Test.3; ret Test.2; else - let Test.5 = 0i64; + let Test.5 : Builtin(Int(I64)) = 0i64; ret Test.5; diff --git a/compiler/test_mono/generated/ir_when_record.txt b/compiler/test_mono/generated/ir_when_record.txt index ed777a23d0..b9e3ef94fc 100644 --- a/compiler/test_mono/generated/ir_when_record.txt +++ b/compiler/test_mono/generated/ir_when_record.txt @@ -1,6 +1,6 @@ procedure Test.0 (): - let Test.4 = 1i64; - let Test.5 = 3.14f64; - let Test.2 = Struct {Test.4, Test.5}; - let Test.1 = StructAtIndex 0 Test.2; + let Test.4 : Builtin(Int(I64)) = 1i64; + let Test.5 : Builtin(Float(F64)) = 3.14f64; + let Test.2 : Struct([Builtin(Int(I64)), Builtin(Float(F64))]) = Struct {Test.4, Test.5}; + let Test.1 : Builtin(Int(I64)) = StructAtIndex 0 Test.2; ret Test.1; diff --git a/compiler/test_mono/generated/ir_when_these.txt b/compiler/test_mono/generated/ir_when_these.txt index bcba501785..08c3614351 100644 --- a/compiler/test_mono/generated/ir_when_these.txt +++ b/compiler/test_mono/generated/ir_when_these.txt @@ -1,18 +1,18 @@ procedure Test.0 (): - let Test.10 = 1i64; - let Test.11 = 2i64; - let Test.5 = These Test.10 Test.11; - let Test.9 = GetTagId Test.5; + let Test.10 : Builtin(Int(I64)) = 1i64; + let Test.11 : Builtin(Int(I64)) = 2i64; + let Test.5 : Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64)), Builtin(Int(I64))], [Builtin(Int(I64))]])) = These Test.10 Test.11; + let Test.9 : Builtin(Int(U8)) = GetTagId Test.5; switch Test.9: case 2: - let Test.2 = UnionAtIndex (Id 2) (Index 0) Test.5; + let Test.2 : Builtin(Int(I64)) = UnionAtIndex (Id 2) (Index 0) Test.5; ret Test.2; case 0: - let Test.3 = UnionAtIndex (Id 0) (Index 0) Test.5; + let Test.3 : Builtin(Int(I64)) = UnionAtIndex (Id 0) (Index 0) Test.5; ret Test.3; default: - let Test.4 = UnionAtIndex (Id 1) (Index 0) Test.5; + let Test.4 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) Test.5; ret Test.4; diff --git a/compiler/test_mono/generated/is_nil.txt b/compiler/test_mono/generated/is_nil.txt index 55329f43fc..5968c5178c 100644 --- a/compiler/test_mono/generated/is_nil.txt +++ b/compiler/test_mono/generated/is_nil.txt @@ -1,18 +1,18 @@ procedure Test.2 (Test.3): - let Test.12 = 1i64; - let Test.13 = GetTagId Test.3; - let Test.14 = lowlevel Eq Test.12 Test.13; + let Test.12 : Builtin(Bool) = 1i64; + let Test.13 : Builtin(Bool) = GetTagId Test.3; + let Test.14 : Builtin(Bool) = lowlevel Eq Test.12 Test.13; if Test.14 then - let Test.10 = true; + let Test.10 : Builtin(Bool) = true; ret Test.10; else - let Test.11 = false; + let Test.11 : Builtin(Bool) = false; ret Test.11; procedure Test.0 (): - let Test.15 = 2i64; - let Test.16 = Nil ; - let Test.9 = Cons Test.15 Test.16; - let Test.8 = CallByName Test.2 Test.9; + let Test.15 : Builtin(Int(I64)) = 2i64; + let Test.16 : Union(NullableUnwrapped { nullable_id: true, other_fields: [Builtin(Int(I64)), RecursivePointer] }) = Nil ; + let Test.9 : Union(NullableUnwrapped { nullable_id: true, other_fields: [Builtin(Int(I64)), RecursivePointer] }) = Cons Test.15 Test.16; + let Test.8 : Builtin(Bool) = CallByName Test.2 Test.9; dec Test.9; ret Test.8; diff --git a/compiler/test_mono/generated/let_with_record_pattern.txt b/compiler/test_mono/generated/let_with_record_pattern.txt index 51df825ebf..302c1de690 100644 --- a/compiler/test_mono/generated/let_with_record_pattern.txt +++ b/compiler/test_mono/generated/let_with_record_pattern.txt @@ -1,6 +1,6 @@ procedure Test.0 (): - let Test.4 = 2i64; - let Test.5 = 3.14f64; - let Test.3 = Struct {Test.4, Test.5}; - let Test.1 = StructAtIndex 0 Test.3; + let Test.4 : Builtin(Int(I64)) = 2i64; + let Test.5 : Builtin(Float(F64)) = 3.14f64; + let Test.3 : Struct([Builtin(Int(I64)), Builtin(Float(F64))]) = Struct {Test.4, Test.5}; + let Test.1 : Builtin(Int(I64)) = StructAtIndex 0 Test.3; ret Test.1; diff --git a/compiler/test_mono/generated/let_with_record_pattern_list.txt b/compiler/test_mono/generated/let_with_record_pattern_list.txt index 91f6ea8249..1c22f23866 100644 --- a/compiler/test_mono/generated/let_with_record_pattern_list.txt +++ b/compiler/test_mono/generated/let_with_record_pattern_list.txt @@ -1,8 +1,8 @@ procedure Test.0 (): - let Test.4 = Array [1i64, 3i64, 4i64]; - let Test.5 = 3.14f64; - let Test.3 = Struct {Test.4, Test.5}; - let Test.1 = StructAtIndex 0 Test.3; + let Test.4 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 3i64, 4i64]; + let Test.5 : Builtin(Float(F64)) = 3.14f64; + let Test.3 : Struct([Builtin(List(Builtin(Int(I64)))), Builtin(Float(F64))]) = Struct {Test.4, Test.5}; + let Test.1 : Builtin(List(Builtin(Int(I64)))) = StructAtIndex 0 Test.3; inc Test.1; dec Test.3; ret Test.1; diff --git a/compiler/test_mono/generated/let_x_in_x.txt b/compiler/test_mono/generated/let_x_in_x.txt index 567439fa80..1ae2b5dd47 100644 --- a/compiler/test_mono/generated/let_x_in_x.txt +++ b/compiler/test_mono/generated/let_x_in_x.txt @@ -1,5 +1,3 @@ procedure Test.0 (): - let Test.1 = 5i64; - let Test.4 = 17i64; - let Test.2 = 1337i64; + let Test.2 : Builtin(Int(I64)) = 1337i64; ret Test.2; diff --git a/compiler/test_mono/generated/let_x_in_x_indirect.txt b/compiler/test_mono/generated/let_x_in_x_indirect.txt index 4fe43f886b..8897207353 100644 --- a/compiler/test_mono/generated/let_x_in_x_indirect.txt +++ b/compiler/test_mono/generated/let_x_in_x_indirect.txt @@ -1,8 +1,6 @@ procedure Test.0 (): - let Test.1 = 5i64; - let Test.4 = 17i64; - let Test.5 = 1i64; - let Test.2 = 1337i64; - let Test.7 = Struct {Test.2, Test.4}; - let Test.6 = StructAtIndex 0 Test.7; + let Test.2 : Builtin(Int(I64)) = 1337i64; + let Test.3 : Builtin(Int(I64)) = 17i64; + let Test.7 : Struct([Builtin(Int(I64)), Builtin(Int(I64))]) = Struct {Test.2, Test.3}; + let Test.6 : Builtin(Int(I64)) = StructAtIndex 0 Test.7; ret Test.6; diff --git a/compiler/test_mono/generated/linked_list_length_twice.txt b/compiler/test_mono/generated/linked_list_length_twice.txt index a540fcc435..c791ad1cd4 100644 --- a/compiler/test_mono/generated/linked_list_length_twice.txt +++ b/compiler/test_mono/generated/linked_list_length_twice.txt @@ -1,25 +1,25 @@ -procedure Num.24 (#Attr.2, #Attr.3): - let Test.10 = lowlevel NumAdd #Attr.2 #Attr.3; +procedure Num.22 (#Attr.2, #Attr.3): + let Test.10 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.10; procedure Test.3 (Test.5): - let Test.16 = 1i64; - let Test.17 = GetTagId Test.5; - let Test.18 = lowlevel Eq Test.16 Test.17; + let Test.16 : Builtin(Bool) = 1i64; + let Test.17 : Builtin(Bool) = GetTagId Test.5; + let Test.18 : Builtin(Bool) = lowlevel Eq Test.16 Test.17; if Test.18 then - let Test.12 = 0i64; + let Test.12 : Builtin(Int(I64)) = 0i64; ret Test.12; else - let Test.6 = UnionAtIndex (Id 0) (Index 1) Test.5; - let Test.14 = 1i64; - let Test.15 = CallByName Test.3 Test.6; - let Test.13 = CallByName Num.24 Test.14 Test.15; + let Test.6 : Union(NullableUnwrapped { nullable_id: true, other_fields: [Builtin(Int(I64)), RecursivePointer] }) = UnionAtIndex (Id 0) (Index 1) Test.5; + let Test.14 : Builtin(Int(I64)) = 1i64; + let Test.15 : Builtin(Int(I64)) = CallByName Test.3 Test.6; + let Test.13 : Builtin(Int(I64)) = CallByName Num.22 Test.14 Test.15; ret Test.13; procedure Test.0 (): - let Test.2 = Nil ; - let Test.8 = CallByName Test.3 Test.2; - let Test.9 = CallByName Test.3 Test.2; + let Test.2 : Union(NullableUnwrapped { nullable_id: true, other_fields: [Builtin(Int(I64)), RecursivePointer] }) = Nil ; + let Test.8 : Builtin(Int(I64)) = CallByName Test.3 Test.2; + let Test.9 : Builtin(Int(I64)) = CallByName Test.3 Test.2; dec Test.2; - let Test.7 = CallByName Num.24 Test.8 Test.9; + let Test.7 : Builtin(Int(I64)) = CallByName Num.22 Test.8 Test.9; ret Test.7; diff --git a/compiler/test_mono/generated/list_append.txt b/compiler/test_mono/generated/list_append.txt index 8cb3ba9758..2b0be9dd16 100644 --- a/compiler/test_mono/generated/list_append.txt +++ b/compiler/test_mono/generated/list_append.txt @@ -1,9 +1,9 @@ procedure List.5 (#Attr.2, #Attr.3): - let Test.4 = lowlevel ListAppend #Attr.2 #Attr.3; + let Test.4 : Builtin(List(Builtin(Int(I64)))) = lowlevel ListAppend #Attr.2 #Attr.3; ret Test.4; procedure Test.0 (): - let Test.2 = Array [1i64]; - let Test.3 = 2i64; - let Test.1 = CallByName List.5 Test.2 Test.3; + let Test.2 : Builtin(List(Builtin(Int(I64)))) = Array [1i64]; + let Test.3 : Builtin(Int(I64)) = 2i64; + let Test.1 : Builtin(List(Builtin(Int(I64)))) = CallByName List.5 Test.2 Test.3; ret Test.1; diff --git a/compiler/test_mono/generated/list_append_closure.txt b/compiler/test_mono/generated/list_append_closure.txt index cffdbe7e48..3cd9bffd51 100644 --- a/compiler/test_mono/generated/list_append_closure.txt +++ b/compiler/test_mono/generated/list_append_closure.txt @@ -1,13 +1,13 @@ procedure List.5 (#Attr.2, #Attr.3): - let Test.7 = lowlevel ListAppend #Attr.2 #Attr.3; + let Test.7 : Builtin(List(Builtin(Int(I64)))) = lowlevel ListAppend #Attr.2 #Attr.3; ret Test.7; procedure Test.1 (Test.2): - let Test.6 = 42i64; - let Test.5 = CallByName List.5 Test.2 Test.6; + let Test.6 : Builtin(Int(I64)) = 42i64; + let Test.5 : Builtin(List(Builtin(Int(I64)))) = CallByName List.5 Test.2 Test.6; ret Test.5; procedure Test.0 (): - let Test.4 = Array [1i64, 2i64]; - let Test.3 = CallByName Test.1 Test.4; + let Test.4 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64]; + let Test.3 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.1 Test.4; ret Test.3; diff --git a/compiler/test_mono/generated/list_cannot_update_inplace.txt b/compiler/test_mono/generated/list_cannot_update_inplace.txt index 88039ce8a0..c2087e2d74 100644 --- a/compiler/test_mono/generated/list_cannot_update_inplace.txt +++ b/compiler/test_mono/generated/list_cannot_update_inplace.txt @@ -1,37 +1,37 @@ procedure List.4 (#Attr.2, #Attr.3, #Attr.4): - let Test.19 = lowlevel ListLen #Attr.2; - let Test.17 = lowlevel NumLt #Attr.3 Test.19; + let Test.19 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; + let Test.17 : Builtin(Bool) = lowlevel NumLt #Attr.3 Test.19; if Test.17 then - let Test.18 = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; + let Test.18 : Builtin(List(Builtin(Int(I64)))) = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; ret Test.18; else ret #Attr.2; procedure List.7 (#Attr.2): - let Test.9 = lowlevel ListLen #Attr.2; + let Test.9 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; ret Test.9; -procedure Num.24 (#Attr.2, #Attr.3): - let Test.7 = lowlevel NumAdd #Attr.2 #Attr.3; +procedure Num.22 (#Attr.2, #Attr.3): + let Test.7 : Builtin(Int(U64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.7; procedure Test.1 (): - let Test.10 = Array [1i64, 2i64, 3i64]; + let Test.10 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64, 3i64]; ret Test.10; procedure Test.2 (Test.3): - let Test.14 = 0i64; - let Test.15 = 0i64; - let Test.13 = CallByName List.4 Test.3 Test.14 Test.15; + let Test.14 : Builtin(Int(U64)) = 0i64; + let Test.15 : Builtin(Int(I64)) = 0i64; + let Test.13 : Builtin(List(Builtin(Int(I64)))) = CallByName List.4 Test.3 Test.14 Test.15; ret Test.13; procedure Test.0 (): - let Test.12 = CallByName Test.1; - let Test.11 = CallByName Test.2 Test.12; - let Test.5 = CallByName List.7 Test.11; + let Test.12 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.1; + let Test.11 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.2 Test.12; + let Test.5 : Builtin(Int(U64)) = CallByName List.7 Test.11; dec Test.11; - let Test.8 = CallByName Test.1; - let Test.6 = CallByName List.7 Test.8; + let Test.8 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.1; + let Test.6 : Builtin(Int(U64)) = CallByName List.7 Test.8; dec Test.8; - let Test.4 = CallByName Num.24 Test.5 Test.6; + let Test.4 : Builtin(Int(U64)) = CallByName Num.22 Test.5 Test.6; ret Test.4; diff --git a/compiler/test_mono/generated/list_get.txt b/compiler/test_mono/generated/list_get.txt index 49280478d1..1ea41edce5 100644 --- a/compiler/test_mono/generated/list_get.txt +++ b/compiler/test_mono/generated/list_get.txt @@ -1,23 +1,23 @@ procedure List.3 (#Attr.2, #Attr.3): - let Test.13 = lowlevel ListLen #Attr.2; - let Test.10 = lowlevel NumLt #Attr.3 Test.13; + let Test.13 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; + let Test.10 : Builtin(Bool) = lowlevel NumLt #Attr.3 Test.13; if Test.10 then - let Test.12 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - let Test.11 = Ok Test.12; + let Test.12 : Builtin(Int(I64)) = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + let Test.11 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = Ok Test.12; ret Test.11; else - let Test.9 = Struct {}; - let Test.8 = Err Test.9; + let Test.9 : Struct([]) = Struct {}; + let Test.8 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = Err Test.9; ret Test.8; procedure Test.1 (Test.2): - let Test.6 = Array [1i64, 2i64, 3i64]; - let Test.7 = 0i64; - let Test.5 = CallByName List.3 Test.6 Test.7; + let Test.6 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64, 3i64]; + let Test.7 : Builtin(Int(U64)) = 0i64; + let Test.5 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = CallByName List.3 Test.6 Test.7; dec Test.6; ret Test.5; procedure Test.0 (): - let Test.4 = Struct {}; - let Test.3 = CallByName Test.1 Test.4; + let Test.4 : Struct([]) = Struct {}; + let Test.3 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = CallByName Test.1 Test.4; ret Test.3; diff --git a/compiler/test_mono/generated/list_len.txt b/compiler/test_mono/generated/list_len.txt index 3cc3d980bd..80b6093747 100644 --- a/compiler/test_mono/generated/list_len.txt +++ b/compiler/test_mono/generated/list_len.txt @@ -1,21 +1,21 @@ procedure List.7 (#Attr.2): - let Test.7 = lowlevel ListLen #Attr.2; - ret Test.7; + let Test.10 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; + ret Test.10; procedure List.7 (#Attr.2): - let Test.8 = lowlevel ListLen #Attr.2; + let Test.8 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; ret Test.8; -procedure Num.24 (#Attr.2, #Attr.3): - let Test.6 = lowlevel NumAdd #Attr.2 #Attr.3; +procedure Num.22 (#Attr.2, #Attr.3): + let Test.6 : Builtin(Int(U64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.6; procedure Test.0 (): - let Test.1 = Array [1i64, 2i64, 3i64]; - let Test.2 = Array [1f64]; - let Test.4 = CallByName List.7 Test.1; - dec Test.1; - let Test.5 = CallByName List.7 Test.2; - dec Test.2; - let Test.3 = CallByName Num.24 Test.4 Test.5; + let Test.9 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64, 3i64]; + let Test.4 : Builtin(Int(U64)) = CallByName List.7 Test.9; + dec Test.9; + let Test.7 : Builtin(List(Builtin(Float(F64)))) = Array [1f64]; + let Test.5 : Builtin(Int(U64)) = CallByName List.7 Test.7; + dec Test.7; + let Test.3 : Builtin(Int(U64)) = CallByName Num.22 Test.4 Test.5; ret Test.3; diff --git a/compiler/test_mono/generated/list_pass_to_function.txt b/compiler/test_mono/generated/list_pass_to_function.txt index c2d21a9d87..090b5affdd 100644 --- a/compiler/test_mono/generated/list_pass_to_function.txt +++ b/compiler/test_mono/generated/list_pass_to_function.txt @@ -1,19 +1,19 @@ procedure List.4 (#Attr.2, #Attr.3, #Attr.4): - let Test.11 = lowlevel ListLen #Attr.2; - let Test.9 = lowlevel NumLt #Attr.3 Test.11; + let Test.11 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; + let Test.9 : Builtin(Bool) = lowlevel NumLt #Attr.3 Test.11; if Test.9 then - let Test.10 = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; + let Test.10 : Builtin(List(Builtin(Int(I64)))) = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; ret Test.10; else ret #Attr.2; procedure Test.2 (Test.3): - let Test.6 = 0i64; - let Test.7 = 0i64; - let Test.5 = CallByName List.4 Test.3 Test.6 Test.7; + let Test.6 : Builtin(Int(U64)) = 0i64; + let Test.7 : Builtin(Int(I64)) = 0i64; + let Test.5 : Builtin(List(Builtin(Int(I64)))) = CallByName List.4 Test.3 Test.6 Test.7; ret Test.5; procedure Test.0 (): - let Test.1 = Array [1i64, 2i64, 3i64]; - let Test.4 = CallByName Test.2 Test.1; + let Test.1 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64, 3i64]; + let Test.4 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.2 Test.1; ret Test.4; diff --git a/compiler/test_mono/generated/mk_pair_of.txt b/compiler/test_mono/generated/mk_pair_of.txt index a223ddd1c2..98bd4b0d18 100644 --- a/compiler/test_mono/generated/mk_pair_of.txt +++ b/compiler/test_mono/generated/mk_pair_of.txt @@ -1,9 +1,9 @@ procedure Test.1 (Test.2): inc Test.2; - let Test.6 = Struct {Test.2, Test.2}; + let Test.6 : Struct([Builtin(List(Builtin(Int(I64)))), Builtin(List(Builtin(Int(I64))))]) = Struct {Test.2, Test.2}; ret Test.6; procedure Test.0 (): - let Test.5 = Array [1i64, 2i64, 3i64]; - let Test.4 = CallByName Test.1 Test.5; + let Test.5 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64, 3i64]; + let Test.4 : Struct([Builtin(List(Builtin(Int(I64)))), Builtin(List(Builtin(Int(I64))))]) = CallByName Test.1 Test.5; ret Test.4; diff --git a/compiler/test_mono/generated/monomorphized_applied_tag.txt b/compiler/test_mono/generated/monomorphized_applied_tag.txt new file mode 100644 index 0000000000..90dd27c931 --- /dev/null +++ b/compiler/test_mono/generated/monomorphized_applied_tag.txt @@ -0,0 +1,20 @@ +procedure Test.2 (Test.4): + let Test.11 : Builtin(Int(U8)) = 0i64; + let Test.12 : Builtin(Int(U8)) = GetTagId Test.4; + let Test.13 : Builtin(Bool) = lowlevel Eq Test.11 Test.12; + if Test.13 then + let Test.5 : Builtin(Str) = UnionAtIndex (Id 0) (Index 0) Test.4; + inc Test.5; + dec Test.4; + ret Test.5; + else + let Test.6 : Builtin(Str) = UnionAtIndex (Id 1) (Index 0) Test.4; + inc Test.6; + dec Test.4; + ret Test.6; + +procedure Test.0 (): + let Test.14 : Builtin(Str) = "A"; + let Test.8 : Union(NonRecursive([[Builtin(Str)], [Builtin(Str)]])) = A Test.14; + let Test.7 : Builtin(Str) = CallByName Test.2 Test.8; + ret Test.7; diff --git a/compiler/test_mono/generated/monomorphized_floats.txt b/compiler/test_mono/generated/monomorphized_floats.txt new file mode 100644 index 0000000000..90eef8acc7 --- /dev/null +++ b/compiler/test_mono/generated/monomorphized_floats.txt @@ -0,0 +1,9 @@ +procedure Test.2 (Test.3, Test.4): + let Test.8 : Builtin(Int(U64)) = 18i64; + ret Test.8; + +procedure Test.0 (): + let Test.6 : Builtin(Float(F32)) = 100f64; + let Test.7 : Builtin(Float(F64)) = 100f64; + let Test.5 : Builtin(Int(U64)) = CallByName Test.2 Test.6 Test.7; + ret Test.5; diff --git a/compiler/test_mono/generated/monomorphized_ints.txt b/compiler/test_mono/generated/monomorphized_ints.txt new file mode 100644 index 0000000000..5ffdd51f13 --- /dev/null +++ b/compiler/test_mono/generated/monomorphized_ints.txt @@ -0,0 +1,9 @@ +procedure Test.2 (Test.3, Test.4): + let Test.8 : Builtin(Int(U64)) = 18i64; + ret Test.8; + +procedure Test.0 (): + let Test.6 : Builtin(Int(U8)) = 100i64; + let Test.7 : Builtin(Int(U32)) = 100i64; + let Test.5 : Builtin(Int(U64)) = CallByName Test.2 Test.6 Test.7; + ret Test.5; diff --git a/compiler/test_mono/generated/monomorphized_ints_aliased.txt b/compiler/test_mono/generated/monomorphized_ints_aliased.txt new file mode 100644 index 0000000000..50bd650f9d --- /dev/null +++ b/compiler/test_mono/generated/monomorphized_ints_aliased.txt @@ -0,0 +1,23 @@ +procedure Num.22 (#Attr.2, #Attr.3): + let Test.12 : Builtin(Int(U64)) = lowlevel NumAdd #Attr.2 #Attr.3; + ret Test.12; + +procedure Test.4 (Test.7, Test.8): + let Test.17 : Builtin(Int(U64)) = 1i64; + ret Test.17; + +procedure Test.4 (Test.7, Test.8): + let Test.18 : Builtin(Int(U64)) = 1i64; + ret Test.18; + +procedure Test.0 (): + let Test.4 : LambdaSet(LambdaSet { set: [( Test.4, [])], representation: Struct([]) }) = Struct {}; + let Test.4 : LambdaSet(LambdaSet { set: [( Test.4, [])], representation: Struct([]) }) = Struct {}; + let Test.15 : Builtin(Int(U8)) = 100i64; + let Test.16 : Builtin(Int(U32)) = 100i64; + let Test.10 : Builtin(Int(U64)) = CallByName Test.4 Test.15 Test.16; + let Test.13 : Builtin(Int(U32)) = 100i64; + let Test.14 : Builtin(Int(U8)) = 100i64; + let Test.11 : Builtin(Int(U64)) = CallByName Test.4 Test.13 Test.14; + let Test.9 : Builtin(Int(U64)) = CallByName Num.22 Test.10 Test.11; + ret Test.9; diff --git a/compiler/test_mono/generated/monomorphized_list.txt b/compiler/test_mono/generated/monomorphized_list.txt new file mode 100644 index 0000000000..d38a0682ff --- /dev/null +++ b/compiler/test_mono/generated/monomorphized_list.txt @@ -0,0 +1,11 @@ +procedure Test.2 (Test.3, Test.4): + let Test.8 : Builtin(Int(U64)) = 18i64; + ret Test.8; + +procedure Test.0 (): + let Test.6 : Builtin(List(Builtin(Int(U8)))) = Array [1i64, 2i64, 3i64]; + let Test.7 : Builtin(List(Builtin(Int(U16)))) = Array [1i64, 2i64, 3i64]; + let Test.5 : Builtin(Int(U64)) = CallByName Test.2 Test.6 Test.7; + dec Test.7; + dec Test.6; + ret Test.5; diff --git a/compiler/test_mono/generated/monomorphized_tag.txt b/compiler/test_mono/generated/monomorphized_tag.txt new file mode 100644 index 0000000000..a1cb92209d --- /dev/null +++ b/compiler/test_mono/generated/monomorphized_tag.txt @@ -0,0 +1,9 @@ +procedure Test.2 (Test.4, Test.5): + let Test.9 : Builtin(Int(U8)) = 18i64; + ret Test.9; + +procedure Test.0 (): + let Test.7 : Builtin(Bool) = false; + let Test.8 : Builtin(Int(U8)) = 0u8; + let Test.6 : Builtin(Int(U8)) = CallByName Test.2 Test.7 Test.8; + ret Test.6; diff --git a/compiler/test_mono/generated/monomorphized_tag_with_aliased_args.txt b/compiler/test_mono/generated/monomorphized_tag_with_aliased_args.txt new file mode 100644 index 0000000000..02a88fb375 --- /dev/null +++ b/compiler/test_mono/generated/monomorphized_tag_with_aliased_args.txt @@ -0,0 +1,10 @@ +procedure Test.4 (Test.8): + let Test.11 : Builtin(Int(U64)) = 1i64; + ret Test.11; + +procedure Test.0 (): + let Test.13 : Builtin(Bool) = false; + let Test.12 : Builtin(Bool) = false; + let Test.10 : Struct([Builtin(Bool), Builtin(Bool)]) = Struct {Test.12, Test.13}; + let Test.9 : Builtin(Int(U64)) = CallByName Test.4 Test.10; + ret Test.9; diff --git a/compiler/test_mono/generated/nested_closure.txt b/compiler/test_mono/generated/nested_closure.txt index 3d5c1411d7..9b15123922 100644 --- a/compiler/test_mono/generated/nested_closure.txt +++ b/compiler/test_mono/generated/nested_closure.txt @@ -1,15 +1,16 @@ procedure Test.1 (Test.5): - let Test.2 = 42i64; - let Test.3 = Struct {Test.2}; + let Test.2 : Builtin(Int(I64)) = 42i64; + let Test.3 : LambdaSet(LambdaSet { set: [( Test.3, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }) = Struct {Test.2}; ret Test.3; -procedure Test.3 (Test.9, #Attr.12): - let Test.2 = StructAtIndex 0 #Attr.12; +procedure Test.3 (Test.10, #Attr.12): + let Test.2 : Builtin(Int(I64)) = StructAtIndex 0 #Attr.12; + let Test.2 : Builtin(Int(I64)) = 42i64; ret Test.2; procedure Test.0 (): - let Test.8 = Struct {}; - let Test.4 = CallByName Test.1 Test.8; - let Test.7 = Struct {}; - let Test.6 = CallByName Test.3 Test.7 Test.4; + let Test.7 : Struct([]) = Struct {}; + let Test.9 : Struct([]) = Struct {}; + let Test.8 : LambdaSet(LambdaSet { set: [( Test.3, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }) = CallByName Test.1 Test.9; + let Test.6 : Builtin(Int(I64)) = CallByName Test.3 Test.7 Test.8; ret Test.6; diff --git a/compiler/test_mono/generated/nested_pattern_match.txt b/compiler/test_mono/generated/nested_pattern_match.txt index eba5cb4940..facbc46055 100644 --- a/compiler/test_mono/generated/nested_pattern_match.txt +++ b/compiler/test_mono/generated/nested_pattern_match.txt @@ -1,28 +1,28 @@ -procedure Num.24 (#Attr.2, #Attr.3): - let Test.8 = lowlevel NumAdd #Attr.2 #Attr.3; +procedure Num.22 (#Attr.2, #Attr.3): + let Test.8 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.8; procedure Test.0 (): - let Test.20 = 41i64; - let Test.19 = Just Test.20; - let Test.2 = Just Test.19; + let Test.20 : Builtin(Int(I64)) = 41i64; + let Test.19 : Union(NonRecursive([[Builtin(Int(I64))], []])) = Just Test.20; + let Test.2 : Union(NonRecursive([[Union(NonRecursive([[Builtin(Int(I64))], []]))], []])) = Just Test.19; joinpoint Test.16: - let Test.9 = 1i64; + let Test.9 : Builtin(Int(I64)) = 1i64; ret Test.9; in - let Test.14 = 0i64; - let Test.15 = GetTagId Test.2; - let Test.18 = lowlevel Eq Test.14 Test.15; + let Test.14 : Builtin(Int(U8)) = 0i64; + let Test.15 : Builtin(Int(U8)) = GetTagId Test.2; + let Test.18 : Builtin(Bool) = lowlevel Eq Test.14 Test.15; if Test.18 then - let Test.11 = UnionAtIndex (Id 0) (Index 0) Test.2; - let Test.12 = 0i64; - let Test.13 = GetTagId Test.11; - let Test.17 = lowlevel Eq Test.12 Test.13; + let Test.11 : Union(NonRecursive([[Builtin(Int(I64))], []])) = UnionAtIndex (Id 0) (Index 0) Test.2; + let Test.12 : Builtin(Int(U8)) = 0i64; + let Test.13 : Builtin(Int(U8)) = GetTagId Test.11; + let Test.17 : Builtin(Bool) = lowlevel Eq Test.12 Test.13; if Test.17 then - let Test.10 = UnionAtIndex (Id 0) (Index 0) Test.2; - let Test.5 = UnionAtIndex (Id 0) (Index 0) Test.10; - let Test.7 = 1i64; - let Test.6 = CallByName Num.24 Test.5 Test.7; + let Test.10 : Union(NonRecursive([[Builtin(Int(I64))], []])) = UnionAtIndex (Id 0) (Index 0) Test.2; + let Test.5 : Builtin(Int(I64)) = UnionAtIndex (Id 0) (Index 0) Test.10; + let Test.7 : Builtin(Int(I64)) = 1i64; + let Test.6 : Builtin(Int(I64)) = CallByName Num.22 Test.5 Test.7; ret Test.6; else jump Test.16; diff --git a/compiler/test_mono/generated/one_element_tag.txt b/compiler/test_mono/generated/one_element_tag.txt index 4ecfe61168..701a8080f6 100644 --- a/compiler/test_mono/generated/one_element_tag.txt +++ b/compiler/test_mono/generated/one_element_tag.txt @@ -1,3 +1,3 @@ procedure Test.0 (): - let Test.4 = 2i64; + let Test.4 : Builtin(Int(I64)) = 2i64; ret Test.4; diff --git a/compiler/test_mono/generated/optional_when.txt b/compiler/test_mono/generated/optional_when.txt index 37f5c5108f..1dc1fdca6b 100644 --- a/compiler/test_mono/generated/optional_when.txt +++ b/compiler/test_mono/generated/optional_when.txt @@ -1,42 +1,42 @@ -procedure Num.26 (#Attr.2, #Attr.3): - let Test.17 = lowlevel NumMul #Attr.2 #Attr.3; - ret Test.17; +procedure Num.24 (#Attr.2, #Attr.3): + let Test.18 : Builtin(Int(I64)) = lowlevel NumMul #Attr.2 #Attr.3; + ret Test.18; procedure Test.1 (Test.6): - let Test.22 = StructAtIndex 1 Test.6; - let Test.23 = false; - let Test.24 = lowlevel Eq Test.23 Test.22; - if Test.24 then - let Test.8 = StructAtIndex 0 Test.6; + let Test.22 : Builtin(Bool) = false; + let Test.23 : Builtin(Bool) = lowlevel Eq Test.22 Test.6; + if Test.23 then + let Test.8 : Builtin(Int(I64)) = 3i64; ret Test.8; else - let Test.10 = StructAtIndex 0 Test.6; + let Test.10 : Builtin(Int(I64)) = 5i64; ret Test.10; procedure Test.1 (Test.6): - let Test.33 = false; - let Test.34 = lowlevel Eq Test.33 Test.6; - if Test.34 then - let Test.8 = 3i64; + let Test.30 : Builtin(Bool) = StructAtIndex 1 Test.6; + let Test.31 : Builtin(Bool) = false; + let Test.32 : Builtin(Bool) = lowlevel Eq Test.31 Test.30; + if Test.32 then + let Test.8 : Builtin(Int(I64)) = StructAtIndex 0 Test.6; ret Test.8; else - let Test.10 = 5i64; + let Test.10 : Builtin(Int(I64)) = StructAtIndex 0 Test.6; ret Test.10; procedure Test.0 (): - let Test.37 = true; - let Test.5 = CallByName Test.1 Test.37; - let Test.35 = false; - let Test.3 = CallByName Test.1 Test.35; - let Test.28 = 11i64; - let Test.29 = true; - let Test.27 = Struct {Test.28, Test.29}; - let Test.4 = CallByName Test.1 Test.27; - let Test.25 = 7i64; - let Test.26 = false; - let Test.19 = Struct {Test.25, Test.26}; - let Test.2 = CallByName Test.1 Test.19; - let Test.18 = CallByName Num.26 Test.2 Test.3; - let Test.16 = CallByName Num.26 Test.18 Test.4; - let Test.15 = CallByName Num.26 Test.16 Test.5; + let Test.40 : Builtin(Int(I64)) = 7i64; + let Test.41 : Builtin(Bool) = false; + let Test.39 : Struct([Builtin(Int(I64)), Builtin(Bool)]) = Struct {Test.40, Test.41}; + let Test.35 : Builtin(Int(I64)) = CallByName Test.1 Test.39; + let Test.38 : Builtin(Bool) = false; + let Test.36 : Builtin(Int(I64)) = CallByName Test.1 Test.38; + let Test.25 : Builtin(Int(I64)) = CallByName Num.24 Test.35 Test.36; + let Test.33 : Builtin(Int(I64)) = 11i64; + let Test.34 : Builtin(Bool) = true; + let Test.27 : Struct([Builtin(Int(I64)), Builtin(Bool)]) = Struct {Test.33, Test.34}; + let Test.26 : Builtin(Int(I64)) = CallByName Test.1 Test.27; + let Test.16 : Builtin(Int(I64)) = CallByName Num.24 Test.25 Test.26; + let Test.24 : Builtin(Bool) = true; + let Test.17 : Builtin(Int(I64)) = CallByName Test.1 Test.24; + let Test.15 : Builtin(Int(I64)) = CallByName Num.24 Test.16 Test.17; ret Test.15; diff --git a/compiler/test_mono/generated/peano.txt b/compiler/test_mono/generated/peano.txt index 49814c63b4..b20043d155 100644 --- a/compiler/test_mono/generated/peano.txt +++ b/compiler/test_mono/generated/peano.txt @@ -1,6 +1,6 @@ procedure Test.0 (): - let Test.10 = Z ; - let Test.9 = S Test.10; - let Test.8 = S Test.9; - let Test.2 = S Test.8; + let Test.10 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = Z ; + let Test.9 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = S Test.10; + let Test.8 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = S Test.9; + let Test.2 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = S Test.8; ret Test.2; diff --git a/compiler/test_mono/generated/peano1.txt b/compiler/test_mono/generated/peano1.txt index bcf0d7a27d..55c0c229b4 100644 --- a/compiler/test_mono/generated/peano1.txt +++ b/compiler/test_mono/generated/peano1.txt @@ -1,15 +1,15 @@ procedure Test.0 (): - let Test.14 = Z ; - let Test.13 = S Test.14; - let Test.12 = S Test.13; - let Test.2 = S Test.12; - let Test.9 = 1i64; - let Test.10 = GetTagId Test.2; + let Test.14 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = Z ; + let Test.13 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = S Test.14; + let Test.12 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = S Test.13; + let Test.2 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = S Test.12; + let Test.9 : Builtin(Bool) = 1i64; + let Test.10 : Builtin(Bool) = GetTagId Test.2; dec Test.2; - let Test.11 = lowlevel Eq Test.9 Test.10; + let Test.11 : Builtin(Bool) = lowlevel Eq Test.9 Test.10; if Test.11 then - let Test.7 = 0i64; + let Test.7 : Builtin(Int(I64)) = 0i64; ret Test.7; else - let Test.8 = 1i64; + let Test.8 : Builtin(Int(I64)) = 1i64; ret Test.8; diff --git a/compiler/test_mono/generated/peano2.txt b/compiler/test_mono/generated/peano2.txt index 11ad76dbaf..152e09e19f 100644 --- a/compiler/test_mono/generated/peano2.txt +++ b/compiler/test_mono/generated/peano2.txt @@ -1,26 +1,26 @@ procedure Test.0 (): - let Test.20 = Z ; - let Test.19 = S Test.20; - let Test.18 = S Test.19; - let Test.2 = S Test.18; - let Test.15 = 0i64; - let Test.16 = GetTagId Test.2; - let Test.17 = lowlevel Eq Test.15 Test.16; + let Test.20 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = Z ; + let Test.19 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = S Test.20; + let Test.18 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = S Test.19; + let Test.2 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = S Test.18; + let Test.15 : Builtin(Bool) = 0i64; + let Test.16 : Builtin(Bool) = GetTagId Test.2; + let Test.17 : Builtin(Bool) = lowlevel Eq Test.15 Test.16; if Test.17 then - let Test.11 = UnionAtIndex (Id 0) (Index 0) Test.2; + let Test.11 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = UnionAtIndex (Id 0) (Index 0) Test.2; inc Test.11; dec Test.2; - let Test.12 = 0i64; - let Test.13 = GetTagId Test.11; + let Test.12 : Builtin(Bool) = 0i64; + let Test.13 : Builtin(Bool) = GetTagId Test.11; dec Test.11; - let Test.14 = lowlevel Eq Test.12 Test.13; + let Test.14 : Builtin(Bool) = lowlevel Eq Test.12 Test.13; if Test.14 then - let Test.7 = 1i64; + let Test.7 : Builtin(Int(I64)) = 1i64; ret Test.7; else - let Test.8 = 0i64; + let Test.8 : Builtin(Int(I64)) = 0i64; ret Test.8; else dec Test.2; - let Test.9 = 0i64; + let Test.9 : Builtin(Int(I64)) = 0i64; ret Test.9; diff --git a/compiler/test_mono/generated/quicksort_help.txt b/compiler/test_mono/generated/quicksort_help.txt index 81ba5892c3..911b38e71c 100644 --- a/compiler/test_mono/generated/quicksort_help.txt +++ b/compiler/test_mono/generated/quicksort_help.txt @@ -1,32 +1,32 @@ -procedure Num.24 (#Attr.2, #Attr.3): - let Test.19 = lowlevel NumAdd #Attr.2 #Attr.3; +procedure Num.22 (#Attr.2, #Attr.3): + let Test.19 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.19; -procedure Num.25 (#Attr.2, #Attr.3): - let Test.22 = lowlevel NumSub #Attr.2 #Attr.3; +procedure Num.23 (#Attr.2, #Attr.3): + let Test.22 : Builtin(Int(I64)) = lowlevel NumSub #Attr.2 #Attr.3; ret Test.22; -procedure Num.27 (#Attr.2, #Attr.3): - let Test.26 = lowlevel NumLt #Attr.2 #Attr.3; +procedure Num.25 (#Attr.2, #Attr.3): + let Test.26 : Builtin(Bool) = lowlevel NumLt #Attr.2 #Attr.3; ret Test.26; procedure Test.1 (Test.27, Test.28, Test.29): joinpoint Test.12 Test.2 Test.3 Test.4: - let Test.14 = CallByName Num.27 Test.3 Test.4; + let Test.14 : Builtin(Bool) = CallByName Num.25 Test.3 Test.4; if Test.14 then dec Test.2; - let Test.25 = Array []; - let Test.24 = 0i64; - let Test.23 = Struct {Test.24, Test.25}; - let Test.5 = StructAtIndex 0 Test.23; - let Test.6 = StructAtIndex 1 Test.23; + let Test.25 : Builtin(List(Union(NonRecursive([])))) = Array []; + let Test.24 : Builtin(Int(I64)) = 0i64; + let Test.23 : Struct([Builtin(Int(I64)), Builtin(List(Union(NonRecursive([]))))]) = Struct {Test.24, Test.25}; + let Test.5 : Builtin(Int(I64)) = StructAtIndex 0 Test.23; + let Test.6 : Builtin(List(Union(NonRecursive([])))) = StructAtIndex 1 Test.23; inc Test.6; dec Test.23; - let Test.21 = 1i64; - let Test.20 = CallByName Num.25 Test.5 Test.21; - let Test.16 = CallByName Test.1 Test.6 Test.3 Test.20; - let Test.18 = 1i64; - let Test.17 = CallByName Num.24 Test.5 Test.18; + let Test.21 : Builtin(Int(I64)) = 1i64; + let Test.20 : Builtin(Int(I64)) = CallByName Num.23 Test.5 Test.21; + let Test.16 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.1 Test.6 Test.3 Test.20; + let Test.18 : Builtin(Int(I64)) = 1i64; + let Test.17 : Builtin(Int(I64)) = CallByName Num.22 Test.5 Test.18; jump Test.12 Test.16 Test.17 Test.4; else ret Test.2; @@ -34,8 +34,8 @@ procedure Test.1 (Test.27, Test.28, Test.29): jump Test.12 Test.27 Test.28 Test.29; procedure Test.0 (): - let Test.9 = Array []; - let Test.10 = 0i64; - let Test.11 = 0i64; - let Test.8 = CallByName Test.1 Test.9 Test.10 Test.11; + let Test.9 : Builtin(List(Builtin(Int(I64)))) = Array []; + let Test.10 : Builtin(Int(I64)) = 0i64; + let Test.11 : Builtin(Int(I64)) = 0i64; + let Test.8 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.1 Test.9 Test.10 Test.11; ret Test.8; diff --git a/compiler/test_mono/generated/quicksort_swap.txt b/compiler/test_mono/generated/quicksort_swap.txt index 5bb60a4c11..13e3c2824f 100644 --- a/compiler/test_mono/generated/quicksort_swap.txt +++ b/compiler/test_mono/generated/quicksort_swap.txt @@ -1,52 +1,52 @@ procedure List.3 (#Attr.2, #Attr.3): - let Test.37 = lowlevel ListLen #Attr.2; - let Test.34 = lowlevel NumLt #Attr.3 Test.37; + let Test.37 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; + let Test.34 : Builtin(Bool) = lowlevel NumLt #Attr.3 Test.37; if Test.34 then - let Test.36 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - let Test.35 = Ok Test.36; + let Test.36 : Builtin(Int(I64)) = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + let Test.35 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = Ok Test.36; ret Test.35; else - let Test.33 = Struct {}; - let Test.32 = Err Test.33; + let Test.33 : Struct([]) = Struct {}; + let Test.32 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = Err Test.33; ret Test.32; procedure List.4 (#Attr.2, #Attr.3, #Attr.4): - let Test.15 = lowlevel ListLen #Attr.2; - let Test.13 = lowlevel NumLt #Attr.3 Test.15; + let Test.15 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; + let Test.13 : Builtin(Bool) = lowlevel NumLt #Attr.3 Test.15; if Test.13 then - let Test.14 = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; + let Test.14 : Builtin(List(Builtin(Int(I64)))) = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; ret Test.14; else ret #Attr.2; procedure Test.1 (Test.2): - let Test.38 = 0i64; - let Test.30 = CallByName List.3 Test.2 Test.38; - let Test.31 = 0i64; - let Test.29 = CallByName List.3 Test.2 Test.31; - let Test.8 = Struct {Test.29, Test.30}; + let Test.38 : Builtin(Int(U64)) = 0i64; + let Test.30 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = CallByName List.3 Test.2 Test.38; + let Test.31 : Builtin(Int(U64)) = 0i64; + let Test.29 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = CallByName List.3 Test.2 Test.31; + let Test.8 : Struct([Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])), Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]]))]) = Struct {Test.29, Test.30}; joinpoint Test.26: - let Test.17 = Array []; + let Test.17 : Builtin(List(Builtin(Int(I64)))) = Array []; ret Test.17; in - let Test.23 = StructAtIndex 1 Test.8; - let Test.24 = 1i64; - let Test.25 = GetTagId Test.23; - let Test.28 = lowlevel Eq Test.24 Test.25; + let Test.23 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = StructAtIndex 1 Test.8; + let Test.24 : Builtin(Int(U8)) = 1i64; + let Test.25 : Builtin(Int(U8)) = GetTagId Test.23; + let Test.28 : Builtin(Bool) = lowlevel Eq Test.24 Test.25; if Test.28 then - let Test.20 = StructAtIndex 0 Test.8; - let Test.21 = 1i64; - let Test.22 = GetTagId Test.20; - let Test.27 = lowlevel Eq Test.21 Test.22; + let Test.20 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = StructAtIndex 0 Test.8; + let Test.21 : Builtin(Int(U8)) = 1i64; + let Test.22 : Builtin(Int(U8)) = GetTagId Test.20; + let Test.27 : Builtin(Bool) = lowlevel Eq Test.21 Test.22; if Test.27 then - let Test.19 = StructAtIndex 0 Test.8; - let Test.4 = UnionAtIndex (Id 1) (Index 0) Test.19; - let Test.18 = StructAtIndex 1 Test.8; - let Test.5 = UnionAtIndex (Id 1) (Index 0) Test.18; - let Test.16 = 0i64; - let Test.10 = CallByName List.4 Test.2 Test.16 Test.5; - let Test.11 = 0i64; - let Test.9 = CallByName List.4 Test.10 Test.11 Test.4; + let Test.19 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = StructAtIndex 0 Test.8; + let Test.4 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) Test.19; + let Test.18 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = StructAtIndex 1 Test.8; + let Test.5 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) Test.18; + let Test.16 : Builtin(Int(U64)) = 0i64; + let Test.10 : Builtin(List(Builtin(Int(I64)))) = CallByName List.4 Test.2 Test.16 Test.5; + let Test.11 : Builtin(Int(U64)) = 0i64; + let Test.9 : Builtin(List(Builtin(Int(I64)))) = CallByName List.4 Test.10 Test.11 Test.4; ret Test.9; else dec Test.2; @@ -56,6 +56,6 @@ procedure Test.1 (Test.2): jump Test.26; procedure Test.0 (): - let Test.7 = Array [1i64, 2i64]; - let Test.6 = CallByName Test.1 Test.7; + let Test.7 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64]; + let Test.6 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.1 Test.7; ret Test.6; diff --git a/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt b/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt index 3992960017..3c98d6876b 100644 --- a/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt @@ -1,17 +1,16 @@ -procedure Num.24 (#Attr.2, #Attr.3): - let Test.8 = lowlevel NumAdd #Attr.2 #Attr.3; +procedure Num.22 (#Attr.2, #Attr.3): + let Test.8 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.8; procedure Test.1 (Test.4): - let Test.2 = StructAtIndex 0 Test.4; - let Test.3 = StructAtIndex 1 Test.4; - let Test.2 = 10i64; - let Test.7 = CallByName Num.24 Test.2 Test.3; + let Test.2 : Builtin(Int(I64)) = StructAtIndex 0 Test.4; + let Test.3 : Builtin(Int(I64)) = StructAtIndex 1 Test.4; + let Test.7 : Builtin(Int(I64)) = CallByName Num.22 Test.2 Test.3; ret Test.7; procedure Test.0 (): - let Test.9 = 4i64; - let Test.10 = 9i64; - let Test.6 = Struct {Test.9, Test.10}; - let Test.5 = CallByName Test.1 Test.6; + let Test.9 : Builtin(Int(I64)) = 4i64; + let Test.10 : Builtin(Int(I64)) = 9i64; + let Test.6 : Struct([Builtin(Int(I64)), Builtin(Int(I64))]) = Struct {Test.9, Test.10}; + let Test.5 : Builtin(Int(I64)) = CallByName Test.1 Test.6; ret Test.5; diff --git a/compiler/test_mono/generated/record_optional_field_function_use_default.txt b/compiler/test_mono/generated/record_optional_field_function_use_default.txt index 6b9b4191df..aee1aa3793 100644 --- a/compiler/test_mono/generated/record_optional_field_function_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_function_use_default.txt @@ -1,14 +1,13 @@ -procedure Num.24 (#Attr.2, #Attr.3): - let Test.8 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.8; +procedure Num.22 (#Attr.2, #Attr.3): + let Test.9 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; + ret Test.9; procedure Test.1 (Test.4): - let Test.2 = 10i64; - let Test.2 = 10i64; - let Test.7 = CallByName Num.24 Test.2 Test.4; + let Test.8 : Builtin(Int(I64)) = 10i64; + let Test.7 : Builtin(Int(I64)) = CallByName Num.22 Test.8 Test.4; ret Test.7; procedure Test.0 (): - let Test.9 = 9i64; - let Test.5 = CallByName Test.1 Test.9; + let Test.10 : Builtin(Int(I64)) = 9i64; + let Test.5 : Builtin(Int(I64)) = CallByName Test.1 Test.10; ret Test.5; diff --git a/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt b/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt index 2c5a7ec53f..255130f13b 100644 --- a/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt @@ -1,16 +1,16 @@ -procedure Num.24 (#Attr.2, #Attr.3): - let Test.8 = lowlevel NumAdd #Attr.2 #Attr.3; +procedure Num.22 (#Attr.2, #Attr.3): + let Test.8 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.8; procedure Test.1 (Test.2): - let Test.3 = StructAtIndex 0 Test.2; - let Test.4 = StructAtIndex 1 Test.2; - let Test.7 = CallByName Num.24 Test.3 Test.4; + let Test.3 : Builtin(Int(I64)) = StructAtIndex 0 Test.2; + let Test.4 : Builtin(Int(I64)) = StructAtIndex 1 Test.2; + let Test.7 : Builtin(Int(I64)) = CallByName Num.22 Test.3 Test.4; ret Test.7; procedure Test.0 (): - let Test.9 = 4i64; - let Test.10 = 9i64; - let Test.6 = Struct {Test.9, Test.10}; - let Test.5 = CallByName Test.1 Test.6; + let Test.9 : Builtin(Int(I64)) = 4i64; + let Test.10 : Builtin(Int(I64)) = 9i64; + let Test.6 : Struct([Builtin(Int(I64)), Builtin(Int(I64))]) = Struct {Test.9, Test.10}; + let Test.5 : Builtin(Int(I64)) = CallByName Test.1 Test.6; ret Test.5; diff --git a/compiler/test_mono/generated/record_optional_field_let_use_default.txt b/compiler/test_mono/generated/record_optional_field_let_use_default.txt index 7ea40fe7ab..978c5b026d 100644 --- a/compiler/test_mono/generated/record_optional_field_let_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_let_use_default.txt @@ -1,13 +1,13 @@ -procedure Num.24 (#Attr.2, #Attr.3): - let Test.8 = lowlevel NumAdd #Attr.2 #Attr.3; +procedure Num.22 (#Attr.2, #Attr.3): + let Test.8 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.8; procedure Test.1 (Test.2): - let Test.3 = 10i64; - let Test.7 = CallByName Num.24 Test.3 Test.2; + let Test.3 : Builtin(Int(I64)) = 10i64; + let Test.7 : Builtin(Int(I64)) = CallByName Num.22 Test.3 Test.2; ret Test.7; procedure Test.0 (): - let Test.9 = 9i64; - let Test.5 = CallByName Test.1 Test.9; + let Test.9 : Builtin(Int(I64)) = 9i64; + let Test.5 : Builtin(Int(I64)) = CallByName Test.1 Test.9; ret Test.5; diff --git a/compiler/test_mono/generated/rigids.txt b/compiler/test_mono/generated/rigids.txt index 583b0a0182..5f0124657f 100644 --- a/compiler/test_mono/generated/rigids.txt +++ b/compiler/test_mono/generated/rigids.txt @@ -1,48 +1,48 @@ procedure List.3 (#Attr.2, #Attr.3): - let Test.39 = lowlevel ListLen #Attr.2; - let Test.36 = lowlevel NumLt #Attr.3 Test.39; + let Test.39 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; + let Test.36 : Builtin(Bool) = lowlevel NumLt #Attr.3 Test.39; if Test.36 then - let Test.38 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - let Test.37 = Ok Test.38; + let Test.38 : Builtin(Int(I64)) = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + let Test.37 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = Ok Test.38; ret Test.37; else - let Test.35 = Struct {}; - let Test.34 = Err Test.35; + let Test.35 : Struct([]) = Struct {}; + let Test.34 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = Err Test.35; ret Test.34; procedure List.4 (#Attr.2, #Attr.3, #Attr.4): - let Test.19 = lowlevel ListLen #Attr.2; - let Test.17 = lowlevel NumLt #Attr.3 Test.19; + let Test.19 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; + let Test.17 : Builtin(Bool) = lowlevel NumLt #Attr.3 Test.19; if Test.17 then - let Test.18 = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; + let Test.18 : Builtin(List(Builtin(Int(I64)))) = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; ret Test.18; else ret #Attr.2; procedure Test.1 (Test.2, Test.3, Test.4): - let Test.33 = CallByName List.3 Test.4 Test.3; - let Test.32 = CallByName List.3 Test.4 Test.2; - let Test.13 = Struct {Test.32, Test.33}; + let Test.33 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = CallByName List.3 Test.4 Test.3; + let Test.32 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = CallByName List.3 Test.4 Test.2; + let Test.13 : Struct([Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])), Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]]))]) = Struct {Test.32, Test.33}; joinpoint Test.29: - let Test.20 = Array []; + let Test.20 : Builtin(List(Builtin(Int(I64)))) = Array []; ret Test.20; in - let Test.26 = StructAtIndex 1 Test.13; - let Test.27 = 1i64; - let Test.28 = GetTagId Test.26; - let Test.31 = lowlevel Eq Test.27 Test.28; + let Test.26 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = StructAtIndex 1 Test.13; + let Test.27 : Builtin(Int(U8)) = 1i64; + let Test.28 : Builtin(Int(U8)) = GetTagId Test.26; + let Test.31 : Builtin(Bool) = lowlevel Eq Test.27 Test.28; if Test.31 then - let Test.23 = StructAtIndex 0 Test.13; - let Test.24 = 1i64; - let Test.25 = GetTagId Test.23; - let Test.30 = lowlevel Eq Test.24 Test.25; + let Test.23 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = StructAtIndex 0 Test.13; + let Test.24 : Builtin(Int(U8)) = 1i64; + let Test.25 : Builtin(Int(U8)) = GetTagId Test.23; + let Test.30 : Builtin(Bool) = lowlevel Eq Test.24 Test.25; if Test.30 then - let Test.22 = StructAtIndex 0 Test.13; - let Test.6 = UnionAtIndex (Id 1) (Index 0) Test.22; - let Test.21 = StructAtIndex 1 Test.13; - let Test.7 = UnionAtIndex (Id 1) (Index 0) Test.21; - let Test.15 = CallByName List.4 Test.4 Test.2 Test.7; - let Test.14 = CallByName List.4 Test.15 Test.3 Test.6; + let Test.22 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = StructAtIndex 0 Test.13; + let Test.6 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) Test.22; + let Test.21 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = StructAtIndex 1 Test.13; + let Test.7 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) Test.21; + let Test.15 : Builtin(List(Builtin(Int(I64)))) = CallByName List.4 Test.4 Test.2 Test.7; + let Test.14 : Builtin(List(Builtin(Int(I64)))) = CallByName List.4 Test.15 Test.3 Test.6; ret Test.14; else dec Test.4; @@ -52,8 +52,8 @@ procedure Test.1 (Test.2, Test.3, Test.4): jump Test.29; procedure Test.0 (): - let Test.10 = 0i64; - let Test.11 = 0i64; - let Test.12 = Array [1i64]; - let Test.9 = CallByName Test.1 Test.10 Test.11 Test.12; + let Test.10 : Builtin(Int(U64)) = 0i64; + let Test.11 : Builtin(Int(U64)) = 0i64; + let Test.12 : Builtin(List(Builtin(Int(I64)))) = Array [1i64]; + let Test.9 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.1 Test.10 Test.11 Test.12; ret Test.9; diff --git a/compiler/test_mono/generated/simple_if.txt b/compiler/test_mono/generated/simple_if.txt index f796d9d4f2..ba3e24661c 100644 --- a/compiler/test_mono/generated/simple_if.txt +++ b/compiler/test_mono/generated/simple_if.txt @@ -1,8 +1,8 @@ procedure Test.0 (): - let Test.3 = true; + let Test.3 : Builtin(Bool) = true; if Test.3 then - let Test.4 = 1i64; + let Test.4 : Builtin(Int(I64)) = 1i64; ret Test.4; else - let Test.2 = 2i64; + let Test.2 : Builtin(Int(I64)) = 2i64; ret Test.2; diff --git a/compiler/test_mono/generated/somehow_drops_definitions.txt b/compiler/test_mono/generated/somehow_drops_definitions.txt index 2e7a497919..1dd4ed9c40 100644 --- a/compiler/test_mono/generated/somehow_drops_definitions.txt +++ b/compiler/test_mono/generated/somehow_drops_definitions.txt @@ -1,27 +1,27 @@ -procedure Num.24 (#Attr.2, #Attr.3): - let Test.27 = lowlevel NumAdd #Attr.2 #Attr.3; +procedure Num.22 (#Attr.2, #Attr.3): + let Test.27 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.27; -procedure Num.26 (#Attr.2, #Attr.3): - let Test.22 = lowlevel NumMul #Attr.2 #Attr.3; +procedure Num.24 (#Attr.2, #Attr.3): + let Test.22 : Builtin(Int(I64)) = lowlevel NumMul #Attr.2 #Attr.3; ret Test.22; procedure Test.1 (): - let Test.28 = 1i64; + let Test.28 : Builtin(Int(I64)) = 1i64; ret Test.28; procedure Test.2 (): - let Test.23 = 2i64; + let Test.23 : Builtin(Int(I64)) = 2i64; ret Test.23; procedure Test.3 (Test.6): - let Test.26 = CallByName Test.1; - let Test.25 = CallByName Num.24 Test.6 Test.26; + let Test.26 : Builtin(Int(I64)) = CallByName Test.1; + let Test.25 : Builtin(Int(I64)) = CallByName Num.22 Test.6 Test.26; ret Test.25; procedure Test.4 (Test.7): - let Test.21 = CallByName Test.2; - let Test.20 = CallByName Num.26 Test.7 Test.21; + let Test.21 : Builtin(Int(I64)) = CallByName Test.2; + let Test.20 : Builtin(Int(I64)) = CallByName Num.24 Test.7 Test.21; ret Test.20; procedure Test.5 (Test.8, Test.9): @@ -30,24 +30,24 @@ procedure Test.5 (Test.8, Test.9): in switch Test.8: case 0: - let Test.16 = CallByName Test.3 Test.9; + let Test.16 : Builtin(Int(I64)) = CallByName Test.3 Test.9; jump Test.15 Test.16; default: - let Test.17 = CallByName Test.4 Test.9; + let Test.17 : Builtin(Int(I64)) = CallByName Test.4 Test.9; jump Test.15 Test.17; procedure Test.0 (): joinpoint Test.19 Test.12: - let Test.13 = 42i64; - let Test.11 = CallByName Test.5 Test.12 Test.13; + let Test.13 : Builtin(Int(I64)) = 42i64; + let Test.11 : Builtin(Int(I64)) = CallByName Test.5 Test.12 Test.13; ret Test.11; in - let Test.24 = true; + let Test.24 : Builtin(Bool) = true; if Test.24 then - let Test.3 = false; + let Test.3 : LambdaSet(LambdaSet { set: [( Test.3, []), ( Test.4, [])], representation: Builtin(Bool) }) = false; jump Test.19 Test.3; else - let Test.4 = true; + let Test.4 : LambdaSet(LambdaSet { set: [( Test.3, []), ( Test.4, [])], representation: Builtin(Bool) }) = true; jump Test.19 Test.4; diff --git a/compiler/test_mono/generated/specialize_closures.txt b/compiler/test_mono/generated/specialize_closures.txt index d5d4592f76..772676526b 100644 --- a/compiler/test_mono/generated/specialize_closures.txt +++ b/compiler/test_mono/generated/specialize_closures.txt @@ -1,53 +1,53 @@ -procedure Num.24 (#Attr.2, #Attr.3): - let Test.28 = lowlevel NumAdd #Attr.2 #Attr.3; +procedure Num.22 (#Attr.2, #Attr.3): + let Test.28 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.28; -procedure Num.26 (#Attr.2, #Attr.3): - let Test.25 = lowlevel NumMul #Attr.2 #Attr.3; +procedure Num.24 (#Attr.2, #Attr.3): + let Test.25 : Builtin(Int(I64)) = lowlevel NumMul #Attr.2 #Attr.3; ret Test.25; procedure Test.1 (Test.2, Test.3): - let Test.17 = GetTagId Test.2; + let Test.17 : Builtin(Int(U8)) = GetTagId Test.2; joinpoint Test.18 Test.16: ret Test.16; in switch Test.17: case 0: - let Test.19 = CallByName Test.7 Test.3 Test.2; + let Test.19 : Builtin(Int(I64)) = CallByName Test.7 Test.3 Test.2; jump Test.18 Test.19; default: - let Test.20 = CallByName Test.8 Test.3 Test.2; + let Test.20 : Builtin(Int(I64)) = CallByName Test.8 Test.3 Test.2; jump Test.18 Test.20; procedure Test.7 (Test.10, #Attr.12): - let Test.4 = UnionAtIndex (Id 0) (Index 0) #Attr.12; - let Test.27 = CallByName Num.24 Test.10 Test.4; + let Test.4 : Builtin(Int(I64)) = UnionAtIndex (Id 0) (Index 0) #Attr.12; + let Test.27 : Builtin(Int(I64)) = CallByName Num.22 Test.10 Test.4; ret Test.27; procedure Test.8 (Test.11, #Attr.12): - let Test.6 = UnionAtIndex (Id 1) (Index 1) #Attr.12; - let Test.5 = UnionAtIndex (Id 1) (Index 0) #Attr.12; + let Test.6 : Builtin(Bool) = UnionAtIndex (Id 1) (Index 1) #Attr.12; + let Test.5 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) #Attr.12; if Test.6 then - let Test.24 = CallByName Num.26 Test.11 Test.5; + let Test.24 : Builtin(Int(I64)) = CallByName Num.24 Test.11 Test.5; ret Test.24; else ret Test.11; procedure Test.0 (): - let Test.6 = true; - let Test.4 = 1i64; - let Test.5 = 2i64; + let Test.6 : Builtin(Bool) = true; + let Test.4 : Builtin(Int(I64)) = 1i64; + let Test.5 : Builtin(Int(I64)) = 2i64; joinpoint Test.22 Test.14: - let Test.15 = 42i64; - let Test.13 = CallByName Test.1 Test.14 Test.15; + let Test.15 : Builtin(Int(I64)) = 42i64; + let Test.13 : Builtin(Int(I64)) = CallByName Test.1 Test.14 Test.15; ret Test.13; in - let Test.26 = true; + let Test.26 : Builtin(Bool) = true; if Test.26 then - let Test.7 = ClosureTag(Test.7) Test.4; + let Test.7 : LambdaSet(LambdaSet { set: [( Test.7, [Builtin(Int(I64))]), ( Test.8, [Builtin(Int(I64)), Builtin(Bool)])], representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64)), Builtin(Bool)]])) }) = ClosureTag(Test.7) Test.4; jump Test.22 Test.7; else - let Test.8 = ClosureTag(Test.8) Test.5 Test.6; + let Test.8 : LambdaSet(LambdaSet { set: [( Test.7, [Builtin(Int(I64))]), ( Test.8, [Builtin(Int(I64)), Builtin(Bool)])], representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64)), Builtin(Bool)]])) }) = ClosureTag(Test.8) Test.5 Test.6; jump Test.22 Test.8; diff --git a/compiler/test_mono/generated/specialize_lowlevel.txt b/compiler/test_mono/generated/specialize_lowlevel.txt index 40dd98733d..4dba44f752 100644 --- a/compiler/test_mono/generated/specialize_lowlevel.txt +++ b/compiler/test_mono/generated/specialize_lowlevel.txt @@ -1,44 +1,44 @@ -procedure Num.24 (#Attr.2, #Attr.3): - let Test.24 = lowlevel NumAdd #Attr.2 #Attr.3; +procedure Num.22 (#Attr.2, #Attr.3): + let Test.24 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.24; -procedure Num.26 (#Attr.2, #Attr.3): - let Test.21 = lowlevel NumMul #Attr.2 #Attr.3; +procedure Num.24 (#Attr.2, #Attr.3): + let Test.21 : Builtin(Int(I64)) = lowlevel NumMul #Attr.2 #Attr.3; ret Test.21; procedure Test.6 (Test.8, #Attr.12): - let Test.4 = UnionAtIndex (Id 0) (Index 0) #Attr.12; - let Test.23 = CallByName Num.24 Test.8 Test.4; + let Test.4 : Builtin(Int(I64)) = UnionAtIndex (Id 0) (Index 0) #Attr.12; + let Test.23 : Builtin(Int(I64)) = CallByName Num.22 Test.8 Test.4; ret Test.23; procedure Test.7 (Test.9, #Attr.12): - let Test.5 = UnionAtIndex (Id 1) (Index 0) #Attr.12; - let Test.20 = CallByName Num.26 Test.9 Test.5; + let Test.5 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) #Attr.12; + let Test.20 : Builtin(Int(I64)) = CallByName Num.24 Test.9 Test.5; ret Test.20; procedure Test.0 (): - let Test.4 = 1i64; - let Test.5 = 2i64; - let Test.12 = 42i64; + let Test.4 : Builtin(Int(I64)) = 1i64; + let Test.5 : Builtin(Int(I64)) = 2i64; + let Test.12 : Builtin(Int(I64)) = 42i64; joinpoint Test.19 Test.13: - let Test.14 = GetTagId Test.13; + let Test.14 : Builtin(Int(U8)) = GetTagId Test.13; joinpoint Test.15 Test.11: ret Test.11; in switch Test.14: case 0: - let Test.16 = CallByName Test.6 Test.12 Test.13; + let Test.16 : Builtin(Int(I64)) = CallByName Test.6 Test.12 Test.13; jump Test.15 Test.16; default: - let Test.17 = CallByName Test.7 Test.12 Test.13; + let Test.17 : Builtin(Int(I64)) = CallByName Test.7 Test.12 Test.13; jump Test.15 Test.17; in - let Test.22 = true; + let Test.22 : Builtin(Bool) = true; if Test.22 then - let Test.6 = ClosureTag(Test.6) Test.4; + let Test.6 : LambdaSet(LambdaSet { set: [( Test.6, [Builtin(Int(I64))]), ( Test.7, [Builtin(Int(I64))])], representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64))]])) }) = ClosureTag(Test.6) Test.4; jump Test.19 Test.6; else - let Test.7 = ClosureTag(Test.7) Test.5; + let Test.7 : LambdaSet(LambdaSet { set: [( Test.6, [Builtin(Int(I64))]), ( Test.7, [Builtin(Int(I64))])], representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64))]])) }) = ClosureTag(Test.7) Test.5; jump Test.19 Test.7; diff --git a/compiler/test_mono/generated/when_joinpoint.txt b/compiler/test_mono/generated/when_joinpoint.txt index 3277764e1b..3366717fca 100644 --- a/compiler/test_mono/generated/when_joinpoint.txt +++ b/compiler/test_mono/generated/when_joinpoint.txt @@ -1,23 +1,23 @@ procedure Test.1 (Test.5): - let Test.2 = 0u8; + let Test.2 : Builtin(Int(U8)) = 0u8; joinpoint Test.9 Test.3: ret Test.3; in switch Test.2: case 1: - let Test.10 = 1i64; + let Test.10 : Builtin(Int(I64)) = 1i64; jump Test.9 Test.10; case 2: - let Test.11 = 2i64; + let Test.11 : Builtin(Int(I64)) = 2i64; jump Test.9 Test.11; default: - let Test.12 = 3i64; + let Test.12 : Builtin(Int(I64)) = 3i64; jump Test.9 Test.12; procedure Test.0 (): - let Test.7 = Struct {}; - let Test.6 = CallByName Test.1 Test.7; + let Test.7 : Struct([]) = Struct {}; + let Test.6 : Builtin(Int(I64)) = CallByName Test.1 Test.7; ret Test.6; diff --git a/compiler/test_mono/generated/when_nested_maybe.txt b/compiler/test_mono/generated/when_nested_maybe.txt index eba5cb4940..facbc46055 100644 --- a/compiler/test_mono/generated/when_nested_maybe.txt +++ b/compiler/test_mono/generated/when_nested_maybe.txt @@ -1,28 +1,28 @@ -procedure Num.24 (#Attr.2, #Attr.3): - let Test.8 = lowlevel NumAdd #Attr.2 #Attr.3; +procedure Num.22 (#Attr.2, #Attr.3): + let Test.8 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.8; procedure Test.0 (): - let Test.20 = 41i64; - let Test.19 = Just Test.20; - let Test.2 = Just Test.19; + let Test.20 : Builtin(Int(I64)) = 41i64; + let Test.19 : Union(NonRecursive([[Builtin(Int(I64))], []])) = Just Test.20; + let Test.2 : Union(NonRecursive([[Union(NonRecursive([[Builtin(Int(I64))], []]))], []])) = Just Test.19; joinpoint Test.16: - let Test.9 = 1i64; + let Test.9 : Builtin(Int(I64)) = 1i64; ret Test.9; in - let Test.14 = 0i64; - let Test.15 = GetTagId Test.2; - let Test.18 = lowlevel Eq Test.14 Test.15; + let Test.14 : Builtin(Int(U8)) = 0i64; + let Test.15 : Builtin(Int(U8)) = GetTagId Test.2; + let Test.18 : Builtin(Bool) = lowlevel Eq Test.14 Test.15; if Test.18 then - let Test.11 = UnionAtIndex (Id 0) (Index 0) Test.2; - let Test.12 = 0i64; - let Test.13 = GetTagId Test.11; - let Test.17 = lowlevel Eq Test.12 Test.13; + let Test.11 : Union(NonRecursive([[Builtin(Int(I64))], []])) = UnionAtIndex (Id 0) (Index 0) Test.2; + let Test.12 : Builtin(Int(U8)) = 0i64; + let Test.13 : Builtin(Int(U8)) = GetTagId Test.11; + let Test.17 : Builtin(Bool) = lowlevel Eq Test.12 Test.13; if Test.17 then - let Test.10 = UnionAtIndex (Id 0) (Index 0) Test.2; - let Test.5 = UnionAtIndex (Id 0) (Index 0) Test.10; - let Test.7 = 1i64; - let Test.6 = CallByName Num.24 Test.5 Test.7; + let Test.10 : Union(NonRecursive([[Builtin(Int(I64))], []])) = UnionAtIndex (Id 0) (Index 0) Test.2; + let Test.5 : Builtin(Int(I64)) = UnionAtIndex (Id 0) (Index 0) Test.10; + let Test.7 : Builtin(Int(I64)) = 1i64; + let Test.6 : Builtin(Int(I64)) = CallByName Num.22 Test.5 Test.7; ret Test.6; else jump Test.16; diff --git a/compiler/test_mono/generated/when_on_record.txt b/compiler/test_mono/generated/when_on_record.txt index 339304f50f..b606928426 100644 --- a/compiler/test_mono/generated/when_on_record.txt +++ b/compiler/test_mono/generated/when_on_record.txt @@ -1,9 +1,9 @@ -procedure Num.24 (#Attr.2, #Attr.3): - let Test.5 = lowlevel NumAdd #Attr.2 #Attr.3; +procedure Num.22 (#Attr.2, #Attr.3): + let Test.5 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.5; procedure Test.0 (): - let Test.6 = 2i64; - let Test.4 = 3i64; - let Test.3 = CallByName Num.24 Test.6 Test.4; + let Test.6 : Builtin(Int(I64)) = 2i64; + let Test.4 : Builtin(Int(I64)) = 3i64; + let Test.3 : Builtin(Int(I64)) = CallByName Num.22 Test.6 Test.4; ret Test.3; diff --git a/compiler/test_mono/generated/when_on_result.txt b/compiler/test_mono/generated/when_on_result.txt index 6b7b74b4ed..84144767fc 100644 --- a/compiler/test_mono/generated/when_on_result.txt +++ b/compiler/test_mono/generated/when_on_result.txt @@ -1,27 +1,27 @@ procedure Test.1 (Test.5): - let Test.19 = 2i64; - let Test.2 = Ok Test.19; + let Test.19 : Builtin(Int(I64)) = 2i64; + let Test.2 : Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64))]])) = Ok Test.19; joinpoint Test.9 Test.3: ret Test.3; in - let Test.16 = 1i64; - let Test.17 = GetTagId Test.2; - let Test.18 = lowlevel Eq Test.16 Test.17; + let Test.16 : Builtin(Int(U8)) = 1i64; + let Test.17 : Builtin(Int(U8)) = GetTagId Test.2; + let Test.18 : Builtin(Bool) = lowlevel Eq Test.16 Test.17; if Test.18 then - let Test.13 = UnionAtIndex (Id 1) (Index 0) Test.2; - let Test.14 = 3i64; - let Test.15 = lowlevel Eq Test.14 Test.13; + let Test.13 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) Test.2; + let Test.14 : Builtin(Int(I64)) = 3i64; + let Test.15 : Builtin(Bool) = lowlevel Eq Test.14 Test.13; if Test.15 then - let Test.10 = 1i64; + let Test.10 : Builtin(Int(I64)) = 1i64; jump Test.9 Test.10; else - let Test.11 = 2i64; + let Test.11 : Builtin(Int(I64)) = 2i64; jump Test.9 Test.11; else - let Test.12 = 3i64; + let Test.12 : Builtin(Int(I64)) = 3i64; jump Test.9 Test.12; procedure Test.0 (): - let Test.7 = Struct {}; - let Test.6 = CallByName Test.1 Test.7; + let Test.7 : Struct([]) = Struct {}; + let Test.6 : Builtin(Int(I64)) = CallByName Test.1 Test.7; ret Test.6; diff --git a/compiler/test_mono/generated/when_on_two_values.txt b/compiler/test_mono/generated/when_on_two_values.txt index c3de46f38e..4af8530b49 100644 --- a/compiler/test_mono/generated/when_on_two_values.txt +++ b/compiler/test_mono/generated/when_on_two_values.txt @@ -1,26 +1,26 @@ -procedure Num.24 (#Attr.2, #Attr.3): - let Test.7 = lowlevel NumAdd #Attr.2 #Attr.3; +procedure Num.22 (#Attr.2, #Attr.3): + let Test.7 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.7; procedure Test.0 (): - let Test.16 = 3i64; - let Test.15 = 2i64; - let Test.4 = Struct {Test.15, Test.16}; + let Test.16 : Builtin(Int(I64)) = 3i64; + let Test.15 : Builtin(Int(I64)) = 2i64; + let Test.4 : Struct([Builtin(Int(I64)), Builtin(Int(I64))]) = Struct {Test.15, Test.16}; joinpoint Test.12: - let Test.2 = StructAtIndex 0 Test.4; - let Test.3 = StructAtIndex 1 Test.4; - let Test.6 = CallByName Num.24 Test.2 Test.3; + let Test.2 : Builtin(Int(I64)) = StructAtIndex 0 Test.4; + let Test.3 : Builtin(Int(I64)) = StructAtIndex 1 Test.4; + let Test.6 : Builtin(Int(I64)) = CallByName Num.22 Test.2 Test.3; ret Test.6; in - let Test.10 = StructAtIndex 1 Test.4; - let Test.11 = 3i64; - let Test.14 = lowlevel Eq Test.11 Test.10; + let Test.10 : Builtin(Int(I64)) = StructAtIndex 1 Test.4; + let Test.11 : Builtin(Int(I64)) = 3i64; + let Test.14 : Builtin(Bool) = lowlevel Eq Test.11 Test.10; if Test.14 then - let Test.8 = StructAtIndex 0 Test.4; - let Test.9 = 4i64; - let Test.13 = lowlevel Eq Test.9 Test.8; + let Test.8 : Builtin(Int(I64)) = StructAtIndex 0 Test.4; + let Test.9 : Builtin(Int(I64)) = 4i64; + let Test.13 : Builtin(Bool) = lowlevel Eq Test.9 Test.8; if Test.13 then - let Test.5 = 9i64; + let Test.5 : Builtin(Int(I64)) = 9i64; ret Test.5; else jump Test.12; diff --git a/compiler/test_mono/src/tests.rs b/compiler/test_mono/src/tests.rs index cbcdbed066..34001fe46a 100644 --- a/compiler/test_mono/src/tests.rs +++ b/compiler/test_mono/src/tests.rs @@ -25,6 +25,8 @@ use roc_mono::ir::Proc; use roc_mono::ir::ProcLayout; +const TARGET_INFO: roc_target::TargetInfo = roc_target::TargetInfo::default_x86_64(); + /// Without this, some tests pass in `cargo test --release` but fail without /// the --release flag because they run out of stack space. This increases /// stack size for debug builds only, while leaving the stack space at the default @@ -104,7 +106,7 @@ fn compiles_to_ir(test_name: &str, src: &str) { &stdlib, src_dir, exposed_types, - 8, + TARGET_INFO, builtin_defs_map, ); @@ -136,9 +138,9 @@ fn compiles_to_ir(test_name: &str, src: &str) { assert_eq!(type_problems, Vec::new()); assert_eq!(mono_problems, Vec::new()); - debug_assert_eq!(exposed_to_host.len(), 1); + debug_assert_eq!(exposed_to_host.values.len(), 1); - let main_fn_symbol = exposed_to_host.keys().copied().next().unwrap(); + let main_fn_symbol = exposed_to_host.values.keys().copied().next().unwrap(); verify_procedures(test_name, procedures, main_fn_symbol); } @@ -982,17 +984,17 @@ fn closure_in_list() { indoc!( r#" app "test" provides [ main ] to "./platform" - + foo = \{} -> x = 41 - + f = \{} -> x - + [ f ] - + main = items = foo {} - + List.len items "# ) @@ -1005,7 +1007,7 @@ fn somehow_drops_definitions() { r#" app "test" provides [ main ] to "./platform" - one : I64 + one : I64 one = 1 two : I64 @@ -1037,7 +1039,7 @@ fn specialize_closures() { apply = \f, x -> f x main = - one : I64 + one : I64 one = 1 two : I64 @@ -1111,6 +1113,144 @@ fn empty_list_of_function_type() { ) } +#[mono_test] +fn monomorphized_ints() { + indoc!( + r#" + app "test" provides [main] to "./platform" + + main = + x = 100 + + f : U8, U32 -> Nat + f = \_, _ -> 18 + + f x x + "# + ) +} + +#[mono_test] +fn monomorphized_floats() { + indoc!( + r#" + app "test" provides [main] to "./platform" + + main = + x = 100.0 + + f : F32, F64 -> Nat + f = \_, _ -> 18 + + f x x + "# + ) +} + +#[mono_test] +#[ignore = "TODO"] +fn monomorphized_ints_aliased() { + indoc!( + r#" + app "test" provides [main] to "./platform" + + main = + y = 100 + w1 = y + w2 = y + + f = \_, _ -> 1 + + f1 : U8, U32 -> Nat + f1 = f + + f2 : U32, U8 -> Nat + f2 = f + + f1 w1 w2 + f2 w1 w2 + "# + ) +} + +#[mono_test] +fn monomorphized_tag() { + indoc!( + r#" + app "test" provides [main] to "./platform" + + main = + b = False + f : Bool, [True, False, Idk] -> U8 + f = \_, _ -> 18 + f b b + "# + ) +} + +#[mono_test] +fn monomorphized_tag_with_aliased_args() { + indoc!( + r#" + app "test" provides [main] to "./platform" + + main = + b = False + c = False + a = A b c + f : [A Bool Bool] -> Nat + f = \_ -> 1 + f a + "# + ) +} + +#[mono_test] +fn monomorphized_list() { + indoc!( + r#" + app "test" provides [main] to "./platform" + + main = + l = [1, 2, 3] + + f : List U8, List U16 -> Nat + f = \_, _ -> 18 + + f l l + "# + ) +} + +#[mono_test] +fn monomorphized_applied_tag() { + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + main = + a = A "A" + f = \x -> + when x is + A y -> y + B y -> y + f a + "# + ) +} + +#[mono_test] +fn aliased_polymorphic_closure() { + indoc!( + r#" + n : U8 + n = 1 + f = \{} -> (\a -> n) + g = f {} + g {} + "# + ) +} + // #[ignore] // #[mono_test] // fn static_str_closure() { diff --git a/compiler/types/Cargo.toml b/compiler/types/Cargo.toml index 97cd23ac45..550cb5f333 100644 --- a/compiler/types/Cargo.toml +++ b/compiler/types/Cargo.toml @@ -9,6 +9,7 @@ edition = "2018" roc_collections = { path = "../collections" } roc_region = { path = "../region" } roc_module = { path = "../module" } +roc_error_macros = {path="../../error_macros"} ven_ena = { path = "../../vendor/ena" } bumpalo = { version = "3.8.0", features = ["collections"] } static_assertions = "1.1.0" diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index 6a48f7088f..a84eb8b43b 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -1,4 +1,4 @@ -use crate::subs::{Content, FlatType, GetSubsSlice, Subs, UnionTags, Variable}; +use crate::subs::{AliasVariables, Content, FlatType, GetSubsSlice, Subs, UnionTags, Variable}; use crate::types::{name_type_var, RecordField}; use roc_collections::all::{MutMap, MutSet}; use roc_module::ident::{Lowercase, TagName}; @@ -206,6 +206,13 @@ fn find_names_needed( // TODO should we also look in the actual variable? // find_names_needed(_actual, subs, roots, root_appearances, names_taken); } + &RangedNumber(typ, vars) => { + find_names_needed(typ, subs, roots, root_appearances, names_taken); + for var_index in vars { + let var = subs[var_index]; + find_names_needed(var, subs, roots, root_appearances, names_taken); + } + } Error | Structure(Erroneous(_)) | Structure(EmptyRecord) | Structure(EmptyTagUnion) => { // Errors and empty records don't need names. } @@ -289,6 +296,17 @@ pub fn content_to_string( buf } +pub fn get_single_arg<'a>(subs: &'a Subs, args: &'a AliasVariables) -> &'a Content { + debug_assert_eq!(args.len(), 1); + + let arg_var_index = args + .into_iter() + .next() + .expect("Num was not applied to a type argument!"); + let arg_var = subs[arg_var_index]; + subs.get_content_without_compacting(arg_var) +} + fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, parens: Parens) { use crate::subs::Content::*; @@ -306,18 +324,19 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa match *symbol { Symbol::NUM_NUM => { - debug_assert_eq!(args.len(), 1); - - let arg_var_index = args - .into_iter() - .next() - .expect("Num was not applied to a type argument!"); - let arg_var = subs[arg_var_index]; - let content = subs.get_content_without_compacting(arg_var); - - match &content { - Alias(nested, _, _) => match *nested { - Symbol::NUM_INTEGER => buf.push_str("I64"), + let content = get_single_arg(subs, args); + match *content { + Alias(nested, args, _actual) => match nested { + Symbol::NUM_INTEGER => { + write_integer( + env, + get_single_arg(subs, &args), + subs, + buf, + parens, + false, + ); + } Symbol::NUM_FLOATINGPOINT => buf.push_str("F64"), _ => write_parens!(write_parens, buf, { @@ -333,6 +352,33 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa } } + Symbol::NUM_INT => { + let content = get_single_arg(subs, args); + + write_integer(env, content, subs, buf, parens, write_parens) + } + + Symbol::NUM_FLOAT => { + debug_assert_eq!(args.len(), 1); + + let arg_var_index = args + .into_iter() + .next() + .expect("Num was not applied to a type argument!"); + let arg_var = subs[arg_var_index]; + let content = subs.get_content_without_compacting(arg_var); + + match content { + Alias(Symbol::NUM_BINARY32, _, _) => buf.push_str("F32"), + Alias(Symbol::NUM_BINARY64, _, _) => buf.push_str("F64"), + Alias(Symbol::NUM_DECIMAL, _, _) => buf.push_str("Dec"), + _ => write_parens!(write_parens, buf, { + buf.push_str("Float "); + write_content(env, content, subs, buf, parens); + }), + } + } + _ => write_parens!(write_parens, buf, { write_symbol(env, *symbol, buf); @@ -358,10 +404,62 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa }), } } + RangedNumber(typ, _range_vars) => write_content( + env, + subs.get_content_without_compacting(*typ), + subs, + buf, + parens, + ), Error => buf.push_str(""), } } +fn write_integer( + env: &Env, + content: &Content, + subs: &Subs, + buf: &mut String, + parens: Parens, + write_parens: bool, +) { + use crate::subs::Content::*; + + macro_rules! derive_num_writes { + ($($lit:expr, $tag:path)*) => { + write_parens!( + write_parens, + buf, + match content { + $( + &Alias($tag, _, _) => { + buf.push_str($lit) + }, + )* + actual => { + buf.push_str("Int "); + write_content(env, actual, subs, buf, parens); + } + } + ) + } + } + + derive_num_writes! { + "U8", Symbol::NUM_UNSIGNED8 + "U16", Symbol::NUM_UNSIGNED16 + "U32", Symbol::NUM_UNSIGNED32 + "U64", Symbol::NUM_UNSIGNED64 + "U128", Symbol::NUM_UNSIGNED128 + "I8", Symbol::NUM_SIGNED8 + "I16", Symbol::NUM_SIGNED16 + "I32", Symbol::NUM_SIGNED32 + "I64", Symbol::NUM_SIGNED64 + "I128", Symbol::NUM_SIGNED128 + "Nat", Symbol::NUM_NATURAL + } +} + enum ExtContent<'a> { Empty, Content(Variable, &'a Content), @@ -406,8 +504,8 @@ fn write_sorted_tags2<'a>( ext_var: Variable, ) -> ExtContent<'a> { // Sort the fields so they always end up in the same order. - let (it, new_ext_var) = tags.unsorted_iterator_and_ext(subs, ext_var); - let mut sorted_fields: Vec<_> = it.collect(); + let (tags, new_ext_var) = tags.unsorted_tags_and_ext(subs, ext_var); + let mut sorted_fields = tags.tags; let interns = &env.interns; let home = env.home; diff --git a/compiler/types/src/solved_types.rs b/compiler/types/src/solved_types.rs index 92282b0e55..fb5f20e7f0 100644 --- a/compiler/types/src/solved_types.rs +++ b/compiler/types/src/solved_types.rs @@ -85,7 +85,7 @@ impl SolvedType { match typ { EmptyRec => SolvedType::EmptyRecord, EmptyTagUnion => SolvedType::EmptyTagUnion, - Apply(symbol, types) => { + Apply(symbol, types, _) => { let mut solved_types = Vec::with_capacity(types.len()); for typ in types { @@ -231,6 +231,7 @@ impl SolvedType { } } Variable(var) => Self::from_var(solved_subs.inner(), *var), + RangedNumber(typ, _) => Self::from_type(solved_subs, typ), } } @@ -284,6 +285,7 @@ impl SolvedType { SolvedType::Alias(*symbol, new_args, solved_lambda_sets, Box::new(aliased_to)) } + RangedNumber(typ, _range_vars) => Self::from_var_help(subs, recursion_vars, *typ), Error => SolvedType::Error, } } @@ -454,7 +456,7 @@ pub fn to_type( new_args.push(to_type(arg, free_vars, var_store)); } - Type::Apply(*symbol, new_args) + Type::Apply(*symbol, new_args, Region::zero()) } Rigid(lowercase) => { if let Some(var) = free_vars.named_vars.get(lowercase) { diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 0f758d1f13..7dac3c50c7 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -9,12 +9,15 @@ use ven_ena::unify::{InPlace, Snapshot, UnificationTable, UnifyKey}; // if your changes cause this number to go down, great! // please change it to the lower number. // if it went up, maybe check that the change is really required -static_assertions::assert_eq_size!([u8; 6 * 8], Descriptor); -static_assertions::assert_eq_size!([u8; 4 * 8], Content); -static_assertions::assert_eq_size!([u8; 3 * 8], FlatType); -static_assertions::assert_eq_size!([u8; 6 * 8], Problem); -static_assertions::assert_eq_size!([u8; 12], UnionTags); -static_assertions::assert_eq_size!([u8; 2 * 8], RecordFields); +roc_error_macros::assert_sizeof_all!(Descriptor, 6 * 8); +roc_error_macros::assert_sizeof_all!(Content, 4 * 8); +roc_error_macros::assert_sizeof_all!(FlatType, 3 * 8); +roc_error_macros::assert_sizeof_all!(UnionTags, 12); +roc_error_macros::assert_sizeof_all!(RecordFields, 2 * 8); + +roc_error_macros::assert_sizeof_aarch64!(Problem, 6 * 8); +roc_error_macros::assert_sizeof_wasm!(Problem, 32); +roc_error_macros::assert_sizeof_default!(Problem, 6 * 8); #[derive(Clone, Copy, Hash, PartialEq, Eq)] pub struct Mark(i32); @@ -44,11 +47,17 @@ impl fmt::Debug for Mark { } } -#[derive(Default)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum ErrorTypeContext { + None, + ExpandRanges, +} + struct ErrorTypeState { taken: MutSet, normals: u32, problems: Vec, + context: ErrorTypeContext, } #[derive(Clone)] @@ -371,6 +380,10 @@ fn subs_fmt_content(this: &Content, subs: &Subs, f: &mut fmt::Formatter) -> fmt: write!(f, "Alias({:?}, {:?}, {:?})", name, slice, actual) } + Content::RangedNumber(typ, range) => { + let slice = subs.get_subs_slice(*range); + write!(f, "RangedNumber({:?}, {:?})", typ, slice) + } Content::Error => write!(f, "Error"), } } @@ -588,11 +601,6 @@ define_const_var! { AT_NATURAL, - AT_BINARY32, - AT_BINARY64, - - AT_DECIMAL, - // Signed8 : [ @Signed8 ] :pub SIGNED8, :pub SIGNED16, @@ -608,11 +616,6 @@ define_const_var! { :pub NATURAL, - :pub BINARY32, - :pub BINARY64, - - :pub DECIMAL, - // [ @Integer Signed8 ] AT_INTEGER_SIGNED8, AT_INTEGER_SIGNED16, @@ -688,6 +691,36 @@ define_const_var! { :pub NAT, + // [ @Binary32 ] + AT_BINARY32, + AT_BINARY64, + AT_DECIMAL, + + // Binary32 : [ @Binary32 ] + BINARY32, + BINARY64, + DECIMAL, + + // [ @Float Binary32 ] + AT_FLOAT_BINARY32, + AT_FLOAT_BINARY64, + AT_FLOAT_DECIMAL, + + // Float Binary32 : [ @Float Binary32 ] + FLOAT_BINARY32, + FLOAT_BINARY64, + FLOAT_DECIMAL, + + // [ @Num (Float Binary32) ] + AT_NUM_FLOAT_BINARY32, + AT_NUM_FLOAT_BINARY64, + AT_NUM_FLOAT_DECIMAL, + + // Num (Float Binary32) + NUM_FLOAT_BINARY32, + NUM_FLOAT_BINARY64, + NUM_FLOAT_DECIMAL, + :pub F32, :pub F64, @@ -1034,6 +1067,119 @@ fn define_integer_types(subs: &mut Subs) { ); } +#[allow(clippy::too_many_arguments)] +fn float_type( + subs: &mut Subs, + + num_at_binary64: Symbol, + num_binary64: Symbol, + num_f64: Symbol, + + at_binary64: Variable, + binary64: Variable, + + at_float_binary64: Variable, + float_binary64: Variable, + + at_num_float_binary64: Variable, + num_float_binary64: Variable, + + var_f64: Variable, +) { + // define the type Binary64 (which is an alias for [ @Binary64 ]) + { + let tags = UnionTags::insert_into_subs(subs, [(TagName::Private(num_at_binary64), [])]); + + subs.set_content(at_binary64, { + Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)) + }); + + subs.set_content(binary64, { + Content::Alias(num_binary64, AliasVariables::default(), at_binary64) + }); + } + + // define the type `Num.Float Num.Binary64` + { + let tags = UnionTags::insert_into_subs( + subs, + [(TagName::Private(Symbol::NUM_AT_FLOATINGPOINT), [binary64])], + ); + subs.set_content(at_float_binary64, { + Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)) + }); + + let vars = AliasVariables::insert_into_subs(subs, [binary64], []); + subs.set_content(float_binary64, { + Content::Alias(Symbol::NUM_FLOATINGPOINT, vars, at_binary64) + }); + } + + // define the type `F64: Num.Num (Num.Float Num.Binary64)` + { + let tags = UnionTags::insert_into_subs( + subs, + [(TagName::Private(Symbol::NUM_AT_NUM), [float_binary64])], + ); + subs.set_content(at_num_float_binary64, { + Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)) + }); + + let vars = AliasVariables::insert_into_subs(subs, [float_binary64], []); + subs.set_content(num_float_binary64, { + Content::Alias(Symbol::NUM_NUM, vars, at_num_float_binary64) + }); + + subs.set_content(var_f64, { + Content::Alias(num_f64, AliasVariables::default(), num_float_binary64) + }); + } +} + +fn define_float_types(subs: &mut Subs) { + float_type( + subs, + Symbol::NUM_AT_BINARY32, + Symbol::NUM_BINARY32, + Symbol::NUM_F32, + Variable::AT_BINARY32, + Variable::BINARY32, + Variable::AT_FLOAT_BINARY32, + Variable::FLOAT_BINARY32, + Variable::AT_NUM_FLOAT_BINARY32, + Variable::NUM_FLOAT_BINARY32, + Variable::F32, + ); + + float_type( + subs, + Symbol::NUM_AT_BINARY64, + Symbol::NUM_BINARY64, + Symbol::NUM_F64, + Variable::AT_BINARY64, + Variable::BINARY64, + Variable::AT_FLOAT_BINARY64, + Variable::FLOAT_BINARY64, + Variable::AT_NUM_FLOAT_BINARY64, + Variable::NUM_FLOAT_BINARY64, + Variable::F64, + ); + + float_type( + subs, + Symbol::NUM_AT_DECIMAL, + Symbol::NUM_DECIMAL, + Symbol::NUM_DEC, + Variable::AT_DECIMAL, + Variable::DECIMAL, + Variable::AT_FLOAT_DECIMAL, + Variable::FLOAT_DECIMAL, + Variable::AT_NUM_FLOAT_DECIMAL, + Variable::NUM_FLOAT_DECIMAL, + Variable::DEC, + ); +} + impl Subs { pub const RESULT_TAG_NAMES: SubsSlice = SubsSlice::new(0, 2); @@ -1072,6 +1218,7 @@ impl Subs { } define_integer_types(&mut subs); + define_float_types(&mut subs); subs.set_content( Variable::EMPTY_RECORD, @@ -1320,7 +1467,15 @@ impl Subs { explicit_substitute(self, x, y, z, &mut seen) } - pub fn var_to_error_type(&mut self, var: Variable) -> (ErrorType, Vec) { + pub fn var_to_error_type(&mut self, var: Variable) -> (ErrorType, Vec) { + self.var_to_error_type_contextual(var, ErrorTypeContext::None) + } + + pub fn var_to_error_type_contextual( + &mut self, + var: Variable, + context: ErrorTypeContext, + ) -> (ErrorType, Vec) { let names = get_var_names(self, var, ImMap::default()); let mut taken = MutSet::default(); @@ -1332,6 +1487,7 @@ impl Subs { taken, normals: 0, problems: Vec::new(), + context, }; (var_to_err_type(self, &mut state, var), state.problems) @@ -1364,6 +1520,15 @@ impl Subs { pub fn commit_snapshot(&mut self, snapshot: Snapshot>) { self.utable.commit(snapshot) } + + /// Checks whether the content of `var`, or any nested content, satisfies the `predicate`. + pub fn var_contains_content

(&self, var: Variable, predicate: P) -> bool + where + P: Fn(&Content) -> bool + Copy, + { + let mut seen_recursion_vars = MutSet::default(); + var_contains_content_help(self, var, predicate, &mut seen_recursion_vars) + } } #[inline(always)] @@ -1461,11 +1626,14 @@ impl From for Descriptor { } } -static_assertions::assert_eq_size!([u8; 4 * 8], Content); -static_assertions::assert_eq_size!([u8; 4 * 8], (Variable, Option)); -static_assertions::assert_eq_size!([u8; 3 * 8], (Symbol, AliasVariables, Variable)); -static_assertions::assert_eq_size!([u8; 8], AliasVariables); -static_assertions::assert_eq_size!([u8; 3 * 8], FlatType); +roc_error_macros::assert_sizeof_all!(Content, 4 * 8); +roc_error_macros::assert_sizeof_all!((Symbol, AliasVariables, Variable), 3 * 8); +roc_error_macros::assert_sizeof_all!(AliasVariables, 8); +roc_error_macros::assert_sizeof_all!(FlatType, 3 * 8); + +roc_error_macros::assert_sizeof_aarch64!((Variable, Option), 4 * 8); +roc_error_macros::assert_sizeof_wasm!((Variable, Option), 4 * 4); +roc_error_macros::assert_sizeof_all!((Variable, Option), 4 * 8); #[derive(Clone, Debug)] pub enum Content { @@ -1483,6 +1651,7 @@ pub enum Content { }, Structure(FlatType), Alias(Symbol, AliasVariables, Variable), + RangedNumber(Variable, VariableSubsSlice), Error, } @@ -1602,8 +1771,6 @@ impl Content { } } -static_assertions::assert_eq_size!([u8; 3 * 8], FlatType); - #[derive(Clone, Debug)] pub enum FlatType { Apply(Symbol, VariableSubsSlice), @@ -1617,6 +1784,25 @@ pub enum FlatType { EmptyTagUnion, } +impl FlatType { + pub fn get_singleton_tag_union<'a>(&'a self, subs: &'a Subs) -> Option<&'a TagName> { + match self { + Self::TagUnion(tags, ext) => { + let tags = tags.unsorted_tags_and_ext(subs, *ext).0.tags; + if tags.len() != 1 { + return None; + } + let (tag_name, vars) = tags[0]; + if !vars.is_empty() { + return None; + } + Some(tag_name) + } + _ => None, + } + } +} + #[derive(PartialEq, Eq, Debug, Clone, Copy)] pub enum Builtin { Str, @@ -1804,16 +1990,17 @@ impl UnionTags { it.map(f) } - pub fn unsorted_iterator_and_ext<'a>( + #[inline(always)] + pub fn unsorted_tags_and_ext<'a>( &'a self, subs: &'a Subs, ext: Variable, - ) -> (impl Iterator + 'a, Variable) { + ) -> (UnsortedUnionTags<'a>, Variable) { let (it, ext) = crate::types::gather_tags_unsorted_iter(subs, *self, ext); - let f = move |(label, slice): (_, SubsSlice)| (label, subs.get_subs_slice(slice)); + let it = it.map(f); - (it.map(f), ext) + (UnsortedUnionTags { tags: it.collect() }, ext) } #[inline(always)] @@ -1868,6 +2055,25 @@ impl UnionTags { } } +#[derive(Debug)] +pub struct UnsortedUnionTags<'a> { + pub tags: Vec<(&'a TagName, &'a [Variable])>, +} + +impl<'a> UnsortedUnionTags<'a> { + pub fn is_newtype_wrapper(&self, _subs: &Subs) -> bool { + if self.tags.len() != 1 { + return false; + } + self.tags[0].1.len() == 1 + } + + pub fn get_newtype(&self, _subs: &Subs) -> (&TagName, Variable) { + let (tag_name, vars) = self.tags[0]; + (tag_name, vars[0]) + } +} + pub type SortedTagsIterator<'a> = Box + 'a>; pub type SortedTagsSlicesIterator<'a> = Box + 'a>; @@ -2016,6 +2222,21 @@ impl RecordFields { it } + #[inline(always)] + pub fn unsorted_iterator_and_ext<'a>( + &'a self, + subs: &'a Subs, + ext: Variable, + ) -> ( + impl Iterator)> + 'a, + Variable, + ) { + let (it, ext) = crate::types::gather_fields_unsorted_iter(subs, *self, ext) + .expect("Something weird ended up in a record type"); + + (it, ext) + } + /// Get a sorted iterator over the fields of this record type /// /// Implementation: When the record has an `ext` variable that is the empty record, then @@ -2181,6 +2402,15 @@ fn occurs( short_circuit_help(subs, root_var, &new_seen, var)?; } + Ok(()) + } + RangedNumber(typ, _range_vars) => { + let mut new_seen = seen.clone(); + new_seen.insert(root_var); + + short_circuit_help(subs, root_var, &new_seen, *typ)?; + // _range_vars excluded because they are not explicitly part of the type. + Ok(()) } } @@ -2370,6 +2600,19 @@ fn explicit_substitute( subs.set_content(in_var, Alias(symbol, args, new_actual)); + in_var + } + RangedNumber(typ, vars) => { + for index in vars.into_iter() { + let var = subs[index]; + let new_var = explicit_substitute(subs, from, to, var, seen); + subs[index] = new_var; + } + + let new_typ = explicit_substitute(subs, from, to, typ, seen); + + subs.set_content(in_var, RangedNumber(new_typ, vars)); + in_var } } @@ -2421,6 +2664,13 @@ fn get_var_names( get_var_names(subs, subs[arg_var], answer) }), + RangedNumber(typ, vars) => { + let taken_names = get_var_names(subs, typ, taken_names); + vars.into_iter().fold(taken_names, |answer, var| { + get_var_names(subs, subs[var], answer) + }) + } + Structure(flat_type) => match flat_type { FlatType::Apply(_, args) => { args.into_iter().fold(taken_names, |answer, arg_var| { @@ -2633,6 +2883,22 @@ fn content_to_err_type( ErrorType::Alias(symbol, err_args, Box::new(err_type)) } + RangedNumber(typ, range) => { + let err_type = var_to_err_type(subs, state, typ); + + if state.context == ErrorTypeContext::ExpandRanges { + let mut types = Vec::with_capacity(range.len()); + for var_index in range { + let var = subs[var_index]; + + types.push(var_to_err_type(subs, state, var)); + } + ErrorType::Range(Box::new(err_type), types) + } else { + err_type + } + } + Error => ErrorType::Error, } } @@ -2911,6 +3177,11 @@ fn restore_help(subs: &mut Subs, initial: Variable) { stack.push(*var); } + + RangedNumber(typ, vars) => { + stack.push(*typ); + stack.extend(var_slice(*vars)); + } } } } @@ -3070,6 +3341,10 @@ impl StorageSubs { Self::offset_alias_variables(offsets, *alias_variables), Self::offset_variable(offsets, *actual), ), + RangedNumber(typ, vars) => RangedNumber( + Self::offset_variable(offsets, *typ), + Self::offset_variable_slice(offsets, *vars), + ), Error => Content::Error, } } @@ -3462,5 +3737,102 @@ fn deep_copy_var_to_help<'a>( copy } + + RangedNumber(typ, vars) => { + let new_typ = deep_copy_var_to_help(arena, visited, source, target, max_rank, typ); + + let new_vars = SubsSlice::reserve_into_subs(target, vars.len()); + + for (target_index, var_index) in (new_vars.indices()).zip(vars) { + let var = source[var_index]; + let copy_var = deep_copy_var_to_help(arena, visited, source, target, max_rank, var); + target.variables[target_index] = copy_var; + } + + let new_content = RangedNumber(new_typ, new_vars); + + target.set(copy, make_descriptor(new_content)); + copy + } } } + +fn var_contains_content_help

( + subs: &Subs, + var: Variable, + predicate: P, + seen_recursion_vars: &mut MutSet, +) -> bool +where + P: Fn(&Content) -> bool + Copy, +{ + let mut stack = vec![var]; + + macro_rules! push_var_slice { + ($slice:expr) => { + stack.extend(subs.get_subs_slice($slice)) + }; + } + + while let Some(var) = stack.pop() { + if seen_recursion_vars.contains(&var) { + continue; + } + + let content = subs.get_content_without_compacting(var); + + if predicate(content) { + return true; + } + + use Content::*; + use FlatType::*; + match content { + FlexVar(_) | RigidVar(_) => {} + RecursionVar { + structure, + opt_name: _, + } => { + seen_recursion_vars.insert(var); + stack.push(*structure); + } + Structure(flat_type) => match flat_type { + Apply(_, vars) => push_var_slice!(*vars), + Func(args, clos, ret) => { + push_var_slice!(*args); + stack.push(*clos); + stack.push(*ret); + } + Record(fields, var) => { + push_var_slice!(fields.variables()); + stack.push(*var); + } + TagUnion(tags, ext_var) => { + for i in tags.variables() { + push_var_slice!(subs[i]); + } + stack.push(*ext_var); + } + FunctionOrTagUnion(_, _, var) => stack.push(*var), + RecursiveTagUnion(rec_var, tags, ext_var) => { + seen_recursion_vars.insert(*rec_var); + for i in tags.variables() { + push_var_slice!(subs[i]); + } + stack.push(*ext_var); + } + Erroneous(_) | EmptyRecord | EmptyTagUnion => {} + }, + Alias(_, arguments, real_type_var) => { + push_var_slice!(arguments.variables()); + stack.push(*real_type_var); + } + RangedNumber(typ, vars) => { + stack.push(*typ); + push_var_slice!(*vars); + } + Error => {} + } + } + false +} diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index c7c093dd18..15fa24a597 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -94,13 +94,18 @@ impl RecordField { } } - pub fn substitute_alias(&mut self, rep_symbol: Symbol, actual: &Type) { + pub fn substitute_alias( + &mut self, + rep_symbol: Symbol, + rep_args: &[Type], + actual: &Type, + ) -> Result<(), Region> { use RecordField::*; match self { - Optional(typ) => typ.substitute_alias(rep_symbol, actual), - Required(typ) => typ.substitute_alias(rep_symbol, actual), - Demanded(typ) => typ.substitute_alias(rep_symbol, actual), + Optional(typ) => typ.substitute_alias(rep_symbol, rep_args, actual), + Required(typ) => typ.substitute_alias(rep_symbol, rep_args, actual), + Demanded(typ) => typ.substitute_alias(rep_symbol, rep_args, actual), } } @@ -189,8 +194,9 @@ pub enum Type { }, RecursiveTagUnion(Variable, Vec<(TagName, Vec)>, Box), /// Applying a type to some arguments (e.g. Dict.Dict String Int) - Apply(Symbol, Vec), + Apply(Symbol, Vec, Region), Variable(Variable), + RangedNumber(Box, Vec), /// A type error, which will code gen to a runtime error Erroneous(Problem), } @@ -220,7 +226,7 @@ impl fmt::Debug for Type { } Type::Variable(var) => write!(f, "<{:?}>", var), - Type::Apply(symbol, args) => { + Type::Apply(symbol, args, _) => { write!(f, "({:?}", symbol)?; for arg in args { @@ -434,6 +440,9 @@ impl fmt::Debug for Type { write!(f, " as <{:?}>", rec) } + Type::RangedNumber(typ, range_vars) => { + write!(f, "Ranged({:?}, {:?})", typ, range_vars) + } } } } @@ -539,72 +548,83 @@ impl Type { } actual_type.substitute(substitutions); } - Apply(_, args) => { + Apply(_, args, _) => { for arg in args { arg.substitute(substitutions); } } + RangedNumber(typ, _) => { + typ.substitute(substitutions); + } EmptyRec | EmptyTagUnion | Erroneous(_) => {} } } - // swap Apply with Alias if their module and tag match - pub fn substitute_alias(&mut self, rep_symbol: Symbol, actual: &Type) { + /// Swap Apply(rep_symbol, rep_args) with `actual`. Returns `Err` if there is an + /// `Apply(rep_symbol, _)`, but the args don't match. + pub fn substitute_alias( + &mut self, + rep_symbol: Symbol, + rep_args: &[Type], + actual: &Type, + ) -> Result<(), Region> { use Type::*; match self { Function(args, closure, ret) => { for arg in args { - arg.substitute_alias(rep_symbol, actual); + arg.substitute_alias(rep_symbol, rep_args, actual)?; } - closure.substitute_alias(rep_symbol, actual); - ret.substitute_alias(rep_symbol, actual); - } - FunctionOrTagUnion(_, _, ext) => { - ext.substitute_alias(rep_symbol, actual); + closure.substitute_alias(rep_symbol, rep_args, actual)?; + ret.substitute_alias(rep_symbol, rep_args, actual) } + FunctionOrTagUnion(_, _, ext) => ext.substitute_alias(rep_symbol, rep_args, actual), RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => { for (_, args) in tags { for x in args { - x.substitute_alias(rep_symbol, actual); + x.substitute_alias(rep_symbol, rep_args, actual)?; } } - ext.substitute_alias(rep_symbol, actual); + ext.substitute_alias(rep_symbol, rep_args, actual) } Record(fields, ext) => { for (_, x) in fields.iter_mut() { - x.substitute_alias(rep_symbol, actual); + x.substitute_alias(rep_symbol, rep_args, actual)?; } - ext.substitute_alias(rep_symbol, actual); + ext.substitute_alias(rep_symbol, rep_args, actual) } Alias { actual: alias_actual, .. - } => { - alias_actual.substitute_alias(rep_symbol, actual); - } + } => alias_actual.substitute_alias(rep_symbol, rep_args, actual), HostExposedAlias { actual: actual_type, .. - } => { - actual_type.substitute_alias(rep_symbol, actual); - } - Apply(symbol, _) if *symbol == rep_symbol => { - *self = actual.clone(); + } => actual_type.substitute_alias(rep_symbol, rep_args, actual), + Apply(symbol, args, region) if *symbol == rep_symbol => { + if args.len() == rep_args.len() + && args.iter().zip(rep_args.iter()).all(|(t1, t2)| t1 == t2) + { + *self = actual.clone(); - if let Apply(_, args) = self { - for arg in args { - arg.substitute_alias(rep_symbol, actual); + if let Apply(_, args, _) = self { + for arg in args { + arg.substitute_alias(rep_symbol, rep_args, actual)?; + } } + return Ok(()); } + Err(*region) } - Apply(_, args) => { + Apply(_, args, _) => { for arg in args { - arg.substitute_alias(rep_symbol, actual); + arg.substitute_alias(rep_symbol, rep_args, actual)?; } + Ok(()) } - EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => {} + RangedNumber(typ, _) => typ.substitute_alias(rep_symbol, rep_args, actual), + EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => Ok(()), } } @@ -639,8 +659,9 @@ impl Type { HostExposedAlias { name, actual, .. } => { name == &rep_symbol || actual.contains_symbol(rep_symbol) } - Apply(symbol, _) if *symbol == rep_symbol => true, - Apply(_, args) => args.iter().any(|arg| arg.contains_symbol(rep_symbol)), + Apply(symbol, _, _) if *symbol == rep_symbol => true, + Apply(_, args, _) => args.iter().any(|arg| arg.contains_symbol(rep_symbol)), + RangedNumber(typ, _) => typ.contains_symbol(rep_symbol), EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => false, } } @@ -676,7 +697,10 @@ impl Type { .. } => actual_type.contains_variable(rep_variable), HostExposedAlias { actual, .. } => actual.contains_variable(rep_variable), - Apply(_, args) => args.iter().any(|arg| arg.contains_variable(rep_variable)), + Apply(_, args, _) => args.iter().any(|arg| arg.contains_variable(rep_variable)), + RangedNumber(typ, vars) => { + typ.contains_variable(rep_variable) || vars.iter().any(|&v| v == rep_variable) + } EmptyRec | EmptyTagUnion | Erroneous(_) => false, } } @@ -753,7 +777,7 @@ impl Type { actual_type.instantiate_aliases(region, aliases, var_store, introduced); } - Apply(symbol, args) => { + Apply(symbol, args, _) => { if let Some(alias) = aliases.get(symbol) { if args.len() != alias.type_variables.len() { *self = Type::Erroneous(Problem::BadTypeArguments { @@ -833,6 +857,9 @@ impl Type { } } } + RangedNumber(typ, _) => { + typ.instantiate_aliases(region, aliases, var_store, introduced); + } EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => {} } } @@ -882,10 +909,16 @@ fn symbols_help(tipe: &Type, accum: &mut ImSet) { accum.insert(*name); symbols_help(actual, accum); } - Apply(symbol, args) => { + Apply(symbol, args, _) => { accum.insert(*symbol); args.iter().for_each(|arg| symbols_help(arg, accum)); } + Erroneous(Problem::CyclicAlias(alias, _, _)) => { + accum.insert(*alias); + } + RangedNumber(typ, _) => { + symbols_help(typ, accum); + } EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => {} } } @@ -964,7 +997,11 @@ fn variables_help(tipe: &Type, accum: &mut ImSet) { } variables_help(actual, accum); } - Apply(_, args) => { + RangedNumber(typ, vars) => { + variables_help(typ, accum); + accum.extend(vars.iter().copied()); + } + Apply(_, args, _) => { for x in args { variables_help(x, accum); } @@ -1068,7 +1105,11 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) { } variables_help_detailed(actual, accum); } - Apply(_, args) => { + RangedNumber(typ, vars) => { + variables_help_detailed(typ, accum); + accum.type_variables.extend(vars); + } + Apply(_, args, _) => { for x in args { variables_help_detailed(x, accum); } @@ -1171,6 +1212,7 @@ pub enum Reason { RecordUpdateValue(Lowercase), RecordUpdateKeys(Symbol, SendMap), RecordDefaultField(Lowercase), + NumericLiteralSuffix, } #[derive(PartialEq, Debug, Clone)] @@ -1238,6 +1280,16 @@ pub struct Alias { pub typ: Type, } +impl Alias { + pub fn header_region(&self) -> Region { + Region::across_all( + [self.region] + .iter() + .chain(self.type_variables.iter().map(|tv| &tv.region)), + ) + } +} + #[derive(PartialEq, Eq, Debug, Clone, Hash)] pub enum Problem { CanonicalizationProblem, @@ -1262,6 +1314,7 @@ pub enum Mismatch { InconsistentIfElse, InconsistentWhenBranches, CanonicalizationProblem, + TypeNotInRange, } #[derive(PartialEq, Eq, Clone, Hash)] @@ -1275,6 +1328,7 @@ pub enum ErrorType { RecursiveTagUnion(Box, SendMap>, TypeExt), Function(Vec, Box, Box), Alias(Symbol, Vec, Box), + Range(Box, Vec), Error, } @@ -1333,6 +1387,12 @@ impl ErrorType { }); t.add_names(taken); } + Range(typ, ts) => { + typ.add_names(taken); + ts.iter().for_each(|t| { + t.add_names(taken); + }); + } Error => {} } } @@ -1644,6 +1704,21 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens: write_debug_error_type_help(*rec, buf, Parens::Unnecessary); } + Range(typ, types) => { + write_debug_error_type_help(*typ, buf, parens); + buf.push('<'); + + let mut it = types.into_iter().peekable(); + while let Some(typ) = it.next() { + write_debug_error_type_help(typ, buf, Parens::Unnecessary); + + if it.peek().is_some() { + buf.push_str(", "); + } + } + + buf.push('>'); + } } } diff --git a/compiler/unify/Cargo.toml b/compiler/unify/Cargo.toml index c4a2c783c1..e15626603c 100644 --- a/compiler/unify/Cargo.toml +++ b/compiler/unify/Cargo.toml @@ -1,11 +1,18 @@ [package] +authors = ["The Roc Contributors"] +edition = "2018" +license = "UPL-1.0" name = "roc_unify" version = "0.1.0" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2018" [dependencies] -roc_collections = { path = "../collections" } -roc_module = { path = "../module" } -roc_types = { path = "../types" } +bitflags = "1.3.2" + +[dependencies.roc_collections] +path = "../collections" + +[dependencies.roc_module] +path = "../module" + +[dependencies.roc_types] +path = "../types" diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index e9ee4d42db..befcf26b1c 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -1,15 +1,16 @@ +use bitflags::bitflags; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_types::subs::Content::{self, *}; use roc_types::subs::{ - AliasVariables, Descriptor, FlatType, GetSubsSlice, Mark, OptVariable, RecordFields, Subs, - SubsIndex, SubsSlice, UnionTags, Variable, VariableSubsSlice, + AliasVariables, Descriptor, ErrorTypeContext, FlatType, GetSubsSlice, Mark, OptVariable, + RecordFields, Subs, SubsIndex, SubsSlice, UnionTags, Variable, VariableSubsSlice, }; use roc_types::types::{ErrorType, Mismatch, RecordField}; macro_rules! mismatch { () => {{ - if cfg!(debug_assertions) { + if cfg!(debug_assertions) && std::env::var("ROC_PRINT_MISMATCHES").is_some() { println!( "Mismatch in {} Line {} Column {}", file!(), @@ -21,7 +22,7 @@ macro_rules! mismatch { vec![Mismatch::TypeMismatch] }}; ($msg:expr) => {{ - if cfg!(debug_assertions) { + if cfg!(debug_assertions) && std::env::var("ROC_PRINT_MISMATCHES").is_ok() { println!( "Mismatch in {} Line {} Column {}", file!(), @@ -39,7 +40,7 @@ macro_rules! mismatch { mismatch!($msg) }}; ($msg:expr, $($arg:tt)*) => {{ - if cfg!(debug_assertions) { + if cfg!(debug_assertions) && std::env::var("ROC_PRINT_MISMATCHES").is_ok() { println!( "Mismatch in {} Line {} Column {}", file!(), @@ -56,17 +57,41 @@ macro_rules! mismatch { type Pool = Vec; -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum Mode { - /// Instructs the unifier to solve two types for equality. - /// - /// For example, { n : Str }a ~ { n: Str, m : Str } will solve "a" to "{ m : Str }". - Eq, - /// Instructs the unifier to treat the right-hand-side of a constraint as - /// present in the left-hand-side, rather than strictly equal. - /// - /// For example, t1 += [A Str] says we should "add" the tag "A Str" to the type of "t1". - Present, +bitflags! { + pub struct Mode : u8 { + /// Instructs the unifier to solve two types for equality. + /// + /// For example, { n : Str }a ~ { n: Str, m : Str } will solve "a" to "{ m : Str }". + const EQ = 1 << 0; + /// Instructs the unifier to treat the right-hand-side of a constraint as + /// present in the left-hand-side, rather than strictly equal. + /// + /// For example, t1 += [A Str] says we should "add" the tag "A Str" to the type of "t1". + const PRESENT = 1 << 1; + /// Instructs the unifier to treat rigids exactly like flex vars. + /// Usually rigids can only unify with flex vars, because rigids are named and bound + /// explicitly. + /// However, when checking type ranges, as we do for `RangedNumber` types, we must loosen + /// this restriction because otherwise an admissible range will appear inadmissible. + /// For example, Int * is in the range . + const RIGID_AS_FLEX = 1 << 2; + } +} + +impl Mode { + fn is_eq(&self) -> bool { + debug_assert!(!self.contains(Mode::EQ | Mode::PRESENT)); + self.contains(Mode::EQ) + } + + fn is_present(&self) -> bool { + debug_assert!(!self.contains(Mode::EQ | Mode::PRESENT)); + self.contains(Mode::PRESENT) + } + + fn as_eq(self) -> Self { + (self - Mode::PRESENT) | Mode::EQ + } } #[derive(Debug)] @@ -95,8 +120,14 @@ pub fn unify(subs: &mut Subs, var1: Variable, var2: Variable, mode: Mode) -> Uni if mismatches.is_empty() { Unified::Success(vars) } else { - let (type1, mut problems) = subs.var_to_error_type(var1); - let (type2, problems2) = subs.var_to_error_type(var2); + let error_context = if mismatches.contains(&Mismatch::TypeNotInRange) { + ErrorTypeContext::ExpandRanges + } else { + ErrorTypeContext::None + }; + + let (type1, mut problems) = subs.var_to_error_type_contextual(var1, error_context); + let (type2, problems2) = subs.var_to_error_type_contextual(var2, error_context); problems.extend(problems2); @@ -175,6 +206,7 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome { unify_structure(subs, pool, &ctx, flat_type, &ctx.second_desc.content) } Alias(symbol, args, real_var) => unify_alias(subs, pool, &ctx, *symbol, *args, *real_var), + &RangedNumber(typ, range_vars) => unify_ranged_number(subs, pool, &ctx, typ, range_vars), Error => { // Error propagates. Whatever we're comparing it to doesn't matter! merge(subs, &ctx, Error) @@ -182,6 +214,78 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome { } } +#[inline(always)] +fn unify_ranged_number( + subs: &mut Subs, + pool: &mut Pool, + ctx: &Context, + real_var: Variable, + range_vars: VariableSubsSlice, +) -> Outcome { + let other_content = &ctx.second_desc.content; + + let outcome = match other_content { + FlexVar(_) => { + // Ranged number wins + merge(subs, ctx, RangedNumber(real_var, range_vars)) + } + RecursionVar { .. } | RigidVar(..) | Alias(..) | Structure(..) => { + unify_pool(subs, pool, real_var, ctx.second, ctx.mode) + } + &RangedNumber(other_real_var, other_range_vars) => { + let outcome = unify_pool(subs, pool, real_var, other_real_var, ctx.mode); + if outcome.is_empty() { + check_valid_range(subs, pool, ctx.first, other_range_vars, ctx.mode) + } else { + outcome + } + // TODO: We should probably check that "range_vars" and "other_range_vars" intersect + } + Error => merge(subs, ctx, Error), + }; + + if !outcome.is_empty() { + return outcome; + } + + check_valid_range(subs, pool, ctx.second, range_vars, ctx.mode) +} + +fn check_valid_range( + subs: &mut Subs, + pool: &mut Pool, + var: Variable, + range: VariableSubsSlice, + mode: Mode, +) -> Outcome { + let slice = subs + .get_subs_slice(range) + .iter() + .copied() + .collect::>(); + + let mut it = slice.iter().peekable(); + while let Some(&possible_var) = it.next() { + let snapshot = subs.snapshot(); + let old_pool = pool.clone(); + let outcome = unify_pool(subs, pool, var, possible_var, mode | Mode::RIGID_AS_FLEX); + if outcome.is_empty() { + // Okay, we matched some type in the range. + subs.rollback_to(snapshot); + *pool = old_pool; + return vec![]; + } else if it.peek().is_some() { + // We failed to match something in the range, but there are still things we can try. + subs.rollback_to(snapshot); + *pool = old_pool; + } else { + subs.commit_snapshot(snapshot); + } + } + + return vec![Mismatch::TypeNotInRange]; +} + #[inline(always)] fn unify_alias( subs: &mut Subs, @@ -231,6 +335,14 @@ fn unify_alias( } } Structure(_) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode), + RangedNumber(other_real_var, other_range_vars) => { + let outcome = unify_pool(subs, pool, real_var, *other_real_var, ctx.mode); + if outcome.is_empty() { + check_valid_range(subs, pool, real_var, *other_range_vars, ctx.mode) + } else { + outcome + } + } Error => merge(subs, ctx, Error), } } @@ -250,14 +362,14 @@ fn unify_structure( // And if we see a flex variable on the right hand side of a presence // constraint, we know we need to open up the structure we're trying to unify with. - match (ctx.mode, flat_type) { - (Mode::Present, FlatType::TagUnion(tags, _ext)) => { + match (ctx.mode.is_present(), flat_type) { + (true, FlatType::TagUnion(tags, _ext)) => { let new_ext = subs.fresh_unnamed_flex_var(); let mut new_desc = ctx.first_desc.clone(); new_desc.content = Structure(FlatType::TagUnion(*tags, new_ext)); subs.set(ctx.first, new_desc); } - (Mode::Present, FlatType::FunctionOrTagUnion(tn, sym, _ext)) => { + (true, FlatType::FunctionOrTagUnion(tn, sym, _ext)) => { let new_ext = subs.fresh_unnamed_flex_var(); let mut new_desc = ctx.first_desc.clone(); new_desc.content = Structure(FlatType::FunctionOrTagUnion(*tn, *sym, new_ext)); @@ -285,7 +397,12 @@ fn unify_structure( // unify the structure with this unrecursive tag union unify_pool(subs, pool, ctx.first, *structure, ctx.mode) } - _ => todo!("rec structure {:?}", &flat_type), + // Only tag unions can be recursive; everything else is an error. + _ => mismatch!( + "trying to unify {:?} with recursive type var {:?}", + &flat_type, + structure + ), }, Structure(ref other_flat_type) => { @@ -295,7 +412,15 @@ fn unify_structure( Alias(_, _, real_var) => { // NB: not treating this as a presence constraint seems pivotal! I // can't quite figure out why, but it doesn't seem to impact other types. - unify_pool(subs, pool, ctx.first, *real_var, Mode::Eq) + unify_pool(subs, pool, ctx.first, *real_var, ctx.mode.as_eq()) + } + RangedNumber(other_real_var, other_range_vars) => { + let outcome = unify_pool(subs, pool, ctx.first, *other_real_var, ctx.mode); + if outcome.is_empty() { + check_valid_range(subs, pool, ctx.first, *other_range_vars, ctx.mode) + } else { + outcome + } } Error => merge(subs, ctx, Error), } @@ -669,8 +794,8 @@ fn unify_tag_union_new( let shared_tags = separate.in_both; - if let (Mode::Present, Content::Structure(FlatType::EmptyTagUnion)) = - (ctx.mode, subs.get(ext1).content) + if let (true, Content::Structure(FlatType::EmptyTagUnion)) = + (ctx.mode.is_present(), subs.get(ext1).content) { if !separate.only_in_2.is_empty() { // Create a new extension variable that we'll fill in with the @@ -699,7 +824,7 @@ fn unify_tag_union_new( if separate.only_in_1.is_empty() { if separate.only_in_2.is_empty() { - let ext_problems = if ctx.mode == Mode::Eq { + let ext_problems = if ctx.mode.is_eq() { unify_pool(subs, pool, ext1, ext2, ctx.mode) } else { // In a presence context, we don't care about ext2 being equal to ext1 @@ -753,7 +878,7 @@ fn unify_tag_union_new( let sub_record = fresh(subs, pool, ctx, Structure(flat_type)); // In a presence context, we don't care about ext2 being equal to tags1 - if ctx.mode == Mode::Eq { + if ctx.mode.is_eq() { let ext_problems = unify_pool(subs, pool, sub_record, ext2, ctx.mode); if !ext_problems.is_empty() { @@ -776,7 +901,7 @@ fn unify_tag_union_new( let unique_tags1 = UnionTags::insert_slices_into_subs(subs, separate.only_in_1); let unique_tags2 = UnionTags::insert_slices_into_subs(subs, separate.only_in_2); - let ext_content = if ctx.mode == Mode::Present { + let ext_content = if ctx.mode.is_present() { Content::Structure(FlatType::EmptyTagUnion) } else { Content::FlexVar(None) @@ -810,7 +935,7 @@ fn unify_tag_union_new( return ext1_problems; } - if ctx.mode == Mode::Eq { + if ctx.mode.is_eq() { let ext2_problems = unify_pool(subs, pool, sub1, ext2, ctx.mode); if !ext2_problems.is_empty() { subs.rollback_to(snapshot); @@ -824,6 +949,7 @@ fn unify_tag_union_new( } } +#[derive(Debug)] enum OtherTags2 { Empty, Union( @@ -833,10 +959,21 @@ enum OtherTags2 { } fn maybe_mark_tag_union_recursive(subs: &mut Subs, tag_union_var: Variable) { - while let Err((recursive, _chain)) = subs.occurs(tag_union_var) { + 'outer: while let Err((recursive, chain)) = subs.occurs(tag_union_var) { let description = subs.get(recursive); if let Content::Structure(FlatType::TagUnion(tags, ext_var)) = description.content { subs.mark_tag_union_recursive(recursive, tags, ext_var); + } else { + // walk the chain till we find a tag union + for v in &chain[..chain.len() - 1] { + let description = subs.get(*v); + if let Content::Structure(FlatType::TagUnion(tags, ext_var)) = description.content { + subs.mark_tag_union_recursive(*v, tags, ext_var); + continue 'outer; + } + } + + panic!("recursive loop does not contain a tag union") } } } @@ -1193,7 +1330,7 @@ fn unify_zip_slices( let l_var = subs[l_index]; let r_var = subs[r_index]; - problems.extend(unify_pool(subs, pool, l_var, r_var, Mode::Eq)); + problems.extend(unify_pool(subs, pool, l_var, r_var, Mode::EQ)); } problems @@ -1206,10 +1343,15 @@ fn unify_rigid(subs: &mut Subs, ctx: &Context, name: &Lowercase, other: &Content // If the other is flex, rigid wins! merge(subs, ctx, RigidVar(name.clone())) } - RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _) => { - // Type mismatch! Rigid can only unify with flex, even if the - // rigid names are the same. - mismatch!("Rigid {:?} with {:?}", ctx.first, &other) + RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _) | RangedNumber(..) => { + if !ctx.mode.contains(Mode::RIGID_AS_FLEX) { + // Type mismatch! Rigid can only unify with flex, even if the + // rigid names are the same. + mismatch!("Rigid {:?} with {:?}", ctx.first, &other) + } else { + // We are treating rigid vars as flex vars; admit this + merge(subs, ctx, other.clone()) + } } Error => { // Error propagates. @@ -1231,7 +1373,12 @@ fn unify_flex( merge(subs, ctx, FlexVar(opt_name.clone())) } - FlexVar(Some(_)) | RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _) => { + FlexVar(Some(_)) + | RigidVar(_) + | RecursionVar { .. } + | Structure(_) + | Alias(_, _, _) + | RangedNumber(..) => { // TODO special-case boolean here // In all other cases, if left is flex, defer to right. // (This includes using right's name if both are flex and named.) @@ -1290,6 +1437,12 @@ fn unify_recursion( unify_pool(subs, pool, ctx.first, *actual, ctx.mode) } + RangedNumber(..) => mismatch!( + "RecursionVar {:?} with ranged number {:?}", + ctx.first, + &other + ), + Error => merge(subs, ctx, Error), } } diff --git a/docs/Cargo.toml b/docs/Cargo.toml index 8a1e2637cb..0f92636b9a 100644 --- a/docs/Cargo.toml +++ b/docs/Cargo.toml @@ -18,6 +18,7 @@ roc_module = { path = "../compiler/module" } roc_region = { path = "../compiler/region" } roc_types = { path = "../compiler/types" } roc_parse = { path = "../compiler/parse" } +roc_target = { path = "../compiler/roc_target" } roc_collections = { path = "../compiler/collections" } bumpalo = { version = "3.8.0", features = ["collections"] } snafu = { version = "0.6.10", features = ["backtraces"] } diff --git a/docs/src/lib.rs b/docs/src/lib.rs index 305e7c45b8..8c791b72ee 100644 --- a/docs/src/lib.rs +++ b/docs/src/lib.rs @@ -16,7 +16,7 @@ use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds}; use roc_parse::ident::{parse_ident, Ident}; use roc_parse::parser::SyntaxError; use roc_parse::state::State; -use roc_region::all::Region; +use roc_region::all::{Position, Region}; use std::fs; use std::path::{Path, PathBuf}; @@ -128,7 +128,7 @@ pub fn syntax_highlight_expr<'a>( Ok(buf.to_string()) } - Err(fail) => Err(SyntaxError::Expr(fail)), + Err(fail) => Err(SyntaxError::Expr(fail, Position::default())), } } @@ -428,7 +428,7 @@ pub fn load_modules_for_files(filenames: Vec, std_lib: StdLib) -> Vec() as u32, // This is just type-checking for docs, so "target" doesn't matter + roc_target::TargetInfo::default_x86_64(), // This is just type-checking for docs, so "target" doesn't matter builtin_defs_map, ) { Ok(loaded) => modules.push(loaded), diff --git a/docs/tests/insert_syntax_highlighting.rs b/docs/tests/insert_syntax_highlighting.rs index 0cc30c2b4a..44ea2054b6 100644 --- a/docs/tests/insert_syntax_highlighting.rs +++ b/docs/tests/insert_syntax_highlighting.rs @@ -56,11 +56,7 @@ mod insert_doc_syntax_highlighting { } } - pub const HELLO_WORLD: &str = r#" -app "test-app" - packages { pf: "platform" } - imports [] - provides [ main ] to pf + pub const HELLO_WORLD: &str = r#"interface Test exposes [ ] imports [ ] main = "Hello, world!" diff --git a/editor/README.md b/editor/README.md index 24ca818e9c..48ec92969f 100644 --- a/editor/README.md +++ b/editor/README.md @@ -42,7 +42,7 @@ From roc to render: - The `ed_model` is filled in part with data obtained by loading and typechecking the roc file with the same function (`load_and_typecheck`) that is used by the compiler. - `ed_model` also contains an `EdModule`, which holds the parsed abstract syntax tree (AST). - In the `init_model` function: - + The AST is converted into a tree of `MarkupNode`. The different types of `MarkupNode` are similar to the elements/nodes in HTML. A line of roc code is represented as a nested `MarkupNode` containing mostly text `MarkupNode`s. The line `foo = "bar"` is represented as + + The AST is converted into a tree of `MarkupNode`. The different types of `MarkupNode` are similar to the elements/nodes in HTML. A line of roc code is represented as a nested `MarkupNode` containing mostly text `MarkupNode`s. The line `foo = "bar"` is represented as three text `MarkupNode` representing `foo`, ` = ` and `bar`. Multiple lines of roc code are represented as nested `MarkupNode` that contain other nested `MarkupNode`. + `CodeLines` holds a `Vec` of `String`, each line of code is a `String`. When saving the file, the content of `CodeLines` is written to disk. + `GridNodeMap` maps every position of a char of roc code to a `MarkNodeId`, for easy interaction with the caret. diff --git a/editor/editor-ideas.md b/editor/editor-ideas.md index b69b2f4ce3..4a227d6c96 100644 --- a/editor/editor-ideas.md +++ b/editor/editor-ideas.md @@ -48,7 +48,7 @@ Nice collection of research on innovative editors, [link](https://futureofcoding * [Hest](https://ivanish.ca/hest-time-travel/) tool for making highly interactive simulations. * [replit](https://replit.com/) collaborative browser based IDE. * [paper](https://openreview.net/pdf?id=SJeqs6EFvB) on finding and fixing bugs automatically. -* [specialized editors that can be embedded in main editor](https://elliot.website/editor/) +* [specialized editors that can be embedded in main editor](https://elliot.website/editor/) * Say you have a failing test that used to work, it would be very valuable to see all code that was changed that was used only by that test. e.g. you have a test `calculate_sum_test` that only uses the function `add`, when the test fails you should be able to see a diff showing only what changed for the function `add`. It would also be great to have a diff of [expression values](https://homepages.cwi.nl/~storm/livelit/images/bret.png) Bret Victor style. An ambitious project would be to suggest or automatically try fixes based on these diffs. * I think it could be possible to create a minimal reproduction of a program / block of code / code used by a single test. So for a failing unit test I would expect it to extract imports, the platform, types and functions that are necessary to run only that unit test and put them in a standalone roc project. This would be useful for sharing bugs with library+application authors and colleagues, for profiling or debugging with all "clutter" removed. @@ -138,7 +138,7 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe * "complex" filtered search: search for all occurrences of `"#` but ignore all like `"#,` * color this debug print orange * remove unused imports - + #### Inspiration * Voice control and eye tracking with [Talon](https://github.com/Gauteab/talon-tree-sitter-service) @@ -157,13 +157,13 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe ### Productivity features -* When refactoring; +* When refactoring; - Cutting and pasting code to a new file should automatically add imports to the new file and delete them from the old file. - Ability to link e.g. variable name in comments to actual variable name. Comment is automatically updated when variable name is changed. - When updating dependencies with breaking changes; show similar diffs from github projects that have successfully updated that dependency. - - AST backed renaming, changing variable/function/type name should change it all over the codebase. + - AST backed renaming, changing variable/function/type name should change it all over the codebase. * Automatically create all "arms" when pattern matching after entering `when var is` based on the type. - - All `when ... is` should be updated if the type is changed, e.g. adding Indigo to the Color type should add an arm everywhere where `when color is` is used. + - All `when ... is` should be updated if the type is changed, e.g. adding Indigo to the Color type should add an arm everywhere where `when color is` is used. * When a function is called like `foo(false)`, the name of the boolean argument should be shown automatically; `foo(`*is_active:*`false)`. This should be done for booleans and numbers. * Suggest automatically creating a function if the compiler says it does not exist. * Integrated search: @@ -171,7 +171,7 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe * Show productivity/feature tips on startup. Show link to page with all tips. Allow not seeing tips next time. * Search friendly editor docs inside the editor. Offer to send search string to Roc maintainers when no results, or if no results were clicked. * File history timeline view. Show timeline with commits that changed this file, the number of lines added and deleted as well as which user made the changes. Arrow navigation should allow you to quickly view different versions of the file. -* Suggested quick fixes should be directly visible and clickable. Not like in vs code where you put the caret on an error until a lightbulb appears in the margin which you have to click for the fixes to appear, after which you click to apply the fix you want :( . You should be able to apply suggestions in rapid succession. e.g. if you copy some roc code from the internet you should be able to apply 5 import suggestions quickly. +* Suggested quick fixes should be directly visible and clickable. Not like in vs code where you put the caret on an error until a lightbulb appears in the margin which you have to click for the fixes to appear, after which you click to apply the fix you want :( . You should be able to apply suggestions in rapid succession. e.g. if you copy some roc code from the internet you should be able to apply 5 import suggestions quickly. * Regex-like find and substitution based on plain english description and example (replacement). i.e. replace all `[` between double quotes with `{`. [Inspiration](https://alexmoltzau.medium.com/english-to-regex-thanks-to-gpt-3-13f03b68236e). * Show productivity tips based on behavior. i.e. if the user is scrolling through the error bar and clicking on the next error several times, show a tip with "go to next error" shortcut. * Command to "benchmark this function" or "benchmark this test" with flamegraph and execution time per line. @@ -201,7 +201,7 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe * Intelligent search: "search this folder for ", "search all tests for " * Show some kind of warning if path str in code does not exist locally. * repl on panic/error: ability to inspect all values and try executing some things at the location of the error. -* show values in memory on panic/error +* show values in memory on panic/error * automatic clustering of (text) search results in groups by similarity * fill screen with little windows of clustered search results * clustering of examples similar to current code @@ -222,7 +222,7 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe * GPT-3 can generate correct python functions based on a comment describing the functionality, video [here](https://www.youtube.com/watch?v=utuz7wBGjKM). It's possible that training a model using ast's may lead to better results than text based models. - Current autocomplete lacks flow, moving through suggestions with arrows is slow. Being able to code by weaving together autocomplete suggestions laid out in rows using eye tracking, that could flow. - It's possible that with strong static types, pure functions and a good search algorithm we can develop a more reliable autocomplete than one with machine learning. -- When ranking autocomplete suggestions, take into account how new a function is. Newly created functions are likely to be used soon. +- When ranking autocomplete suggestions, take into account how new a function is. Newly created functions are likely to be used soon. #### Productivity Inspiration @@ -239,7 +239,7 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe * [Codesee](https://www.codesee.io/) code base visualization. * [Loopy](https://dl.acm.org/doi/10.1145/3485530?sid=SCITRUS) interactive program synthesis. * [bracket guides](https://mobile.twitter.com/elyktrix/status/1461380028609048576) - + ### Non-Code Related Inspiration * [Scrivner](https://www.literatureandlatte.com/scrivener/overview) writing app for novelists, screenwriters, and more @@ -266,7 +266,7 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe ## Testing - + * From Google Docs' comments, adding tests in a similar manner, where they exists in the same "document" but parallel to the code being written * Makes sense for unit tests, keeps the test close to the source * Doesn't necessarily make sense for integration or e2e testing @@ -295,7 +295,7 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe * Library should have cheat sheet with most used/important docs summarized. * With explicit user permission, anonymously track viewing statistics for documentation. Can be used to show most important documentation, report pain points to library authors. * Easy side-by-side docs for multiple versions of library. -* ability to add questions and answers to library documentation +* ability to add questions and answers to library documentation ## Tutorials @@ -350,28 +350,28 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe Thoughts and ideas possibly taken from above inspirations or separate. * ACCESSIBILITY === EMPATHY - * Visual Imapirments + * Visual Imapirments No Animation is most benign form of cognitive disabity but really important base line of people with tense nerve system. Insensitivity to certain or all colors. Need of highcontrast - Or Everything Magnified for me with no glasses. + Or Everything Magnified for me with no glasses. Or Total blindness where we need to trough sound to communicate to the user Screen readers read trees of labeled elements. Each platform has different apis, but I think they are horrible. Just close your eyes and imagine listening to screen reader all day while you are using this majectic machines called computers. But blind people walk with a tool and they can react much better to sound/space relations than full on visal majority does. They are acute to sound as a spatial hint. And a hand for most of them is a very sensitive tool that can make sounds in space. Imagine if everytime for the user doesnt want to rely on shining rendered pixels on the screen for a feedback from machine, we make a acoustic room simulation, where with moving the "stick", either with mouse or with key arrows, we bump into one of the objects and that produces certain contextually appropriate sound (clean)*ding* - + On the each level of abstraction they can make sounds more deeper, so then when you type letters you feel like you are playing with the sand (soft)*shh*. We would need help from some sound engineer about it, but imagine moving down, which can be voice triggered command for motion impaired, you hear (soft)*pup* and the name of the module, and then you have options and commands appropriate for the module, they could map to those basic 4 buttons that we trained user on, and he would shortcut all the soft talk with click of a button. Think of the satisfaction when you can skip the dialog of the game and get straight into action. (X) Open functions! each function would make a sound and say its name, unless you press search and start searching for a specific function inside module, if you want one you select or move to next. - Related idea: Playing sounds in rapid succession for different expressions in your program might be a high throughput alternative to stepping through your code line by line. I'd bet you quickly learn what your program should sound like. The difference in throughput would be even larger for those who need to rely on voice transcription. - + * Motor impariments [rant]BACKS OF CODERS ARE NOT HEALTHY! We need to change that![/neverstop] Too much mouse waving and sitting for too long is bad for humans. - Keyboard is basic accessability tool but - Keyboard is also optional, some people have too shaky hands even for keyboard. + Keyboard is basic accessability tool but + Keyboard is also optional, some people have too shaky hands even for keyboard. They rely on eye tracking to move mouse cursor arond. If we employ _some_ voice recognition functions we could make same interface as we could do for consoles where 4+2 buttons and directional pad would suffice. That is 10 phrases that need to be pulled trough as many possible translations so people don't have to pretend that they are from Maine or Texas so they get voice recognition to work. Believe me I was there with Apple's Siri :D That is why we have 10 phrases for movement and management and most basic syntax. - * Builtin fonts that can be read more easily by those with dyslexia. + * Builtin fonts that can be read more easily by those with dyslexia. * Nice backtraces that highlight important information * Ability to show import connection within project visually diff --git a/editor/snippet-ideas.md b/editor/snippet-ideas.md index ea701cea0f..16014f7ba3 100644 --- a/editor/snippet-ideas.md +++ b/editor/snippet-ideas.md @@ -67,10 +67,10 @@ Snippets are inserted based on type of value on which the cursor is located. - command: sort ^List *^ (by ^Record Field^) {ascending/descending} + example: sort people by age descending >> ... -- command: escape url +- command: escape url + example: >> `percEncodedString = Url.percentEncode ^String^` - command: list files in directory - + example: >> + + example: >> ``` path <- File.pathFromStr ^String^ dirContents <- File.enumerateDir path @@ -90,9 +90,9 @@ Snippets are inserted based on type of value on which the cursor is located. * repeat list > List.repeat ^elem^ ^Nat^ * len list (fuzzy matches should be length of list) - append element to list - + # fuzzy matching - + some pairs for fuzzy matching unit tests: - hashmap > Dict - map > map (function), Dict @@ -108,6 +108,6 @@ Snippets are inserted based on type of value on which the cursor is located. - [grepper](https://www.codegrepper.com/) snippet collection that embeds in google search results. See also this [collection of common questions](https://www.codegrepper.com/code-examples/rust). - [github copilot](https://copilot.github.com/) snippet generation with machine learning -- [stackoverflow](https://stackoverflow.com) +- [stackoverflow](https://stackoverflow.com) - [rosetta code](http://www.rosettacode.org/wiki/Rosetta_Code) snippets in many different programming languages. Many [snippets](https://www.rosettacode.org/wiki/Category:Programming_Tasks) are programming contest style problems, but there also problems that demonstrate the use of JSON, SHA-256, read a file line by line... - check docs of popular languages to cross reference function/snippet names for fuzzy matching diff --git a/editor/src/editor/mvc/ed_model.rs b/editor/src/editor/mvc/ed_model.rs index 51c12f7e7d..7419cf8331 100644 --- a/editor/src/editor/mvc/ed_model.rs +++ b/editor/src/editor/mvc/ed_model.rs @@ -206,7 +206,7 @@ impl<'a> EdModule<'a> { pub mod test_ed_model { use crate::editor::ed_error::EdResult; use crate::editor::mvc::ed_model; - use crate::editor::resources::strings::HELLO_WORLD; + use crate::editor::resources::strings::{HELLO_WORLD, PLATFORM_STR}; use crate::ui::text::caret_w_select::test_caret_w_select::convert_dsl_to_selection; use crate::ui::text::caret_w_select::test_caret_w_select::convert_selection_to_dsl; use crate::ui::text::caret_w_select::CaretPos; @@ -222,6 +222,7 @@ pub mod test_ed_model { use roc_module::symbol::IdentIds; use roc_module::symbol::ModuleIds; use roc_types::subs::VarStore; + use std::fs; use std::fs::File; use std::io::Write; use std::path::Path; @@ -290,6 +291,15 @@ pub mod test_ed_model { *clean_code_str = full_code.join("\n"); let temp_dir = tempdir().expect("Failed to create temporary directory for test."); + + let platform_dir = temp_dir.path().join("platform"); + fs::create_dir(platform_dir.clone()).expect("Failed to create platform directory"); + let package_config_path = platform_dir.join("Package-Config.roc"); + let mut package_config_file = + File::create(package_config_path).expect("Failed to create Package-Config.roc"); + writeln!(package_config_file, "{}", PLATFORM_STR) + .expect("Failed to write to Package-Config.roc"); + let temp_file_path_buf = PathBuf::from([Uuid::new_v4().to_string(), ".roc".to_string()].join("")); let temp_file_full_path = temp_dir.path().join(temp_file_path_buf); diff --git a/editor/src/editor/resources/strings.rs b/editor/src/editor/resources/strings.rs index df130f4bf0..c99234e626 100644 --- a/editor/src/editor/resources/strings.rs +++ b/editor/src/editor/resources/strings.rs @@ -25,3 +25,15 @@ main = "Hello, world!" "#; + +pub const PLATFORM_STR: &str = r#" +platform "test-platform" + requires {} { main : Str } + exposes [] + packages {} + imports [] + provides [ mainForHost ] + +mainForHost : Str +mainForHost = main +"#; diff --git a/editor/src/graphics/shaders/shader.wgsl b/editor/src/graphics/shaders/shader.wgsl index 645032ecbf..343f8b0d14 100644 --- a/editor/src/graphics/shaders/shader.wgsl +++ b/editor/src/graphics/shaders/shader.wgsl @@ -18,10 +18,10 @@ fn vs_main( [[location(1)]] in_color: vec4, ) -> VertexOutput { var out: VertexOutput; - + out.position = u_globals.ortho * vec4(in_position, 0.0, 1.0); out.color = in_color; - + return out; } diff --git a/editor/src/window/keyboard_input.rs b/editor/src/window/keyboard_input.rs index 71addc230a..1f06293d1c 100644 --- a/editor/src/window/keyboard_input.rs +++ b/editor/src/window/keyboard_input.rs @@ -21,7 +21,7 @@ impl Modifiers { // returns true if modifiers are active that can be active when the user wants to insert a new char; e.g.: shift+a to make A pub fn new_char_modifiers(&self) -> bool { self.no_modifiers() - || (self.shift && !self.ctrl && !self.alt && !self.logo) // e.g.: shift+a to make A + || (self.shift && !self.ctrl && !self.alt && !self.logo) // e.g.: shift+a to make A || (self.cmd_or_ctrl() && self.alt) // e.g.: ctrl+alt+2 to make @ on azerty keyboard } diff --git a/error_macros/Cargo.toml b/error_macros/Cargo.toml new file mode 100644 index 0000000000..d5c7276898 --- /dev/null +++ b/error_macros/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "roc_error_macros" +version = "0.1.0" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +edition = "2018" + +[dependencies] diff --git a/error_macros/src/lib.rs b/error_macros/src/lib.rs new file mode 100644 index 0000000000..39aeca5aa0 --- /dev/null +++ b/error_macros/src/lib.rs @@ -0,0 +1,67 @@ +/// `internal_error!` should be used whenever a compiler invariant is broken. +/// It is a wrapper around panic that tells the user to file a bug. +/// This should only be used in cases where there would be a compiler bug and the user can't fix it. +/// If there is simply an unimplemented feature, please use `unimplemented!` +/// If there is a user error, please use roc_reporting to print a nice error message. +#[macro_export] +macro_rules! internal_error { + ($($arg:tt)*) => ({ + eprintln!("An internal compiler expectation was broken."); + eprintln!("This is definitely a compiler bug."); + // TODO: update this to the new bug template. + eprintln!("Please file an issue here: https://github.com/rtfeldman/roc/issues/new/choose"); + #[allow(clippy::panic)] { + panic!($($arg)*); + } + }) +} + +/// `user_error!` should only ever be used temporarily. +/// It is a way to document locations where we do not yet have nice error reporting. +/// All cases of `user_error!` should eventually be replaced with pretty error printing using roc_reporting. +#[macro_export] +macro_rules! user_error { + ($($arg:tt)*) => ({ + eprintln!("We ran into an issue while compiling your code."); + eprintln!("Sadly, we don't havs a pretty error message for this case yet."); + eprintln!("If you can't figure out the problem from the context below, please reach out at: https://roc.zulipchat.com/"); + eprintln!($($arg)*); + std::process::exit(1); + }) +} + +/// Assert that a type has the expected size on ARM +#[macro_export] +macro_rules! assert_sizeof_aarch64 { + ($t: ty, $expected_size: expr) => { + #[cfg(target_arch = "aarch64")] + static_assertions::assert_eq_size!($t, [u8; $expected_size]); + }; +} + +/// Assert that a type has the expected size in Wasm +#[macro_export] +macro_rules! assert_sizeof_wasm { + ($t: ty, $expected_size: expr) => { + #[cfg(target_family = "wasm")] + static_assertions::assert_eq_size!($t, [u8; $expected_size]); + }; +} + +/// Assert that a type has the expected size on any target not covered above +/// In practice we use this for x86_64, and add specific macros for other platforms +#[macro_export] +macro_rules! assert_sizeof_default { + ($t: ty, $expected_size: expr) => { + #[cfg(not(any(target_family = "wasm", target_arch = "aarch64")))] + static_assertions::assert_eq_size!($t, [u8; $expected_size]); + }; +} + +/// Assert that a type has the expected size on all targets +#[macro_export] +macro_rules! assert_sizeof_all { + ($t: ty, $expected_size: expr) => { + static_assertions::assert_eq_size!($t, [u8; $expected_size]); + }; +} diff --git a/examples/benchmarks/.gitignore b/examples/benchmarks/.gitignore index a9ac429d87..b42405dacb 100644 --- a/examples/benchmarks/.gitignore +++ b/examples/benchmarks/.gitignore @@ -12,6 +12,7 @@ closure cfold rbtree-insert rbtree-del +issue2279 rbtree-ck test-astar test-base64 diff --git a/examples/benchmarks/Deriv.roc b/examples/benchmarks/Deriv.roc index 5e5a81f8b2..2cad781765 100644 --- a/examples/benchmarks/Deriv.roc +++ b/examples/benchmarks/Deriv.roc @@ -21,6 +21,22 @@ main = |> Task.map (\_ -> {}) +nest : (I64, Expr -> IO Expr), I64, Expr -> IO Expr +nest = \f, n, e -> Task.loop { s: n, f, m: n, x: e } nestHelp + +State : { s : I64, f : I64, Expr -> IO Expr, m : I64, x : Expr } + +nestHelp : State -> IO [ Step State, Done Expr ] +nestHelp = \{ s, f, m, x } -> + when m is + 0 -> + Task.succeed (Done x) + + _ -> + w <- Task.after (f (s - m) x) + + Task.succeed (Step { s, f, m: (m - 1), x: w }) + Expr : [ Val I64, Var Str, Add Expr Expr, Mul Expr Expr, Pow Expr Expr, Ln Expr ] divmod : I64, I64 -> Result { div : I64, mod : I64 } [ DivByZero ]* @@ -180,18 +196,6 @@ count = \expr -> Ln f -> count f -nest : (I64, Expr -> IO Expr), I64, Expr -> IO Expr -nest = \f, n, e -> nestHelp n f n e - -nestHelp : I64, (I64, Expr -> IO Expr), I64, Expr -> IO Expr -nestHelp = \s, f, m, x -> - when m is - 0 -> - Task.succeed x - - _ -> - f (s - m) x |> Task.after \w -> nestHelp s f (m - 1) w - deriv : I64, Expr -> IO Expr deriv = \i, f -> fprime = d "x" f diff --git a/examples/benchmarks/Issue2279.roc b/examples/benchmarks/Issue2279.roc new file mode 100644 index 0000000000..0b85cdd904 --- /dev/null +++ b/examples/benchmarks/Issue2279.roc @@ -0,0 +1,13 @@ +app "issue2279" + packages { pf: "platform" } + imports [ Issue2279Help, pf.Task ] + provides [ main ] to pf + +main = + text = + if True then + Issue2279Help.text + else + Issue2279Help.asText 42 + + Task.putLine text diff --git a/examples/benchmarks/Issue2279Help.roc b/examples/benchmarks/Issue2279Help.roc new file mode 100644 index 0000000000..15fb04cf0f --- /dev/null +++ b/examples/benchmarks/Issue2279Help.roc @@ -0,0 +1,7 @@ +interface Issue2279Help + exposes [ text, asText ] + imports [] + +text = "Hello, world!" + +asText = Num.toStr diff --git a/examples/benchmarks/Quicksort.roc b/examples/benchmarks/Quicksort.roc index ce85190a06..cc83902313 100644 --- a/examples/benchmarks/Quicksort.roc +++ b/examples/benchmarks/Quicksort.roc @@ -30,7 +30,7 @@ quicksortHelp = \list, order, low, high -> when partition low high list order is Pair partitionIndex partitioned -> partitioned - |> quicksortHelp order low (partitionIndex - 1) + |> quicksortHelp order low (Num.subSaturated partitionIndex 1) |> quicksortHelp order (partitionIndex + 1) high else list @@ -39,12 +39,12 @@ partition : Nat, Nat, List a, Order a -> [ Pair Nat (List a) ] partition = \low, high, initialList, order -> when List.get initialList high is Ok pivot -> - when partitionHelp (low - 1) low initialList order high pivot is + when partitionHelp low low initialList order high pivot is Pair newI newList -> - Pair (newI + 1) (swap (newI + 1) high newList) + Pair newI (swap newI high newList) Err _ -> - Pair (low - 1) initialList + Pair low initialList partitionHelp : Nat, Nat, List c, Order c, Nat, c -> [ Pair Nat (List c) ] partitionHelp = \i, j, list, order, high, pivot -> @@ -53,7 +53,7 @@ partitionHelp = \i, j, list, order, high, pivot -> Ok value -> when order value pivot is LT | EQ -> - partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) order high pivot + partitionHelp (i + 1) (j + 1) (swap i j list) order high pivot GT -> partitionHelp i (j + 1) list order high pivot diff --git a/examples/benchmarks/platform/Effect.roc b/examples/benchmarks/platform/Effect.roc new file mode 100644 index 0000000000..dfbd8c4db4 --- /dev/null +++ b/examples/benchmarks/platform/Effect.roc @@ -0,0 +1,10 @@ +hosted Effect + exposes [ Effect, after, map, always, forever, loop, putLine, putInt, getInt ] + imports [] + generates Effect with [ after, map, always, forever, loop ] + +putLine : Str -> Effect {} + +putInt : I64 -> Effect {} + +getInt : Effect { value : I64, errorCode : [ A, B ], isError : Bool } diff --git a/examples/benchmarks/platform/Package-Config.roc b/examples/benchmarks/platform/Package-Config.roc index 214364498a..fe634bbc7f 100644 --- a/examples/benchmarks/platform/Package-Config.roc +++ b/examples/benchmarks/platform/Package-Config.roc @@ -1,15 +1,9 @@ platform "folkertdev/foo" - requires { model=>Model, msg=>Msg } { main : Effect {} } + requires {} { main : Effect {} } exposes [] packages {} imports [ Task.{ Task } ] provides [ mainForHost ] - effects fx.Effect - { - putLine : Str -> Effect {}, - putInt : I64 -> Effect {}, - getInt : Effect { value : I64, errorCode : [ A, B ], isError : Bool } - } mainForHost : Task {} [] as Fx mainForHost = main diff --git a/examples/benchmarks/platform/Task.roc b/examples/benchmarks/platform/Task.roc index 603abb83f1..df1188794c 100644 --- a/examples/benchmarks/platform/Task.roc +++ b/examples/benchmarks/platform/Task.roc @@ -1,9 +1,42 @@ interface Task - exposes [ Task, succeed, fail, after, map, putLine, putInt, getInt ] - imports [ fx.Effect ] + exposes [ Task, succeed, fail, after, map, putLine, putInt, getInt, forever, loop ] + imports [ pf.Effect ] Task ok err : Effect.Effect (Result ok err) +forever : Task val err -> Task * err +forever = \task -> + looper = \{ } -> + task + |> Effect.map + \res -> + when res is + Ok _ -> + Step {} + + Err e -> + Done (Err e) + + Effect.loop {} looper + +loop : state, (state -> Task [ Step state, Done done ] err) -> Task done err +loop = \state, step -> + looper = \current -> + step current + |> Effect.map + \res -> + when res is + Ok (Step newState) -> + Step newState + + Ok (Done result) -> + Done (Ok result) + + Err e -> + Done (Err e) + + Effect.loop state looper + succeed : val -> Task val * succeed = \val -> Effect.always (Ok val) diff --git a/examples/cli/.gitignore b/examples/cli/.gitignore index fa11a6a9c5..a87a68d9e2 100644 --- a/examples/cli/.gitignore +++ b/examples/cli/.gitignore @@ -1 +1,3 @@ +countdown echo +form diff --git a/examples/cli/Echo.roc b/examples/cli/Echo.roc deleted file mode 100644 index fcb9cf5acf..0000000000 --- a/examples/cli/Echo.roc +++ /dev/null @@ -1,16 +0,0 @@ -app "echo" - packages { pf: "platform" } - imports [ pf.Task.{ Task, await }, pf.Stdout, pf.Stdin ] - provides [ main ] to pf - -main : Task {} * -main = - { } <- await (Stdout.line "What's your first name?") - - firstName <- await Stdin.line - - { } <- await (Stdout.line "What's your last name?") - - lastName <- await Stdin.line - - Stdout.line "Hi, \(firstName) \(lastName)!" diff --git a/examples/cli/countdown.roc b/examples/cli/countdown.roc new file mode 100644 index 0000000000..79f9decb7c --- /dev/null +++ b/examples/cli/countdown.roc @@ -0,0 +1,18 @@ +app "countdown" + packages { pf: "platform" } + imports [ pf.Stdin, pf.Stdout, pf.Task.{ await, loop, succeed } ] + provides [ main ] to pf + +main = + _ <- await (Stdout.line "\nLet's count down from 10 together - all you have to do is press .") + _ <- await Stdin.line + loop 10 tick + +tick = \n -> + if n == 0 then + _ <- await (Stdout.line "🎉 SURPRISE! Happy Birthday! 🎂") + succeed (Done {}) + else + _ <- await (n |> Num.toStr |> \s -> "\(s)..." |> Stdout.line) + _ <- await Stdin.line + succeed (Step (n - 1)) diff --git a/examples/cli/echo.roc b/examples/cli/echo.roc new file mode 100644 index 0000000000..9f5401ab3c --- /dev/null +++ b/examples/cli/echo.roc @@ -0,0 +1,33 @@ +app "echo" + packages { pf: "platform" } + imports [ pf.Stdin, pf.Stdout, pf.Task ] + provides [ main ] to pf + +main : Task.Task {} [] +main = + _ <- Task.await (Stdout.line "🗣 Shout into this cave and hear the echo! 👂👂👂") + Task.loop {} (\_ -> Task.map tick Step) + +tick : Task.Task {} [] +tick = + shout <- Task.await Stdin.line + Stdout.line (echo shout) + +echo : Str -> Str +echo = \shout -> + silence = \length -> + spaceInUtf8 = 32 + + List.repeat length spaceInUtf8 + + shout + |> Str.toUtf8 + |> List.mapWithIndex + (\i, _ -> + length = (List.len (Str.toUtf8 shout) - i) + phrase = (List.split (Str.toUtf8 shout) length).before + + List.concat (silence (if i == 0 then 2 * length else length)) phrase) + |> List.join + |> Str.fromUtf8 + |> Result.withDefault "" diff --git a/examples/cli/form.roc b/examples/cli/form.roc new file mode 100644 index 0000000000..735c752527 --- /dev/null +++ b/examples/cli/form.roc @@ -0,0 +1,12 @@ +app "form" + packages { pf: "platform" } + imports [ pf.Stdin, pf.Stdout, pf.Task.{ await, Task } ] + provides [ main ] to pf + +main : Task {} * +main = + _ <- await (Stdout.line "What's your first name?") + firstName <- await Stdin.line + _ <- await (Stdout.line "What's your last name?") + lastName <- await Stdin.line + Stdout.line "Hi, \(firstName) \(lastName)! 👋" diff --git a/examples/cli/platform/Effect.roc b/examples/cli/platform/Effect.roc new file mode 100644 index 0000000000..d883c2b685 --- /dev/null +++ b/examples/cli/platform/Effect.roc @@ -0,0 +1,8 @@ +hosted Effect + exposes [ Effect, after, map, always, forever, loop, putLine, getLine ] + imports [] + generates Effect with [ after, map, always, forever, loop ] + +putLine : Str -> Effect {} + +getLine : Effect Str diff --git a/examples/cli/platform/Package-Config.roc b/examples/cli/platform/Package-Config.roc index 93a045a908..4f9c8bdf62 100644 --- a/examples/cli/platform/Package-Config.roc +++ b/examples/cli/platform/Package-Config.roc @@ -1,14 +1,9 @@ platform "examples/cli" - requires {} { main : Task {} [] }# TODO FIXME + requires {} { main : Task {} [] } exposes [] packages {} imports [ Task.{ Task } ] provides [ mainForHost ] - effects fx.Effect - { - putLine : Str -> Effect {}, - getLine : Effect Str - } mainForHost : Task {} [] as Fx mainForHost = main diff --git a/examples/cli/platform/Stdin.roc b/examples/cli/platform/Stdin.roc index 4e5bafa0df..d6f2ad2ae2 100644 --- a/examples/cli/platform/Stdin.roc +++ b/examples/cli/platform/Stdin.roc @@ -1,6 +1,6 @@ interface Stdin exposes [ line ] - imports [ fx.Effect, Task ] + imports [ pf.Effect, Task ] line : Task.Task Str * line = Effect.after Effect.getLine Task.succeed# TODO FIXME Effect.getLine should suffice diff --git a/examples/cli/platform/Stdout.roc b/examples/cli/platform/Stdout.roc index c843296ce2..ffb6190c7d 100644 --- a/examples/cli/platform/Stdout.roc +++ b/examples/cli/platform/Stdout.roc @@ -1,8 +1,6 @@ interface Stdout exposes [ line ] - imports [ fx.Effect, Task.{ Task } ] + imports [ pf.Effect, Task.{ Task } ] -# line : Str -> Task.Task {} * -# line = \line -> Effect.map (Effect.putLine line) (\_ -> Ok {}) line : Str -> Task {} * line = \str -> Effect.map (Effect.putLine str) (\_ -> Ok {}) diff --git a/examples/cli/platform/Task.roc b/examples/cli/platform/Task.roc index f12060cd5e..1721e641f2 100644 --- a/examples/cli/platform/Task.roc +++ b/examples/cli/platform/Task.roc @@ -1,9 +1,42 @@ interface Task - exposes [ Task, succeed, fail, await, map, onFail, attempt ] - imports [ fx.Effect ] + exposes [ Task, succeed, fail, await, map, onFail, attempt, forever, loop ] + imports [ pf.Effect ] Task ok err : Effect.Effect (Result ok err) +forever : Task val err -> Task * err +forever = \task -> + looper = \{ } -> + task + |> Effect.map + \res -> + when res is + Ok _ -> + Step {} + + Err e -> + Done (Err e) + + Effect.loop {} looper + +loop : state, (state -> Task [ Step state, Done done ] err) -> Task done err +loop = \state, step -> + looper = \current -> + step current + |> Effect.map + \res -> + when res is + Ok (Step newState) -> + Step newState + + Ok (Done result) -> + Done (Ok result) + + Err e -> + Done (Err e) + + Effect.loop state looper + succeed : val -> Task val * succeed = \val -> Effect.always (Ok val) diff --git a/examples/cli/platform/src/lib.rs b/examples/cli/platform/src/lib.rs index e072cf5d21..d584592716 100644 --- a/examples/cli/platform/src/lib.rs +++ b/examples/cli/platform/src/lib.rs @@ -9,7 +9,7 @@ use std::ffi::CStr; use std::os::raw::c_char; extern "C" { - #[link_name = "roc__mainForHost_1_exposed"] + #[link_name = "roc__mainForHost_1_exposed_generic"] fn roc_main(output: *mut u8) -> (); #[link_name = "roc__mainForHost_size"] diff --git a/examples/effect/Main.roc b/examples/effect/Main.roc index 5687bfa94e..d980596cfe 100644 --- a/examples/effect/Main.roc +++ b/examples/effect/Main.roc @@ -1,6 +1,6 @@ app "effect-example" packages { pf: "thing/platform-dir" } - imports [ fx.Effect ] + imports [ pf.Effect ] provides [ main ] to pf main : Effect.Effect {} diff --git a/examples/effect/thing/platform-dir/Effect.roc b/examples/effect/thing/platform-dir/Effect.roc new file mode 100644 index 0000000000..64c1897183 --- /dev/null +++ b/examples/effect/thing/platform-dir/Effect.roc @@ -0,0 +1,8 @@ +hosted Effect + exposes [ Effect, after, map, always, forever, putLine, getLine ] + imports [] + generates Effect with [ after, map, always, forever ] + +putLine : Str -> Effect {} + +getLine : Effect Str diff --git a/examples/effect/thing/platform-dir/Package-Config.roc b/examples/effect/thing/platform-dir/Package-Config.roc index 667e960a28..b7377c6412 100644 --- a/examples/effect/thing/platform-dir/Package-Config.roc +++ b/examples/effect/thing/platform-dir/Package-Config.roc @@ -1,14 +1,9 @@ -platform "folkertdev/foo" +platform "roc-examples/cli" requires {} { main : Effect {} } exposes [] packages {} - imports [ fx.Effect ] + imports [ pf.Effect ] provides [ mainForHost ] - effects fx.Effect - { - putLine : Str -> Effect {}, - getLine : Effect Str - } mainForHost : Effect.Effect {} as Fx mainForHost = main diff --git a/examples/effect/thing/platform-dir/host.zig b/examples/effect/thing/platform-dir/host.zig index 35d30c99ab..94f6fbe87d 100644 --- a/examples/effect/thing/platform-dir/host.zig +++ b/examples/effect/thing/platform-dir/host.zig @@ -23,7 +23,7 @@ comptime { const mem = std.mem; const Allocator = mem.Allocator; -extern fn roc__mainForHost_1_exposed([*]u8) void; +extern fn roc__mainForHost_1_exposed_generic([*]u8) void; extern fn roc__mainForHost_size() i64; extern fn roc__mainForHost_1_Fx_caller(*const u8, [*]u8, [*]u8) void; extern fn roc__mainForHost_1_Fx_size() i64; @@ -82,7 +82,7 @@ pub export fn main() u8 { var ts1: std.os.timespec = undefined; std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable; - roc__mainForHost_1_exposed(output); + roc__mainForHost_1_exposed_generic(output); call_the_closure(output); diff --git a/examples/false-interpreter/False.roc b/examples/false-interpreter/False.roc index 57fc3c14a9..d7e0ac0c4f 100644 --- a/examples/false-interpreter/False.roc +++ b/examples/false-interpreter/False.roc @@ -89,6 +89,10 @@ isWhitespace = \char -> == 0x9# tab interpretCtx : Context -> Task Context InterpreterErrors interpretCtx = \ctx -> + Task.loop ctx interpretCtxLoop + +interpretCtxLoop : Context -> Task [ Step Context, Done Context ] InterpreterErrors +interpretCtxLoop = \ctx -> when ctx.state is Executing if Context.inWhileScope ctx -> # Deal with the current while loop potentially looping. @@ -104,11 +108,11 @@ interpretCtx = \ctx -> if n == 0 then newScope = { scope & whileInfo: None } - interpretCtx { popCtx & scopes: List.set ctx.scopes last newScope } + Task.succeed (Step { popCtx & scopes: List.set ctx.scopes last newScope }) else newScope = { scope & whileInfo: Some { state: InBody, body, cond } } - interpretCtx { popCtx & scopes: List.append (List.set ctx.scopes last newScope) { data: None, buf: body, index: 0, whileInfo: None } } + Task.succeed (Step { popCtx & scopes: List.append (List.set ctx.scopes last newScope) { data: None, buf: body, index: 0, whileInfo: None } }) Err e -> Task.fail e @@ -117,7 +121,7 @@ interpretCtx = \ctx -> # Just rand the body. Run the condition again. newScope = { scope & whileInfo: Some { state: InCond, body, cond } } - interpretCtx { ctx & scopes: List.append (List.set ctx.scopes last newScope) { data: None, buf: cond, index: 0, whileInfo: None } } + Task.succeed (Step { ctx & scopes: List.append (List.set ctx.scopes last newScope) { data: None, buf: cond, index: 0, whileInfo: None } }) None -> Task.fail NoScope @@ -131,7 +135,7 @@ interpretCtx = \ctx -> when result is Ok (T val newCtx) -> execCtx <- Task.await (stepExecCtx newCtx val) - interpretCtx execCtx + Task.succeed (Step execCtx) Err NoScope -> Task.fail NoScope @@ -143,9 +147,9 @@ interpretCtx = \ctx -> # If no scopes left, all execution complete. if List.isEmpty dropCtx.scopes then - Task.succeed dropCtx + Task.succeed (Done dropCtx) else - interpretCtx dropCtx + Task.succeed (Step dropCtx) InComment -> result <- Task.attempt (Context.getChar ctx) @@ -153,9 +157,9 @@ interpretCtx = \ctx -> Ok (T val newCtx) -> if val == 0x7D then # `}` end of comment - interpretCtx { newCtx & state: Executing } + Task.succeed (Step { newCtx & state: Executing }) else - interpretCtx { newCtx & state: InComment } + Task.succeed (Step { newCtx & state: InComment }) Err NoScope -> Task.fail NoScope @@ -174,13 +178,13 @@ interpretCtx = \ctx -> # so this is make i64 mul by 10 then convert back to i32. nextAccum = (10 * Num.intCast accum) + Num.intCast (val - 0x30) - interpretCtx { newCtx & state: InNumber (Num.intCast nextAccum) } + Task.succeed (Step { newCtx & state: InNumber (Num.intCast nextAccum) }) else # outside of number now, this needs to be executed. pushCtx = Context.pushStack newCtx (Number accum) execCtx <- Task.await (stepExecCtx { pushCtx & state: Executing } val) - interpretCtx execCtx + Task.succeed (Step execCtx) Err NoScope -> Task.fail NoScope @@ -197,12 +201,12 @@ interpretCtx = \ctx -> when Str.fromUtf8 bytes is Ok str -> { } <- Task.await (Stdout.raw str) - interpretCtx { newCtx & state: Executing } + Task.succeed (Step { newCtx & state: Executing }) Err _ -> Task.fail BadUtf8 else - interpretCtx { newCtx & state: InString (List.append bytes val) } + Task.succeed (Step { newCtx & state: InString (List.append bytes val) }) Err NoScope -> Task.fail NoScope @@ -216,17 +220,17 @@ interpretCtx = \ctx -> Ok (T val newCtx) -> if val == 0x5B then # start of a nested lambda `[` - interpretCtx { newCtx & state: InLambda (depth + 1) (List.append bytes val) } + Task.succeed (Step { newCtx & state: InLambda (depth + 1) (List.append bytes val) }) else if val == 0x5D then # `]` end of current lambda if depth == 0 then # end of all lambdas - interpretCtx (Context.pushStack { newCtx & state: Executing } (Lambda bytes)) + Task.succeed (Step (Context.pushStack { newCtx & state: Executing } (Lambda bytes))) else # end of nested lambda - interpretCtx { newCtx & state: InLambda (depth - 1) (List.append bytes val) } + Task.succeed (Step { newCtx & state: InLambda (depth - 1) (List.append bytes val) }) else - interpretCtx { newCtx & state: InLambda depth (List.append bytes val) } + Task.succeed (Step { newCtx & state: InLambda depth (List.append bytes val) }) Err NoScope -> Task.fail NoScope @@ -252,14 +256,14 @@ interpretCtx = \ctx -> when result2 is Ok a -> - interpretCtx a + Task.succeed (Step a) Err e -> Task.fail e Ok (T 0x9F newCtx) -> # This is supposed to flush io buffers. We don't buffer, so it does nothing - interpretCtx newCtx + Task.succeed (Step newCtx) Ok (T x _) -> data = Num.toStr (Num.intCast x) @@ -276,7 +280,7 @@ interpretCtx = \ctx -> result <- Task.attempt (Context.getChar { ctx & state: Executing }) when result is Ok (T x newCtx) -> - interpretCtx (Context.pushStack newCtx (Number (Num.intCast x))) + Task.succeed (Step (Context.pushStack newCtx (Number (Num.intCast x)))) Err NoScope -> Task.fail NoScope diff --git a/examples/false-interpreter/examples/cksum.false b/examples/false-interpreter/examples/cksum.false index 0711b2da1d..49e63ef392 100644 --- a/examples/false-interpreter/examples/cksum.false +++ b/examples/false-interpreter/examples/cksum.false @@ -8,7 +8,7 @@ With the interpreter, it currently runs about 350x slower though and requires ex Load 256 constants from https://github.com/wertarbyte/coreutils/blob/f70c7b785b93dd436788d34827b209453157a6f2/src/cksum.c#L117 To support the original false interpreter, numbers must be less than 32000. To deal with loading, just split all the numbers in two chunks. -First chunk is lower 16 bits, second chunk is higher 16 bits shift to the right. +First chunk is lower 16 bits, second chunk is higher 16 bits shift to the right. Its values are then shifted back and merged together. } 16564 45559 65536*| 23811 46390 65536*| 31706 47221 65536*| 26221 48308 65536*| @@ -199,7 +199,7 @@ i; {left shift it by 8 (multiply by 0x100)} 256* - + {xor with the loaded constant} x;! diff --git a/examples/false-interpreter/platform/Effect.roc b/examples/false-interpreter/platform/Effect.roc new file mode 100644 index 0000000000..b54ae3fd1d --- /dev/null +++ b/examples/false-interpreter/platform/Effect.roc @@ -0,0 +1,22 @@ +hosted Effect + exposes [ Effect, after, map, always, forever, loop, openFile, closeFile, withFileOpen, getFileLine, getFileBytes, putLine, putRaw, getLine, getChar ] + imports [] + generates Effect with [ after, map, always, forever, loop ] + +openFile : Str -> Effect U64 + +closeFile : U64 -> Effect {} + +withFileOpen : Str, (U64 -> Effect (Result ok err)) -> Effect {} + +getFileLine : U64 -> Effect Str + +getFileBytes : U64 -> Effect (List U8) + +putLine : Str -> Effect {} + +putRaw : Str -> Effect {} + +getLine : Effect Str + +getChar : Effect U8 diff --git a/examples/false-interpreter/platform/File.roc b/examples/false-interpreter/platform/File.roc index 36d8887640..939f31b7e8 100644 --- a/examples/false-interpreter/platform/File.roc +++ b/examples/false-interpreter/platform/File.roc @@ -1,6 +1,6 @@ interface File exposes [ line, Handle, withOpen, chunk ] - imports [ fx.Effect, Task.{ Task } ] + imports [ pf.Effect, Task.{ Task } ] Handle : [ @Handle U64 ] @@ -24,4 +24,4 @@ withOpen = \path, callback -> handle <- Task.await (open path) result <- Task.attempt (callback handle) { } <- Task.await (close handle) - Task.fromResult result \ No newline at end of file + Task.fromResult result diff --git a/examples/false-interpreter/platform/Package-Config.roc b/examples/false-interpreter/platform/Package-Config.roc index 55003feeea..304d4d0fe5 100644 --- a/examples/false-interpreter/platform/Package-Config.roc +++ b/examples/false-interpreter/platform/Package-Config.roc @@ -1,22 +1,9 @@ platform "examples/cli" - requires {} { main : Str -> Task {} [] }# TODO FIXME + requires {} { main : Str -> Task {} [] } exposes [] packages {} imports [ Task.{ Task } ] provides [ mainForHost ] - effects fx.Effect - { - openFile : Str -> Effect U64, - closeFile : U64 -> Effect {}, - withFileOpen : Str, (U64 -> Effect (Result ok err)) -> Effect {}, - getFileLine : U64 -> Effect Str, - getFileBytes : U64 -> Effect (List U8), - putLine : Str -> Effect {}, - putRaw : Str -> Effect {}, - # Is there a limit to the number of effect, uncomment the next line and it crashes - # getLine : Effect Str, - getChar : Effect U8 - } mainForHost : Str -> Task {} [] as Fx mainForHost = \file -> main file diff --git a/examples/false-interpreter/platform/Stdin.roc b/examples/false-interpreter/platform/Stdin.roc index 9ff2a3de2a..22f397fd0c 100644 --- a/examples/false-interpreter/platform/Stdin.roc +++ b/examples/false-interpreter/platform/Stdin.roc @@ -1,6 +1,6 @@ interface Stdin exposes [ char ] - imports [ fx.Effect, Task ] + imports [ pf.Effect, Task ] # line : Task.Task Str * # line = Effect.after Effect.getLine Task.succeed # TODO FIXME Effect.getLine should suffice diff --git a/examples/false-interpreter/platform/Stdout.roc b/examples/false-interpreter/platform/Stdout.roc index 6acf849588..424ec0318a 100644 --- a/examples/false-interpreter/platform/Stdout.roc +++ b/examples/false-interpreter/platform/Stdout.roc @@ -1,9 +1,9 @@ interface Stdout exposes [ line, raw ] - imports [ fx.Effect, Task.{ Task } ] + imports [ pf.Effect, Task.{ Task } ] line : Str -> Task {} * line = \str -> Effect.map (Effect.putLine str) (\_ -> Ok {}) raw : Str -> Task {} * -raw = \str -> Effect.map (Effect.putRaw str) (\_ -> Ok {}) \ No newline at end of file +raw = \str -> Effect.map (Effect.putRaw str) (\_ -> Ok {}) diff --git a/examples/false-interpreter/platform/Task.roc b/examples/false-interpreter/platform/Task.roc index 520eba4976..fde39ed9b2 100644 --- a/examples/false-interpreter/platform/Task.roc +++ b/examples/false-interpreter/platform/Task.roc @@ -1,9 +1,27 @@ interface Task - exposes [ Task, succeed, fail, await, map, onFail, attempt, fromResult ] - imports [ fx.Effect ] + exposes [ Task, succeed, fail, await, map, onFail, attempt, fromResult, loop ] + imports [ pf.Effect ] Task ok err : Effect.Effect (Result ok err) +loop : state, (state -> Task [ Step state, Done done ] err) -> Task done err +loop = \state, step -> + looper = \current -> + step current + |> Effect.map + \res -> + when res is + Ok (Step newState) -> + Step newState + + Ok (Done result) -> + Done (Ok result) + + Err e -> + Done (Err e) + + Effect.loop state looper + succeed : val -> Task val * succeed = \val -> Effect.always (Ok val) diff --git a/examples/false-interpreter/platform/src/lib.rs b/examples/false-interpreter/platform/src/lib.rs index 18584164d6..4f97dfb72d 100644 --- a/examples/false-interpreter/platform/src/lib.rs +++ b/examples/false-interpreter/platform/src/lib.rs @@ -12,7 +12,7 @@ use std::io::{BufRead, BufReader, Read, Write}; use std::os::raw::c_char; extern "C" { - #[link_name = "roc__mainForHost_1_exposed"] + #[link_name = "roc__mainForHost_1_exposed_generic"] fn roc_main(args: RocStr, output: *mut u8) -> (); #[link_name = "roc__mainForHost_size"] @@ -188,10 +188,16 @@ pub extern "C" fn roc_fx_closeFile(br_ptr: *mut BufReader) { #[no_mangle] pub extern "C" fn roc_fx_openFile(name: ManuallyDrop) -> *mut BufReader { - let f = File::open(name.as_str()).expect("Unable to open file"); - let br = BufReader::new(f); + match File::open(name.as_str()) { + Ok(f) => { + let br = BufReader::new(f); - Box::into_raw(Box::new(br)) + Box::into_raw(Box::new(br)) + } + Err(_) => { + panic!("unable to open file {:?}", name) + } + } } #[no_mangle] diff --git a/examples/fib/platform/Package-Config.roc b/examples/fib/platform/Package-Config.roc index 2391724dda..8d28005b1a 100644 --- a/examples/fib/platform/Package-Config.roc +++ b/examples/fib/platform/Package-Config.roc @@ -4,7 +4,6 @@ platform "examples/add" packages {} imports [] provides [ mainForHost ] - effects fx.Effect {} mainForHost : I64 -> I64 mainForHost = \a -> main a diff --git a/examples/hello-rust/platform/Package-Config.roc b/examples/hello-rust/platform/Package-Config.roc index 5ef87710de..61e4c96bf2 100644 --- a/examples/hello-rust/platform/Package-Config.roc +++ b/examples/hello-rust/platform/Package-Config.roc @@ -4,7 +4,6 @@ platform "examples/hello-world" packages {} imports [] provides [ mainForHost ] - effects fx.Effect {} mainForHost : Str mainForHost = main diff --git a/examples/hello-swift/platform/Package-Config.roc b/examples/hello-swift/platform/Package-Config.roc index 9285a43b6b..5eca9b81b4 100644 --- a/examples/hello-swift/platform/Package-Config.roc +++ b/examples/hello-swift/platform/Package-Config.roc @@ -4,7 +4,6 @@ platform "examples/hello-swift" packages {} imports [] provides [ mainForHost ] - effects fx.Effect {} mainForHost : Str mainForHost = main diff --git a/examples/hello-swift/platform/host.swift b/examples/hello-swift/platform/host.swift index 747f62b6ee..74edc494d2 100644 --- a/examples/hello-swift/platform/host.swift +++ b/examples/hello-swift/platform/host.swift @@ -25,7 +25,7 @@ extension RocStr { var isSmallString: Bool { len < 0 } - + var length: Int { if isSmallString { var len = len @@ -37,7 +37,7 @@ extension RocStr { return len } } - + var string: String { if isSmallString { let data: Data = withUnsafePointer(to: self) { ptr in diff --git a/examples/hello-web/platform/Package-Config.roc b/examples/hello-web/platform/Package-Config.roc index 5ef87710de..61e4c96bf2 100644 --- a/examples/hello-web/platform/Package-Config.roc +++ b/examples/hello-web/platform/Package-Config.roc @@ -4,7 +4,6 @@ platform "examples/hello-world" packages {} imports [] provides [ mainForHost ] - effects fx.Effect {} mainForHost : Str mainForHost = main diff --git a/examples/hello-world/README.md b/examples/hello-world/README.md index 4f0ec8dfd1..26d4c8e7d9 100644 --- a/examples/hello-world/README.md +++ b/examples/hello-world/README.md @@ -14,17 +14,17 @@ $ cargo run --release Hello.roc ## Design Notes -This demonstrates the basic design of hosts: Roc code gets compiled into a pure +This demonstrates the basic design of hosts: Roc code gets compiled into a pure function (in this case, a thunk that always returns `"Hello, World!"`) and then the host calls that function. Fundamentally, that's the whole idea! The host might not even have a `main` - it could be a library, a plugin, anything. Everything else is built on this basic "hosts calling linked pure functions" design. For example, things get more interesting when the compiled Roc function returns -a `Task` - that is, a tagged union data structure containing function pointers -to callback closures. This lets the Roc pure function describe arbitrary -chainable effects, which the host can interpret to perform I/O as requested by -the Roc program. (The tagged union `Task` would have a variant for each supported +a `Task` - that is, a tagged union data structure containing function pointers +to callback closures. This lets the Roc pure function describe arbitrary +chainable effects, which the host can interpret to perform I/O as requested by +the Roc program. (The tagged union `Task` would have a variant for each supported I/O operation.) In this trivial example, it's very easy to line up the API between the host and @@ -39,6 +39,6 @@ Roc application authors only care about the Roc-host/Roc-app portion, and the host author only cares about the Roc-host/C boundary when implementing the host. Using this glue code, the Roc compiler can generate C header files describing the -boundary. This not only gets us host compatibility with C compilers, but also -Rust FFI for free, because [`rust-bindgen`](https://github.com/rust-lang/rust-bindgen) +boundary. This not only gets us host compatibility with C compilers, but also +Rust FFI for free, because [`rust-bindgen`](https://github.com/rust-lang/rust-bindgen) generates correct Rust FFI bindings from C headers. diff --git a/examples/hello-world/platform/Package-Config.roc b/examples/hello-world/platform/Package-Config.roc index 5ef87710de..61e4c96bf2 100644 --- a/examples/hello-world/platform/Package-Config.roc +++ b/examples/hello-world/platform/Package-Config.roc @@ -4,7 +4,6 @@ platform "examples/hello-world" packages {} imports [] provides [ mainForHost ] - effects fx.Effect {} mainForHost : Str mainForHost = main diff --git a/examples/hello-zig/README.md b/examples/hello-zig/README.md index 4f0ec8dfd1..26d4c8e7d9 100644 --- a/examples/hello-zig/README.md +++ b/examples/hello-zig/README.md @@ -14,17 +14,17 @@ $ cargo run --release Hello.roc ## Design Notes -This demonstrates the basic design of hosts: Roc code gets compiled into a pure +This demonstrates the basic design of hosts: Roc code gets compiled into a pure function (in this case, a thunk that always returns `"Hello, World!"`) and then the host calls that function. Fundamentally, that's the whole idea! The host might not even have a `main` - it could be a library, a plugin, anything. Everything else is built on this basic "hosts calling linked pure functions" design. For example, things get more interesting when the compiled Roc function returns -a `Task` - that is, a tagged union data structure containing function pointers -to callback closures. This lets the Roc pure function describe arbitrary -chainable effects, which the host can interpret to perform I/O as requested by -the Roc program. (The tagged union `Task` would have a variant for each supported +a `Task` - that is, a tagged union data structure containing function pointers +to callback closures. This lets the Roc pure function describe arbitrary +chainable effects, which the host can interpret to perform I/O as requested by +the Roc program. (The tagged union `Task` would have a variant for each supported I/O operation.) In this trivial example, it's very easy to line up the API between the host and @@ -39,6 +39,6 @@ Roc application authors only care about the Roc-host/Roc-app portion, and the host author only cares about the Roc-host/C boundary when implementing the host. Using this glue code, the Roc compiler can generate C header files describing the -boundary. This not only gets us host compatibility with C compilers, but also -Rust FFI for free, because [`rust-bindgen`](https://github.com/rust-lang/rust-bindgen) +boundary. This not only gets us host compatibility with C compilers, but also +Rust FFI for free, because [`rust-bindgen`](https://github.com/rust-lang/rust-bindgen) generates correct Rust FFI bindings from C headers. diff --git a/examples/hello-zig/platform/Package-Config.roc b/examples/hello-zig/platform/Package-Config.roc index 5ef87710de..61e4c96bf2 100644 --- a/examples/hello-zig/platform/Package-Config.roc +++ b/examples/hello-zig/platform/Package-Config.roc @@ -4,7 +4,6 @@ platform "examples/hello-world" packages {} imports [] provides [ mainForHost ] - effects fx.Effect {} mainForHost : Str mainForHost = main diff --git a/examples/quicksort/Quicksort.roc b/examples/quicksort/Quicksort.roc index d86752e502..f4820c5ed0 100644 --- a/examples/quicksort/Quicksort.roc +++ b/examples/quicksort/Quicksort.roc @@ -23,12 +23,12 @@ partition : Nat, Nat, List (Num a) -> [ Pair Nat (List (Num a)) ] partition = \low, high, initialList -> when List.get initialList high is Ok pivot -> - when partitionHelp (low - 1) low initialList high pivot is + when partitionHelp low low initialList high pivot is Pair newI newList -> - Pair (newI + 1) (swap (newI + 1) high newList) + Pair newI (swap newI high newList) Err _ -> - Pair (low - 1) initialList + Pair low initialList partitionHelp : Nat, Nat, List (Num c), Nat, Num c -> [ Pair Nat (List (Num c)) ] partitionHelp = \i, j, list, high, pivot -> @@ -36,7 +36,7 @@ partitionHelp = \i, j, list, high, pivot -> when List.get list j is Ok value -> if value <= pivot then - partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot + partitionHelp (i + 1) (j + 1) (swap i j list) high pivot else partitionHelp i (j + 1) list high pivot diff --git a/examples/quicksort/platform/Package-Config.roc b/examples/quicksort/platform/Package-Config.roc index a29fb7925d..08d8bf0f0b 100644 --- a/examples/quicksort/platform/Package-Config.roc +++ b/examples/quicksort/platform/Package-Config.roc @@ -4,7 +4,6 @@ platform "examples/quicksort" packages {} imports [] provides [ mainForHost ] - effects fx.Effect {} mainForHost : List I64 -> List I64 mainForHost = \list -> quicksort list diff --git a/examples/tui/.gitignore b/examples/tui/.gitignore new file mode 100644 index 0000000000..ba0da0793e --- /dev/null +++ b/examples/tui/.gitignore @@ -0,0 +1 @@ +tui diff --git a/examples/tui/Main.roc b/examples/tui/Main.roc new file mode 100644 index 0000000000..12b1a8da01 --- /dev/null +++ b/examples/tui/Main.roc @@ -0,0 +1,14 @@ +app "tui" + packages { pf: "platform" } + imports [ pf.Program.{ Program } ] + provides [ main ] { Model } to pf + +Model : Str + +main : Program Model +main = + { + init: \{ } -> "Hello World", + update: \model, new -> Str.concat model new, + view: \model -> Str.concat model "!", + } diff --git a/examples/tui/platform/Package-Config.roc b/examples/tui/platform/Package-Config.roc new file mode 100644 index 0000000000..8196beeb56 --- /dev/null +++ b/examples/tui/platform/Package-Config.roc @@ -0,0 +1,9 @@ +platform "folkertdev/foo" + requires { Model } { main : Effect {} } + exposes [] + packages {} + imports [] + provides [ mainForHost ] + +mainForHost : { init : ({} -> Model) as Init, update : (Model, Str -> Model) as Update, view : (Model -> Str) as View } +mainForHost = main diff --git a/examples/tui/platform/Program.roc b/examples/tui/platform/Program.roc new file mode 100644 index 0000000000..346661ba20 --- /dev/null +++ b/examples/tui/platform/Program.roc @@ -0,0 +1,10 @@ +interface Program + exposes [ Program ] + imports [] + +Program model : + { + init : {} -> model, + update : model, Str -> model, + view : model -> Str, + } diff --git a/examples/tui/platform/host.zig b/examples/tui/platform/host.zig new file mode 100644 index 0000000000..2f8dcca83b --- /dev/null +++ b/examples/tui/platform/host.zig @@ -0,0 +1,273 @@ +const std = @import("std"); +const str = @import("str"); +const RocStr = str.RocStr; +const testing = std.testing; +const expectEqual = testing.expectEqual; +const expect = testing.expect; +const maxInt = std.math.maxInt; + +comptime { + // This is a workaround for https://github.com/ziglang/zig/issues/8218 + // which is only necessary on macOS. + // + // Once that issue is fixed, we can undo the changes in + // 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing + // -fcompiler-rt in link.rs instead of doing this. Note that this + // workaround is present in many host.zig files, so make sure to undo + // it everywhere! + if (std.builtin.os.tag == .macos) { + _ = @import("compiler_rt"); + } +} + +const mem = std.mem; +const Allocator = mem.Allocator; + +const Program = extern struct { init: RocStr, update: Unit, view: Unit }; + +extern fn roc__mainForHost_1_exposed() Program; +extern fn roc__mainForHost_size() i64; + +const ConstModel = [*]const u8; +const MutModel = [*]u8; + +extern fn roc__mainForHost_1_Init_caller([*]u8, [*]u8, MutModel) void; +extern fn roc__mainForHost_1_Init_size() i64; +extern fn roc__mainForHost_1_Init_result_size() i64; + +fn allocate_model(allocator: *Allocator) MutModel { + const size = roc__mainForHost_1_Init_result_size(); + const raw_output = allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable; + var output = @ptrCast([*]u8, raw_output); + + return output; +} + +fn init(allocator: *Allocator) ConstModel { + const closure: [*]u8 = undefined; + const output = allocate_model(allocator); + + roc__mainForHost_1_Init_caller(closure, closure, output); + + return output; +} + +extern fn roc__mainForHost_1_Update_caller(ConstModel, *const RocStr, [*]u8, MutModel) void; +extern fn roc__mainForHost_1_Update_size() i64; +extern fn roc__mainForHost_1_Update_result_size() i64; + +fn update(allocator: *Allocator, model: ConstModel, msg: RocStr) ConstModel { + const closure: [*]u8 = undefined; + const output = allocate_model(allocator); + + roc__mainForHost_1_Update_caller(model, &msg, closure, output); + + return output; +} + +extern fn roc__mainForHost_1_View_caller(ConstModel, [*]u8, *RocStr) void; +extern fn roc__mainForHost_1_View_size() i64; +extern fn roc__mainForHost_1_View_result_size() i64; + +fn view(input: ConstModel) RocStr { + const closure: [*]u8 = undefined; + var output: RocStr = undefined; + + roc__mainForHost_1_View_caller(input, closure, &output); + + return output; +} + +const Align = 2 * @alignOf(usize); +extern fn malloc(size: usize) callconv(.C) ?*align(Align) c_void; +extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*c_void; +extern fn free(c_ptr: [*]align(Align) u8) callconv(.C) void; +extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void; +extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void; + +const DEBUG: bool = false; + +export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void { + if (DEBUG) { + var ptr = malloc(size); + const stdout = std.io.getStdOut().writer(); + stdout.print("alloc: {d} (alignment {d}, size {d})\n", .{ ptr, alignment, size }) catch unreachable; + return ptr; + } else { + return malloc(size); + } +} + +export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void { + if (DEBUG) { + const stdout = std.io.getStdOut().writer(); + stdout.print("realloc: {d} (alignment {d}, old_size {d})\n", .{ c_ptr, alignment, old_size }) catch unreachable; + } + + return realloc(@alignCast(Align, @ptrCast([*]u8, c_ptr)), new_size); +} + +export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { + if (DEBUG) { + const stdout = std.io.getStdOut().writer(); + stdout.print("dealloc: {d} (alignment {d})\n", .{ c_ptr, alignment }) catch unreachable; + } + + free(@alignCast(Align, @ptrCast([*]u8, c_ptr))); +} + +export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { + _ = tag_id; + + const stderr = std.io.getStdErr().writer(); + const msg = @ptrCast([*:0]const u8, c_ptr); + stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable; + std.process.exit(0); +} + +export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void { + return memcpy(dst, src, size); +} + +export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void { + return memset(dst, value, size); +} + +const Unit = extern struct {}; + +pub export fn main() callconv(.C) u8 { + const allocator = std.heap.page_allocator; + + var ts1: std.os.timespec = undefined; + std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable; + + const program = roc__mainForHost_1_exposed(); + + call_the_closure(program); + + var ts2: std.os.timespec = undefined; + std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable; + + const delta = to_seconds(ts2) - to_seconds(ts1); + + const stderr = std.io.getStdErr().writer(); + stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable; + + return 0; +} + +fn to_seconds(tms: std.os.timespec) f64 { + return @intToFloat(f64, tms.tv_sec) + (@intToFloat(f64, tms.tv_nsec) / 1_000_000_000.0); +} + +fn call_the_closure(program: Program) void { + const allocator = std.heap.page_allocator; + const stdout = std.io.getStdOut().writer(); + const stdin = std.io.getStdIn().reader(); + + var buf: [1000]u8 = undefined; + + var model = init(allocator); + + while (true) { + const line = (stdin.readUntilDelimiterOrEof(buf[0..], '\n') catch unreachable) orelse return; + + if (line.len == 1 and line[0] == 'q') { + return; + } + + const to_append = RocStr.init(line.ptr, line.len); + + model = update(allocator, model, to_append); + + const viewed = view(model); + for (viewed.asSlice()) |char| { + stdout.print("{c}", .{char}) catch unreachable; + } + + stdout.print("\n", .{}) catch unreachable; + } + + // The closure returns result, nothing interesting to do with it + return; +} + +pub export fn roc_fx_putInt(int: i64) i64 { + const stdout = std.io.getStdOut().writer(); + + stdout.print("{d}", .{int}) catch unreachable; + + stdout.print("\n", .{}) catch unreachable; + + return 0; +} + +export fn roc_fx_putLine(rocPath: str.RocStr) callconv(.C) void { + const stdout = std.io.getStdOut().writer(); + + for (rocPath.asSlice()) |char| { + stdout.print("{c}", .{char}) catch unreachable; + } + + stdout.print("\n", .{}) catch unreachable; +} + +const GetInt = extern struct { + value: i64, + error_code: bool, + is_error: bool, +}; + +comptime { + if (@sizeOf(usize) == 8) { + @export(roc_fx_getInt_64bit, .{ .name = "roc_fx_getInt" }); + } else { + @export(roc_fx_getInt_32bit, .{ .name = "roc_fx_getInt" }); + } +} + +fn roc_fx_getInt_64bit() callconv(.C) GetInt { + if (roc_fx_getInt_help()) |value| { + const get_int = GetInt{ .is_error = false, .value = value, .error_code = false }; + return get_int; + } else |err| switch (err) { + error.InvalidCharacter => { + return GetInt{ .is_error = true, .value = 0, .error_code = false }; + }, + else => { + return GetInt{ .is_error = true, .value = 0, .error_code = true }; + }, + } + + return 0; +} + +fn roc_fx_getInt_32bit(output: *GetInt) callconv(.C) void { + if (roc_fx_getInt_help()) |value| { + const get_int = GetInt{ .is_error = false, .value = value, .error_code = false }; + output.* = get_int; + } else |err| switch (err) { + error.InvalidCharacter => { + output.* = GetInt{ .is_error = true, .value = 0, .error_code = false }; + }, + else => { + output.* = GetInt{ .is_error = true, .value = 0, .error_code = true }; + }, + } + + return; +} + +fn roc_fx_getInt_help() !i64 { + const stdin = std.io.getStdIn().reader(); + var buf: [40]u8 = undefined; + + const line: []u8 = (try stdin.readUntilDelimiterOrEof(&buf, '\n')) orelse ""; + + return std.fmt.parseInt(i64, line, 10); +} + +fn readLine() []u8 { + const stdin = std.io.getStdIn().reader(); + return (stdin.readUntilDelimiterOrEof(&line_buf, '\n') catch unreachable) orelse ""; +} diff --git a/getting_started/linux_x86.md b/getting_started/linux_x86.md index 1e6218bd7c..5cb46e360b 100644 --- a/getting_started/linux_x86.md +++ b/getting_started/linux_x86.md @@ -30,4 +30,4 @@ ``` 0. See [here](../README.md#examples) for the other examples. -**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. +**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. diff --git a/linker/src/lib.rs b/linker/src/lib.rs index 9bd11cecb6..b9d6ee8519 100644 --- a/linker/src/lib.rs +++ b/linker/src/lib.rs @@ -142,10 +142,11 @@ pub fn build_and_preprocess_host( target: &Triple, host_input_path: &Path, exposed_to_host: Vec, + exported_closure_types: Vec, target_valgrind: bool, ) -> io::Result<()> { let dummy_lib = host_input_path.with_file_name("libapp.so"); - generate_dynamic_lib(target, exposed_to_host, &dummy_lib)?; + generate_dynamic_lib(target, exposed_to_host, exported_closure_types, &dummy_lib)?; rebuild_host( opt_level, target, @@ -193,6 +194,7 @@ pub fn link_preprocessed_host( fn generate_dynamic_lib( _target: &Triple, exposed_to_host: Vec, + exported_closure_types: Vec, dummy_lib_path: &Path, ) -> io::Result<()> { let dummy_obj_file = Builder::new().prefix("roc_lib").suffix(".o").tempfile()?; @@ -203,25 +205,37 @@ fn generate_dynamic_lib( write::Object::new(BinaryFormat::Elf, Architecture::X86_64, Endianness::Little); let text_section = out_object.section_id(write::StandardSection::Text); + + let mut add_symbol = |name: &String| { + out_object.add_symbol(write::Symbol { + name: name.as_bytes().to_vec(), + value: 0, + size: 0, + kind: SymbolKind::Text, + scope: SymbolScope::Dynamic, + weak: false, + section: write::SymbolSection::Section(text_section), + flags: SymbolFlags::None, + }); + }; + for sym in exposed_to_host { for name in &[ format!("roc__{}_1_exposed", sym), format!("roc__{}_1_exposed_generic", sym), - format!("roc__{}_1_Fx_caller", sym), - format!("roc__{}_1_Fx_size", sym), - format!("roc__{}_1_Fx_result_size", sym), format!("roc__{}_size", sym), ] { - out_object.add_symbol(write::Symbol { - name: name.as_bytes().to_vec(), - value: 0, - size: 0, - kind: SymbolKind::Text, - scope: SymbolScope::Dynamic, - weak: false, - section: write::SymbolSection::Section(text_section), - flags: SymbolFlags::None, - }); + add_symbol(name); + } + + for closure_type in &exported_closure_types { + for name in &[ + format!("roc__{}_1_{}_caller", sym, closure_type), + format!("roc__{}_1_{}_size", sym, closure_type), + format!("roc__{}_1_{}_result_size", sym, closure_type), + ] { + add_symbol(name) + } } } std::fs::write( diff --git a/meeting_notes/editor_ui_ux_18-01-2022.md b/meeting_notes/editor_ui_ux_18-01-2022.md new file mode 100644 index 0000000000..55d0d5032f --- /dev/null +++ b/meeting_notes/editor_ui_ux_18-01-2022.md @@ -0,0 +1,32 @@ + +# Meeting notes + +- 18/1/2022 2pm GMT + +## issue 2368 + +- How is AST data accessible to plugins? +- How does plugin UI work (UI components, vector level, pixel-level)? +- Given a selected expression, how do we show all plugins available that can visualize this + expression or have an interactive widget to alter the expression (color picker). What does + this API look like? +- use type driven UX? https://pchiusano.github.io/2013-09-10/type-systems-and-ux-example.html + +## ideas + +- Several "zoom levels" in the editor should show/hide context-appropriate views/buttons/functionality: + + zoomed out view should show type defs and function defs with folded body + + zooming in on function should unfold/show function body + + Traditional IDE's like ecplise can show an overwhelming amount of possible buttons/actions and views. Zoom levels can be used to prevent this excess of available options. + +- There should be a single editable text field to alter AST. This could be the same text field for entering commands, pressing a certain key could switch between command/plain text input into AST. Current part of AST that is being edited is highlighted. + +- Hovering over expression should show button on left sidebar that allows you to pop out a pinned view of the expression. + This pinned view could for example show all variants in a large type definition. + Hovering over a type in a place other than the definition could show all variants, this hover view should also be capable of being pinned and moved around as desired. + +- UI interaction specification from which we can generate both e.g. a window with clickable buttons 'previuous' and `next` that also supports the voice commands `previuous` and `next`. + +Next actions to take: +- Zeljko: draft UI interaction in figma +- Anton: draft plugin API in roc diff --git a/name-and-logo.md b/name-and-logo.md index 39584990ee..9465fab6bf 100644 --- a/name-and-logo.md +++ b/name-and-logo.md @@ -8,16 +8,16 @@ That’s why the logo is a bird. It’s specifically an [*origami* bird](https:/ to [Elm](https://elm-lang.org/)’s tangram logo. Roc is a direct descendant of Elm. The languages are similar, but not the same. -[Origami](https://en.wikipedia.org/wiki/Origami) likewise has similarities to [tangrams](https://en.wikipedia.org/wiki/Tangram), although they are not the same. -Both involve making a surprising variety of things -from simple primitives. [*Folds*](https://en.wikipedia.org/wiki/Fold_(higher-order_function)) +[Origami](https://en.wikipedia.org/wiki/Origami) likewise has similarities to [tangrams](https://en.wikipedia.org/wiki/Tangram), although they are not the same. +Both involve making a surprising variety of things +from simple primitives. [*Folds*](https://en.wikipedia.org/wiki/Fold_(higher-order_function)) are also common in functional programming. -The logo was made by tracing triangles onto a photo of a physical origami bird. -It’s made of triangles because triangles are a foundational primitive in +The logo was made by tracing triangles onto a photo of a physical origami bird. +It’s made of triangles because triangles are a foundational primitive in computer graphics. -The name was chosen because it makes for a three-letter file extension, it means +The name was chosen because it makes for a three-letter file extension, it means something fantastical, and it has incredible potential for puns. # Different Ways to Spell Roc @@ -29,4 +29,4 @@ something fantastical, and it has incredible potential for puns. # Fun Facts -Roc translates to 鹏 in Chinese, [which means](https://www.mdbg.net/chinese/dictionary?page=worddict&wdrst=0&wdqb=%E9%B9%8F) "a large fabulous bird." +Roc translates to 鹏 in Chinese, [which means](https://www.mdbg.net/chinese/dictionary?page=worddict&wdrst=0&wdqb=%E9%B9%8F) "a large fabulous bird." diff --git a/nightly_benches/README.me b/nightly_benches/README.me index fb6013bb8f..1e66e8951f 100644 --- a/nightly_benches/README.me +++ b/nightly_benches/README.me @@ -1,6 +1,6 @@ # Running benchmarks - Install cargo criterion: + Install cargo criterion: ``` cargo install --git https://github.com/Anton-4/cargo-criterion --branch main ``` @@ -8,7 +8,7 @@ ``` sudo sh -c 'echo 1 >/proc/sys/kernel/perf_event_paranoid' ``` - run: + run: ``` cargo criterion ``` \ No newline at end of file diff --git a/nix/sources.json b/nix/sources.json index b126e3bdf2..a7c8c261c0 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -12,15 +12,27 @@ "url_template": "https://github.com///archive/.tar.gz" }, "nixpkgs": { + "branch": "release-21.11", + "description": "Nix Packages collection", + "homepage": "", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "fe6f208d68ac254873b659db9676d44dea9b0555", + "sha256": "0ybvy1zx97k811bz73xmgsb41d33i2kr2dfqcxzq9m9h958178nq", + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/fe6f208d68ac254873b659db9676d44dea9b0555.tar.gz", + "url_template": "https://github.com///archive/.tar.gz" + }, + "nixpkgs-unstable": { "branch": "nixpkgs-unstable", "description": "Nix Packages collection", "homepage": "", "owner": "NixOS", "repo": "nixpkgs", - "rev": "47b35f569e84eb6dbbcf0a9fc75d8729ab8837fd", - "sha256": "0c5ny3yxlxixrd6z99g1wg8i6ysqpja763cxn9bh1g1c0byq8bdj", + "rev": "ea171bc81fcb3c6f21deeb46dbc10000087777ef", + "sha256": "15hh28c98kb6pf7wgydc07bx2ivq04a2cay5mhwnqk5cpa8dbiap", "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/47b35f569e84eb6dbbcf0a9fc75d8729ab8837fd.tar.gz", + "url": "https://github.com/NixOS/nixpkgs/archive/ea171bc81fcb3c6f21deeb46dbc10000087777ef.tar.gz", "url_template": "https://github.com///archive/.tar.gz" } } diff --git a/nix/zig.nix b/nix/zig.nix index 55fd1680e8..b9ac0a4111 100644 --- a/nix/zig.nix +++ b/nix/zig.nix @@ -1,7 +1,7 @@ { pkgs }: let - version = "0.8.0"; + version = "0.8.1"; osName = if pkgs.stdenv.isDarwin then "macos" else "linux"; @@ -14,13 +14,13 @@ let # If your system is not aarch64, we assume it's x86_64 sha256 = if pkgs.stdenv.isDarwin then if isAarch64 then - "b32d13f66d0e1ff740b3326d66a469ee6baddbd7211fa111c066d3bd57683111" + "5351297e3b8408213514b29c0a938002c5cf9f97eee28c2f32920e1227fd8423" # macos-aarch64 else - "279f9360b5cb23103f0395dc4d3d0d30626e699b1b4be55e98fd985b62bc6fbe" + "16b0e1defe4c1807f2e128f72863124bffdd906cefb21043c34b673bf85cd57f" # macos-x86_64 else if isAarch64 then - "ee204ca2c2037952cf3f8b10c609373a08a291efa4af7b3c73be0f2b27720470" + "2166dc9f2d8df387e8b4122883bb979d739281e1ff3f3d5483fec3a23b957510" # linux-aarch64 else - "502625d3da3ae595c5f44a809a87714320b7a40e6dff4a895b5fa7df3391d01e"; + "6c032fc61b5d77a3f3cf781730fa549f8f059ffdb3b3f6ad1c2994d2b2d87983"; # linux-x86_64 in pkgs.stdenv.mkDerivation { pname = "zig"; version = version; diff --git a/repl_cli/Cargo.toml b/repl_cli/Cargo.toml new file mode 100644 index 0000000000..240d7113ba --- /dev/null +++ b/repl_cli/Cargo.toml @@ -0,0 +1,30 @@ +[package] +edition = "2021" +name = "roc_repl_cli" +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bumpalo = {version = "3.8.0", features = ["collections"]} +const_format = "0.2.22" +inkwell = {path = "../vendor/inkwell"} +libloading = {version = "0.7.1"} +rustyline = {git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1"} +rustyline-derive = {git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1"} +target-lexicon = "0.12.2" + +roc_build = {path = "../compiler/build"} +roc_collections = {path = "../compiler/collections"} +roc_gen_llvm = {path = "../compiler/gen_llvm"} +roc_load = {path = "../compiler/load"} +roc_mono = {path = "../compiler/mono"} +roc_parse = {path = "../compiler/parse"} +roc_repl_eval = {path = "../repl_eval"} +roc_target = {path = "../compiler/roc_target"} +roc_types = {path = "../compiler/types"} +roc_builtins = {path = "../compiler/builtins"} + +[lib] +name = "roc_repl_cli" +path = "src/lib.rs" diff --git a/cli/src/repl.rs b/repl_cli/src/lib.rs similarity index 52% rename from cli/src/repl.rs rename to repl_cli/src/lib.rs index 61c10a03a5..08ccbfbdd1 100644 --- a/cli/src/repl.rs +++ b/repl_cli/src/lib.rs @@ -1,12 +1,27 @@ +use bumpalo::Bump; use const_format::concatcp; -#[cfg(feature = "llvm")] -use gen::{gen_and_eval, ReplOutput}; -use roc_parse::parser::{EExpr, SyntaxError}; +use inkwell::context::Context; +use libloading::Library; use rustyline::highlight::{Highlighter, PromptInfo}; use rustyline::validate::{self, ValidationContext, ValidationResult, Validator}; use rustyline_derive::{Completer, Helper, Hinter}; use std::borrow::Cow; use std::io; +use target_lexicon::Triple; + +use roc_build::link::module_to_dylib; +use roc_collections::all::MutSet; +use roc_gen_llvm::llvm::externs::add_default_roc_externs; +use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type}; +use roc_load::file::MonomorphizedModule; +use roc_mono::ir::OptLevel; +use roc_parse::ast::Expr; +use roc_parse::parser::{EExpr, ELambda, SyntaxError}; +use roc_repl_eval::eval::jit_to_ast; +use roc_repl_eval::gen::{compile_to_mono, format_answer, ReplOutput}; +use roc_repl_eval::{ReplApp, ReplAppMemory}; +use roc_target::TargetInfo; +use roc_types::pretty_print::{content_to_string, name_all_type_vars}; const BLUE: &str = "\u{001b}[36m"; const PINK: &str = "\u{001b}[35m"; @@ -27,11 +42,6 @@ pub const INSTRUCTIONS: &str = "Enter an expression, or :help, or :exit/:q.\n"; pub const PROMPT: &str = concatcp!("\n", BLUE, "»", END_COL, " "); pub const CONT_PROMPT: &str = concatcp!(BLUE, "…", END_COL, " "); -#[cfg(feature = "llvm")] -mod eval; -#[cfg(feature = "llvm")] -mod gen; - #[derive(Completer, Helper, Hinter)] struct ReplHelper { validator: InputValidator, @@ -97,7 +107,8 @@ impl Validator for InputValidator { match roc_parse::expr::parse_loc_expr(0, &arena, state) { // Special case some syntax errors to allow for multi-line inputs Err((_, EExpr::DefMissingFinalExpr(_), _)) - | Err((_, EExpr::DefMissingFinalExpr2(_, _), _)) => { + | Err((_, EExpr::DefMissingFinalExpr2(_, _), _)) + | Err((_, EExpr::Lambda(ELambda::Body(_, _), _), _)) => { Ok(ValidationResult::Incomplete) } _ => Ok(ValidationResult::Valid(None)), @@ -106,12 +117,222 @@ impl Validator for InputValidator { } } -#[cfg(not(feature = "llvm"))] -pub fn main() -> io::Result<()> { - panic!("The REPL currently requires being built with LLVM."); +struct CliApp { + lib: Library, +} + +struct CliMemory; + +impl<'a> ReplApp<'a> for CliApp { + type Memory = CliMemory; + + /// Run user code that returns a type with a `Builtin` layout + /// Size of the return value is statically determined from its Rust type + fn call_function(&self, main_fn_name: &str, transform: F) -> Expr<'a> + where + F: Fn(&'a Self::Memory, Return) -> Expr<'a>, + Self::Memory: 'a, + { + run_jit_function!(self.lib, main_fn_name, Return, |v| transform(&CliMemory, v)) + } + + /// Run user code that returns a struct or union, whose size is provided as an argument + fn call_function_dynamic_size( + &self, + main_fn_name: &str, + ret_bytes: usize, + transform: F, + ) -> T + where + F: Fn(&'a Self::Memory, usize) -> T, + Self::Memory: 'a, + { + run_jit_function_dynamic_type!(self.lib, main_fn_name, ret_bytes, |v| transform( + &CliMemory, v + )) + } +} + +macro_rules! deref_number { + ($name: ident, $t: ty) => { + fn $name(&self, addr: usize) -> $t { + let ptr = addr as *const _; + unsafe { *ptr } + } + }; +} + +impl ReplAppMemory for CliMemory { + deref_number!(deref_bool, bool); + + deref_number!(deref_u8, u8); + deref_number!(deref_u16, u16); + deref_number!(deref_u32, u32); + deref_number!(deref_u64, u64); + deref_number!(deref_u128, u128); + deref_number!(deref_usize, usize); + + deref_number!(deref_i8, i8); + deref_number!(deref_i16, i16); + deref_number!(deref_i32, i32); + deref_number!(deref_i64, i64); + deref_number!(deref_i128, i128); + deref_number!(deref_isize, isize); + + deref_number!(deref_f32, f32); + deref_number!(deref_f64, f64); + + fn deref_str(&self, addr: usize) -> &str { + unsafe { *(addr as *const &'static str) } + } +} + +fn gen_and_eval_llvm<'a>( + src: &str, + target: Triple, + opt_level: OptLevel, +) -> Result> { + let arena = Bump::new(); + let target_info = TargetInfo::from(&target); + + let loaded = match compile_to_mono(&arena, src, target_info) { + Ok(x) => x, + Err(prob_strings) => { + return Ok(ReplOutput::Problems(prob_strings)); + } + }; + + let MonomorphizedModule { + procedures, + entry_point, + interns, + exposed_to_host, + mut subs, + module_id: home, + .. + } = loaded; + + let context = Context::create(); + let builder = context.create_builder(); + let module = arena.alloc(roc_gen_llvm::llvm::build::module_from_builtins( + &target, &context, "", + )); + + debug_assert_eq!(exposed_to_host.values.len(), 1); + let (main_fn_symbol, main_fn_var) = exposed_to_host.values.iter().next().unwrap(); + let main_fn_symbol = *main_fn_symbol; + let main_fn_var = *main_fn_var; + + // pretty-print the expr type string for later. + name_all_type_vars(main_fn_var, &mut subs); + let content = subs.get_content_without_compacting(main_fn_var); + let expr_type_str = content_to_string(content, &subs, home, &interns); + + let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) { + Some(layout) => *layout, + None => { + return Ok(ReplOutput::NoProblems { + expr: "".to_string(), + expr_type: expr_type_str, + }); + } + }; + + let module = arena.alloc(module); + let (module_pass, function_pass) = + roc_gen_llvm::llvm::build::construct_optimization_passes(module, opt_level); + + let (dibuilder, compile_unit) = roc_gen_llvm::llvm::build::Env::new_debug_info(module); + + // Compile and add all the Procs before adding main + let env = roc_gen_llvm::llvm::build::Env { + arena: &arena, + builder: &builder, + dibuilder: &dibuilder, + compile_unit: &compile_unit, + context: &context, + interns, + module, + target_info, + is_gen_test: true, // so roc_panic is generated + // important! we don't want any procedures to get the C calling convention + exposed_to_host: MutSet::default(), + }; + + // Add roc_alloc, roc_realloc, and roc_dealloc, since the repl has no + // platform to provide them. + add_default_roc_externs(&env); + + let (main_fn_name, main_fn) = roc_gen_llvm::llvm::build::build_procedures_return_main( + &env, + opt_level, + procedures, + entry_point, + ); + + env.dibuilder.finalize(); + + // we don't use the debug info, and it causes weird errors. + module.strip_debug_info(); + + // Uncomment this to see the module's un-optimized LLVM instruction output: + // env.module.print_to_stderr(); + + if main_fn.verify(true) { + function_pass.run_on(&main_fn); + } else { + panic!("Main function {} failed LLVM verification in build. Uncomment things nearby to see more details.", main_fn_name); + } + + module_pass.run_on(env.module); + + // Uncomment this to see the module's optimized LLVM instruction output: + // env.module.print_to_stderr(); + + // Verify the module + if let Err(errors) = env.module.verify() { + panic!( + "Errors defining module:\n{}\n\nUncomment things nearby to see more details.", + errors.to_string() + ); + } + + let lib = module_to_dylib(env.module, &target, opt_level) + .expect("Error loading compiled dylib for test"); + + let app = CliApp { lib }; + + let res_answer = jit_to_ast( + &arena, + &app, + main_fn_name, + main_fn_layout, + content, + &env.interns, + home, + &subs, + target_info, + ); + + let formatted = format_answer(&arena, res_answer, expr_type_str); + Ok(formatted) +} + +fn eval_and_format<'a>(src: &str) -> Result> { + let format_output = |output| match output { + ReplOutput::NoProblems { expr, expr_type } => { + format!("\n{} {}:{} {}", expr, PINK, END_COL, expr_type) + } + ReplOutput::Problems(lines) => format!("\n{}\n", lines.join("\n\n")), + }; + + gen_and_eval_llvm(src, Triple::host(), OptLevel::Normal).map(format_output) +} + +fn report_parse_error(fail: SyntaxError) { + println!("TODO Gracefully report parse error in repl: {:?}", fail); } -#[cfg(feature = "llvm")] pub fn main() -> io::Result<()> { use rustyline::error::ReadlineError; use rustyline::Editor; @@ -229,20 +450,3 @@ pub fn main() -> io::Result<()> { Ok(()) } - -fn report_parse_error(fail: SyntaxError) { - println!("TODO Gracefully report parse error in repl: {:?}", fail); -} - -#[cfg(feature = "llvm")] -fn eval_and_format<'a>(src: &str) -> Result> { - use roc_mono::ir::OptLevel; - use target_lexicon::Triple; - - gen_and_eval(src.as_bytes(), Triple::host(), OptLevel::Normal).map(|output| match output { - ReplOutput::NoProblems { expr, expr_type } => { - format!("\n{} {}:{} {}", expr, PINK, END_COL, expr_type) - } - ReplOutput::Problems(lines) => format!("\n{}\n", lines.join("\n\n")), - }) -} diff --git a/repl_eval/Cargo.toml b/repl_eval/Cargo.toml new file mode 100644 index 0000000000..562cb27c9e --- /dev/null +++ b/repl_eval/Cargo.toml @@ -0,0 +1,22 @@ +[package] +edition = "2021" +name = "roc_repl_eval" +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bumpalo = {version = "3.8.0", features = ["collections"]} + +roc_builtins = {path = "../compiler/builtins"} +roc_can = {path = "../compiler/can"} +roc_collections = {path = "../compiler/collections"} +roc_fmt = {path = "../compiler/fmt"} +roc_load = {path = "../compiler/load"} +roc_module = {path = "../compiler/module"} +roc_mono = {path = "../compiler/mono"} +roc_parse = {path = "../compiler/parse"} +roc_region = {path = "../compiler/region"} +roc_reporting = {path = "../reporting"} +roc_target = {path = "../compiler/roc_target"} +roc_types = {path = "../compiler/types"} diff --git a/repl_eval/src/eval.rs b/repl_eval/src/eval.rs new file mode 100644 index 0000000000..f97ce94494 --- /dev/null +++ b/repl_eval/src/eval.rs @@ -0,0 +1,1254 @@ +use bumpalo::collections::Vec; +use bumpalo::Bump; +use std::cmp::{max_by_key, min_by_key}; + +use roc_builtins::bitcode::{FloatWidth, IntWidth}; +use roc_collections::all::MutMap; +use roc_module::called_via::CalledVia; +use roc_module::ident::TagName; +use roc_module::symbol::{Interns, ModuleId, Symbol}; +use roc_mono::ir::ProcLayout; +use roc_mono::layout::{ + union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant, WrappedVariant, +}; +use roc_parse::ast::{AssignedField, Collection, Expr, StrLiteral}; +use roc_region::all::{Loc, Region}; +use roc_target::TargetInfo; +use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable}; + +use crate::{ReplApp, ReplAppMemory}; + +struct Env<'a, 'env> { + arena: &'a Bump, + subs: &'env Subs, + target_info: TargetInfo, + interns: &'env Interns, + home: ModuleId, +} + +pub enum ToAstProblem { + FunctionLayout, +} + +/// JIT execute the given main function, and then wrap its results in an Expr +/// so we can display them to the user using the formatter. +/// +/// We need the original types in order to properly render records and tag unions, +/// because at runtime those are structs - that is, unlabeled memory offsets. +/// By traversing the type signature while we're traversing the layout, once +/// we get to a struct or tag, we know what the labels are and can turn them +/// back into the appropriate user-facing literals. +#[allow(clippy::too_many_arguments)] +pub fn jit_to_ast<'a, A: ReplApp<'a>>( + arena: &'a Bump, + app: &'a A, + main_fn_name: &str, + layout: ProcLayout<'a>, + content: &'a Content, + interns: &'a Interns, + home: ModuleId, + subs: &'a Subs, + target_info: TargetInfo, +) -> Result, ToAstProblem> { + let env = Env { + arena, + subs, + target_info, + interns, + home, + }; + + match layout { + ProcLayout { + arguments: [], + result, + } => { + // this is a thunk + jit_to_ast_help(&env, app, main_fn_name, &result, content) + } + _ => Err(ToAstProblem::FunctionLayout), + } +} + +enum NewtypeKind<'a> { + Tag(&'a TagName), + RecordField(&'a str), +} + +/// Unrolls types that are newtypes. These include +/// - Singleton tags with one type argument (e.g. `Container Str`) +/// - Records with exactly one field (e.g. `{ number: Nat }`) +/// +/// This is important in synchronizing `Content`s with `Layout`s, since `Layout`s will +/// always unwrap newtypes and use the content of the underlying type. +/// +/// The returned list of newtype containers is ordered by increasing depth. As an example, +/// `A ({b : C 123})` will have the unrolled list `[Tag(A), RecordField(b), Tag(C)]`. +fn unroll_newtypes<'a>( + env: &Env<'a, 'a>, + mut content: &'a Content, +) -> (Vec<'a, NewtypeKind<'a>>, &'a Content) { + let mut newtype_containers = Vec::with_capacity_in(1, env.arena); + loop { + match content { + Content::Structure(FlatType::TagUnion(tags, _)) + if tags.is_newtype_wrapper(env.subs) => + { + let (tag_name, vars): (&TagName, &[Variable]) = tags + .unsorted_iterator(env.subs, Variable::EMPTY_TAG_UNION) + .next() + .unwrap(); + newtype_containers.push(NewtypeKind::Tag(tag_name)); + let var = vars[0]; + content = env.subs.get_content_without_compacting(var); + } + Content::Structure(FlatType::Record(fields, _)) if fields.len() == 1 => { + let (label, field) = fields + .sorted_iterator(env.subs, Variable::EMPTY_RECORD) + .next() + .unwrap(); + newtype_containers.push(NewtypeKind::RecordField( + env.arena.alloc_str(label.as_str()), + )); + let field_var = *field.as_inner(); + content = env.subs.get_content_without_compacting(field_var); + } + _ => return (newtype_containers, content), + } + } +} + +fn apply_newtypes<'a>( + env: &Env<'a, '_>, + newtype_containers: Vec<'a, NewtypeKind<'a>>, + mut expr: Expr<'a>, +) -> Expr<'a> { + let arena = env.arena; + // Reverse order of what we receieve from `unroll_newtypes` since we want the deepest + // container applied first. + for container in newtype_containers.into_iter().rev() { + match container { + NewtypeKind::Tag(tag_name) => { + let tag_expr = tag_name_to_expr(env, tag_name); + let loc_tag_expr = &*arena.alloc(Loc::at_zero(tag_expr)); + let loc_arg_expr = &*arena.alloc(Loc::at_zero(expr)); + let loc_arg_exprs = arena.alloc_slice_copy(&[loc_arg_expr]); + expr = Expr::Apply(loc_tag_expr, loc_arg_exprs, CalledVia::Space); + } + NewtypeKind::RecordField(field_name) => { + let label = Loc::at_zero(*arena.alloc(field_name)); + let field_val = arena.alloc(Loc::at_zero(expr)); + let field = Loc::at_zero(AssignedField::RequiredValue(label, &[], field_val)); + expr = Expr::Record(Collection::with_items(&*arena.alloc([field]))) + } + } + } + expr +} + +fn unroll_aliases<'a>(env: &Env<'a, 'a>, mut content: &'a Content) -> &'a Content { + while let Content::Alias(_, _, real) = content { + content = env.subs.get_content_without_compacting(*real); + } + content +} + +fn unroll_recursion_var<'a>(env: &Env<'a, 'a>, mut content: &'a Content) -> &'a Content { + while let Content::RecursionVar { structure, .. } = content { + content = env.subs.get_content_without_compacting(*structure); + } + content +} + +fn get_tags_vars_and_variant<'a>( + env: &Env<'a, '_>, + tags: &UnionTags, + opt_rec_var: Option, +) -> (MutMap>, UnionVariant<'a>) { + let tags_vec: std::vec::Vec<(TagName, std::vec::Vec)> = tags + .unsorted_iterator(env.subs, Variable::EMPTY_TAG_UNION) + .map(|(a, b)| (a.clone(), b.to_vec())) + .collect(); + + let vars_of_tag: MutMap<_, _> = tags_vec.iter().cloned().collect(); + + let union_variant = + union_sorted_tags_help(env.arena, tags_vec, opt_rec_var, env.subs, env.target_info); + + (vars_of_tag, union_variant) +} + +fn expr_of_tag<'a, M: ReplAppMemory>( + env: &Env<'a, 'a>, + mem: &'a M, + data_addr: usize, + tag_name: &TagName, + arg_layouts: &'a [Layout<'a>], + arg_vars: &[Variable], + when_recursive: WhenRecursive<'a>, +) -> Expr<'a> { + let tag_expr = tag_name_to_expr(env, tag_name); + let loc_tag_expr = &*env.arena.alloc(Loc::at_zero(tag_expr)); + + debug_assert_eq!(arg_layouts.len(), arg_vars.len()); + + // NOTE assumes the data bytes are the first bytes + let it = arg_vars.iter().copied().zip(arg_layouts.iter()); + let output = sequence_of_expr(env, mem, data_addr, it, when_recursive); + let output = output.into_bump_slice(); + + Expr::Apply(loc_tag_expr, output, CalledVia::Space) +} + +/// Gets the tag ID of a union variant, assuming that the tag ID is stored alongside (after) the +/// tag data. The caller is expected to check that the tag ID is indeed stored this way. +fn tag_id_from_data<'a, M: ReplAppMemory>( + env: &Env<'a, 'a>, + mem: &M, + union_layout: UnionLayout, + data_addr: usize, +) -> i64 { + let offset = union_layout + .data_size_without_tag_id(env.target_info) + .unwrap(); + let tag_id_addr = data_addr + offset as usize; + + match union_layout.tag_id_builtin() { + Builtin::Bool => mem.deref_bool(tag_id_addr) as i64, + Builtin::Int(IntWidth::U8) => mem.deref_u8(tag_id_addr) as i64, + Builtin::Int(IntWidth::U16) => mem.deref_u16(tag_id_addr) as i64, + Builtin::Int(IntWidth::U64) => { + // used by non-recursive unions at the + // moment, remove if that is no longer the case + mem.deref_i64(tag_id_addr) + } + _ => unreachable!("invalid tag id layout"), + } +} + +/// Gets the tag ID of a union variant from its recursive pointer (that is, the pointer to the +/// pointer to the data of the union variant). Returns +/// - the tag ID +/// - the address of the data of the union variant, unmasked if the pointer held the tag ID +fn tag_id_from_recursive_ptr<'a, M: ReplAppMemory>( + env: &Env<'a, 'a>, + mem: &M, + union_layout: UnionLayout, + rec_addr: usize, +) -> (i64, usize) { + let tag_in_ptr = union_layout.stores_tag_id_in_pointer(env.target_info); + let addr_with_id = mem.deref_usize(rec_addr); + + if tag_in_ptr { + let (_, tag_id_mask) = UnionLayout::tag_id_pointer_bits_and_mask(env.target_info); + let tag_id = addr_with_id & tag_id_mask; + let data_addr = addr_with_id & !tag_id_mask; + (tag_id as i64, data_addr) + } else { + let tag_id = tag_id_from_data(env, mem, union_layout, addr_with_id); + (tag_id, addr_with_id) + } +} + +const OPAQUE_FUNCTION: Expr = Expr::Var { + module_name: "", + ident: "", +}; + +fn jit_to_ast_help<'a, A: ReplApp<'a>>( + env: &Env<'a, 'a>, + app: &'a A, + main_fn_name: &str, + layout: &Layout<'a>, + content: &'a Content, +) -> Result, ToAstProblem> { + let (newtype_containers, content) = unroll_newtypes(env, content); + let content = unroll_aliases(env, content); + let result = match layout { + Layout::Builtin(Builtin::Bool) => Ok(app + .call_function(main_fn_name, |mem: &A::Memory, num: bool| { + bool_to_ast(env, mem, num, content) + })), + Layout::Builtin(Builtin::Int(int_width)) => { + use IntWidth::*; + + macro_rules! helper { + ($ty:ty) => { + app.call_function(main_fn_name, |_, num: $ty| { + num_to_ast(env, number_literal_to_ast(env.arena, num), content) + }) + }; + } + + let result = match int_width { + U8 | I8 => { + // NOTE: `helper!` does not handle 8-bit numbers yet + app.call_function(main_fn_name, |mem: &A::Memory, num: u8| { + byte_to_ast(env, mem, num, content) + }) + } + U16 => helper!(u16), + U32 => helper!(u32), + U64 => helper!(u64), + U128 => helper!(u128), + I16 => helper!(i16), + I32 => helper!(i32), + I64 => helper!(i64), + I128 => helper!(i128), + }; + + Ok(result) + } + Layout::Builtin(Builtin::Float(float_width)) => { + use FloatWidth::*; + + macro_rules! helper { + ($ty:ty) => { + app.call_function(main_fn_name, |_, num: $ty| { + num_to_ast(env, number_literal_to_ast(env.arena, num), content) + }) + }; + } + + let result = match float_width { + F32 => helper!(f32), + F64 => helper!(f64), + F128 => todo!("F128 not implemented"), + }; + + Ok(result) + } + Layout::Builtin(Builtin::Str) => Ok(app + .call_function(main_fn_name, |_, string: &'static str| { + str_to_ast(env.arena, env.arena.alloc(string)) + })), + Layout::Builtin(Builtin::List(elem_layout)) => Ok(app.call_function( + main_fn_name, + |mem: &A::Memory, (addr, len): (usize, usize)| { + list_to_ast(env, mem, addr, len, elem_layout, content) + }, + )), + Layout::Builtin(other) => { + todo!("add support for rendering builtin {:?} to the REPL", other) + } + Layout::Struct(field_layouts) => { + let struct_addr_to_ast = |mem: &'a A::Memory, addr: usize| match content { + Content::Structure(FlatType::Record(fields, _)) => { + Ok(struct_to_ast(env, mem, addr, field_layouts, *fields)) + } + Content::Structure(FlatType::EmptyRecord) => Ok(struct_to_ast( + env, + mem, + addr, + field_layouts, + RecordFields::empty(), + )), + Content::Structure(FlatType::TagUnion(tags, _)) => { + debug_assert_eq!(tags.len(), 1); + + let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); + + Ok(single_tag_union_to_ast( + env, + mem, + addr, + field_layouts, + tag_name, + payload_vars, + )) + } + Content::Structure(FlatType::FunctionOrTagUnion(tag_name, _, _)) => { + let tag_name = &env.subs[*tag_name]; + + Ok(single_tag_union_to_ast( + env, + mem, + addr, + field_layouts, + tag_name, + &[], + )) + } + Content::Structure(FlatType::Func(_, _, _)) => { + // a function with a struct as the closure environment + Ok(OPAQUE_FUNCTION) + } + other => { + unreachable!( + "Something had a Struct layout, but instead of a Record or TagUnion type, it had: {:?}", + other + ); + } + }; + + let fields = [Layout::u64(), *layout]; + let layout = Layout::Struct(&fields); + + let result_stack_size = layout.stack_size(env.target_info); + + app.call_function_dynamic_size( + main_fn_name, + result_stack_size as usize, + struct_addr_to_ast, + ) + } + Layout::Union(UnionLayout::NonRecursive(_)) => { + let size = layout.stack_size(env.target_info); + Ok(app.call_function_dynamic_size( + main_fn_name, + size as usize, + |mem: &'a A::Memory, addr: usize| { + addr_to_ast(env, mem, addr, layout, WhenRecursive::Unreachable, content) + }, + )) + } + Layout::Union(UnionLayout::Recursive(_)) + | Layout::Union(UnionLayout::NonNullableUnwrapped(_)) + | Layout::Union(UnionLayout::NullableUnwrapped { .. }) + | Layout::Union(UnionLayout::NullableWrapped { .. }) => { + let size = layout.stack_size(env.target_info); + Ok(app.call_function_dynamic_size( + main_fn_name, + size as usize, + |mem: &'a A::Memory, addr: usize| { + addr_to_ast( + env, + mem, + addr, + layout, + WhenRecursive::Loop(*layout), + content, + ) + }, + )) + } + Layout::RecursivePointer => { + unreachable!("RecursivePointers can only be inside structures") + } + Layout::LambdaSet(_) => Ok(OPAQUE_FUNCTION), + }; + result.map(|e| apply_newtypes(env, newtype_containers, e)) +} + +fn tag_name_to_expr<'a>(env: &Env<'a, '_>, tag_name: &TagName) -> Expr<'a> { + match tag_name { + TagName::Global(_) => Expr::GlobalTag( + env.arena + .alloc_str(&tag_name.as_ident_str(env.interns, env.home)), + ), + TagName::Private(_) => Expr::PrivateTag( + env.arena + .alloc_str(&tag_name.as_ident_str(env.interns, env.home)), + ), + TagName::Closure(_) => unreachable!("User cannot type this"), + } +} + +/// Represents the layout of `RecursivePointer`s in a tag union, when recursive +/// tag unions are relevant. +#[derive(Clone, Copy, Debug, PartialEq)] +enum WhenRecursive<'a> { + Unreachable, + Loop(Layout<'a>), +} + +fn addr_to_ast<'a, M: ReplAppMemory>( + env: &Env<'a, 'a>, + mem: &'a M, + addr: usize, + layout: &Layout<'a>, + when_recursive: WhenRecursive<'a>, + content: &'a Content, +) -> Expr<'a> { + macro_rules! helper { + ($method: ident, $ty: ty) => {{ + let num: $ty = mem.$method(addr); + + num_to_ast(env, number_literal_to_ast(env.arena, num), content) + }}; + } + + let (newtype_containers, content) = unroll_newtypes(env, content); + let content = unroll_aliases(env, content); + let expr = match (content, layout) { + (Content::Structure(FlatType::Func(_, _, _)), _) + | (_, Layout::LambdaSet(_)) => OPAQUE_FUNCTION, + (_, Layout::Builtin(Builtin::Bool)) => { + // TODO: bits are not as expected here. + // num is always false at the moment. + let num: bool = mem.deref_bool(addr); + + bool_to_ast(env, mem, num, content) + } + (_, Layout::Builtin(Builtin::Int(int_width))) => { + use IntWidth::*; + + match int_width { + U8 => helper!(deref_u8, u8), + U16 => helper!(deref_u16, u16), + U32 => helper!(deref_u32, u32), + U64 => helper!(deref_u64, u64), + U128 => helper!(deref_u128, u128), + I8 => helper!(deref_i8, i8), + I16 => helper!(deref_i16, i16), + I32 => helper!(deref_i32, i32), + I64 => helper!(deref_i64, i64), + I128 => helper!(deref_i128, i128), + } + } + (_, Layout::Builtin(Builtin::Float(float_width))) => { + use FloatWidth::*; + + match float_width { + F32 => helper!(deref_f32, f32), + F64 => helper!(deref_f64, f64), + F128 => todo!("F128 not implemented"), + } + } + (_, Layout::Builtin(Builtin::List(elem_layout))) => { + let elem_addr = mem.deref_usize(addr); + let len = mem.deref_usize(addr + env.target_info.ptr_width() as usize); + + list_to_ast(env, mem, elem_addr, len, elem_layout, content) + } + (_, Layout::Builtin(Builtin::Str)) => { + let arena_str = mem.deref_str(addr); + + str_to_ast(env.arena, arena_str) + } + (_, Layout::Struct(field_layouts)) => match content { + Content::Structure(FlatType::Record(fields, _)) => { + struct_to_ast(env, mem, addr, field_layouts, *fields) + } + Content::Structure(FlatType::TagUnion(tags, _)) => { + debug_assert_eq!(tags.len(), 1); + + let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); + single_tag_union_to_ast(env, mem, addr, field_layouts, tag_name, payload_vars) + } + Content::Structure(FlatType::FunctionOrTagUnion(tag_name, _, _)) => { + let tag_name = &env.subs[*tag_name]; + single_tag_union_to_ast(env, mem, addr, field_layouts, tag_name, &[]) + } + Content::Structure(FlatType::EmptyRecord) => { + struct_to_ast(env, mem, addr, &[], RecordFields::empty()) + } + other => { + unreachable!( + "Something had a Struct layout, but instead of a Record type, it had: {:?}", + other + ); + } + }, + (_, Layout::RecursivePointer) => { + match (content, when_recursive) { + (Content::RecursionVar { + structure, + opt_name: _, + }, WhenRecursive::Loop(union_layout)) => { + let content = env.subs.get_content_without_compacting(*structure); + addr_to_ast(env, mem, addr, &union_layout, when_recursive, content) + } + other => unreachable!("Something had a RecursivePointer layout, but instead of being a RecursionVar and having a known recursive layout, I found {:?}", other), + } + } + (_, Layout::Union(UnionLayout::NonRecursive(union_layouts))) => { + let union_layout = UnionLayout::NonRecursive(union_layouts); + + let tags = match content { + Content::Structure(FlatType::TagUnion(tags, _)) => tags, + other => unreachable!("Weird content for nonrecursive Union layout: {:?}", other), + }; + + debug_assert_eq!(union_layouts.len(), tags.len()); + + let (vars_of_tag, union_variant) = get_tags_vars_and_variant(env, tags, None); + + let tags_and_layouts = match union_variant { + UnionVariant::Wrapped(WrappedVariant::NonRecursive { + sorted_tag_layouts + }) => sorted_tag_layouts, + other => unreachable!("This layout tag union layout is nonrecursive but the variant isn't; found variant {:?}", other), + }; + + // Because this is a `NonRecursive`, the tag ID is definitely after the data. + let tag_id = tag_id_from_data(env, mem, union_layout, addr); + + // use the tag ID as an index, to get its name and layout of any arguments + let (tag_name, arg_layouts) = + &tags_and_layouts[tag_id as usize]; + + expr_of_tag( + env, + mem, + addr, + tag_name, + arg_layouts, + &vars_of_tag[tag_name], + WhenRecursive::Unreachable, + ) + } + (_, Layout::Union(union_layout @ UnionLayout::Recursive(union_layouts))) => { + let (rec_var, tags) = match content { + Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), + _ => unreachable!("any other content would have a different layout"), + }; + debug_assert_eq!(union_layouts.len(), tags.len()); + + let (vars_of_tag, union_variant) = + get_tags_vars_and_variant(env, tags, Some(*rec_var)); + + let tags_and_layouts = match union_variant { + UnionVariant::Wrapped(WrappedVariant::Recursive { + sorted_tag_layouts + }) => sorted_tag_layouts, + _ => unreachable!("any other variant would have a different layout"), + }; + + let (tag_id, ptr_to_data) = tag_id_from_recursive_ptr(env, mem, *union_layout, addr); + + let (tag_name, arg_layouts) = &tags_and_layouts[tag_id as usize]; + expr_of_tag( + env, + mem, + ptr_to_data, + tag_name, + arg_layouts, + &vars_of_tag[tag_name], + when_recursive, + ) + } + (_, Layout::Union(UnionLayout::NonNullableUnwrapped(_))) => { + let (rec_var, tags) = match unroll_recursion_var(env, content) { + Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), + other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other), + }; + debug_assert_eq!(tags.len(), 1); + + let (vars_of_tag, union_variant) = get_tags_vars_and_variant(env, tags, Some(*rec_var)); + + let (tag_name, arg_layouts) = match union_variant { + UnionVariant::Wrapped(WrappedVariant::NonNullableUnwrapped { + tag_name, fields, + }) => (tag_name, fields), + _ => unreachable!("any other variant would have a different layout"), + }; + + let data_addr = mem.deref_usize(addr); + + expr_of_tag( + env, + mem, + data_addr, + &tag_name, + arg_layouts, + &vars_of_tag[&tag_name], + when_recursive, + ) + } + (_, Layout::Union(UnionLayout::NullableUnwrapped { .. })) => { + let (rec_var, tags) = match unroll_recursion_var(env, content) { + Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), + other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other), + }; + debug_assert!(tags.len() <= 2); + + let (vars_of_tag, union_variant) = get_tags_vars_and_variant(env, tags, Some(*rec_var)); + + let (nullable_name, other_name, other_arg_layouts) = match union_variant { + UnionVariant::Wrapped(WrappedVariant::NullableUnwrapped { + nullable_id: _, + nullable_name, + other_name, + other_fields, + }) => (nullable_name, other_name, other_fields), + _ => unreachable!("any other variant would have a different layout"), + }; + + let data_addr = mem.deref_usize(addr); + if data_addr == 0 { + tag_name_to_expr(env, &nullable_name) + } else { + expr_of_tag( + env, + mem, + data_addr, + &other_name, + other_arg_layouts, + &vars_of_tag[&other_name], + when_recursive, + ) + } + } + (_, Layout::Union(union_layout @ UnionLayout::NullableWrapped { .. })) => { + let (rec_var, tags) = match unroll_recursion_var(env, content) { + Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), + other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other), + }; + + let (vars_of_tag, union_variant) = get_tags_vars_and_variant(env, tags, Some(*rec_var)); + + let (nullable_id, nullable_name, tags_and_layouts) = match union_variant { + UnionVariant::Wrapped(WrappedVariant::NullableWrapped { + nullable_id, + nullable_name, + sorted_tag_layouts, + }) => (nullable_id, nullable_name, sorted_tag_layouts), + _ => unreachable!("any other variant would have a different layout"), + }; + + let data_addr = mem.deref_usize(addr); + if data_addr == 0 { + tag_name_to_expr(env, &nullable_name) + } else { + let (tag_id, data_addr) = tag_id_from_recursive_ptr(env, mem, *union_layout, addr); + + let tag_id = if tag_id > nullable_id.into() { tag_id - 1 } else { tag_id }; + + let (tag_name, arg_layouts) = &tags_and_layouts[tag_id as usize]; + expr_of_tag( + env, + mem, + data_addr, + tag_name, + arg_layouts, + &vars_of_tag[tag_name], + when_recursive, + ) + } + } + other => { + todo!( + "TODO add support for rendering pointer to {:?} in the REPL", + other + ); + } + }; + apply_newtypes(env, newtype_containers, expr) +} + +fn list_to_ast<'a, M: ReplAppMemory>( + env: &Env<'a, 'a>, + mem: &'a M, + addr: usize, + len: usize, + elem_layout: &Layout<'a>, + content: &Content, +) -> Expr<'a> { + let elem_content = match content { + Content::Structure(FlatType::Apply(Symbol::LIST_LIST, vars)) => { + debug_assert_eq!(vars.len(), 1); + + let elem_var_index = vars.into_iter().next().unwrap(); + let elem_var = env.subs[elem_var_index]; + + env.subs.get_content_without_compacting(elem_var) + } + other => { + unreachable!( + "Something had a Struct layout, but instead of a Record type, it had: {:?}", + other + ); + } + }; + + let arena = env.arena; + let mut output = Vec::with_capacity_in(len, arena); + let elem_size = elem_layout.stack_size(env.target_info) as usize; + + for index in 0..len { + let offset_bytes = index * elem_size; + let elem_addr = addr + offset_bytes; + let (newtype_containers, elem_content) = unroll_newtypes(env, elem_content); + let expr = addr_to_ast( + env, + mem, + elem_addr, + elem_layout, + WhenRecursive::Unreachable, + elem_content, + ); + let expr = Loc::at_zero(apply_newtypes(env, newtype_containers, expr)); + + output.push(&*arena.alloc(expr)); + } + + let output = output.into_bump_slice(); + + Expr::List(Collection::with_items(output)) +} + +fn single_tag_union_to_ast<'a, M: ReplAppMemory>( + env: &Env<'a, 'a>, + mem: &'a M, + addr: usize, + field_layouts: &'a [Layout<'a>], + tag_name: &TagName, + payload_vars: &[Variable], +) -> Expr<'a> { + let arena = env.arena; + let tag_expr = tag_name_to_expr(env, tag_name); + + let loc_tag_expr = &*arena.alloc(Loc::at_zero(tag_expr)); + + let output = if field_layouts.len() == payload_vars.len() { + let it = payload_vars.iter().copied().zip(field_layouts); + sequence_of_expr(env, mem, addr, it, WhenRecursive::Unreachable).into_bump_slice() + } else if field_layouts.is_empty() && !payload_vars.is_empty() { + // happens for e.g. `Foo Bar` where unit structures are nested and the inner one is dropped + let it = payload_vars.iter().copied().zip([&Layout::Struct(&[])]); + sequence_of_expr(env, mem, addr, it, WhenRecursive::Unreachable).into_bump_slice() + } else { + unreachable!() + }; + + Expr::Apply(loc_tag_expr, output, CalledVia::Space) +} + +fn sequence_of_expr<'a, I, M: ReplAppMemory>( + env: &Env<'a, 'a>, + mem: &'a M, + addr: usize, + sequence: I, + when_recursive: WhenRecursive<'a>, +) -> Vec<'a, &'a Loc>> +where + I: Iterator)>, + I: ExactSizeIterator)>, +{ + let arena = env.arena; + let subs = env.subs; + let mut output = Vec::with_capacity_in(sequence.len(), arena); + + // We'll advance this as we iterate through the fields + let mut field_addr = addr; + + for (var, layout) in sequence { + let content = subs.get_content_without_compacting(var); + let expr = addr_to_ast(env, mem, field_addr, layout, when_recursive, content); + let loc_expr = Loc::at_zero(expr); + + output.push(&*arena.alloc(loc_expr)); + + // Advance the field pointer to the next field. + field_addr += layout.stack_size(env.target_info) as usize; + } + + output +} + +fn struct_to_ast<'a, M: ReplAppMemory>( + env: &Env<'a, 'a>, + mem: &'a M, + addr: usize, + field_layouts: &'a [Layout<'a>], + record_fields: RecordFields, +) -> Expr<'a> { + let arena = env.arena; + let subs = env.subs; + let mut output = Vec::with_capacity_in(field_layouts.len(), arena); + + let sorted_fields: Vec<_> = Vec::from_iter_in( + record_fields.sorted_iterator(env.subs, Variable::EMPTY_RECORD), + env.arena, + ); + + if sorted_fields.len() == 1 { + // this is a 1-field wrapper record around another record or 1-tag tag union + let (label, field) = sorted_fields.into_iter().next().unwrap(); + + let inner_content = env.subs.get_content_without_compacting(field.into_inner()); + + let loc_expr = &*arena.alloc(Loc { + value: addr_to_ast( + env, + mem, + addr, + &Layout::Struct(field_layouts), + WhenRecursive::Unreachable, + inner_content, + ), + region: Region::zero(), + }); + + let field_name = Loc { + value: &*arena.alloc_str(label.as_str()), + region: Region::zero(), + }; + let loc_field = Loc { + value: AssignedField::RequiredValue(field_name, &[], loc_expr), + region: Region::zero(), + }; + + let output = env.arena.alloc([loc_field]); + + Expr::Record(Collection::with_items(output)) + } else { + debug_assert_eq!(sorted_fields.len(), field_layouts.len()); + + // We'll advance this as we iterate through the fields + let mut field_addr = addr; + + for ((label, field), field_layout) in sorted_fields.into_iter().zip(field_layouts.iter()) { + let var = field.into_inner(); + + let content = subs.get_content_without_compacting(var); + + let loc_expr = &*arena.alloc(Loc { + value: addr_to_ast( + env, + mem, + field_addr, + field_layout, + WhenRecursive::Unreachable, + content, + ), + region: Region::zero(), + }); + + let field_name = Loc { + value: &*arena.alloc_str(label.as_str()), + region: Region::zero(), + }; + let loc_field = Loc { + value: AssignedField::RequiredValue(field_name, &[], loc_expr), + region: Region::zero(), + }; + + output.push(loc_field); + + // Advance the field pointer to the next field. + field_addr += field_layout.stack_size(env.target_info) as usize; + } + + let output = output.into_bump_slice(); + + Expr::Record(Collection::with_items(output)) + } +} + +fn unpack_single_element_tag_union(subs: &Subs, tags: UnionTags) -> (&TagName, &[Variable]) { + let (tag_name_index, payload_vars_index) = tags.iter_all().next().unwrap(); + + let tag_name = &subs[tag_name_index]; + let subs_slice = subs[payload_vars_index]; + let payload_vars = subs.get_subs_slice(subs_slice); + + (tag_name, payload_vars) +} + +fn unpack_two_element_tag_union( + subs: &Subs, + tags: UnionTags, +) -> (&TagName, &[Variable], &TagName, &[Variable]) { + let mut it = tags.iter_all(); + let (tag_name_index, payload_vars_index) = it.next().unwrap(); + + let tag_name1 = &subs[tag_name_index]; + let subs_slice = subs[payload_vars_index]; + let payload_vars1 = subs.get_subs_slice(subs_slice); + + let (tag_name_index, payload_vars_index) = it.next().unwrap(); + + let tag_name2 = &subs[tag_name_index]; + let subs_slice = subs[payload_vars_index]; + let payload_vars2 = subs.get_subs_slice(subs_slice); + + (tag_name1, payload_vars1, tag_name2, payload_vars2) +} + +fn bool_to_ast<'a, M: ReplAppMemory>( + env: &Env<'a, '_>, + mem: &M, + value: bool, + content: &Content, +) -> Expr<'a> { + use Content::*; + + let arena = env.arena; + + match content { + Structure(flat_type) => { + match flat_type { + FlatType::TagUnion(tags, _) if tags.len() == 1 => { + let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); + + let loc_tag_expr = { + let tag_name = &tag_name.as_ident_str(env.interns, env.home); + let tag_expr = if tag_name.starts_with('@') { + Expr::PrivateTag(arena.alloc_str(tag_name)) + } else { + Expr::GlobalTag(arena.alloc_str(tag_name)) + }; + + &*arena.alloc(Loc { + value: tag_expr, + region: Region::zero(), + }) + }; + + let payload = { + // Since this has the layout of a number, there should be + // exactly one payload in this tag. + debug_assert_eq!(payload_vars.len(), 1); + + let var = *payload_vars.iter().next().unwrap(); + let content = env.subs.get_content_without_compacting(var); + + let loc_payload = &*arena.alloc(Loc { + value: bool_to_ast(env, mem, value, content), + region: Region::zero(), + }); + + arena.alloc([loc_payload]) + }; + + Expr::Apply(loc_tag_expr, payload, CalledVia::Space) + } + FlatType::TagUnion(tags, _) if tags.len() == 2 => { + let (tag_name_1, payload_vars_1, tag_name_2, payload_vars_2) = + unpack_two_element_tag_union(env.subs, *tags); + + debug_assert!(payload_vars_1.is_empty()); + debug_assert!(payload_vars_2.is_empty()); + + let tag_name = if value { + max_by_key(tag_name_1, tag_name_2, |n| { + n.as_ident_str(env.interns, env.home) + }) + } else { + min_by_key(tag_name_1, tag_name_2, |n| { + n.as_ident_str(env.interns, env.home) + }) + }; + + tag_name_to_expr(env, tag_name) + } + other => { + unreachable!("Unexpected FlatType {:?} in bool_to_ast", other); + } + } + } + Alias(_, _, var) => { + let content = env.subs.get_content_without_compacting(*var); + + bool_to_ast(env, mem, value, content) + } + other => { + unreachable!("Unexpected FlatType {:?} in bool_to_ast", other); + } + } +} + +fn byte_to_ast<'a, M: ReplAppMemory>( + env: &Env<'a, '_>, + mem: &M, + value: u8, + content: &Content, +) -> Expr<'a> { + use Content::*; + + let arena = env.arena; + + match content { + Structure(flat_type) => { + match flat_type { + FlatType::TagUnion(tags, _) if tags.len() == 1 => { + let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); + + // If this tag union represents a number, skip right to + // returning it as an Expr::Num + if let TagName::Private(Symbol::NUM_AT_NUM) = &tag_name { + return Expr::Num(env.arena.alloc_str(&value.to_string())); + } + + let loc_tag_expr = { + let tag_name = &tag_name.as_ident_str(env.interns, env.home); + let tag_expr = if tag_name.starts_with('@') { + Expr::PrivateTag(arena.alloc_str(tag_name)) + } else { + Expr::GlobalTag(arena.alloc_str(tag_name)) + }; + + &*arena.alloc(Loc { + value: tag_expr, + region: Region::zero(), + }) + }; + + let payload = { + // Since this has the layout of a number, there should be + // exactly one payload in this tag. + debug_assert_eq!(payload_vars.len(), 1); + + let var = *payload_vars.iter().next().unwrap(); + let content = env.subs.get_content_without_compacting(var); + + let loc_payload = &*arena.alloc(Loc { + value: byte_to_ast(env, mem, value, content), + region: Region::zero(), + }); + + arena.alloc([loc_payload]) + }; + + Expr::Apply(loc_tag_expr, payload, CalledVia::Space) + } + FlatType::TagUnion(tags, _) => { + // anything with fewer tags is not a byte + debug_assert!(tags.len() > 2); + + let tags_vec: std::vec::Vec<(TagName, std::vec::Vec)> = tags + .unsorted_iterator(env.subs, Variable::EMPTY_TAG_UNION) + .map(|(a, b)| (a.clone(), b.to_vec())) + .collect(); + + let union_variant = union_sorted_tags_help( + env.arena, + tags_vec, + None, + env.subs, + env.target_info, + ); + + match union_variant { + UnionVariant::ByteUnion(tagnames) => { + let tag_name = &tagnames[value as usize]; + let tag_expr = tag_name_to_expr(env, tag_name); + let loc_tag_expr = Loc::at_zero(tag_expr); + Expr::Apply(env.arena.alloc(loc_tag_expr), &[], CalledVia::Space) + } + _ => unreachable!("invalid union variant for a Byte!"), + } + } + other => { + unreachable!("Unexpected FlatType {:?} in bool_to_ast", other); + } + } + } + Alias(_, _, var) => { + let content = env.subs.get_content_without_compacting(*var); + + byte_to_ast(env, mem, value, content) + } + other => { + unreachable!("Unexpected FlatType {:?} in bool_to_ast", other); + } + } +} + +fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> Expr<'a> { + use Content::*; + + let arena = env.arena; + + match content { + Structure(flat_type) => { + match flat_type { + FlatType::Apply(Symbol::NUM_NUM, _) => num_expr, + FlatType::TagUnion(tags, _) => { + // This was a single-tag union that got unwrapped at runtime. + debug_assert_eq!(tags.len(), 1); + + let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); + + // If this tag union represents a number, skip right to + // returning it as an Expr::Num + if let TagName::Private(Symbol::NUM_AT_NUM) = &tag_name { + return num_expr; + } + + let loc_tag_expr = { + let tag_name = &tag_name.as_ident_str(env.interns, env.home); + let tag_expr = if tag_name.starts_with('@') { + Expr::PrivateTag(arena.alloc_str(tag_name)) + } else { + Expr::GlobalTag(arena.alloc_str(tag_name)) + }; + + &*arena.alloc(Loc { + value: tag_expr, + region: Region::zero(), + }) + }; + + let payload = { + // Since this has the layout of a number, there should be + // exactly one payload in this tag. + debug_assert_eq!(payload_vars.len(), 1); + + let var = *payload_vars.iter().next().unwrap(); + let content = env.subs.get_content_without_compacting(var); + + let loc_payload = &*arena.alloc(Loc { + value: num_to_ast(env, num_expr, content), + region: Region::zero(), + }); + + arena.alloc([loc_payload]) + }; + + Expr::Apply(loc_tag_expr, payload, CalledVia::Space) + } + other => { + panic!("Unexpected FlatType {:?} in num_to_ast", other); + } + } + } + Alias(_, _, var) => { + let content = env.subs.get_content_without_compacting(*var); + + num_to_ast(env, num_expr, content) + } + RangedNumber(typ, _) => { + num_to_ast(env, num_expr, env.subs.get_content_without_compacting(*typ)) + } + other => { + panic!("Unexpected FlatType {:?} in num_to_ast", other); + } + } +} + +/// This is centralized in case we want to format it differently later, +/// e.g. adding underscores for large numbers +fn number_literal_to_ast(arena: &Bump, num: T) -> Expr<'_> { + Expr::Num(arena.alloc(format!("{}", num))) +} + +#[cfg(target_endian = "little")] +/// NOTE: As of this writing, we don't have big-endian small strings implemented yet! +fn str_to_ast<'a>(arena: &'a Bump, string: &'a str) -> Expr<'a> { + const STR_SIZE: usize = 2 * std::mem::size_of::(); + + let bytes: [u8; STR_SIZE] = unsafe { std::mem::transmute(string) }; + let is_small = (bytes[STR_SIZE - 1] & 0b1000_0000) != 0; + + if is_small { + let len = (bytes[STR_SIZE - 1] & 0b0111_1111) as usize; + let mut string = bumpalo::collections::String::with_capacity_in(len, arena); + + for byte in bytes.iter().take(len) { + string.push(*byte as char); + } + + str_slice_to_ast(arena, arena.alloc(string)) + } else { + // Roc string literals are stored inside the constant section of the program + // That means this memory is gone when the jit function is done + // (as opposed to heap memory, which we can leak and then still use after) + // therefore we must make an owned copy of the string here + let string = bumpalo::collections::String::from_str_in(string, arena).into_bump_str(); + str_slice_to_ast(arena, string) + } +} + +fn str_slice_to_ast<'a>(_arena: &'a Bump, string: &'a str) -> Expr<'a> { + if string.contains('\n') { + todo!( + "this string contains newlines, so render it as a multiline string: {:?}", + Expr::Str(StrLiteral::PlainLine(string)) + ); + } else { + Expr::Str(StrLiteral::PlainLine(string)) + } +} diff --git a/repl_eval/src/gen.rs b/repl_eval/src/gen.rs new file mode 100644 index 0000000000..0874c42b49 --- /dev/null +++ b/repl_eval/src/gen.rs @@ -0,0 +1,158 @@ +use bumpalo::Bump; +use std::path::{Path, PathBuf}; + +use roc_can::builtins::builtin_defs_map; +use roc_collections::all::MutMap; +use roc_fmt::annotation::Formattable; +use roc_fmt::annotation::{Newlines, Parens}; +use roc_load::file::{LoadingProblem, MonomorphizedModule}; +use roc_parse::ast::Expr; +use roc_region::all::LineInfo; +use roc_target::TargetInfo; + +use crate::eval::ToAstProblem; + +pub enum ReplOutput { + Problems(Vec), + NoProblems { expr: String, expr_type: String }, +} + +pub fn format_answer( + arena: &Bump, + res_answer: Result, + expr_type_str: String, +) -> ReplOutput { + let mut expr = roc_fmt::Buf::new_in(arena); + + use ToAstProblem::*; + match res_answer { + Ok(answer) => { + answer.format_with_options(&mut expr, Parens::NotNeeded, Newlines::Yes, 0); + } + Err(FunctionLayout) => { + expr.indent(0); + expr.push_str(""); + } + } + + ReplOutput::NoProblems { + expr: expr.into_bump_str().to_string(), + expr_type: expr_type_str, + } +} + +pub fn compile_to_mono<'a>( + arena: &'a Bump, + src: &str, + target_info: TargetInfo, +) -> Result, Vec> { + use roc_reporting::report::{ + can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE, + }; + + let stdlib = arena.alloc(roc_builtins::std::standard_stdlib()); + let filename = PathBuf::from("REPL.roc"); + let src_dir = Path::new("fake/test/path"); + + let module_src = arena.alloc(promote_expr_to_module(src)); + + let exposed_types = MutMap::default(); + let loaded = roc_load::file::load_and_monomorphize_from_str( + arena, + filename, + module_src, + stdlib, + src_dir, + exposed_types, + target_info, + builtin_defs_map, + ); + + let mut loaded = match loaded { + Ok(v) => v, + Err(LoadingProblem::FormattedReport(report)) => { + return Err(vec![report]); + } + Err(e) => { + panic!("error while loading module: {:?}", e) + } + }; + + let MonomorphizedModule { + interns, + sources, + can_problems, + type_problems, + mono_problems, + .. + } = &mut loaded; + + let mut lines = Vec::new(); + + for (home, (module_path, src)) in sources.iter() { + let can_probs = can_problems.remove(home).unwrap_or_default(); + let type_probs = type_problems.remove(home).unwrap_or_default(); + let mono_probs = mono_problems.remove(home).unwrap_or_default(); + + let error_count = can_probs.len() + type_probs.len() + mono_probs.len(); + + if error_count == 0 { + continue; + } + + let line_info = LineInfo::new(module_src); + let src_lines: Vec<&str> = src.split('\n').collect(); + let palette = DEFAULT_PALETTE; + + // Report parsing and canonicalization problems + let alloc = RocDocAllocator::new(&src_lines, *home, interns); + + for problem in can_probs.into_iter() { + let report = can_problem(&alloc, &line_info, module_path.clone(), problem); + let mut buf = String::new(); + + report.render_color_terminal(&mut buf, &alloc, &palette); + + lines.push(buf); + } + + for problem in type_probs { + if let Some(report) = type_problem(&alloc, &line_info, module_path.clone(), problem) { + let mut buf = String::new(); + + report.render_color_terminal(&mut buf, &alloc, &palette); + + lines.push(buf); + } + } + + for problem in mono_probs { + let report = mono_problem(&alloc, &line_info, module_path.clone(), problem); + let mut buf = String::new(); + + report.render_color_terminal(&mut buf, &alloc, &palette); + + lines.push(buf); + } + } + + if !lines.is_empty() { + Err(lines) + } else { + Ok(loaded) + } +} + +fn promote_expr_to_module(src: &str) -> String { + let mut buffer = + String::from("app \"app\" provides [ replOutput ] to \"./platform\"\n\nreplOutput =\n"); + + for line in src.lines() { + // indent the body! + buffer.push_str(" "); + buffer.push_str(line); + buffer.push('\n'); + } + + buffer +} diff --git a/repl_eval/src/lib.rs b/repl_eval/src/lib.rs new file mode 100644 index 0000000000..0aebe53507 --- /dev/null +++ b/repl_eval/src/lib.rs @@ -0,0 +1,51 @@ +use roc_parse::ast::Expr; + +pub mod eval; +pub mod gen; + +pub trait ReplApp<'a> { + type Memory: 'a + ReplAppMemory; + + /// Run user code that returns a type with a `Builtin` layout + /// Size of the return value is statically determined from its Rust type + /// The `transform` callback takes the app's memory and the returned value + fn call_function(&self, main_fn_name: &str, transform: F) -> Expr<'a> + where + F: Fn(&'a Self::Memory, Return) -> Expr<'a>, + Self::Memory: 'a; + + /// Run user code that returns a struct or union, whose size is provided as an argument + /// The `transform` callback takes the app's memory and the address of the returned value + fn call_function_dynamic_size( + &self, + main_fn_name: &str, + ret_bytes: usize, + transform: F, + ) -> T + where + F: Fn(&'a Self::Memory, usize) -> T, + Self::Memory: 'a; +} + +pub trait ReplAppMemory { + fn deref_bool(&self, addr: usize) -> bool; + + fn deref_u8(&self, addr: usize) -> u8; + fn deref_u16(&self, addr: usize) -> u16; + fn deref_u32(&self, addr: usize) -> u32; + fn deref_u64(&self, addr: usize) -> u64; + fn deref_u128(&self, addr: usize) -> u128; + fn deref_usize(&self, addr: usize) -> usize; + + fn deref_i8(&self, addr: usize) -> i8; + fn deref_i16(&self, addr: usize) -> i16; + fn deref_i32(&self, addr: usize) -> i32; + fn deref_i64(&self, addr: usize) -> i64; + fn deref_i128(&self, addr: usize) -> i128; + fn deref_isize(&self, addr: usize) -> isize; + + fn deref_f32(&self, addr: usize) -> f32; + fn deref_f64(&self, addr: usize) -> f64; + + fn deref_str(&self, addr: usize) -> &str; +} diff --git a/repl_test/Cargo.toml b/repl_test/Cargo.toml new file mode 100644 index 0000000000..2d86ad19d4 --- /dev/null +++ b/repl_test/Cargo.toml @@ -0,0 +1,24 @@ +[package] +edition = "2021" +name = "repl_test" +version = "0.1.0" + +[[test]] +name = "repl_test" +path = "src/tests.rs" + +[build-dependencies] +roc_cli = {path = "../cli"} + +[dev-dependencies] +indoc = "1.0.3" +roc_test_utils = {path = "../test_utils"} +strip-ansi-escapes = "0.1.1" + +roc_repl_cli = {path = "../repl_cli"} +# roc_repl_wasm = {path = "../repl_wasm"} + +[features] +default = ["cli"] +cli = [] +wasm = [] diff --git a/repl_test/src/cli.rs b/repl_test/src/cli.rs new file mode 100644 index 0000000000..74775b88c5 --- /dev/null +++ b/repl_test/src/cli.rs @@ -0,0 +1,154 @@ +use std::env; +use std::io::Write; +use std::path::PathBuf; +use std::process::{Command, ExitStatus, Stdio}; + +use roc_repl_cli::{INSTRUCTIONS, WELCOME_MESSAGE}; +use roc_test_utils::assert_multiline_str_eq; + +const ERROR_MESSAGE_START: char = '─'; + +#[derive(Debug)] +struct Out { + stdout: String, + stderr: String, + status: ExitStatus, +} + +fn path_to_roc_binary() -> PathBuf { + // Adapted from https://github.com/volta-cli/volta/blob/cefdf7436a15af3ce3a38b8fe53bb0cfdb37d3dd/tests/acceptance/support/sandbox.rs#L680 + // by the Volta Contributors - license information can be found in + // the LEGAL_DETAILS file in the root directory of this distribution. + // + // Thank you, Volta contributors! + let mut path = env::var_os("CARGO_BIN_PATH") + .map(PathBuf::from) + .or_else(|| { + env::current_exe().ok().map(|mut path| { + path.pop(); + if path.ends_with("deps") { + path.pop(); + } + path + }) + }) + .unwrap_or_else(|| panic!("CARGO_BIN_PATH wasn't set, and couldn't be inferred from context. Can't run CLI tests.")); + + path.push("roc"); + + path +} + +fn repl_eval(input: &str) -> Out { + let mut cmd = Command::new(path_to_roc_binary()); + + cmd.arg("repl"); + + let mut child = cmd + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .expect("failed to execute compiled `roc` binary in CLI test"); + + { + let stdin = child.stdin.as_mut().expect("Failed to open stdin"); + + // Send the input expression + stdin + .write_all(input.as_bytes()) + .expect("Failed to write input to stdin"); + + // Evaluate the expression + stdin + .write_all(b"\n") + .expect("Failed to write newline to stdin"); + + // Gracefully exit the repl + stdin + .write_all(b":exit\n") + .expect("Failed to write :exit to stdin"); + } + + let output = child + .wait_with_output() + .expect("Error waiting for REPL child process to exit."); + + // Remove the initial instructions from the output. + + let expected_instructions = format!("{}{}", WELCOME_MESSAGE, INSTRUCTIONS); + let stdout = String::from_utf8(output.stdout).unwrap(); + + assert!( + stdout.starts_with(&expected_instructions), + "Unexpected repl output: {}", + stdout + ); + + let (_, answer) = stdout.split_at(expected_instructions.len()); + let answer = if answer.is_empty() { + // The repl crashed before completing the evaluation. + // This is most likely due to a segfault. + if output.status.to_string() == "signal: 11" { + panic!( + "repl segfaulted during the test. Stderr was {:?}", + String::from_utf8(output.stderr).unwrap() + ); + } else { + panic!("repl exited unexpectedly before finishing evaluation. Exit status was {:?} and stderr was {:?}", output.status, String::from_utf8(output.stderr).unwrap()); + } + } else { + let expected_after_answer = "\n".to_string(); + + assert!( + answer.ends_with(&expected_after_answer), + "Unexpected repl output after answer: {}", + answer + ); + + // Use [1..] to trim the leading '\n' + // and (len - 1) to trim the trailing '\n' + let (answer, _) = answer[1..].split_at(answer.len() - expected_after_answer.len() - 1); + + // Remove ANSI escape codes from the answer - for example: + // + // Before: "42 \u{1b}[35m:\u{1b}[0m Num *" + // After: "42 : Num *" + strip_ansi_escapes::strip(answer).unwrap() + }; + + Out { + stdout: String::from_utf8(answer).unwrap(), + stderr: String::from_utf8(output.stderr).unwrap(), + status: output.status, + } +} + +pub fn expect_success(input: &str, expected: &str) { + let out = repl_eval(input); + + assert_multiline_str_eq!("", out.stderr.as_str()); + assert_multiline_str_eq!(expected, out.stdout.as_str()); + assert!(out.status.success()); +} + +pub fn expect_failure(input: &str, expected: &str) { + let out = repl_eval(input); + + // there may be some other stuff printed (e.g. unification errors) + // so skip till the header of the first error + match out.stdout.find(ERROR_MESSAGE_START) { + Some(index) => { + assert_multiline_str_eq!("", out.stderr.as_str()); + assert_multiline_str_eq!(expected, &out.stdout[index..]); + assert!(out.status.success()); + } + None => { + assert_multiline_str_eq!("", out.stderr.as_str()); + assert!(out.status.success()); + panic!( + "I expected a failure, but there is no error message in stdout:\n\n{}", + &out.stdout + ); + } + } +} diff --git a/repl_test/src/tests.rs b/repl_test/src/tests.rs new file mode 100644 index 0000000000..49a166386b --- /dev/null +++ b/repl_test/src/tests.rs @@ -0,0 +1,922 @@ +use indoc::indoc; + +mod cli; + +#[cfg(feature = "cli")] +use crate::cli::{expect_failure, expect_success}; + +#[test] +fn literal_0() { + expect_success("0", "0 : Num *"); +} + +#[test] +fn literal_42() { + expect_success("42", "42 : Num *"); +} + +#[test] +fn literal_0x0() { + expect_success("0x0", "0 : Int *"); +} + +#[test] +fn literal_0x42() { + expect_success("0x42", "66 : Int *"); +} + +#[test] +fn literal_0point0() { + expect_success("0.0", "0 : Float *"); +} + +#[test] +fn literal_4point2() { + expect_success("4.2", "4.2 : Float *"); +} + +#[test] +fn num_addition() { + expect_success("1 + 2", "3 : Num *"); +} + +#[test] +fn int_addition() { + expect_success("0x1 + 2", "3 : Int *"); +} + +#[test] +fn float_addition() { + expect_success("1.1 + 2", "3.1 : F64"); +} + +#[test] +fn num_rem() { + expect_success("299 % 10", "Ok 9 : Result (Int *) [ DivByZero ]*"); +} + +#[test] +fn num_floor_division_success() { + expect_success("Num.divFloor 4 3", "Ok 1 : Result (Int *) [ DivByZero ]*"); +} + +#[test] +fn num_floor_division_divby_zero() { + expect_success( + "Num.divFloor 4 0", + "Err DivByZero : Result (Int *) [ DivByZero ]*", + ); +} + +#[test] +fn num_ceil_division_success() { + expect_success("Num.divCeil 4 3", "Ok 2 : Result (Int *) [ DivByZero ]*") +} + +#[test] +fn bool_in_record() { + expect_success("{ x: 1 == 1 }", "{ x: True } : { x : Bool }"); + expect_success( + "{ z: { y: { x: 1 == 1 } } }", + "{ z: { y: { x: True } } } : { z : { y : { x : Bool } } }", + ); + expect_success("{ x: 1 != 1 }", "{ x: False } : { x : Bool }"); + expect_success( + "{ x: 1 == 1, y: 1 != 1 }", + "{ x: True, y: False } : { x : Bool, y : Bool }", + ); +} + +#[test] +fn bool_basic_equality() { + expect_success("1 == 1", "True : Bool"); + expect_success("1 != 1", "False : Bool"); +} + +#[test] +fn arbitrary_tag_unions() { + expect_success("if 1 == 1 then Red else Green", "Red : [ Green, Red ]*"); + expect_success("if 1 != 1 then Red else Green", "Green : [ Green, Red ]*"); +} + +#[test] +fn tag_without_arguments() { + expect_success("True", "True : [ True ]*"); + expect_success("False", "False : [ False ]*"); +} + +#[test] +fn byte_tag_union() { + expect_success( + "if 1 == 1 then Red else if 1 == 1 then Green else Blue", + "Red : [ Blue, Green, Red ]*", + ); + + expect_success( + "{ y: { x: if 1 == 1 then Red else if 1 == 1 then Green else Blue } }", + "{ y: { x: Red } } : { y : { x : [ Blue, Green, Red ]* } }", + ); +} + +#[test] +fn tag_in_record() { + expect_success( + "{ x: Foo 1 2 3, y : 4 }", + "{ x: Foo 1 2 3, y: 4 } : { x : [ Foo (Num *) (Num *) (Num *) ]*, y : Num * }", + ); + expect_success( + "{ x: Foo 1 2 3 }", + "{ x: Foo 1 2 3 } : { x : [ Foo (Num *) (Num *) (Num *) ]* }", + ); + expect_success("{ x: Unit }", "{ x: Unit } : { x : [ Unit ]* }"); +} + +#[test] +fn single_element_tag_union() { + expect_success("True 1", "True 1 : [ True (Num *) ]*"); + expect_success("Foo 1 3.14", "Foo 1 3.14 : [ Foo (Num *) (Float *) ]*"); +} + +#[test] +fn newtype_of_unit() { + expect_success("Foo Bar", "Foo Bar : [ Foo [ Bar ]* ]*"); +} + +#[test] +fn newtype_of_big_data() { + expect_success( + indoc!( + r#" + Either a b : [ Left a, Right b ] + lefty : Either Str Str + lefty = Left "loosey" + A lefty + "# + ), + r#"A (Left "loosey") : [ A (Either Str Str) ]*"#, + ) +} + +#[test] +fn newtype_nested() { + expect_success( + indoc!( + r#" + Either a b : [ Left a, Right b ] + lefty : Either Str Str + lefty = Left "loosey" + A (B (C lefty)) + "# + ), + r#"A (B (C (Left "loosey"))) : [ A [ B [ C (Either Str Str) ]* ]* ]*"#, + ) +} + +#[test] +fn newtype_of_big_of_newtype() { + expect_success( + indoc!( + r#" + Big a : [ Big a [ Wrapper [ Newtype a ] ] ] + big : Big Str + big = Big "s" (Wrapper (Newtype "t")) + A big + "# + ), + r#"A (Big "s" (Wrapper (Newtype "t"))) : [ A (Big Str) ]*"#, + ) +} + +#[test] +fn tag_with_arguments() { + expect_success("True 1", "True 1 : [ True (Num *) ]*"); + + expect_success( + "if 1 == 1 then True 3 else False 3.14", + "True 3 : [ False (Float *), True (Num *) ]*", + ) +} + +#[test] +fn literal_empty_str() { + expect_success("\"\"", "\"\" : Str"); +} + +#[test] +fn literal_ascii_str() { + expect_success("\"Hello, World!\"", "\"Hello, World!\" : Str"); +} + +#[test] +fn literal_utf8_str() { + expect_success("\"👩‍👩‍👦‍👦\"", "\"👩‍👩‍👦‍👦\" : Str"); +} + +#[test] +fn str_concat() { + expect_success( + "Str.concat \"Hello, \" \"World!\"", + "\"Hello, World!\" : Str", + ); +} + +#[test] +fn str_count_graphemes() { + expect_success("Str.countGraphemes \"å🤔\"", "2 : Nat"); +} + +#[test] +fn literal_empty_list() { + expect_success("[]", "[] : List *"); +} + +#[test] +fn literal_empty_list_empty_record() { + expect_success("[ {} ]", "[ {} ] : List {}"); +} + +#[test] +fn literal_num_list() { + expect_success("[ 1, 2, 3 ]", "[ 1, 2, 3 ] : List (Num *)"); +} + +#[test] +fn literal_int_list() { + expect_success("[ 0x1, 0x2, 0x3 ]", "[ 1, 2, 3 ] : List (Int *)"); +} + +#[test] +fn literal_float_list() { + expect_success("[ 1.1, 2.2, 3.3 ]", "[ 1.1, 2.2, 3.3 ] : List (Float *)"); +} + +#[test] +fn literal_string_list() { + expect_success(r#"[ "a", "b", "cd" ]"#, r#"[ "a", "b", "cd" ] : List Str"#); +} + +#[test] +fn nested_string_list() { + expect_success( + r#"[ [ [ "a", "b", "cd" ], [ "y", "z" ] ], [ [] ], [] ]"#, + r#"[ [ [ "a", "b", "cd" ], [ "y", "z" ] ], [ [] ], [] ] : List (List (List Str))"#, + ); +} + +#[test] +fn nested_num_list() { + expect_success( + r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ]"#, + r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List (Num *)))"#, + ); +} + +#[test] +fn nested_int_list() { + expect_success( + r#"[ [ [ 4, 3, 2 ], [ 1, 0x0 ] ], [ [] ], [] ]"#, + r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List Int *))"#, + ); +} + +#[test] +fn nested_float_list() { + expect_success( + r#"[ [ [ 4, 3, 2 ], [ 1, 0.0 ] ], [ [] ], [] ]"#, + r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List F64))"#, + ); +} + +#[test] +fn num_bitwise_and() { + expect_success("Num.bitwiseAnd 20 20", "20 : Int *"); + + expect_success("Num.bitwiseAnd 25 10", "8 : Int *"); + + expect_success("Num.bitwiseAnd 200 0", "0 : Int *") +} + +#[test] +fn num_bitwise_xor() { + expect_success("Num.bitwiseXor 20 20", "0 : Int *"); + + expect_success("Num.bitwiseXor 15 14", "1 : Int *"); + + expect_success("Num.bitwiseXor 7 15", "8 : Int *"); + + expect_success("Num.bitwiseXor 200 0", "200 : Int *") +} + +#[test] +fn num_add_wrap() { + expect_success("Num.addWrap Num.maxI64 1", "-9223372036854775808 : I64"); +} + +#[test] +fn num_sub_wrap() { + expect_success("Num.subWrap Num.minI64 1", "9223372036854775807 : I64"); +} + +#[test] +fn num_mul_wrap() { + expect_success("Num.mulWrap Num.maxI64 2", "-2 : I64"); +} + +#[test] +fn num_add_checked() { + expect_success("Num.addChecked 1 1", "Ok 2 : Result (Num *) [ Overflow ]*"); + expect_success( + "Num.addChecked Num.maxI64 1", + "Err Overflow : Result I64 [ Overflow ]*", + ); +} + +#[test] +fn num_sub_checked() { + expect_success("Num.subChecked 1 1", "Ok 0 : Result (Num *) [ Overflow ]*"); + expect_success( + "Num.subChecked Num.minI64 1", + "Err Overflow : Result I64 [ Overflow ]*", + ); +} + +#[test] +fn num_mul_checked() { + expect_success( + "Num.mulChecked 20 2", + "Ok 40 : Result (Num *) [ Overflow ]*", + ); + expect_success( + "Num.mulChecked Num.maxI64 2", + "Err Overflow : Result I64 [ Overflow ]*", + ); +} + +#[test] +fn list_concat() { + expect_success( + "List.concat [ 1.1, 2.2 ] [ 3.3, 4.4, 5.5 ]", + "[ 1.1, 2.2, 3.3, 4.4, 5.5 ] : List (Float *)", + ); +} + +#[test] +fn list_contains() { + expect_success("List.contains [] 0", "False : Bool"); + expect_success("List.contains [ 1, 2, 3 ] 2", "True : Bool"); + expect_success("List.contains [ 1, 2, 3 ] 4", "False : Bool"); +} + +#[test] +fn list_sum() { + expect_success("List.sum []", "0 : Num *"); + expect_success("List.sum [ 1, 2, 3 ]", "6 : Num *"); + expect_success("List.sum [ 1.1, 2.2, 3.3 ]", "6.6 : F64"); +} + +#[test] +fn list_first() { + expect_success( + "List.first [ 12, 9, 6, 3 ]", + "Ok 12 : Result (Num *) [ ListWasEmpty ]*", + ); + expect_success( + "List.first []", + "Err ListWasEmpty : Result * [ ListWasEmpty ]*", + ); +} + +#[test] +fn list_last() { + expect_success( + "List.last [ 12, 9, 6, 3 ]", + "Ok 3 : Result (Num *) [ ListWasEmpty ]*", + ); + + expect_success( + "List.last []", + "Err ListWasEmpty : Result * [ ListWasEmpty ]*", + ); +} + +#[test] +fn empty_record() { + expect_success("{}", "{} : {}"); +} + +#[test] +fn basic_1_field_i64_record() { + // Even though this gets unwrapped at runtime, the repl should still + // report it as a record + expect_success("{ foo: 42 }", "{ foo: 42 } : { foo : Num * }"); +} + +#[test] +fn basic_1_field_f64_record() { + // Even though this gets unwrapped at runtime, the repl should still + // report it as a record + expect_success("{ foo: 4.2 }", "{ foo: 4.2 } : { foo : Float * }"); +} + +#[test] +fn nested_1_field_i64_record() { + // Even though this gets unwrapped at runtime, the repl should still + // report it as a record + expect_success( + "{ foo: { bar: { baz: 42 } } }", + "{ foo: { bar: { baz: 42 } } } : { foo : { bar : { baz : Num * } } }", + ); +} + +#[test] +fn nested_1_field_f64_record() { + // Even though this gets unwrapped at runtime, the repl should still + // report it as a record + expect_success( + "{ foo: { bar: { baz: 4.2 } } }", + "{ foo: { bar: { baz: 4.2 } } } : { foo : { bar : { baz : Float * } } }", + ); +} + +#[test] +fn basic_2_field_i64_record() { + expect_success( + "{ foo: 0x4, bar: 0x2 }", + "{ bar: 2, foo: 4 } : { bar : Int *, foo : Int * }", + ); +} + +#[test] +fn basic_2_field_f64_record() { + expect_success( + "{ foo: 4.1, bar: 2.3 }", + "{ bar: 2.3, foo: 4.1 } : { bar : Float *, foo : Float * }", + ); +} + +#[test] +fn basic_2_field_mixed_record() { + expect_success( + "{ foo: 4.1, bar: 2 }", + "{ bar: 2, foo: 4.1 } : { bar : Num *, foo : Float * }", + ); +} + +#[test] +fn basic_3_field_record() { + expect_success( + "{ foo: 4.1, bar: 2, baz: 0x5 }", + "{ bar: 2, baz: 5, foo: 4.1 } : { bar : Num *, baz : Int *, foo : Float * }", + ); +} + +#[test] +fn list_of_1_field_records() { + // Even though these get unwrapped at runtime, the repl should still + // report them as records + expect_success("[ { foo: 42 } ]", "[ { foo: 42 } ] : List { foo : Num * }"); +} + +#[test] +fn list_of_2_field_records() { + expect_success( + "[ { foo: 4.1, bar: 2 } ]", + "[ { bar: 2, foo: 4.1 } ] : List { bar : Num *, foo : Float * }", + ); +} + +#[test] +fn three_element_record() { + // if this tests turns out to fail on 32-bit platforms, look at jit_to_ast_help + expect_success( + "{ a: 1, b: 2, c: 3 }", + "{ a: 1, b: 2, c: 3 } : { a : Num *, b : Num *, c : Num * }", + ); +} + +#[test] +fn four_element_record() { + // if this tests turns out to fail on 32-bit platforms, look at jit_to_ast_help + expect_success( + "{ a: 1, b: 2, c: 3, d: 4 }", + "{ a: 1, b: 2, c: 3, d: 4 } : { a : Num *, b : Num *, c : Num *, d : Num * }", + ); +} + +// #[test] +// fn multiline_string() { +// // If a string contains newlines, format it as a multiline string in the output +// expect_success(r#""\n\nhi!\n\n""#, "\"\"\"\n\nhi!\n\n\"\"\""); +// } + +#[test] +fn list_of_3_field_records() { + expect_success( + "[ { foo: 4.1, bar: 2, baz: 0x3 } ]", + "[ { bar: 2, baz: 3, foo: 4.1 } ] : List { bar : Num *, baz : Int *, foo : Float * }", + ); +} + +#[test] +fn identity_lambda() { + expect_success("\\x -> x", " : a -> a"); +} + +#[test] +fn sum_lambda() { + expect_success("\\x, y -> x + y", " : Num a, Num a -> Num a"); +} + +#[test] +fn stdlib_function() { + expect_success("Num.abs", " : Num a -> Num a"); +} + +#[test] +fn too_few_args() { + expect_failure( + "Num.add 2", + indoc!( + r#" + ── TOO FEW ARGS ──────────────────────────────────────────────────────────────── + + The add function expects 2 arguments, but it got only 1: + + 4│ Num.add 2 + ^^^^^^^ + + Roc does not allow functions to be partially applied. Use a closure to + make partial application explicit. + "# + ), + ); +} + +#[test] +fn type_problem() { + expect_failure( + "1 + \"\"", + indoc!( + r#" + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + The 2nd argument to add is not what I expect: + + 4│ 1 + "" + ^^ + + This argument is a string of type: + + Str + + But add needs the 2nd argument to be: + + Num a + "# + ), + ); +} + +#[test] +fn issue_2149() { + expect_success(r#"Str.toI8 "127""#, "Ok 127 : Result I8 [ InvalidNumStr ]*"); + expect_success( + r#"Str.toI8 "128""#, + "Err InvalidNumStr : Result I8 [ InvalidNumStr ]*", + ); + expect_success( + r#"Str.toI16 "32767""#, + "Ok 32767 : Result I16 [ InvalidNumStr ]*", + ); + expect_success( + r#"Str.toI16 "32768""#, + "Err InvalidNumStr : Result I16 [ InvalidNumStr ]*", + ); +} + +#[test] +fn multiline_input() { + expect_success( + indoc!( + r#" + a : Str + a = "123" + a + "# + ), + r#""123" : Str"#, + ) +} + +#[test] +fn recursive_tag_union_flat_variant() { + expect_success( + indoc!( + r#" + Expr : [ Sym Str, Add Expr Expr ] + s : Expr + s = Sym "levitating" + s + "# + ), + r#"Sym "levitating" : Expr"#, + ) +} + +#[test] +fn large_recursive_tag_union_flat_variant() { + expect_success( + // > 7 variants so that to force tag storage alongside the data + indoc!( + r#" + Item : [ A Str, B Str, C Str, D Str, E Str, F Str, G Str, H Str, I Str, J Str, K Item ] + s : Item + s = H "woo" + s + "# + ), + r#"H "woo" : Item"#, + ) +} + +#[test] +fn recursive_tag_union_recursive_variant() { + expect_success( + indoc!( + r#" + Expr : [ Sym Str, Add Expr Expr ] + s : Expr + s = Add (Add (Sym "one") (Sym "two")) (Sym "four") + s + "# + ), + r#"Add (Add (Sym "one") (Sym "two")) (Sym "four") : Expr"#, + ) +} + +#[test] +fn large_recursive_tag_union_recursive_variant() { + expect_success( + // > 7 variants so that to force tag storage alongside the data + indoc!( + r#" + Item : [ A Str, B Str, C Str, D Str, E Str, F Str, G Str, H Str, I Str, J Str, K Item, L Item ] + s : Item + s = K (L (E "woo")) + s + "# + ), + r#"K (L (E "woo")) : Item"#, + ) +} + +#[test] +fn recursive_tag_union_into_flat_tag_union() { + expect_success( + indoc!( + r#" + Item : [ One [ A Str, B Str ], Deep Item ] + i : Item + i = Deep (One (A "woo")) + i + "# + ), + r#"Deep (One (A "woo")) : Item"#, + ) +} + +#[test] +fn non_nullable_unwrapped_tag_union() { + expect_success( + indoc!( + r#" + RoseTree a : [ Tree a (List (RoseTree a)) ] + e1 : RoseTree Str + e1 = Tree "e1" [] + e2 : RoseTree Str + e2 = Tree "e2" [] + combo : RoseTree Str + combo = Tree "combo" [e1, e2] + combo + "# + ), + r#"Tree "combo" [ Tree "e1" [], Tree "e2" [] ] : RoseTree Str"#, + ) +} + +#[test] +fn nullable_unwrapped_tag_union() { + expect_success( + indoc!( + r#" + LinkedList a : [ Nil, Cons a (LinkedList a) ] + c1 : LinkedList Str + c1 = Cons "Red" Nil + c2 : LinkedList Str + c2 = Cons "Yellow" c1 + c3 : LinkedList Str + c3 = Cons "Green" c2 + c3 + "# + ), + r#"Cons "Green" (Cons "Yellow" (Cons "Red" Nil)) : LinkedList Str"#, + ) +} + +#[test] +fn nullable_wrapped_tag_union() { + expect_success( + indoc!( + r#" + Container a : [ Empty, Whole a, Halved (Container a) (Container a) ] + + meats : Container Str + meats = Halved (Whole "Brisket") (Whole "Ribs") + + sides : Container Str + sides = Halved (Whole "Coleslaw") Empty + + bbqPlate : Container Str + bbqPlate = Halved meats sides + + bbqPlate + "# + ), + r#"Halved (Halved (Whole "Brisket") (Whole "Ribs")) (Halved (Whole "Coleslaw") Empty) : Container Str"#, + ) +} + +#[test] +fn large_nullable_wrapped_tag_union() { + // > 7 non-empty variants so that to force tag storage alongside the data + expect_success( + indoc!( + r#" + Cont a : [ Empty, S1 a, S2 a, S3 a, S4 a, S5 a, S6 a, S7 a, Tup (Cont a) (Cont a) ] + + fst : Cont Str + fst = Tup (S1 "S1") (S2 "S2") + + snd : Cont Str + snd = Tup (S5 "S5") Empty + + tup : Cont Str + tup = Tup fst snd + + tup + "# + ), + r#"Tup (Tup (S1 "S1") (S2 "S2")) (Tup (S5 "S5") Empty) : Cont Str"#, + ) +} + +#[test] +fn issue_2300() { + expect_success( + r#"\Email str -> str == """#, + r#" : [ Email Str ] -> Bool"#, + ) +} + +#[test] +fn function_in_list() { + expect_success( + r#"[\x -> x + 1, \s -> s * 2]"#, + r#"[ , ] : List (Num a -> Num a)"#, + ) +} + +#[test] +fn function_in_record() { + expect_success( + r#"{ n: 1, adder: \x -> x + 1 }"#, + r#"{ adder: , n: } : { adder : Num a -> Num a, n : Num * }"#, + ) +} + +#[test] +fn function_in_unwrapped_record() { + expect_success( + r#"{ adder: \x -> x + 1 }"#, + r#"{ adder: } : { adder : Num a -> Num a }"#, + ) +} + +#[test] +fn function_in_tag() { + expect_success( + r#"Adder (\x -> x + 1)"#, + r#"Adder : [ Adder (Num a -> Num a) ]*"#, + ) +} + +#[test] +fn newtype_of_record_of_tag_of_record_of_tag() { + expect_success( + r#"A {b: C {d: 1}}"#, + r#"A { b: C { d: 1 } } : [ A { b : [ C { d : Num * } ]* } ]*"#, + ) +} + +#[test] +fn print_u8s() { + expect_success( + indoc!( + r#" + x : U8 + x = 129 + x + "# + ), + "129 : U8", + ) +} + +#[test] +fn parse_problem() { + expect_failure( + "add m n = m + n", + indoc!( + r#" + ── ARGUMENTS BEFORE EQUALS ───────────────────────────────────────────────────── + + I am partway through parsing a definition, but I got stuck here: + + 1│ app "app" provides [ replOutput ] to "./platform" + 2│ + 3│ replOutput = + 4│ add m n = m + n + ^^^ + + Looks like you are trying to define a function. In roc, functions are + always written as a lambda, like increment = \n -> n + 1. + "# + ), + ); +} + +#[test] +fn mono_problem() { + expect_failure( + r#" + t : [A, B, C] + t = A + + when t is + A -> "a" + "#, + indoc!( + r#" + ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + + This when does not cover all the possibilities: + + 7│> when t is + 8│> A -> "a" + + Other possibilities include: + + B + C + + I would have to crash if I saw one of those! Add branches for them! + + + Enter an expression, or :help, or :exit/:q."# + ), + ); +} + +#[test] +fn issue_2343_complete_mono_with_shadowed_vars() { + expect_failure( + indoc!( + r#" + b = False + f = \b -> + when b is + True -> 5 + False -> 15 + f b + "# + ), + indoc!( + r#" + ── DUPLICATE NAME ────────────────────────────────────────────────────────────── + + The b name is first defined here: + + 4│ b = False + ^ + + But then it's defined a second time here: + + 5│ f = \b -> + ^ + + Since these variables have the same name, it's easy to use the wrong + one on accident. Give one of them a new name. + "# + ), + ); +} diff --git a/repl_wasm/.gitignore b/repl_wasm/.gitignore new file mode 100644 index 0000000000..0613f22c9d --- /dev/null +++ b/repl_wasm/.gitignore @@ -0,0 +1,9 @@ +/*.txt +/dist +/notes.md + +# Binary data (pre-linked Wasm object file) +/data + +# wasm-pack +/pkg diff --git a/repl_wasm/Cargo.toml b/repl_wasm/Cargo.toml new file mode 100644 index 0000000000..03dd9536a2 --- /dev/null +++ b/repl_wasm/Cargo.toml @@ -0,0 +1,24 @@ +[package] +edition = "2021" +name = "roc_repl_wasm" +version = "0.1.0" + +[lib] +crate-type = ["cdylib"] + +[build-dependencies] +roc_builtins = {path = "../compiler/builtins"} + +[dependencies] +bumpalo = {version = "3.8.0", features = ["collections"]} +js-sys = "0.3.56" +wasm-bindgen = "0.2.79" +wasm-bindgen-futures = "0.4.29" + +roc_collections = {path = "../compiler/collections"} +roc_gen_wasm = {path = "../compiler/gen_wasm"} +roc_load = {path = "../compiler/load"} +roc_parse = {path = "../compiler/parse"} +roc_repl_eval = {path = "../repl_eval"} +roc_target = {path = "../compiler/roc_target"} +roc_types = {path = "../compiler/types"} diff --git a/repl_wasm/build.rs b/repl_wasm/build.rs new file mode 100644 index 0000000000..3a1ffed494 --- /dev/null +++ b/repl_wasm/build.rs @@ -0,0 +1,67 @@ +use std::ffi::OsStr; +use std::path::Path; +use std::process::Command; + +use roc_builtins::bitcode; + +const PLATFORM_FILENAME: &str = "repl_platform"; +const PRE_LINKED_BINARY: &str = "data/pre_linked_binary.o"; + +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=src/{}.c", PLATFORM_FILENAME); + + std::fs::create_dir_all("./data").unwrap(); + + // Build a pre-linked binary with platform, builtins and all their libc dependencies + // This builds a library file that exports all symbols. It has no linker data but we don't need it. + // See discussion with Luuk de Gram (Zig contributor) + // https://github.com/rtfeldman/roc/pull/2181#pullrequestreview-839608063 + let args = [ + "build-lib", + "-target", + "wasm32-wasi", + "-lc", + "-dynamic", // -dynamic ensures libc code goes into the binary + bitcode::BUILTINS_WASM32_OBJ_PATH, + &format!("src/{}.c", PLATFORM_FILENAME), + &format!("-femit-bin={}", PRE_LINKED_BINARY), + ]; + + let zig = zig_executable(); + + // println!("{} {}", zig, args.join(" ")); + + run_command(Path::new("."), &zig, args); +} + +fn zig_executable() -> String { + match std::env::var("ROC_ZIG") { + Ok(path) => path, + Err(_) => "zig".into(), + } +} + +fn run_command>(path: P, command_str: &str, args: I) -> String +where + I: IntoIterator, + S: AsRef, +{ + let output_result = Command::new(OsStr::new(&command_str)) + .current_dir(path) + .args(args) + .output(); + match output_result { + Ok(output) => match output.status.success() { + true => std::str::from_utf8(&output.stdout).unwrap().to_string(), + false => { + let error_str = match std::str::from_utf8(&output.stderr) { + Ok(stderr) => stderr.to_string(), + Err(_) => format!("Failed to run \"{}\"", command_str), + }; + panic!("{} failed: {}", command_str, error_str); + } + }, + Err(reason) => panic!("{} failed: {}", command_str, reason), + } +} diff --git a/repl_wasm/src/lib.rs b/repl_wasm/src/lib.rs new file mode 100644 index 0000000000..48910defba --- /dev/null +++ b/repl_wasm/src/lib.rs @@ -0,0 +1,258 @@ +// wasm_bindgen procedural macro breaks this clippy rule +// https://github.com/rustwasm/wasm-bindgen/issues/2774 +#![allow(clippy::unused_unit)] + +use bumpalo::{collections::vec::Vec, Bump}; +use std::mem::size_of; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsValue; + +use roc_collections::all::MutSet; +use roc_gen_wasm::wasm32_result; +use roc_load::file::MonomorphizedModule; +use roc_parse::ast::Expr; +use roc_repl_eval::{ + eval::jit_to_ast, + gen::{compile_to_mono, format_answer, ReplOutput}, + ReplApp, ReplAppMemory, +}; +use roc_target::TargetInfo; +use roc_types::pretty_print::{content_to_string, name_all_type_vars}; + +const WRAPPER_NAME: &str = "wrapper"; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(catch)] + async fn js_create_app(wasm_module_bytes: &[u8]) -> Result<(), JsValue>; + + fn js_run_app() -> usize; + + fn js_get_result_and_memory(buffer_alloc_addr: *mut u8) -> usize; + + #[wasm_bindgen(js_namespace = console)] + fn log(s: &str); +} + +// In-browser debugging +#[allow(unused_macros)] +macro_rules! console_log { + ($($t:tt)*) => (log(&format_args!($($t)*).to_string())) +} + +pub struct WasmReplApp<'a> { + arena: &'a Bump, +} + +/// A copy of the app's memory, made after running the main function +/// The Wasm app ran in a separate address space from the compiler and the eval code. +/// This means we can't simply dereference its pointers as if they were local, because +/// an unrelated value may exist at the same-numbered address in our own address space! +/// Instead we have dereferencing methods that index into the copied bytes. +pub struct WasmMemory<'a> { + copied_bytes: &'a [u8], +} + +macro_rules! deref_number { + ($name: ident, $t: ty) => { + fn $name(&self, address: usize) -> $t { + const N: usize = size_of::<$t>(); + let mut array = [0; N]; + array.copy_from_slice(&self.copied_bytes[address..][..N]); + <$t>::from_le_bytes(array) + } + }; +} + +impl<'a> ReplAppMemory for WasmMemory<'a> { + fn deref_bool(&self, address: usize) -> bool { + self.copied_bytes[address] != 0 + } + + deref_number!(deref_u8, u8); + deref_number!(deref_u16, u16); + deref_number!(deref_u32, u32); + deref_number!(deref_u64, u64); + deref_number!(deref_u128, u128); + deref_number!(deref_usize, usize); + + deref_number!(deref_i8, i8); + deref_number!(deref_i16, i16); + deref_number!(deref_i32, i32); + deref_number!(deref_i64, i64); + deref_number!(deref_i128, i128); + deref_number!(deref_isize, isize); + + deref_number!(deref_f32, f32); + deref_number!(deref_f64, f64); + + fn deref_str(&self, addr: usize) -> &str { + let elems_addr = self.deref_usize(addr); + let len = self.deref_usize(addr + size_of::()); + let bytes = &self.copied_bytes[elems_addr..][..len]; + std::str::from_utf8(bytes).unwrap() + } +} + +impl<'a> ReplApp<'a> for WasmReplApp<'a> { + type Memory = WasmMemory<'a>; + + /// Run user code that returns a type with a `Builtin` layout + /// Size of the return value is statically determined from its Rust type + /// The `transform` callback takes the app's memory and the returned value + /// _main_fn_name is always the same and we don't use it here + fn call_function(&self, _main_fn_name: &str, transform: F) -> Expr<'a> + where + F: Fn(&'a Self::Memory, Return) -> Expr<'a>, + Self::Memory: 'a, + { + let app_final_memory_size: usize = js_run_app(); + + // Allocate a buffer to copy the app memory into + // Aligning it to 64 bits will preserve the original alignment of all Wasm numbers + let copy_buffer_aligned: &mut [u64] = self + .arena + .alloc_slice_fill_default((app_final_memory_size / size_of::()) + 1); + let copied_bytes: &mut [u8] = unsafe { std::mem::transmute(copy_buffer_aligned) }; + + let app_result_addr = js_get_result_and_memory(copied_bytes.as_mut_ptr()); + + let result: Return = unsafe { + let ptr: *const Return = std::mem::transmute(&copied_bytes[app_result_addr]); + ptr.read() + }; + let mem = self.arena.alloc(WasmMemory { copied_bytes }); + + transform(mem, result) + } + + /// Run user code that returns a struct or union, whose size is provided as an argument + /// The `transform` callback takes the app's memory and the address of the returned value + /// _main_fn_name and _ret_bytes are only used for the CLI REPL. For Wasm they are compiled-in + /// to the test_wrapper function of the app itself + fn call_function_dynamic_size( + &self, + _main_fn_name: &str, + _ret_bytes: usize, + transform: F, + ) -> T + where + F: Fn(&'a Self::Memory, usize) -> T, + Self::Memory: 'a, + { + let app_final_memory_size: usize = js_run_app(); + + // Allocate a buffer to copy the app memory into + // Aligning it to 64 bits will preserve the original alignment of all Wasm numbers + let copy_buffer_aligned: &mut [u64] = self + .arena + .alloc_slice_fill_default((app_final_memory_size / size_of::()) + 1); + let copied_bytes: &mut [u8] = unsafe { std::mem::transmute(copy_buffer_aligned) }; + + let app_result_addr = js_get_result_and_memory(copied_bytes.as_mut_ptr()); + let mem = self.arena.alloc(WasmMemory { copied_bytes }); + + transform(mem, app_result_addr) + } +} + +#[wasm_bindgen] +pub async fn entrypoint_from_js(src: String) -> Result { + let arena = &Bump::new(); + let pre_linked_binary: &'static [u8] = include_bytes!("../data/pre_linked_binary.o"); + + // Compile the app + let target_info = TargetInfo::default_wasm32(); + let mono = match compile_to_mono(arena, &src, target_info) { + Ok(m) => m, + Err(messages) => return Err(messages.join("\n\n")), + }; + + let MonomorphizedModule { + module_id, + procedures, + mut interns, + mut subs, + exposed_to_host, + .. + } = mono; + + debug_assert_eq!(exposed_to_host.values.len(), 1); + let (main_fn_symbol, main_fn_var) = exposed_to_host.values.iter().next().unwrap(); + let main_fn_symbol = *main_fn_symbol; + let main_fn_var = *main_fn_var; + + // pretty-print the expr type string for later. + name_all_type_vars(main_fn_var, &mut subs); + let content = subs.get_content_without_compacting(main_fn_var); + let expr_type_str = content_to_string(content, &subs, module_id, &interns); + + let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) { + Some(layout) => *layout, + None => return Ok(format!(" : {}", expr_type_str)), + }; + + let app_module_bytes = { + let env = roc_gen_wasm::Env { + arena, + module_id, + exposed_to_host: exposed_to_host + .values + .keys() + .copied() + .collect::>(), + }; + + let (mut module, called_preload_fns, main_fn_index) = { + roc_gen_wasm::build_module_without_wrapper( + &env, + &mut interns, // NOTE: must drop this mutable ref before jit_to_ast + pre_linked_binary, + procedures, + ) + }; + + wasm32_result::insert_wrapper_for_layout( + arena, + &mut module, + WRAPPER_NAME, + main_fn_index, + &main_fn_layout.result, + ); + + module.remove_dead_preloads(env.arena, called_preload_fns); + + let mut buffer = Vec::with_capacity_in(module.size(), arena); + module.serialize(&mut buffer); + + buffer + }; + + // Send the compiled binary out to JS and create an executable instance from it + js_create_app(&app_module_bytes) + .await + .map_err(|js| format!("{:?}", js))?; + + let app = WasmReplApp { arena }; + + // Run the app and transform the result value to an AST `Expr` + // Restore type constructor names, and other user-facing info that was erased during compilation. + let res_answer = jit_to_ast( + arena, + &app, + "", // main_fn_name is ignored (only passed to WasmReplApp methods) + main_fn_layout, + content, + &interns, + module_id, + &subs, + target_info, + ); + + // Transform the Expr to a string + // `Result::Err` becomes a JS exception that will be caught and displayed + match format_answer(arena, res_answer, expr_type_str) { + ReplOutput::NoProblems { expr, expr_type } => Ok(format!("\n{}: {}", expr, expr_type)), + ReplOutput::Problems(lines) => Err(format!("\n{}\n", lines.join("\n\n"))), + } +} diff --git a/repl_wasm/src/repl_platform.c b/repl_wasm/src/repl_platform.c new file mode 100644 index 0000000000..1f288159e9 --- /dev/null +++ b/repl_wasm/src/repl_platform.c @@ -0,0 +1,77 @@ +#include + +/* + A bare-bones Roc "platform" for REPL code, providing heap allocation for builtins. +*/ + +// Enable/disable printf debugging. Leave disabled to avoid bloating .wasm files and slowing down Wasmer tests. +#define ENABLE_PRINTF 0 + +//-------------------------- + +void *roc_alloc(size_t size, unsigned int alignment) +{ + void *allocated = malloc(size); + +#if ENABLE_PRINTF + if (!allocated) + { + fprintf(stderr, "roc_alloc failed\n"); + exit(1); + } + else + { + printf("roc_alloc allocated %d bytes with alignment %d at %p\n", size, alignment, allocated); + } +#endif + return allocated; +} + +//-------------------------- + +void *roc_realloc(void *ptr, size_t new_size, size_t old_size, + unsigned int alignment) +{ +#if ENABLE_PRINTF + printf("roc_realloc reallocated %p from %d to %d with alignment %zd\n", + ptr, old_size, new_size, alignment); +#endif + return realloc(ptr, new_size); +} + +//-------------------------- + +void roc_dealloc(void *ptr, unsigned int alignment) +{ + +#if ENABLE_PRINTF + printf("roc_dealloc deallocated %p with alignment %zd\n", ptr, alignment); +#endif + free(ptr); +} + +//-------------------------- + +void roc_panic(void *ptr, unsigned int alignment) +{ +#if ENABLE_PRINTF + char *msg = (char *)ptr; + fprintf(stderr, + "Application crashed with message\n\n %s\n\nShutting down\n", msg); +#endif + abort(); +} + +//-------------------------- + +void *roc_memcpy(void *dest, const void *src, size_t n) +{ + return memcpy(dest, src, n); +} + +//-------------------------- + +void *roc_memset(void *str, int c, size_t n) +{ + return memset(str, c, n); +} diff --git a/repl_www/.gitignore b/repl_www/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/repl_www/.gitignore @@ -0,0 +1 @@ +/build diff --git a/repl_www/build.sh b/repl_www/build.sh new file mode 100755 index 0000000000..2b9c6e4023 --- /dev/null +++ b/repl_www/build.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +set -eux + +if [[ ! -d repl_www ]] +then + echo "This script should be run from the project root" + exit 1 +fi + +if ! which wasm-pack +then + cargo install wasm-pack +fi + +WWW_DIR="repl_www/build" +mkdir -p $WWW_DIR +cp repl_www/public/* $WWW_DIR + +# Pass all script arguments through to wasm-pack (such as --release) +wasm-pack build --target web "$@" repl_wasm + +cp repl_wasm/pkg/*.wasm $WWW_DIR + +# Copy the JS from wasm_bindgen, replacing its invalid `import` statement with a `var`. +# The JS import from the invalid path 'env', seems to be generated when there are unresolved symbols. +BINDGEN_FILE="roc_repl_wasm.js" +echo 'var __wbg_star0 = { now: Date.now };' > $WWW_DIR/$BINDGEN_FILE +grep -v '^import' repl_wasm/pkg/$BINDGEN_FILE >> $WWW_DIR/$BINDGEN_FILE diff --git a/repl_www/public/index.html b/repl_www/public/index.html new file mode 100644 index 0000000000..20e4ed3ba2 --- /dev/null +++ b/repl_www/public/index.html @@ -0,0 +1,97 @@ + + + + Mock REPL + + +

+
+

The rockin' Roc REPL

+
+ +
+
+
+
+
+ +
+ +
+
+ + + diff --git a/repl_www/public/repl.js b/repl_www/public/repl.js new file mode 100644 index 0000000000..ecd07d8b5b --- /dev/null +++ b/repl_www/public/repl.js @@ -0,0 +1,135 @@ +// wasm_bindgen treats our `extern` declarations as JS globals, so let's keep it happy +window.js_create_app = js_create_app; +window.js_run_app = js_run_app; +window.js_get_result_and_memory = js_get_result_and_memory; +import * as roc_repl_wasm from "./roc_repl_wasm.js"; + +// ---------------------------------------------------------------------------- +// REPL state +// ---------------------------------------------------------------------------- + +const repl = { + elemHistory: document.getElementById("history-text"), + elemSourceInput: document.getElementById("source-input"), + + inputQueue: [], + inputHistory: [], + + textDecoder: new TextDecoder(), + textEncoder: new TextEncoder(), + + compiler: null, + app: null, + + // Temporary storage for values passing back and forth between JS and Wasm + result: { addr: 0, buffer: new ArrayBuffer() }, +}; + +// Initialise +repl.elemSourceInput.addEventListener("change", onInputChange); +roc_repl_wasm.default().then((instance) => { + repl.compiler = instance; +}); + +// ---------------------------------------------------------------------------- +// Handle inputs +// ---------------------------------------------------------------------------- + +function onInputChange(event) { + const inputText = event.target.value; + event.target.value = ""; + + repl.inputQueue.push(inputText); + if (repl.inputQueue.length === 1) { + processInputQueue(); + } +} + +// Use a queue just in case we somehow get inputs very fast +// We want the REPL to only process one at a time, since we're using some global state. +// In normal usage we shouldn't see this edge case anyway. Maybe with copy/paste? +async function processInputQueue() { + while (repl.inputQueue.length) { + const inputText = repl.inputQueue[0]; + const historyIndex = createHistoryEntry(inputText); + + let outputText; + let ok = true; + try { + outputText = await roc_repl_wasm.entrypoint_from_js(inputText); + } catch (e) { + outputText = `${e}`; + ok = false; + } + + updateHistoryEntry(historyIndex, ok, outputText); + repl.inputQueue.shift(); + } +} + +// ---------------------------------------------------------------------------- +// Callbacks to JS from Rust +// ---------------------------------------------------------------------------- + +// Create an executable Wasm instance from an array of bytes +// (Browser validates the module and does the final compilation to the host's machine code.) +async function js_create_app(wasm_module_bytes) { + const { instance } = await WebAssembly.instantiate(wasm_module_bytes); + repl.app = instance; +} + +// Call the main function of the app, via the test wrapper +// Cache the result and return the size of the app's memory +function js_run_app() { + const { run, memory } = repl.app.exports; + const addr = run(); + const { buffer } = memory; + repl.result = { addr, buffer }; + + // Tell Rust how much space to reserve for its copy of the app's memory buffer. + // This is not predictable, since the app can resize its own memory via malloc. + return buffer.byteLength; +} + +// After the Rust app has allocated space for the app's memory buffer, +// it calls this function and we copy it, and return the result too +function js_get_result_and_memory(buffer_alloc_addr) { + const { addr, buffer } = repl.result; + const appMemory = new Uint8Array(buffer); + const compilerMemory = new Uint8Array(repl.compiler.memory.buffer); + compilerMemory.set(appMemory, buffer_alloc_addr); + return addr; +} + +// ---------------------------------------------------------------------------- +// Rendering +// ---------------------------------------------------------------------------- + +function createHistoryEntry(inputText) { + const historyIndex = repl.inputHistory.length; + repl.inputHistory.push(inputText); + + const inputElem = document.createElement("div"); + inputElem.textContent = "> " + inputText; + inputElem.classList.add("input"); + + const historyItem = document.createElement("div"); + historyItem.appendChild(inputElem); + + repl.elemHistory.appendChild(historyItem); + repl.elemHistory.scrollTop = repl.elemHistory.scrollHeight; + + return historyIndex; +} + +function updateHistoryEntry(index, ok, outputText) { + const outputElem = document.createElement("div"); + outputElem.textContent = outputText; + outputElem.classList.add("output"); + outputElem.classList.add(ok ? "output-ok" : "output-error"); + + const historyItem = repl.elemHistory.childNodes[index]; + historyItem.appendChild(outputElem); + + repl.elemHistory.scrollTop = repl.elemHistory.scrollHeight; +} diff --git a/reporting/Cargo.toml b/reporting/Cargo.toml index 7fa1d2cfee..2add55b827 100644 --- a/reporting/Cargo.toml +++ b/reporting/Cargo.toml @@ -24,5 +24,7 @@ roc_constrain = { path = "../compiler/constrain" } roc_builtins = { path = "../compiler/builtins" } roc_problem = { path = "../compiler/problem" } roc_parse = { path = "../compiler/parse" } +roc_target = { path = "../compiler/roc_target" } +roc_test_utils = { path = "../test_utils" } pretty_assertions = "1.0.0" indoc = "1.0.3" diff --git a/reporting/src/error/canonicalize.rs b/reporting/src/error/canonicalize.rs index f57d106952..3759c26afa 100644 --- a/reporting/src/error/canonicalize.rs +++ b/reporting/src/error/canonicalize.rs @@ -2,7 +2,7 @@ use roc_collections::all::MutSet; use roc_module::ident::{Ident, Lowercase, ModuleName}; use roc_problem::can::PrecedenceProblem::BothNonAssociative; use roc_problem::can::{BadPattern, FloatErrorKind, IntErrorKind, Problem, RuntimeError}; -use roc_region::all::{Loc, Position, Region}; +use roc_region::all::{LineColumn, LineColumnRegion, LineInfo, Loc, Region}; use std::path::PathBuf; use crate::error::r#type::suggest; @@ -16,7 +16,8 @@ const UNUSED_DEF: &str = "UNUSED DEFINITION"; const UNUSED_IMPORT: &str = "UNUSED IMPORT"; const UNUSED_ALIAS_PARAM: &str = "UNUSED TYPE ALIAS PARAMETER"; const UNUSED_ARG: &str = "UNUSED ARGUMENT"; -const MISSING_DEFINITION: &str = "MISSING_DEFINITION"; +const MISSING_DEFINITION: &str = "MISSING DEFINITION"; +const UNKNOWN_GENERATES_WITH: &str = "UNKNOWN GENERATES FUNCTION"; const DUPLICATE_FIELD_NAME: &str = "DUPLICATE FIELD NAME"; const DUPLICATE_TAG_NAME: &str = "DUPLICATE TAG NAME"; const INVALID_UNICODE: &str = "INVALID UNICODE"; @@ -24,9 +25,14 @@ const CIRCULAR_DEF: &str = "CIRCULAR DEFINITION"; const DUPLICATE_NAME: &str = "DUPLICATE NAME"; const VALUE_NOT_EXPOSED: &str = "NOT EXPOSED"; const MODULE_NOT_IMPORTED: &str = "MODULE NOT IMPORTED"; +const NESTED_DATATYPE: &str = "NESTED DATATYPE"; +const CONFLICTING_NUMBER_SUFFIX: &str = "CONFLICTING NUMBER SUFFIX"; +const NUMBER_OVERFLOWS_SUFFIX: &str = "NUMBER OVERFLOWS SUFFIX"; +const NUMBER_UNDERFLOWS_SUFFIX: &str = "NUMBER UNDERFLOWS SUFFIX"; pub fn can_problem<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, filename: PathBuf, problem: Problem, ) -> Report<'b> { @@ -43,7 +49,7 @@ pub fn can_problem<'b>( alloc .symbol_unqualified(symbol) .append(alloc.reflow(" is not used anywhere in your code.")), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc .reflow("If you didn't intend on using ") .append(alloc.symbol_unqualified(symbol)) @@ -60,7 +66,7 @@ pub fn can_problem<'b>( alloc.module(module_id), alloc.reflow(" is used in this module."), ]), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("Since "), alloc.module(module_id), @@ -87,6 +93,21 @@ pub fn can_problem<'b>( title = MISSING_DEFINITION.to_string(); severity = Severity::RuntimeError; } + Problem::UnknownGeneratesWith(loc_ident) => { + doc = alloc.stack(vec![ + alloc + .reflow("I don't know how to generate the ") + .append(alloc.ident(loc_ident.value)) + .append(alloc.reflow(" function.")), + alloc.region(lines.convert_region(loc_ident.region)), + alloc + .reflow("Only specific functions like `after` and `map` can be generated.") + .append(alloc.reflow("Learn more about hosted modules at TODO.")), + ]); + + title = UNKNOWN_GENERATES_WITH.to_string(); + severity = Severity::RuntimeError; + } Problem::UnusedArgument(closure_symbol, argument_symbol, region) => { let line = "\". Adding an underscore at the start of a variable name is a way of saying that the variable is not used."; @@ -97,7 +118,7 @@ pub fn can_problem<'b>( alloc.symbol_unqualified(argument_symbol), alloc.text("."), ]), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("If you don't need "), alloc.symbol_unqualified(argument_symbol), @@ -137,7 +158,7 @@ pub fn can_problem<'b>( )), ]) }, - alloc.region(region), + alloc.region(lines.convert_region(region)), ]); title = SYNTAX_PROBLEM.to_string(); @@ -146,7 +167,7 @@ pub fn can_problem<'b>( Problem::UnsupportedPattern(BadPattern::UnderscoreInDef, region) => { doc = alloc.stack(vec![ alloc.reflow("Underscore patterns are not allowed in definitions"), - alloc.region(region), + alloc.region(lines.convert_region(region)), ]); title = SYNTAX_PROBLEM.to_string(); @@ -176,7 +197,7 @@ pub fn can_problem<'b>( alloc .reflow("This pattern is not allowed in ") .append(alloc.reflow(this_thing)), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.concat(suggestion), ]); @@ -187,13 +208,13 @@ pub fn can_problem<'b>( original_region, shadow, } => { - doc = report_shadowing(alloc, original_region, shadow); + doc = report_shadowing(alloc, lines, original_region, shadow); title = DUPLICATE_NAME.to_string(); severity = Severity::RuntimeError; } Problem::CyclicAlias(symbol, region, others) => { - let answer = crate::error::r#type::cyclic_alias(alloc, symbol, region, others); + let answer = crate::error::r#type::cyclic_alias(alloc, lines, symbol, region, others); doc = answer.0; title = answer.1; @@ -212,7 +233,7 @@ pub fn can_problem<'b>( alloc.symbol_unqualified(alias), alloc.reflow(" alias definition:"), ]), - alloc.region(variable_region), + alloc.region(lines.convert_region(variable_region)), alloc.reflow("Roc does not allow unused type alias parameters!"), // TODO add link to this guide section alloc.tip().append(alloc.reflow( @@ -225,7 +246,7 @@ pub fn can_problem<'b>( severity = Severity::RuntimeError; } Problem::BadRecursion(entries) => { - doc = to_circular_def_doc(alloc, &entries); + doc = to_circular_def_doc(alloc, lines, &entries); title = CIRCULAR_DEF.to_string(); severity = Severity::RuntimeError; } @@ -242,16 +263,16 @@ pub fn can_problem<'b>( alloc.reflow(" field twice!"), ]), alloc.region_all_the_things( - record_region, - replaced_region, - field_region, + lines.convert_region(record_region), + lines.convert_region(replaced_region), + lines.convert_region(field_region), Annotation::Error, ), alloc.reflow(r"In the rest of the program, I will only use the latter definition:"), alloc.region_all_the_things( - record_region, - field_region, - field_region, + lines.convert_region(record_region), + lines.convert_region(field_region), + lines.convert_region(field_region), Annotation::TypoSuggestion, ), alloc.concat(vec![ @@ -271,6 +292,7 @@ pub fn can_problem<'b>( } => { return to_invalid_optional_value_report( alloc, + lines, filename, field_name, field_region, @@ -290,16 +312,16 @@ pub fn can_problem<'b>( alloc.reflow(" field twice!"), ]), alloc.region_all_the_things( - record_region, - replaced_region, - field_region, + lines.convert_region(record_region), + lines.convert_region(replaced_region), + lines.convert_region(field_region), Annotation::Error, ), alloc.reflow("In the rest of the program, I will only use the latter definition:"), alloc.region_all_the_things( - record_region, - field_region, - field_region, + lines.convert_region(record_region), + lines.convert_region(field_region), + lines.convert_region(field_region), Annotation::TypoSuggestion, ), alloc.concat(vec![ @@ -325,16 +347,16 @@ pub fn can_problem<'b>( alloc.reflow(" tag twice!"), ]), alloc.region_all_the_things( - tag_union_region, - replaced_region, - tag_region, + lines.convert_region(tag_union_region), + lines.convert_region(replaced_region), + lines.convert_region(tag_region), Annotation::Error, ), alloc.reflow("In the rest of the program, I will only use the latter definition:"), alloc.region_all_the_things( - tag_union_region, - tag_region, - tag_region, + lines.convert_region(tag_union_region), + lines.convert_region(tag_region), + lines.convert_region(tag_region), Annotation::TypoSuggestion, ), alloc.concat(vec![ @@ -355,7 +377,9 @@ pub fn can_problem<'b>( alloc.reflow( "This annotation does not match the definition immediately following it:", ), - alloc.region(Region::span_across(annotation_pattern, def_pattern)), + alloc.region( + lines.convert_region(Region::span_across(annotation_pattern, def_pattern)), + ), alloc.reflow("Is it a typo? If not, put either a newline or comment between them."), ]); @@ -369,7 +393,7 @@ pub fn can_problem<'b>( alloc.symbol_unqualified(alias_name), alloc.reflow(" is not what I expect:"), ]), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("Only type variables like "), alloc.type_variable("a".into()), @@ -385,7 +409,7 @@ pub fn can_problem<'b>( Problem::InvalidHexadecimal(region) => { doc = alloc.stack(vec![ alloc.reflow("This unicode code point is invalid:"), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"I was expecting a hexadecimal number, like "), alloc.parser_suggestion("\\u(1100)"), @@ -402,7 +426,7 @@ pub fn can_problem<'b>( Problem::InvalidUnicodeCodePt(region) => { doc = alloc.stack(vec![ alloc.reflow("This unicode code point is invalid:"), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.reflow("Learn more about working with unicode in roc at TODO"), ]); @@ -412,7 +436,7 @@ pub fn can_problem<'b>( Problem::InvalidInterpolation(region) => { doc = alloc.stack(vec![ alloc.reflow("This string interpolation is invalid:"), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.concat(vec![ alloc.reflow(r"I was expecting an identifier, like "), alloc.parser_suggestion("\\u(message)"), @@ -427,12 +451,40 @@ pub fn can_problem<'b>( severity = Severity::RuntimeError; } Problem::RuntimeError(runtime_error) => { - let answer = pretty_runtime_error(alloc, runtime_error); + let answer = pretty_runtime_error(alloc, lines, runtime_error); doc = answer.0; title = answer.1.to_string(); severity = Severity::RuntimeError; } + Problem::NestedDatatype { + alias, + def_region, + differing_recursion_region, + } => { + doc = alloc.stack(vec![ + alloc.concat(vec![ + alloc.symbol_unqualified(alias), + alloc.reflow(" is a nested datatype. Here is one recursive usage of it:"), + ]), + alloc.region(lines.convert_region(differing_recursion_region)), + alloc.concat(vec![ + alloc.reflow("But recursive usages of "), + alloc.symbol_unqualified(alias), + alloc.reflow(" must match its definition:"), + ]), + alloc.region(lines.convert_region(def_region)), + alloc.reflow("Nested datatypes are not supported in Roc."), + alloc.concat(vec![ + alloc.hint("Consider rewriting the definition of "), + alloc.symbol_unqualified(alias), + alloc.text(" to use the recursive type with the same arguments."), + ]), + ]); + + title = NESTED_DATATYPE.to_string(); + severity = Severity::RuntimeError; + } }; Report { @@ -445,12 +497,19 @@ pub fn can_problem<'b>( fn to_invalid_optional_value_report<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, filename: PathBuf, field_name: Lowercase, field_region: Region, record_region: Region, -) -> Report { - let doc = to_invalid_optional_value_report_help(alloc, field_name, field_region, record_region); +) -> Report<'b> { + let doc = to_invalid_optional_value_report_help( + alloc, + lines, + field_name, + field_region, + record_region, + ); Report { title: "BAD OPTIONAL VALUE".to_string(), @@ -462,6 +521,7 @@ fn to_invalid_optional_value_report<'b>( fn to_invalid_optional_value_report_help<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, field_name: Lowercase, field_region: Region, record_region: Region, @@ -472,7 +532,12 @@ fn to_invalid_optional_value_report_help<'b>( alloc.record_field(field_name), alloc.reflow(" field in an incorrect context!"), ]), - alloc.region_all_the_things(record_region, field_region, field_region, Annotation::Error), + alloc.region_all_the_things( + lines.convert_region(record_region), + lines.convert_region(field_region), + lines.convert_region(field_region), + Annotation::Error, + ), alloc.reflow(r"You can only use optional values in record destructuring, like:"), alloc .reflow(r"{ answer ? 42, otherField } = myRecord") @@ -482,6 +547,7 @@ fn to_invalid_optional_value_report_help<'b>( fn to_bad_ident_expr_report<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, bad_ident: roc_parse::ident::BadIdent, surroundings: Region, ) -> RocDocBuilder<'b> { @@ -490,11 +556,11 @@ fn to_bad_ident_expr_report<'b>( match bad_ident { Start(_) | Space(_, _) => unreachable!("these are handled in the parser"), WeirdDotAccess(pos) | StrayDot(pos) => { - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); alloc.stack(vec![ alloc.reflow(r"I trying to parse a record field access here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("So I expect to see a lowercase letter next, like "), alloc.parser_suggestion(".name"), @@ -507,7 +573,7 @@ fn to_bad_ident_expr_report<'b>( WeirdAccessor(_pos) => alloc.stack(vec![ alloc.reflow("I am very confused by this field access"), - alloc.region(surroundings), + alloc.region(lines.convert_region(surroundings)), alloc.concat(vec![ alloc.reflow("It looks like a field access on an accessor. I parse"), alloc.parser_suggestion(".client.name"), @@ -521,11 +587,11 @@ fn to_bad_ident_expr_report<'b>( ]), WeirdDotQualified(pos) => { - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); alloc.stack(vec![ alloc.reflow("I am trying to parse a qualified name here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I was expecting to see an identifier next, like "), alloc.parser_suggestion("height"), @@ -536,11 +602,11 @@ fn to_bad_ident_expr_report<'b>( ]) } QualifiedTag(pos) => { - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); alloc.stack(vec![ alloc.reflow("I am trying to parse a qualified name here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"This looks like a qualified tag name to me, "), alloc.reflow(r"but tags cannot be qualified! "), @@ -555,7 +621,10 @@ fn to_bad_ident_expr_report<'b>( let region = Region::new(surroundings.start(), pos); alloc.stack(vec![ alloc.reflow("Underscores are not allowed in identifier names:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion( + lines.convert_region(surroundings), + lines.convert_region(region), + ), alloc.concat(vec![alloc.reflow( r"I recommend using camelCase, it is the standard in the Roc ecosystem.", )]), @@ -564,12 +633,15 @@ fn to_bad_ident_expr_report<'b>( BadPrivateTag(pos) => { use BadIdentNext::*; - match what_is_next(alloc.src_lines, pos) { + match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { LowercaseAccess(width) => { let region = Region::new(pos, pos.bump_column(width)); alloc.stack(vec![ alloc.reflow("I am very confused by this field access:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion( + lines.convert_region(surroundings), + lines.convert_region(region), + ), alloc.concat(vec![ alloc.reflow(r"It looks like a record field access on a private tag.") ]), @@ -579,7 +651,10 @@ fn to_bad_ident_expr_report<'b>( let region = Region::new(pos, pos.bump_column(width)); alloc.stack(vec![ alloc.reflow("I am very confused by this expression:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion( + lines.convert_region(surroundings), + lines.convert_region(region), + ), alloc.concat(vec![ alloc.reflow( r"Looks like a private tag is treated like a module name. ", @@ -595,7 +670,10 @@ fn to_bad_ident_expr_report<'b>( Region::new(surroundings.start().bump_column(1), pos.bump_column(1)); alloc.stack(vec![ alloc.reflow("I am trying to parse a private tag here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion( + lines.convert_region(surroundings), + lines.convert_region(region), + ), alloc.concat(vec![ alloc.reflow(r"But after the "), alloc.keyword("@"), @@ -617,6 +695,7 @@ fn to_bad_ident_expr_report<'b>( fn to_bad_ident_pattern_report<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, bad_ident: roc_parse::ident::BadIdent, surroundings: Region, ) -> RocDocBuilder<'b> { @@ -625,11 +704,11 @@ fn to_bad_ident_pattern_report<'b>( match bad_ident { Start(_) | Space(_, _) => unreachable!("these are handled in the parser"), WeirdDotAccess(pos) | StrayDot(pos) => { - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); alloc.stack(vec![ alloc.reflow(r"I trying to parse a record field accessor here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("Something like "), alloc.parser_suggestion(".name"), @@ -642,7 +721,7 @@ fn to_bad_ident_pattern_report<'b>( WeirdAccessor(_pos) => alloc.stack(vec![ alloc.reflow("I am very confused by this field access"), - alloc.region(surroundings), + alloc.region(lines.convert_region(surroundings)), alloc.concat(vec![ alloc.reflow("It looks like a field access on an accessor. I parse"), alloc.parser_suggestion(".client.name"), @@ -656,11 +735,11 @@ fn to_bad_ident_pattern_report<'b>( ]), WeirdDotQualified(pos) => { - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); alloc.stack(vec![ alloc.reflow("I am trying to parse a qualified name here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I was expecting to see an identifier next, like "), alloc.parser_suggestion("height"), @@ -671,11 +750,11 @@ fn to_bad_ident_pattern_report<'b>( ]) } QualifiedTag(pos) => { - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); alloc.stack(vec![ alloc.reflow("I am trying to parse a qualified name here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"This looks like a qualified tag name to me, "), alloc.reflow(r"but tags cannot be qualified! "), @@ -687,14 +766,14 @@ fn to_bad_ident_pattern_report<'b>( } Underscore(pos) => { - let region = Region::from_pos(Position { - line: pos.line, - column: pos.column - 1, - }); + let region = Region::from_pos(pos.sub(1)); alloc.stack(vec![ alloc.reflow("I am trying to parse an identifier here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion( + lines.convert_region(surroundings), + lines.convert_region(region), + ), alloc.concat(vec![alloc.reflow( r"Underscores are not allowed in identifiers. Use camelCase instead!", )]), @@ -707,15 +786,15 @@ fn to_bad_ident_pattern_report<'b>( #[derive(Debug)] enum BadIdentNext<'a> { - LowercaseAccess(u16), - UppercaseAccess(u16), - NumberAccess(u16), + LowercaseAccess(u32), + UppercaseAccess(u32), + NumberAccess(u32), Keyword(&'a str), DanglingDot, Other(Option), } -fn what_is_next<'a>(source_lines: &'a [&'a str], pos: Position) -> BadIdentNext<'a> { +fn what_is_next<'a>(source_lines: &'a [&'a str], pos: LineColumn) -> BadIdentNext<'a> { let row_index = pos.line as usize; let col_index = pos.column as usize; match source_lines.get(row_index) { @@ -733,13 +812,13 @@ fn what_is_next<'a>(source_lines: &'a [&'a str], pos: Position) -> BadIdentNext< None => BadIdentNext::Other(None), Some('.') => match it.next() { Some(c) if c.is_lowercase() => { - BadIdentNext::LowercaseAccess(2 + till_whitespace(it) as u16) + BadIdentNext::LowercaseAccess(2 + till_whitespace(it) as u32) } Some(c) if c.is_uppercase() => { - BadIdentNext::UppercaseAccess(2 + till_whitespace(it) as u16) + BadIdentNext::UppercaseAccess(2 + till_whitespace(it) as u32) } Some(c) if c.is_ascii_digit() => { - BadIdentNext::NumberAccess(2 + till_whitespace(it) as u16) + BadIdentNext::NumberAccess(2 + till_whitespace(it) as u32) } _ => BadIdentNext::DanglingDot, }, @@ -770,6 +849,7 @@ where fn report_shadowing<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, original_region: Region, shadow: Loc, ) -> RocDocBuilder<'b> { @@ -780,15 +860,16 @@ fn report_shadowing<'b>( .text("The ") .append(alloc.ident(shadow.value)) .append(alloc.reflow(" name is first defined here:")), - alloc.region(original_region), + alloc.region(lines.convert_region(original_region)), alloc.reflow("But then it's defined a second time here:"), - alloc.region(shadow.region), + alloc.region(lines.convert_region(shadow.region)), alloc.reflow(line), ]) } fn pretty_runtime_error<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, runtime_error: RuntimeError, ) -> (RocDocBuilder<'b>, &'static str) { let doc; @@ -810,16 +891,23 @@ fn pretty_runtime_error<'b>( original_region, shadow, } => { - doc = report_shadowing(alloc, original_region, shadow); + doc = report_shadowing(alloc, lines, original_region, shadow); title = DUPLICATE_NAME; } RuntimeError::LookupNotInScope(loc_name, options) => { - doc = not_found(alloc, loc_name.region, &loc_name.value, "value", options); + doc = not_found( + alloc, + lines, + loc_name.region, + &loc_name.value, + "value", + options, + ); title = UNRECOGNIZED_NAME; } RuntimeError::CircularDef(entries) => { - doc = to_circular_def_doc(alloc, &entries); + doc = to_circular_def_doc(alloc, lines, &entries); title = CIRCULAR_DEF; } RuntimeError::MalformedPattern(problem, region) => { @@ -835,7 +923,7 @@ fn pretty_runtime_error<'b>( MalformedBase(Base::Decimal) => " integer ", BadIdent(bad_ident) => { title = NAMING_PROBLEM; - doc = to_bad_ident_pattern_report(alloc, bad_ident, region); + doc = to_bad_ident_pattern_report(alloc, lines, bad_ident, region); return (doc, title); } @@ -859,7 +947,7 @@ fn pretty_runtime_error<'b>( alloc.text(name), alloc.reflow("pattern is malformed:"), ]), - alloc.region(region), + alloc.region(lines.convert_region(region)), tip, ]); @@ -900,7 +988,7 @@ fn pretty_runtime_error<'b>( alloc.string(ident.to_string()), alloc.reflow("`:"), ]), - alloc.region(region), + alloc.region(lines.convert_region(region)), did_you_mean, ]); @@ -912,7 +1000,7 @@ fn pretty_runtime_error<'b>( imported_modules, region, } => { - doc = module_not_found(alloc, region, &module_name, imported_modules); + doc = module_not_found(alloc, lines, region, &module_name, imported_modules); title = MODULE_NOT_IMPORTED; } @@ -921,14 +1009,14 @@ fn pretty_runtime_error<'b>( unreachable!(); } RuntimeError::MalformedIdentifier(_box_str, bad_ident, surroundings) => { - doc = to_bad_ident_expr_report(alloc, bad_ident, surroundings); + doc = to_bad_ident_expr_report(alloc, lines, bad_ident, surroundings); title = SYNTAX_PROBLEM; } RuntimeError::MalformedTypeName(_box_str, surroundings) => { doc = alloc.stack(vec![ alloc.reflow(r"I am confused by this type name:"), - alloc.region(surroundings), + alloc.region(lines.convert_region(surroundings)), alloc.concat(vec![ alloc.reflow("Type names start with an uppercase letter, "), alloc.reflow("and can optionally be qualified by a module name, like "), @@ -962,7 +1050,7 @@ fn pretty_runtime_error<'b>( alloc.text(big_or_small), alloc.reflow(":"), ]), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.concat(vec![ alloc .reflow("Roc uses signed 64-bit floating points, allowing values between "), @@ -984,15 +1072,25 @@ fn pretty_runtime_error<'b>( alloc.concat(vec![ alloc.reflow("This float literal contains an invalid digit:"), ]), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.concat(vec![ - alloc.reflow("Floating point literals can only contain the digits 0-9, or use scientific notation 10e4"), + alloc.reflow("Floating point literals can only contain the digits 0-9, or use scientific notation 10e4, or have a float suffix."), ]), tip, ]); title = SYNTAX_PROBLEM; } + RuntimeError::InvalidFloat(FloatErrorKind::IntSuffix, region, _raw_str) => { + doc = alloc.stack(vec![ + alloc.concat(vec![alloc.reflow( + "This number literal is a float, but it has an integer suffix:", + )]), + alloc.region(lines.convert_region(region)), + ]); + + title = CONFLICTING_NUMBER_SUFFIX; + } RuntimeError::InvalidInt(error @ IntErrorKind::InvalidDigit, base, region, _raw_str) | RuntimeError::InvalidInt(error @ IntErrorKind::Empty, base, region, _raw_str) => { use roc_parse::ast::Base::*; @@ -1042,12 +1140,12 @@ fn pretty_runtime_error<'b>( alloc.text(problem), alloc.text(":"), ]), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.concat(vec![ alloc.text(plurals), contains, alloc.text(charset), - alloc.text("."), + alloc.text(", or have an integer suffix."), ]), tip, ]); @@ -1056,10 +1154,28 @@ fn pretty_runtime_error<'b>( } RuntimeError::InvalidInt(error_kind @ IntErrorKind::Underflow, _base, region, _raw_str) | RuntimeError::InvalidInt(error_kind @ IntErrorKind::Overflow, _base, region, _raw_str) => { - let big_or_small = if let IntErrorKind::Underflow = error_kind { - "small" + let (big_or_small, info) = if let IntErrorKind::Underflow = error_kind { + ( + "small", + alloc.concat(vec![ + alloc.reflow( + "The smallest number representable in Roc is the minimum I128 value, ", + ), + alloc.int_literal(i128::MIN), + alloc.text("."), + ]), + ) } else { - "big" + ( + "big", + alloc.concat(vec![ + alloc.reflow( + "The largest number representable in Roc is the maximum U128 value, ", + ), + alloc.int_literal(u128::MAX), + alloc.text("."), + ]), + ) }; let tip = alloc @@ -1072,13 +1188,73 @@ fn pretty_runtime_error<'b>( alloc.text(big_or_small), alloc.reflow(":"), ]), - alloc.region(region), - alloc.reflow("Roc uses signed 64-bit integers, allowing values between −9_223_372_036_854_775_808 and 9_223_372_036_854_775_807."), + alloc.region(lines.convert_region(region)), + info, tip, ]); title = SYNTAX_PROBLEM; } + RuntimeError::InvalidInt(IntErrorKind::FloatSuffix, _base, region, _raw_str) => { + doc = alloc.stack(vec![ + alloc.concat(vec![alloc.reflow( + "This number literal is an integer, but it has a float suffix:", + )]), + alloc.region(lines.convert_region(region)), + ]); + + title = CONFLICTING_NUMBER_SUFFIX; + } + RuntimeError::InvalidInt( + IntErrorKind::OverflowsSuffix { + suffix_type, + max_value, + }, + _base, + region, + _raw_str, + ) => { + doc = alloc.stack(vec![ + alloc.concat(vec![alloc.reflow( + "This integer literal overflows the type indicated by its suffix:", + )]), + alloc.region(lines.convert_region(region)), + alloc.tip().append(alloc.concat(vec![ + alloc.reflow("The suffix indicates this integer is a "), + alloc.type_str(suffix_type), + alloc.reflow(", whose maximum value is "), + alloc.int_literal(max_value), + alloc.reflow("."), + ])), + ]); + + title = NUMBER_OVERFLOWS_SUFFIX; + } + RuntimeError::InvalidInt( + IntErrorKind::UnderflowsSuffix { + suffix_type, + min_value, + }, + _base, + region, + _raw_str, + ) => { + doc = alloc.stack(vec![ + alloc.concat(vec![alloc.reflow( + "This integer literal underflows the type indicated by its suffix:", + )]), + alloc.region(lines.convert_region(region)), + alloc.tip().append(alloc.concat(vec![ + alloc.reflow("The suffix indicates this integer is a "), + alloc.type_str(suffix_type), + alloc.reflow(", whose minimum value is "), + alloc.int_literal(min_value), + alloc.reflow("."), + ])), + ]); + + title = NUMBER_UNDERFLOWS_SUFFIX; + } RuntimeError::InvalidOptionalValue { field_name, field_region, @@ -1086,6 +1262,7 @@ fn pretty_runtime_error<'b>( } => { doc = to_invalid_optional_value_report_help( alloc, + lines, field_name, field_region, record_region, @@ -1099,7 +1276,7 @@ fn pretty_runtime_error<'b>( alloc.reflow("This expression cannot be updated"), alloc.reflow(":"), ]), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.reflow("Only variables can be updated with record update syntax."), ]); @@ -1147,6 +1324,7 @@ fn pretty_runtime_error<'b>( fn to_circular_def_doc<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, entries: &[roc_problem::can::CycleEntry], ) -> RocDocBuilder<'b> { // TODO "are you trying to mutate a variable? @@ -1165,7 +1343,7 @@ fn to_circular_def_doc<'b>( .reflow("The ") .append(alloc.symbol_unqualified(first.symbol)) .append(alloc.reflow(" definition is causing a very tricky infinite loop:")), - alloc.region(first.symbol_region), + alloc.region(lines.convert_region(first.symbol_region)), alloc .reflow("The ") .append(alloc.symbol_unqualified(first.symbol)) @@ -1189,6 +1367,7 @@ fn to_circular_def_doc<'b>( fn not_found<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, region: roc_region::all::Region, name: &Ident, thing: &'b str, @@ -1230,13 +1409,14 @@ fn not_found<'b>( alloc.reflow("` "), alloc.reflow(thing), ]), - alloc.region(region), + alloc.region(lines.convert_region(region)), to_details(default_no, default_yes), ]) } fn module_not_found<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, region: roc_region::all::Region, name: &ModuleName, options: MutSet>, @@ -1275,7 +1455,7 @@ fn module_not_found<'b>( alloc.string(name.to_string()), alloc.reflow("` module is not imported:"), ]), - alloc.region(region), + alloc.region(lines.convert_region(region)), to_details(default_no, default_yes), ]) } diff --git a/reporting/src/error/mono.rs b/reporting/src/error/mono.rs index 3a7e0e7bf0..6b5518a717 100644 --- a/reporting/src/error/mono.rs +++ b/reporting/src/error/mono.rs @@ -1,9 +1,11 @@ use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity}; +use roc_region::all::LineInfo; use std::path::PathBuf; use ven_pretty::DocAllocator; pub fn mono_problem<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, filename: PathBuf, problem: roc_mono::ir::MonoProblem, ) -> Report<'b> { @@ -16,7 +18,7 @@ pub fn mono_problem<'b>( BadArg => { let doc = alloc.stack(vec![ alloc.reflow("This pattern does not cover all the possibilities:"), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.reflow("Other possibilities include:"), unhandled_patterns_to_doc_block(alloc, missing), alloc.concat(vec![ @@ -39,7 +41,7 @@ pub fn mono_problem<'b>( BadDestruct => { let doc = alloc.stack(vec![ alloc.reflow("This pattern does not cover all the possibilities:"), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.reflow("Other possibilities include:"), unhandled_patterns_to_doc_block(alloc, missing), alloc.concat(vec![ @@ -67,7 +69,7 @@ pub fn mono_problem<'b>( alloc.keyword("when"), alloc.reflow(" does not cover all the possibilities:"), ]), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.reflow("Other possibilities include:"), unhandled_patterns_to_doc_block(alloc, missing), alloc.reflow( @@ -96,7 +98,10 @@ pub fn mono_problem<'b>( alloc.string(index.ordinal()), alloc.reflow(" pattern is redundant:"), ]), - alloc.region_with_subregion(overall_region, branch_region), + alloc.region_with_subregion( + lines.convert_region(overall_region), + lines.convert_region(branch_region), + ), alloc.reflow( "Any value of this shape will be handled by \ a previous pattern, so this one should be removed.", @@ -143,6 +148,7 @@ fn pattern_to_doc_help<'b>( Anything => alloc.text("_"), Literal(l) => match l { Int(i) => alloc.text(i.to_string()), + U128(i) => alloc.text(i.to_string()), Bit(true) => alloc.text("True"), Bit(false) => alloc.text("False"), Byte(b) => alloc.text(b.to_string()), diff --git a/reporting/src/error/parse.rs b/reporting/src/error/parse.rs index 259239ac50..6f5aa33346 100644 --- a/reporting/src/error/parse.rs +++ b/reporting/src/error/parse.rs @@ -1,5 +1,5 @@ -use roc_parse::parser::{ParseProblem, SyntaxError}; -use roc_region::all::{Position, Region}; +use roc_parse::parser::{ENumber, FileError, SyntaxError}; +use roc_region::all::{LineColumn, LineColumnRegion, LineInfo, Position, Region}; use std::path::PathBuf; use crate::report::{Report, RocDocAllocator, RocDocBuilder, Severity}; @@ -7,11 +7,12 @@ use ven_pretty::DocAllocator; pub fn parse_problem<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, _starting_line: u32, - parse_problem: ParseProblem>, + parse_problem: FileError>, ) -> Report<'a> { - to_syntax_report(alloc, filename, &parse_problem.problem, parse_problem.pos) + to_syntax_report(alloc, lines, filename, &parse_problem.problem.problem) } fn note_for_record_type_indent<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuilder<'a> { @@ -58,9 +59,9 @@ fn record_patterns_look_like<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuilde fn to_syntax_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::SyntaxError<'a>, - start: Position, ) -> Report<'a> { use SyntaxError::*; @@ -71,26 +72,11 @@ fn to_syntax_report<'a>( severity: Severity::RuntimeError, }; - let region = Region::from_pos(start); - match parse_problem { - SyntaxError::ConditionFailed => { - let doc = alloc.stack(vec![ - alloc.reflow("A condition failed:"), - alloc.region(region), - ]); - - Report { - filename, - doc, - title: "PARSE PROBLEM".to_string(), - severity: Severity::RuntimeError, - } - } SyntaxError::ArgumentsBeforeEquals(region) => { let doc = alloc.stack(vec![ alloc.reflow("Unexpected tokens in front of the `=` symbol:"), - alloc.region(*region), + alloc.region(lines.convert_region(*region)), ]); Report { @@ -100,9 +86,10 @@ fn to_syntax_report<'a>( severity: Severity::RuntimeError, } } - Unexpected(mut region) => { + Unexpected(region) => { + let mut region = lines.convert_region(*region); if region.start().column == region.end().column { - region = Region::new(region.start(), region.end().bump_column(1)); + region = LineColumnRegion::new(region.start(), region.end().bump_column(1)); } let doc = alloc.stack(vec![ @@ -117,13 +104,11 @@ fn to_syntax_report<'a>( report(doc) } NotEndOfFile(pos) => { - let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I expected to reach the end of the file, but got stuck here:"), - alloc.region_with_subregion(surroundings, region), - alloc.concat(vec![alloc.reflow("no hints")]), + alloc.region(region), ]); Report { @@ -134,7 +119,10 @@ fn to_syntax_report<'a>( } } SyntaxError::Eof(region) => { - let doc = alloc.stack(vec![alloc.reflow("End of Field"), alloc.region(*region)]); + let doc = alloc.stack(vec![ + alloc.reflow("End of Field"), + alloc.region(lines.convert_region(*region)), + ]); Report { filename, @@ -153,16 +141,17 @@ fn to_syntax_report<'a>( severity: Severity::RuntimeError, } } - Type(typ) => to_type_report(alloc, filename, typ, Position::default()), - Pattern(pat) => to_pattern_report(alloc, filename, pat, Position::default()), - Expr(expr) => to_expr_report( + Type(typ) => to_type_report(alloc, lines, filename, typ, Position::default()), + Pattern(pat) => to_pattern_report(alloc, lines, filename, pat, Position::default()), + Expr(expr, start) => to_expr_report( alloc, + lines, filename, - Context::InDef(start), + Context::InDef(*start), expr, Position::default(), ), - Header(header) => to_header_report(alloc, filename, header, Position::default()), + Header(header) => to_header_report(alloc, lines, filename, header, Position::default()), _ => todo!("unhandled parse error: {:?}", parse_problem), } } @@ -189,6 +178,7 @@ enum Node { fn to_expr_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, context: Context, parse_problem: &roc_parse::parser::EExpr<'a>, @@ -197,22 +187,24 @@ fn to_expr_report<'a>( use roc_parse::parser::EExpr; match parse_problem { - EExpr::If(if_, pos) => to_if_report(alloc, filename, context, if_, *pos), - EExpr::When(when, pos) => to_when_report(alloc, filename, context, when, *pos), - EExpr::Lambda(lambda, pos) => to_lambda_report(alloc, filename, context, lambda, *pos), - EExpr::List(list, pos) => to_list_report(alloc, filename, context, list, *pos), - EExpr::Str(string, pos) => to_str_report(alloc, filename, context, string, *pos), - EExpr::InParens(expr, pos) => { - to_expr_in_parens_report(alloc, filename, context, expr, *pos) + EExpr::If(if_, pos) => to_if_report(alloc, lines, filename, context, if_, *pos), + EExpr::When(when, pos) => to_when_report(alloc, lines, filename, context, when, *pos), + EExpr::Lambda(lambda, pos) => { + to_lambda_report(alloc, lines, filename, context, lambda, *pos) } - EExpr::Type(tipe, pos) => to_type_report(alloc, filename, tipe, *pos), + EExpr::List(list, pos) => to_list_report(alloc, lines, filename, context, list, *pos), + EExpr::Str(string, pos) => to_str_report(alloc, lines, filename, context, string, *pos), + EExpr::InParens(expr, pos) => { + to_expr_in_parens_report(alloc, lines, filename, context, expr, *pos) + } + EExpr::Type(tipe, pos) => to_type_report(alloc, lines, filename, tipe, *pos), EExpr::ElmStyleFunction(region, pos) => { let surroundings = Region::new(start, *pos); - let region = *region; + let region = lines.convert_region(*region); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a definition, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("Looks like you are trying to define a function. "), alloc.reflow("In roc, functions are always written as a lambda, like "), @@ -231,7 +223,7 @@ fn to_expr_report<'a>( EExpr::BadOperator(op, pos) => { let surroundings = Region::new(start, *pos); - let region = Region::new(*pos, pos.bump_column(op.len() as u16)); + let region = Region::new(*pos, pos.bump_column(op.len() as u32)); let suggestion = match *op { "|" => vec![ @@ -263,7 +255,7 @@ fn to_expr_report<'a>( ])], "->" => match context { Context::InNode(Node::WhenBranch, _pos, _) => { - return to_unexpected_arrow_report(alloc, filename, *pos, start); + return to_unexpected_arrow_report(alloc, lines, filename, *pos, start); } _ => { vec![alloc.stack(vec![ @@ -301,7 +293,10 @@ fn to_expr_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"This looks like an operator, but it's not one I recognize!"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion( + lines.convert_region(surroundings), + lines.convert_region(region), + ), alloc.concat(suggestion), ]); @@ -317,11 +312,11 @@ fn to_expr_report<'a>( EExpr::QualifiedTag(pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am very confused by this identifier:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("Are you trying to qualify a name? I am execting something like "), alloc.parser_suggestion("Json.Decode.string"), @@ -399,7 +394,7 @@ fn to_expr_report<'a>( }; let surroundings = Region::new(context_pos, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.concat(vec![ @@ -407,7 +402,7 @@ fn to_expr_report<'a>( a_thing, alloc.reflow(", but I got stuck here:"), ]), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), expecting, ]); @@ -421,11 +416,11 @@ fn to_expr_report<'a>( EExpr::DefMissingFinalExpr(pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a definition, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("This definition is missing a final expression."), alloc.reflow(" A nested definition must be followed by"), @@ -447,17 +442,22 @@ fn to_expr_report<'a>( } } - EExpr::DefMissingFinalExpr2(expr, pos) => { - to_expr_report(alloc, filename, Context::InDefFinalExpr(start), expr, *pos) - } + EExpr::DefMissingFinalExpr2(expr, pos) => to_expr_report( + alloc, + lines, + filename, + Context::InDefFinalExpr(start), + expr, + *pos, + ), EExpr::BadExprEnd(pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("Whatever I am running into is confusing me a lot! "), alloc.reflow("Normally I can give fairly specific hints, "), @@ -475,11 +475,11 @@ fn to_expr_report<'a>( EExpr::Colon(pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a definition, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("Looks like you are trying to define a function. "), alloc.reflow("In roc, functions are always written as a lambda, like "), @@ -498,11 +498,11 @@ fn to_expr_report<'a>( EExpr::BackpassArrow(pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing an expression, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("Looks like you are trying to define a function. ") ]), @@ -516,7 +516,29 @@ fn to_expr_report<'a>( } } - EExpr::Space(error, pos) => to_space_report(alloc, filename, error, *pos), + EExpr::Record(_erecord, pos) => { + let surroundings = Region::new(start, *pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); + + let doc = alloc.stack(vec![ + alloc.reflow(r"I am partway through parsing an record, but I got stuck here:"), + alloc.region_with_subregion(lines.convert_region(surroundings), region), + alloc.concat(vec![alloc.reflow("TODO provide more context.")]), + ]); + + Report { + filename, + doc, + title: "RECORD PARSE PROBLEM".to_string(), + severity: Severity::RuntimeError, + } + } + + EExpr::Space(error, pos) => to_space_report(alloc, lines, filename, error, *pos), + + &EExpr::Number(ENumber::End, pos) => { + to_malformed_number_literal_report(alloc, lines, filename, pos) + } _ => todo!("unhandled parse error: {:?}", parse_problem), } @@ -524,6 +546,7 @@ fn to_expr_report<'a>( fn to_lambda_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, _context: Context, parse_problem: &roc_parse::parser::ELambda<'a>, @@ -532,15 +555,15 @@ fn to_lambda_report<'a>( use roc_parse::parser::ELambda; match *parse_problem { - ELambda::Arrow(pos) => match what_is_next(alloc.src_lines, pos) { + ELambda::Arrow(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Token("=>") => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc .reflow(r"I am partway through parsing a function argument list, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I was expecting a "), alloc.parser_suggestion("->"), @@ -557,12 +580,12 @@ fn to_lambda_report<'a>( } _ => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc .reflow(r"I am partway through parsing a function argument list, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I was expecting a "), alloc.parser_suggestion("->"), @@ -579,15 +602,15 @@ fn to_lambda_report<'a>( } }, - ELambda::Comma(pos) => match what_is_next(alloc.src_lines, pos) { + ELambda::Comma(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Token("=>") => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc .reflow(r"I am partway through parsing a function argument list, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I was expecting a "), alloc.parser_suggestion("->"), @@ -604,12 +627,12 @@ fn to_lambda_report<'a>( } _ => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc .reflow(r"I am partway through parsing a function argument list, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I was expecting a "), alloc.parser_suggestion("->"), @@ -626,15 +649,15 @@ fn to_lambda_report<'a>( } }, - ELambda::Arg(pos) => match what_is_next(alloc.src_lines, pos) { + ELambda::Arg(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Other(Some(',')) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc .reflow(r"I am partway through parsing a function argument list, but I got stuck at this comma:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I was expecting an argument pattern before this, "), alloc.reflow("so try adding an argument before the comma and see if that helps?"), @@ -650,12 +673,12 @@ fn to_lambda_report<'a>( } _ => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc .reflow(r"I am partway through parsing a function argument list, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I was expecting an argument pattern before this, "), alloc.reflow("so try adding an argument and see if that helps?"), @@ -674,13 +697,16 @@ fn to_lambda_report<'a>( ELambda::Start(_pos) => unreachable!("another branch would have been taken"), ELambda::Body(expr, pos) => { - to_expr_report(alloc, filename, Context::InDef(start), expr, pos) + to_expr_report(alloc, lines, filename, Context::InDef(start), expr, pos) } - ELambda::Pattern(ref pattern, pos) => to_pattern_report(alloc, filename, pattern, pos), - ELambda::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + ELambda::Pattern(ref pattern, pos) => { + to_pattern_report(alloc, lines, filename, pattern, pos) + } + ELambda::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), ELambda::IndentArrow(pos) => to_unfinished_lambda_report( alloc, + lines, filename, pos, start, @@ -693,6 +719,7 @@ fn to_lambda_report<'a>( ELambda::IndentBody(pos) => to_unfinished_lambda_report( alloc, + lines, filename, pos, start, @@ -705,6 +732,7 @@ fn to_lambda_report<'a>( ELambda::IndentArg(pos) => to_unfinished_lambda_report( alloc, + lines, filename, pos, start, @@ -720,20 +748,21 @@ fn to_lambda_report<'a>( fn to_unfinished_lambda_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, pos: Position, start: Position, message: RocDocBuilder<'a>, ) -> Report<'a> { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.concat(vec![ alloc.reflow(r"I was partway through parsing a "), alloc.reflow(r" function, but I got stuck here:"), ]), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), message, ]); @@ -747,6 +776,7 @@ fn to_unfinished_lambda_report<'a>( fn to_str_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, context: Context, parse_problem: &roc_parse::parser::EString<'a>, @@ -758,12 +788,13 @@ fn to_str_report<'a>( EString::Open(_pos) => unreachable!("another branch would be taken"), EString::Format(expr, pos) => to_expr_report( alloc, + lines, filename, Context::InNode(Node::StringFormat, start, Box::new(context)), expr, pos, ), - EString::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + EString::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), EString::UnknownEscape(pos) => { let surroundings = Region::new(start, pos); let region = Region::new(pos, pos.bump_column(2)); @@ -780,7 +811,10 @@ fn to_str_report<'a>( alloc.reflow(r"I was partway through parsing a "), alloc.reflow(r" string literal, but I got stuck here:"), ]), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion( + lines.convert_region(surroundings), + lines.convert_region(region), + ), alloc.concat(vec![ alloc.reflow(r"This is not an escape sequence I recognize."), alloc.reflow(r" After a backslash, I am looking for one of these:"), @@ -807,13 +841,13 @@ fn to_str_report<'a>( } EString::CodePtOpen(pos) | EString::CodePtEnd(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow( r"I am partway through parsing a unicode code point, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"I was expecting a hexadecimal number, like "), alloc.parser_suggestion("\\u(1100)"), @@ -833,11 +867,11 @@ fn to_str_report<'a>( } EString::FormatEnd(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I cannot find the end of this format expression:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"You could change it to something like "), alloc.parser_suggestion("\"The count is \\(count\\)\""), @@ -854,11 +888,11 @@ fn to_str_report<'a>( } EString::EndlessSingle(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I cannot find the end of this string:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"You could change it to something like "), alloc.parser_suggestion("\"to be or not to be\""), @@ -877,11 +911,11 @@ fn to_str_report<'a>( } EString::EndlessMulti(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I cannot find the end of this block string:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"You could change it to something like "), alloc.parser_suggestion("\"\"\"to be or not to be\"\"\""), @@ -902,6 +936,7 @@ fn to_str_report<'a>( } fn to_expr_in_parens_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, context: Context, parse_problem: &roc_parse::parser::EInParens<'a>, @@ -910,9 +945,10 @@ fn to_expr_in_parens_report<'a>( use roc_parse::parser::EInParens; match *parse_problem { - EInParens::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + EInParens::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), EInParens::Expr(expr, pos) => to_expr_report( alloc, + lines, filename, Context::InNode(Node::InsideParens, start, Box::new(context)), expr, @@ -920,12 +956,12 @@ fn to_expr_in_parens_report<'a>( ), EInParens::End(pos) | EInParens::IndentEnd(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc .reflow("I am partway through parsing a record pattern, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow( r"I was expecting to see a closing parenthesis next, so try adding a ", @@ -944,13 +980,13 @@ fn to_expr_in_parens_report<'a>( } EInParens::Open(pos) | EInParens::IndentOpen(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow( r"I just started parsing an expression in parentheses, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"An expression in parentheses looks like "), alloc.parser_suggestion("(32)"), @@ -972,6 +1008,7 @@ fn to_expr_in_parens_report<'a>( fn to_list_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, context: Context, parse_problem: &roc_parse::parser::EList<'a>, @@ -980,81 +1017,85 @@ fn to_list_report<'a>( use roc_parse::parser::EList; match *parse_problem { - EList::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + EList::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), EList::Expr(expr, pos) => to_expr_report( alloc, + lines, filename, Context::InNode(Node::ListElement, start, Box::new(context)), expr, pos, ), - EList::Open(pos) | EList::End(pos) => match what_is_next(alloc.src_lines, pos) { - Next::Other(Some(',')) => { - let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + EList::Open(pos) | EList::End(pos) => { + match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { + Next::Other(Some(',')) => { + let surroundings = Region::new(start, pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ - alloc.reflow( - r"I am partway through started parsing a list, but I got stuck here:", - ), - alloc.region_with_subregion(surroundings, region), - alloc.concat(vec![ - alloc.reflow(r"I was expecting to see a list entry before this comma, "), - alloc.reflow(r"so try adding a list entry"), - alloc.reflow(r" and see if that helps?"), - ]), - ]); - Report { - filename, - doc, - title: "UNFINISHED LIST".to_string(), - severity: Severity::RuntimeError, - } - } - _ => { - let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); - - let doc = alloc.stack(vec![ - alloc.reflow( - r"I am partway through started parsing a list, but I got stuck here:", - ), - alloc.region_with_subregion(surroundings, region), - alloc.concat(vec![ + let doc = alloc.stack(vec![ alloc.reflow( - r"I was expecting to see a closing square bracket before this, ", + r"I am partway through started parsing a list, but I got stuck here:", ), - alloc.reflow(r"so try adding a "), - alloc.parser_suggestion("]"), - alloc.reflow(r" and see if that helps?"), - ]), - alloc.concat(vec![ - alloc.note("When "), - alloc.reflow(r"I get stuck like this, "), - alloc.reflow(r"it usually means that there is a missing parenthesis "), - alloc.reflow(r"or bracket somewhere earlier. "), - alloc.reflow(r"It could also be a stray keyword or operator."), - ]), - ]); + alloc.region_with_subregion(lines.convert_region(surroundings), region), + alloc.concat(vec![ + alloc + .reflow(r"I was expecting to see a list entry before this comma, "), + alloc.reflow(r"so try adding a list entry"), + alloc.reflow(r" and see if that helps?"), + ]), + ]); + Report { + filename, + doc, + title: "UNFINISHED LIST".to_string(), + severity: Severity::RuntimeError, + } + } + _ => { + let surroundings = Region::new(start, pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - Report { - filename, - doc, - title: "UNFINISHED LIST".to_string(), - severity: Severity::RuntimeError, + let doc = alloc.stack(vec![ + alloc.reflow( + r"I am partway through started parsing a list, but I got stuck here:", + ), + alloc.region_with_subregion(lines.convert_region(surroundings), region), + alloc.concat(vec![ + alloc.reflow( + r"I was expecting to see a closing square bracket before this, ", + ), + alloc.reflow(r"so try adding a "), + alloc.parser_suggestion("]"), + alloc.reflow(r" and see if that helps?"), + ]), + alloc.concat(vec![ + alloc.note("When "), + alloc.reflow(r"I get stuck like this, "), + alloc.reflow(r"it usually means that there is a missing parenthesis "), + alloc.reflow(r"or bracket somewhere earlier. "), + alloc.reflow(r"It could also be a stray keyword or operator."), + ]), + ]); + + Report { + filename, + doc, + title: "UNFINISHED LIST".to_string(), + severity: Severity::RuntimeError, + } } } - }, + } EList::IndentOpen(pos) | EList::IndentEnd(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I cannot find the end of this list:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"You could change it to something like "), alloc.parser_suggestion("[ 1, 2, 3 ]"), @@ -1078,6 +1119,7 @@ fn to_list_report<'a>( fn to_if_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, context: Context, parse_problem: &roc_parse::parser::EIf<'a>, @@ -1086,10 +1128,11 @@ fn to_if_report<'a>( use roc_parse::parser::EIf; match *parse_problem { - EIf::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + EIf::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), EIf::Condition(expr, pos) => to_expr_report( alloc, + lines, filename, Context::InNode(Node::IfCondition, start, Box::new(context)), expr, @@ -1098,6 +1141,7 @@ fn to_if_report<'a>( EIf::ThenBranch(expr, pos) => to_expr_report( alloc, + lines, filename, Context::InNode(Node::IfThenBranch, start, Box::new(context)), expr, @@ -1106,6 +1150,7 @@ fn to_if_report<'a>( EIf::ElseBranch(expr, pos) => to_expr_report( alloc, + lines, filename, Context::InNode(Node::IfElseBranch, start, Box::new(context)), expr, @@ -1118,6 +1163,7 @@ fn to_if_report<'a>( EIf::Then(pos) | EIf::IndentThenBranch(pos) | EIf::IndentThenToken(pos) => { to_unfinished_if_report( alloc, + lines, filename, pos, start, @@ -1132,6 +1178,7 @@ fn to_if_report<'a>( EIf::Else(pos) | EIf::IndentElseBranch(pos) | EIf::IndentElseToken(pos) => { to_unfinished_if_report( alloc, + lines, filename, pos, start, @@ -1145,6 +1192,7 @@ fn to_if_report<'a>( EIf::IndentCondition(pos) => to_unfinished_if_report( alloc, + lines, filename, pos, start, @@ -1157,13 +1205,14 @@ fn to_if_report<'a>( fn to_unfinished_if_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, pos: Position, start: Position, message: RocDocBuilder<'a>, ) -> Report<'a> { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.concat(vec![ @@ -1171,7 +1220,7 @@ fn to_unfinished_if_report<'a>( alloc.keyword("if"), alloc.reflow(r" expression, but I got stuck here:"), ]), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), message, ]); @@ -1185,6 +1234,7 @@ fn to_unfinished_if_report<'a>( fn to_when_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, context: Context, parse_problem: &roc_parse::parser::EWhen<'a>, @@ -1193,39 +1243,42 @@ fn to_when_report<'a>( use roc_parse::parser::EWhen; match *parse_problem { - EWhen::IfGuard(nested, pos) => match what_is_next(alloc.src_lines, pos) { - Next::Token("->") => { - let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + EWhen::IfGuard(nested, pos) => { + match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { + Next::Token("->") => { + let surroundings = Region::new(start, pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ - alloc.reflow( - r"I just started parsing an if guard, but there is no guard condition:", - ), - alloc.region_with_subregion(surroundings, region), - alloc.concat(vec![ - alloc.reflow("Try adding an expression before the arrow!") - ]), - ]); + let doc = alloc.stack(vec![ + alloc.reflow( + r"I just started parsing an if guard, but there is no guard condition:", + ), + alloc.region_with_subregion(lines.convert_region(surroundings), region), + alloc.concat(vec![ + alloc.reflow("Try adding an expression before the arrow!") + ]), + ]); - Report { - filename, - doc, - title: "IF GUARD NO CONDITION".to_string(), - severity: Severity::RuntimeError, + Report { + filename, + doc, + title: "IF GUARD NO CONDITION".to_string(), + severity: Severity::RuntimeError, + } } + _ => to_expr_report( + alloc, + lines, + filename, + Context::InNode(Node::WhenIfGuard, start, Box::new(context)), + nested, + pos, + ), } - _ => to_expr_report( - alloc, - filename, - Context::InNode(Node::WhenIfGuard, start, Box::new(context)), - nested, - pos, - ), - }, + } EWhen::Arrow(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.concat(vec![ @@ -1233,7 +1286,7 @@ fn to_when_report<'a>( alloc.keyword("when"), alloc.reflow(r" expression, but got stuck here:"), ]), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![alloc.reflow("I was expecting to see an arrow next.")]), note_for_when_indent_error(alloc), ]); @@ -1246,10 +1299,11 @@ fn to_when_report<'a>( } } - EWhen::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + EWhen::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), EWhen::Branch(expr, pos) => to_expr_report( alloc, + lines, filename, Context::InNode(Node::WhenBranch, start, Box::new(context)), expr, @@ -1258,6 +1312,7 @@ fn to_when_report<'a>( EWhen::Condition(expr, pos) => to_expr_report( alloc, + lines, filename, Context::InNode(Node::WhenCondition, start, Box::new(context)), expr, @@ -1266,6 +1321,7 @@ fn to_when_report<'a>( EWhen::Bar(pos) => to_unfinished_when_report( alloc, + lines, filename, pos, start, @@ -1281,6 +1337,7 @@ fn to_when_report<'a>( EWhen::Is(pos) | EWhen::IndentIs(pos) => to_unfinished_when_report( alloc, + lines, filename, pos, start, @@ -1293,6 +1350,7 @@ fn to_when_report<'a>( EWhen::IndentCondition(pos) => to_unfinished_when_report( alloc, + lines, filename, pos, start, @@ -1303,6 +1361,7 @@ fn to_when_report<'a>( EWhen::IndentPattern(pos) => to_unfinished_when_report( alloc, + lines, filename, pos, start, @@ -1311,6 +1370,7 @@ fn to_when_report<'a>( EWhen::IndentArrow(pos) => to_unfinished_when_report( alloc, + lines, filename, pos, start, @@ -1323,6 +1383,7 @@ fn to_when_report<'a>( EWhen::IndentIfGuard(pos) => to_unfinished_when_report( alloc, + lines, filename, pos, start, @@ -1335,6 +1396,7 @@ fn to_when_report<'a>( EWhen::IndentBranch(pos) => to_unfinished_when_report( alloc, + lines, filename, pos, start, @@ -1346,6 +1408,7 @@ fn to_when_report<'a>( EWhen::PatternAlignment(indent, pos) => to_unfinished_when_report( alloc, + lines, filename, pos, start, @@ -1355,23 +1418,24 @@ fn to_when_report<'a>( alloc.reflow(" spaces)"), ]), ), - EWhen::Pattern(ref pat, pos) => to_pattern_report(alloc, filename, pat, pos), + EWhen::Pattern(ref pat, pos) => to_pattern_report(alloc, lines, filename, pat, pos), } } fn to_unfinished_when_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, pos: Position, start: Position, message: RocDocBuilder<'a>, ) -> Report<'a> { - match what_is_next(alloc.src_lines, pos) { - Next::Token("->") => to_unexpected_arrow_report(alloc, filename, pos, start), + match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { + Next::Token("->") => to_unexpected_arrow_report(alloc, lines, filename, pos, start), _ => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.concat(vec![ @@ -1379,7 +1443,7 @@ fn to_unfinished_when_report<'a>( alloc.keyword("when"), alloc.reflow(r" expression, but I got stuck here:"), ]), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), message, note_for_when_error(alloc), ]); @@ -1396,6 +1460,7 @@ fn to_unfinished_when_report<'a>( fn to_unexpected_arrow_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, pos: Position, start: Position, @@ -1409,7 +1474,10 @@ fn to_unexpected_arrow_report<'a>( alloc.keyword("when"), alloc.reflow(r" expression right now, but this arrow is confusing me:"), ]), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion( + lines.convert_region(surroundings), + lines.convert_region(region), + ), alloc.concat(vec![ alloc.reflow(r"It makes sense to see arrows around here, "), alloc.reflow(r"so I suspect it is something earlier."), @@ -1478,6 +1546,7 @@ fn note_for_when_indent_error<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuild fn to_pattern_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::EPattern<'a>, start: Position, @@ -1487,11 +1556,11 @@ fn to_pattern_report<'a>( match parse_problem { EPattern::Start(pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a pattern, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.note("I may be confused by indentation"), ]); @@ -1502,9 +1571,12 @@ fn to_pattern_report<'a>( severity: Severity::RuntimeError, } } - EPattern::Record(record, pos) => to_precord_report(alloc, filename, record, *pos), + EPattern::Record(record, pos) => to_precord_report(alloc, lines, filename, record, *pos), EPattern::PInParens(inparens, pos) => { - to_pattern_in_parens_report(alloc, filename, inparens, *pos) + to_pattern_in_parens_report(alloc, lines, filename, inparens, *pos) + } + &EPattern::NumLiteral(ENumber::End, pos) => { + to_malformed_number_literal_report(alloc, lines, filename, pos) } _ => todo!("unhandled parse error: {:?}", parse_problem), } @@ -1512,6 +1584,7 @@ fn to_pattern_report<'a>( fn to_precord_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::PRecord<'a>, start: Position, @@ -1519,14 +1592,14 @@ fn to_precord_report<'a>( use roc_parse::parser::PRecord; match *parse_problem { - PRecord::Open(pos) => match what_is_next(alloc.src_lines, pos) { + PRecord::Open(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Keyword(keyword) => { let surroundings = Region::new(start, pos); - let region = to_keyword_region(pos, keyword); + let region = to_keyword_region(lines.convert_pos(pos), keyword); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a record pattern, but I got stuck on this field name:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"Looks like you are trying to use "), alloc.keyword(keyword), @@ -1543,11 +1616,11 @@ fn to_precord_report<'a>( } _ => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a record pattern, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), record_patterns_look_like(alloc), ]); @@ -1562,13 +1635,13 @@ fn to_precord_report<'a>( PRecord::End(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - match what_is_next(alloc.src_lines, pos) { + match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Other(Some(c)) if c.is_alphabetic() => { let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a record pattern, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow( r"I was expecting to see a colon, question mark, comma or closing curly brace.", @@ -1586,7 +1659,7 @@ fn to_precord_report<'a>( _ => { let doc = alloc.stack(vec![ alloc.reflow("I am partway through parsing a record pattern, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow( r"I was expecting to see a closing curly brace before this, so try adding a ", @@ -1606,14 +1679,14 @@ fn to_precord_report<'a>( } } - PRecord::Field(pos) => match what_is_next(alloc.src_lines, pos) { + PRecord::Field(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Keyword(keyword) => { let surroundings = Region::new(start, pos); - let region = to_keyword_region(pos, keyword); + let region = to_keyword_region(lines.convert_pos(pos), keyword); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a record pattern, but I got stuck on this field name:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"Looks like you are trying to use "), alloc.keyword(keyword), @@ -1632,11 +1705,11 @@ fn to_precord_report<'a>( Next::Other(Some('}')) => unreachable!("or is it?"), _ => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a record pattern, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"I was expecting to see another record field defined next, so I am looking for a name like "), alloc.parser_suggestion("userName"), @@ -1662,10 +1735,11 @@ fn to_precord_report<'a>( unreachable!("because `{ foo }` is a valid field; the question mark is not required") } - PRecord::Pattern(pattern, pos) => to_pattern_report(alloc, filename, pattern, pos), + PRecord::Pattern(pattern, pos) => to_pattern_report(alloc, lines, filename, pattern, pos), PRecord::Expr(expr, pos) => to_expr_report( alloc, + lines, filename, Context::InNode( Node::RecordConditionalDefault, @@ -1678,11 +1752,11 @@ fn to_precord_report<'a>( PRecord::IndentOpen(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a record pattern, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), record_patterns_look_like(alloc), note_for_record_pattern_indent(alloc), ]); @@ -1695,12 +1769,13 @@ fn to_precord_report<'a>( } } - PRecord::IndentEnd(pos) => match next_line_starts_with_close_curly(alloc.src_lines, pos) { - Some(curly_pos) => { - let surroundings = Region::new(start, curly_pos); - let region = Region::from_pos(curly_pos); + PRecord::IndentEnd(pos) => { + match next_line_starts_with_close_curly(alloc.src_lines, lines.convert_pos(pos)) { + Some(curly_pos) => { + let surroundings = LineColumnRegion::new(lines.convert_pos(start), curly_pos); + let region = LineColumnRegion::from_pos(curly_pos); - let doc = alloc.stack(vec![ + let doc = alloc.stack(vec![ alloc.reflow( "I am partway through parsing a record pattern, but I got stuck here:", ), @@ -1710,39 +1785,40 @@ fn to_precord_report<'a>( ]), ]); - Report { - filename, - doc, - title: "NEED MORE INDENTATION".to_string(), - severity: Severity::RuntimeError, + Report { + filename, + doc, + title: "NEED MORE INDENTATION".to_string(), + severity: Severity::RuntimeError, + } + } + None => { + let surroundings = Region::new(start, pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); + + let doc = alloc.stack(vec![ + alloc.reflow( + r"I am partway through parsing a record pattern, but I got stuck here:", + ), + alloc.region_with_subregion(lines.convert_region(surroundings), region), + alloc.concat(vec![ + alloc.reflow("I was expecting to see a closing curly "), + alloc.reflow("brace before this, so try adding a "), + alloc.parser_suggestion("}"), + alloc.reflow(" and see if that helps?"), + ]), + note_for_record_pattern_indent(alloc), + ]); + + Report { + filename, + doc, + title: "UNFINISHED RECORD PATTERN".to_string(), + severity: Severity::RuntimeError, + } } } - None => { - let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); - - let doc = alloc.stack(vec![ - alloc.reflow( - r"I am partway through parsing a record pattern, but I got stuck here:", - ), - alloc.region_with_subregion(surroundings, region), - alloc.concat(vec![ - alloc.reflow("I was expecting to see a closing curly "), - alloc.reflow("brace before this, so try adding a "), - alloc.parser_suggestion("}"), - alloc.reflow(" and see if that helps?"), - ]), - note_for_record_pattern_indent(alloc), - ]); - - Report { - filename, - doc, - title: "UNFINISHED RECORD PATTERN".to_string(), - severity: Severity::RuntimeError, - } - } - }, + } PRecord::IndentColon(_) => { unreachable!("because `{ foo }` is a valid field; the colon is not required") @@ -1752,12 +1828,13 @@ fn to_precord_report<'a>( unreachable!("because `{ foo }` is a valid field; the question mark is not required") } - PRecord::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + PRecord::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), } } fn to_pattern_in_parens_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::PInParens<'a>, start: Position, @@ -1768,13 +1845,13 @@ fn to_pattern_in_parens_report<'a>( PInParens::Open(pos) => { // `Open` case is for exhaustiveness, this case shouldn not be reachable practically. let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow( r"I just started parsing a pattern in parentheses, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"A pattern in parentheses looks like "), alloc.parser_suggestion("(Ok 32)"), @@ -1794,11 +1871,11 @@ fn to_pattern_in_parens_report<'a>( PInParens::End(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow("I am partway through parsing a pattern in parentheses, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow( r"I was expecting to see a closing parenthesis before this, so try adding a ", @@ -1816,17 +1893,17 @@ fn to_pattern_in_parens_report<'a>( } } - PInParens::Pattern(pattern, pos) => to_pattern_report(alloc, filename, pattern, pos), + PInParens::Pattern(pattern, pos) => to_pattern_report(alloc, lines, filename, pattern, pos), PInParens::IndentOpen(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow( r"I just started parsing a pattern in parentheses, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), record_patterns_look_like(alloc), note_for_record_pattern_indent(alloc), ]); @@ -1840,10 +1917,10 @@ fn to_pattern_in_parens_report<'a>( } PInParens::IndentEnd(pos) => { - match next_line_starts_with_close_parenthesis(alloc.src_lines, pos) { + match next_line_starts_with_close_parenthesis(alloc.src_lines, lines.convert_pos(pos)) { Some(curly_pos) => { - let surroundings = Region::new(start, curly_pos); - let region = Region::from_pos(curly_pos); + let surroundings = LineColumnRegion::new(lines.convert_pos(start), curly_pos); + let region = LineColumnRegion::from_pos(curly_pos); let doc = alloc.stack(vec![ alloc.reflow( @@ -1864,13 +1941,13 @@ fn to_pattern_in_parens_report<'a>( } None => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow( r"I am partway through parsing a pattern in parentheses, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I was expecting to see a closing parenthesis "), alloc.reflow("before this, so try adding a "), @@ -1890,12 +1967,35 @@ fn to_pattern_in_parens_report<'a>( } } - PInParens::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + PInParens::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), + } +} + +fn to_malformed_number_literal_report<'a>( + alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, + filename: PathBuf, + start: Position, +) -> Report<'a> { + let surroundings = Region::new(start, start); + let region = LineColumnRegion::from_pos(lines.convert_pos(start)); + + let doc = alloc.stack(vec![ + alloc.reflow(r"This number literal is malformed:"), + alloc.region_with_subregion(lines.convert_region(surroundings), region), + ]); + + Report { + filename, + doc, + title: "INVALID NUMBER LITERAL".to_string(), + severity: Severity::RuntimeError, } } fn to_type_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::EType<'a>, start: Position, @@ -1903,40 +2003,46 @@ fn to_type_report<'a>( use roc_parse::parser::EType; match parse_problem { - EType::TRecord(record, pos) => to_trecord_report(alloc, filename, record, *pos), - EType::TTagUnion(tag_union, pos) => to_ttag_union_report(alloc, filename, tag_union, *pos), - EType::TInParens(tinparens, pos) => to_tinparens_report(alloc, filename, tinparens, *pos), - EType::TApply(tapply, pos) => to_tapply_report(alloc, filename, tapply, *pos), - EType::TInlineAlias(talias, _) => to_talias_report(alloc, filename, talias), + EType::TRecord(record, pos) => to_trecord_report(alloc, lines, filename, record, *pos), + EType::TTagUnion(tag_union, pos) => { + to_ttag_union_report(alloc, lines, filename, tag_union, *pos) + } + EType::TInParens(tinparens, pos) => { + to_tinparens_report(alloc, lines, filename, tinparens, *pos) + } + EType::TApply(tapply, pos) => to_tapply_report(alloc, lines, filename, tapply, *pos), + EType::TInlineAlias(talias, _) => to_talias_report(alloc, lines, filename, talias), - EType::TFunctionArgument(pos) => match what_is_next(alloc.src_lines, *pos) { - Next::Other(Some(',')) => { - let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + EType::TFunctionArgument(pos) => { + match what_is_next(alloc.src_lines, lines.convert_pos(*pos)) { + Next::Other(Some(',')) => { + let surroundings = Region::new(start, *pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a function argument type, but I encountered two commas in a row:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![alloc.reflow("Try removing one of them.")]), ]); - Report { - filename, - doc, - title: "DOUBLE COMMA".to_string(), - severity: Severity::RuntimeError, + Report { + filename, + doc, + title: "DOUBLE COMMA".to_string(), + severity: Severity::RuntimeError, + } } + _ => todo!(), } - _ => todo!(), - }, + } EType::TStart(pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a type, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"I am expecting a type next, like "), alloc.parser_suggestion("Bool"), @@ -1956,11 +2062,11 @@ fn to_type_report<'a>( EType::TIndentStart(pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a type, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.note("I may be confused by indentation"), ]); @@ -1974,11 +2080,11 @@ fn to_type_report<'a>( EType::TIndentEnd(pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a type, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.note("I may be confused by indentation"), ]); @@ -1992,11 +2098,11 @@ fn to_type_report<'a>( EType::TAsIndentStart(pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing an inline type alias, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.note("I may be confused by indentation"), ]); @@ -2010,11 +2116,11 @@ fn to_type_report<'a>( EType::TBadTypeVariable(pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am expecting a type variable, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), ]); Report { @@ -2031,6 +2137,7 @@ fn to_type_report<'a>( fn to_trecord_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::ETypeRecord<'a>, start: Position, @@ -2038,14 +2145,14 @@ fn to_trecord_report<'a>( use roc_parse::parser::ETypeRecord; match *parse_problem { - ETypeRecord::Open(pos) => match what_is_next(alloc.src_lines, pos) { + ETypeRecord::Open(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Keyword(keyword) => { let surroundings = Region::new(start, pos); - let region = to_keyword_region(pos, keyword); + let region = to_keyword_region(lines.convert_pos(pos), keyword); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a record type, but I got stuck on this field name:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"Looks like you are trying to use "), alloc.keyword(keyword), @@ -2062,11 +2169,11 @@ fn to_trecord_report<'a>( } _ => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a record type, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"Record types look like "), alloc.parser_suggestion("{ name : String, age : Int },"), @@ -2085,13 +2192,13 @@ fn to_trecord_report<'a>( ETypeRecord::End(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - match what_is_next(alloc.src_lines, pos) { + match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Other(Some(c)) if c.is_alphabetic() => { let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a record type, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow( r"I was expecting to see a colon, question mark, comma or closing curly brace.", @@ -2109,7 +2216,7 @@ fn to_trecord_report<'a>( _ => { let doc = alloc.stack(vec![ alloc.reflow("I am partway through parsing a record type, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow( r"I was expecting to see a closing curly brace before this, so try adding a ", @@ -2129,14 +2236,14 @@ fn to_trecord_report<'a>( } } - ETypeRecord::Field(pos) => match what_is_next(alloc.src_lines, pos) { + ETypeRecord::Field(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Keyword(keyword) => { let surroundings = Region::new(start, pos); - let region = to_keyword_region(pos, keyword); + let region = to_keyword_region(lines.convert_pos(pos), keyword); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a record type, but I got stuck on this field name:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"Looks like you are trying to use "), alloc.keyword(keyword), @@ -2155,11 +2262,11 @@ fn to_trecord_report<'a>( Next::Other(Some('}')) => unreachable!("or is it?"), _ => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a record type, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"I was expecting to see another record field defined next, so I am looking for a name like "), alloc.parser_suggestion("userName"), @@ -2185,15 +2292,15 @@ fn to_trecord_report<'a>( unreachable!("because `{ foo }` is a valid field; the question mark is not required") } - ETypeRecord::Type(tipe, pos) => to_type_report(alloc, filename, tipe, pos), + ETypeRecord::Type(tipe, pos) => to_type_report(alloc, lines, filename, tipe, pos), ETypeRecord::IndentOpen(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a record type, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"Record types look like "), alloc.parser_suggestion("{ name : String, age : Int },"), @@ -2211,10 +2318,10 @@ fn to_trecord_report<'a>( } ETypeRecord::IndentEnd(pos) => { - match next_line_starts_with_close_curly(alloc.src_lines, pos) { + match next_line_starts_with_close_curly(alloc.src_lines, lines.convert_pos(pos)) { Some(curly_pos) => { - let surroundings = Region::new(start, curly_pos); - let region = Region::from_pos(curly_pos); + let surroundings = LineColumnRegion::new(lines.convert_pos(start), curly_pos); + let region = LineColumnRegion::from_pos(curly_pos); let doc = alloc.stack(vec![ alloc.reflow( @@ -2235,13 +2342,13 @@ fn to_trecord_report<'a>( } None => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow( r"I am partway through parsing a record type, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I was expecting to see a closing curly "), alloc.reflow("brace before this, so try adding a "), @@ -2269,12 +2376,13 @@ fn to_trecord_report<'a>( unreachable!("because `{ foo }` is a valid field; the question mark is not required") } - ETypeRecord::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + ETypeRecord::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), } } fn to_ttag_union_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::ETypeTagUnion<'a>, start: Position, @@ -2282,14 +2390,14 @@ fn to_ttag_union_report<'a>( use roc_parse::parser::ETypeTagUnion; match *parse_problem { - ETypeTagUnion::Open(pos) => match what_is_next(alloc.src_lines, pos) { + ETypeTagUnion::Open(pos) => match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Keyword(keyword) => { let surroundings = Region::new(start, pos); - let region = to_keyword_region(pos, keyword); + let region = to_keyword_region(lines.convert_pos(pos), keyword); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a tag union, but I got stuck on this field name:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"Looks like you are trying to use "), alloc.keyword(keyword), @@ -2308,13 +2416,13 @@ fn to_ttag_union_report<'a>( debug_assert!(c.is_lowercase()); let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow( r"I am partway through parsing a tag union type, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.reflow(r"I was expecting to see a tag name."), hint_for_tag_name(alloc), ]); @@ -2328,11 +2436,11 @@ fn to_ttag_union_report<'a>( } _ => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a tag union type, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"Tag unions look like "), alloc.parser_suggestion("[ Many I64, None ],"), @@ -2351,9 +2459,9 @@ fn to_ttag_union_report<'a>( ETypeTagUnion::End(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - match what_is_next(alloc.src_lines, pos) { + match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Other(Some(c)) if c.is_alphabetic() => { debug_assert!(c.is_lowercase()); @@ -2361,7 +2469,7 @@ fn to_ttag_union_report<'a>( alloc.reflow( r"I am partway through parsing a tag union type, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.reflow(r"I was expecting to see a tag name."), hint_for_tag_name(alloc), ]); @@ -2378,7 +2486,7 @@ fn to_ttag_union_report<'a>( alloc.reflow( r"I am partway through parsing a tag union type, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.reflow(r"I was expecting to see a private tag name."), hint_for_private_tag_name(alloc), ]); @@ -2393,7 +2501,7 @@ fn to_ttag_union_report<'a>( _ => { let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a tag union type, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow( r"I was expecting to see a closing square bracket before this, so try adding a ", @@ -2413,15 +2521,15 @@ fn to_ttag_union_report<'a>( } } - ETypeTagUnion::Type(tipe, pos) => to_type_report(alloc, filename, tipe, pos), + ETypeTagUnion::Type(tipe, pos) => to_type_report(alloc, lines, filename, tipe, pos), ETypeTagUnion::IndentOpen(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a tag union type, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"Tag unions look like "), alloc.parser_suggestion("[ Many I64, None ],"), @@ -2439,10 +2547,13 @@ fn to_ttag_union_report<'a>( } ETypeTagUnion::IndentEnd(pos) => { - match next_line_starts_with_close_square_bracket(alloc.src_lines, pos) { + match next_line_starts_with_close_square_bracket( + alloc.src_lines, + lines.convert_pos(pos), + ) { Some(curly_pos) => { - let surroundings = Region::new(start, curly_pos); - let region = Region::from_pos(curly_pos); + let surroundings = LineColumnRegion::new(lines.convert_pos(start), curly_pos); + let region = LineColumnRegion::from_pos(curly_pos); let doc = alloc.stack(vec![ alloc.reflow( @@ -2463,13 +2574,13 @@ fn to_ttag_union_report<'a>( } None => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow( r"I am partway through parsing a tag union type, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I was expecting to see a closing square "), alloc.reflow("bracket before this, so try adding a "), @@ -2489,12 +2600,13 @@ fn to_ttag_union_report<'a>( } } - ETypeTagUnion::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + ETypeTagUnion::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), } } fn to_tinparens_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::ETypeInParens<'a>, start: Position, @@ -2503,14 +2615,14 @@ fn to_tinparens_report<'a>( match *parse_problem { ETypeInParens::Open(pos) => { - match what_is_next(alloc.src_lines, pos) { + match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Keyword(keyword) => { let surroundings = Region::new(start, pos); - let region = to_keyword_region(pos, keyword); + let region = to_keyword_region(lines.convert_pos(pos), keyword); let doc = alloc.stack(vec![ alloc.reflow(r"I just saw an open parenthesis, so I was expecting to see a type next."), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"Something like "), alloc.parser_suggestion("(List Person)"), @@ -2530,13 +2642,13 @@ fn to_tinparens_report<'a>( debug_assert!(c.is_lowercase()); let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow( r"I am partway through parsing a type in parentheses, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.reflow(r"I was expecting to see a tag name."), hint_for_tag_name(alloc), ]); @@ -2550,13 +2662,13 @@ fn to_tinparens_report<'a>( } _ => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow( r"I just started parsing a type in parentheses, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"Tag unions look like "), alloc.parser_suggestion("[ Many I64, None ],"), @@ -2576,9 +2688,9 @@ fn to_tinparens_report<'a>( ETypeInParens::End(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - match what_is_next(alloc.src_lines, pos) { + match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Other(Some(c)) if c.is_alphabetic() => { debug_assert!(c.is_lowercase()); @@ -2587,7 +2699,7 @@ fn to_tinparens_report<'a>( alloc.reflow( r"I am partway through parsing a type in parentheses, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.reflow(r"I was expecting to see a tag name."), hint_for_tag_name(alloc), ]); @@ -2602,7 +2714,7 @@ fn to_tinparens_report<'a>( _ => { let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a type in parentheses, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow( r"I was expecting to see a closing parenthesis before this, so try adding a ", @@ -2622,16 +2734,16 @@ fn to_tinparens_report<'a>( } } - ETypeInParens::Type(tipe, pos) => to_type_report(alloc, filename, tipe, pos), + ETypeInParens::Type(tipe, pos) => to_type_report(alloc, lines, filename, tipe, pos), ETypeInParens::IndentOpen(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc .reflow(r"I just started parsing a type in parentheses, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow(r"Tag unions look like "), alloc.parser_suggestion("[ Many I64, None ],"), @@ -2649,10 +2761,10 @@ fn to_tinparens_report<'a>( } ETypeInParens::IndentEnd(pos) => { - match next_line_starts_with_close_parenthesis(alloc.src_lines, pos) { + match next_line_starts_with_close_parenthesis(alloc.src_lines, lines.convert_pos(pos)) { Some(curly_pos) => { - let surroundings = Region::new(start, curly_pos); - let region = Region::from_pos(curly_pos); + let surroundings = LineColumnRegion::new(lines.convert_pos(start), curly_pos); + let region = LineColumnRegion::from_pos(curly_pos); let doc = alloc.stack(vec![ alloc.reflow( @@ -2673,13 +2785,13 @@ fn to_tinparens_report<'a>( } None => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow( r"I am partway through parsing a type in parentheses, but I got stuck here:", ), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I was expecting to see a parenthesis "), alloc.reflow("before this, so try adding a "), @@ -2699,12 +2811,13 @@ fn to_tinparens_report<'a>( } } - ETypeInParens::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + ETypeInParens::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), } } fn to_tapply_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::ETypeApply, _start: Position, @@ -2713,7 +2826,7 @@ fn to_tapply_report<'a>( match *parse_problem { ETypeApply::DoubleDot(pos) => { - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I encountered two dots in a row:"), @@ -2729,7 +2842,7 @@ fn to_tapply_report<'a>( } } ETypeApply::TrailingDot(pos) => { - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I encountered a dot with nothing after it:"), @@ -2751,7 +2864,7 @@ fn to_tapply_report<'a>( } } ETypeApply::StartIsNumber(pos) => { - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I encountered a number at the start of a qualified name segment:"), @@ -2773,7 +2886,7 @@ fn to_tapply_report<'a>( } } ETypeApply::StartNotUppercase(pos) => { - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I encountered a lowercase letter at the start of a qualified name segment:"), @@ -2796,7 +2909,7 @@ fn to_tapply_report<'a>( } ETypeApply::End(pos) => { - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow( @@ -2813,12 +2926,13 @@ fn to_tapply_report<'a>( } } - ETypeApply::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + ETypeApply::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), } } fn to_talias_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::ETypeInlineAlias, ) -> Report<'a> { @@ -2834,7 +2948,7 @@ fn to_talias_report<'a>( alloc.keyword("as"), alloc.reflow(" is not a type alias:"), ]), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.concat(vec![ alloc.reflow("Inline alias types must start with an uppercase identifier and be followed by zero or more type arguments, like "), alloc.type_str("Point"), @@ -2856,7 +2970,7 @@ fn to_talias_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"This type alias has a qualified name:"), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.reflow("An alias introduces a new name to the current scope, so it must be unqualified."), ]); @@ -2872,7 +2986,7 @@ fn to_talias_report<'a>( let doc = alloc.stack(vec![ alloc.reflow(r"This alias type argument is not lowercase:"), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.reflow("All type arguments must be lowercase."), ]); @@ -2888,6 +3002,7 @@ fn to_talias_report<'a>( fn to_header_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::EHeader<'a>, start: Position, @@ -2895,25 +3010,29 @@ fn to_header_report<'a>( use roc_parse::parser::EHeader; match parse_problem { - EHeader::Provides(provides, pos) => to_provides_report(alloc, filename, provides, *pos), + EHeader::Provides(provides, pos) => { + to_provides_report(alloc, lines, filename, provides, *pos) + } - EHeader::Exposes(exposes, pos) => to_exposes_report(alloc, filename, exposes, *pos), + EHeader::Exposes(exposes, pos) => to_exposes_report(alloc, lines, filename, exposes, *pos), - EHeader::Imports(imports, pos) => to_imports_report(alloc, filename, imports, *pos), + EHeader::Imports(imports, pos) => to_imports_report(alloc, lines, filename, imports, *pos), - EHeader::Requires(requires, pos) => to_requires_report(alloc, filename, requires, *pos), + EHeader::Requires(requires, pos) => { + to_requires_report(alloc, lines, filename, requires, *pos) + } - EHeader::Packages(packages, pos) => to_packages_report(alloc, filename, packages, *pos), - - EHeader::Effects(effects, pos) => to_effects_report(alloc, filename, effects, *pos), + EHeader::Packages(packages, pos) => { + to_packages_report(alloc, lines, filename, packages, *pos) + } EHeader::IndentStart(pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![alloc.reflow("I may be confused by indentation.")]), ]); @@ -2927,11 +3046,11 @@ fn to_header_report<'a>( EHeader::Start(pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am expecting a header, but got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I am expecting a module keyword next, one of "), alloc.keyword("interface"), @@ -2953,11 +3072,11 @@ fn to_header_report<'a>( EHeader::ModuleName(pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I am expecting a module name next, like "), alloc.parser_suggestion("BigNum"), @@ -2977,11 +3096,11 @@ fn to_header_report<'a>( EHeader::AppName(_, pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I am expecting an application name next, like "), alloc.parser_suggestion("app \"main\""), @@ -3001,11 +3120,11 @@ fn to_header_report<'a>( EHeader::PlatformName(_, pos) => { let surroundings = Region::new(start, *pos); - let region = Region::from_pos(*pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I am expecting a platform name next, like "), alloc.parser_suggestion("\"roc/core\""), @@ -3021,12 +3140,103 @@ fn to_header_report<'a>( } } - EHeader::Space(error, pos) => to_space_report(alloc, filename, error, *pos), + EHeader::Space(error, pos) => to_space_report(alloc, lines, filename, error, *pos), + EHeader::Generates(_, pos) => { + let surroundings = Region::new(start, *pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); + + let doc = alloc.stack(vec![ + alloc.reflow(r"I am partway through parsing a header, but got stuck here:"), + alloc.region_with_subregion(lines.convert_region(surroundings), region), + alloc.concat(vec![ + alloc.reflow("I am expecting a type name next, like "), + alloc.parser_suggestion("Effect"), + alloc.reflow(". Type names must start with an uppercase letter."), + ]), + ]); + + Report { + filename, + doc, + title: "WEIRD GENERATED TYPE NAME".to_string(), + severity: Severity::RuntimeError, + } + } + EHeader::GeneratesWith(generates_with, pos) => { + to_generates_with_report(alloc, lines, filename, generates_with, *pos) + } + } +} + +fn to_generates_with_report<'a>( + alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, + filename: PathBuf, + parse_problem: &roc_parse::parser::EGeneratesWith, + start: Position, +) -> Report<'a> { + use roc_parse::parser::EGeneratesWith; + + match *parse_problem { + EGeneratesWith::ListEnd(pos) | // TODO: give this its own error message + EGeneratesWith::Identifier(pos) => { + let surroundings = Region::new(start, pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); + + let doc = alloc.stack(vec![ + alloc + .reflow(r"I am partway through parsing a provides list, but I got stuck here:"), + alloc.region_with_subregion(lines.convert_region(surroundings), region), + alloc.concat(vec![alloc.reflow( + "I was expecting a type name, value name or function name next, like", + )]), + alloc + .parser_suggestion("provides [ Animal, default, tame ]") + .indent(4), + ]); + + Report { + filename, + doc, + title: "WEIRD GENERATES".to_string(), + severity: Severity::RuntimeError, + } + } + + EGeneratesWith::With(pos) => { + let surroundings = Region::new(start, pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); + + let doc = alloc.stack(vec![ + alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), + alloc.region_with_subregion(lines.convert_region(surroundings), region), + alloc.concat(vec![ + alloc.reflow("I am expecting the "), + alloc.keyword("with"), + alloc.reflow(" keyword next, like "), + ]), + alloc + .parser_suggestion("with [ after, map ]") + .indent(4), + ]); + + Report { + filename, + doc, + title: "WEIRD GENERATES".to_string(), + severity: Severity::RuntimeError, + } + } + + EGeneratesWith::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), + + _ => todo!("unhandled parse error {:?}", parse_problem), } } fn to_provides_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::EProvides, start: Position, @@ -3037,12 +3247,12 @@ fn to_provides_report<'a>( EProvides::ListEnd(pos) | // TODO: give this its own error message EProvides::Identifier(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc .reflow(r"I am partway through parsing a provides list, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![alloc.reflow( "I was expecting a type name, value name or function name next, like", )]), @@ -3061,11 +3271,11 @@ fn to_provides_report<'a>( EProvides::Provides(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I am expecting the "), alloc.keyword("provides"), @@ -3084,7 +3294,7 @@ fn to_provides_report<'a>( } } - EProvides::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + EProvides::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), _ => todo!("unhandled parse error {:?}", parse_problem), } @@ -3092,6 +3302,7 @@ fn to_provides_report<'a>( fn to_exposes_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::EExposes, start: Position, @@ -3102,11 +3313,11 @@ fn to_exposes_report<'a>( EExposes::ListEnd(pos) | // TODO: give this its own error message EExposes::Identifier(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ - alloc.reflow(r"I am partway through parsing a exposes list, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.reflow(r"I am partway through parsing an `exposes` list, but I got stuck here:"), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![alloc.reflow( "I was expecting a type name, value name or function name next, like", )]), @@ -3125,11 +3336,11 @@ fn to_exposes_report<'a>( EExposes::Exposes(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I am expecting the "), alloc.keyword("exposes"), @@ -3148,14 +3359,15 @@ fn to_exposes_report<'a>( } } - EExposes::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + EExposes::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), - _ => todo!("unhandled parse error {:?}", parse_problem), + _ => todo!("unhandled `exposes` parsing error {:?}", parse_problem), } } fn to_imports_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::EImports, start: Position, @@ -3165,11 +3377,11 @@ fn to_imports_report<'a>( match *parse_problem { EImports::Identifier(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a imports list, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![alloc.reflow( "I was expecting a type name, value name or function name next, like ", )]), @@ -3188,11 +3400,11 @@ fn to_imports_report<'a>( EImports::Imports(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I am expecting the "), alloc.keyword("imports"), @@ -3211,15 +3423,15 @@ fn to_imports_report<'a>( } } - EImports::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + EImports::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), EImports::ModuleName(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I am expecting a module name next, like "), alloc.parser_suggestion("BigNum"), @@ -3243,6 +3455,7 @@ fn to_imports_report<'a>( fn to_requires_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::ERequires<'a>, start: Position, @@ -3252,11 +3465,11 @@ fn to_requires_report<'a>( match *parse_problem { ERequires::Requires(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I am expecting the "), alloc.keyword("requires"), @@ -3275,15 +3488,15 @@ fn to_requires_report<'a>( } } - ERequires::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + ERequires::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), ERequires::ListStart(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I am expecting the "), alloc.keyword("requires"), @@ -3304,11 +3517,11 @@ fn to_requires_report<'a>( ERequires::Rigid(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I am expecting a list of rigids like "), alloc.keyword("{}"), @@ -3331,12 +3544,42 @@ fn to_requires_report<'a>( } } + ERequires::Open(pos) => { + let surroundings = Region::new(start, pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); + + let doc = alloc.stack(vec![ + alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), + alloc.region_with_subregion(lines.convert_region(surroundings), region), + alloc.concat(vec![ + alloc.reflow("I am expecting a list of type names like "), + alloc.keyword("{}"), + alloc.reflow(" or "), + alloc.keyword("{ Model }"), + alloc.reflow(" next. A full "), + alloc.keyword("requires"), + alloc.reflow(" definition looks like"), + ]), + alloc + .parser_suggestion("requires { Model, Msg } {main : Effect {}}") + .indent(4), + ]); + + Report { + filename, + doc, + title: "BAD REQUIRES".to_string(), + severity: Severity::RuntimeError, + } + } + _ => todo!("unhandled parse error {:?}", parse_problem), } } fn to_packages_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::EPackages, start: Position, @@ -3346,11 +3589,11 @@ fn to_packages_report<'a>( match *parse_problem { EPackages::Packages(pos) => { let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), + alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.concat(vec![ alloc.reflow("I am expecting the "), alloc.keyword("packages"), @@ -3367,45 +3610,7 @@ fn to_packages_report<'a>( } } - EPackages::Space(error, pos) => to_space_report(alloc, filename, &error, pos), - - _ => todo!("unhandled parse error {:?}", parse_problem), - } -} - -fn to_effects_report<'a>( - alloc: &'a RocDocAllocator<'a>, - filename: PathBuf, - parse_problem: &roc_parse::parser::EEffects, - start: Position, -) -> Report<'a> { - use roc_parse::parser::EEffects; - - match *parse_problem { - EEffects::Effects(pos) => { - let surroundings = Region::new(start, pos); - let region = Region::from_pos(pos); - - let doc = alloc.stack(vec![ - alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), - alloc.region_with_subregion(surroundings, region), - alloc.concat(vec![ - alloc.reflow("I am expecting the "), - alloc.keyword("effects"), - alloc.reflow(" keyword next, like "), - ]), - alloc.parser_suggestion("effects {}").indent(4), - ]); - - Report { - filename, - doc, - title: "MISSING PACKAGES".to_string(), - severity: Severity::RuntimeError, - } - } - - EEffects::Space(error, pos) => to_space_report(alloc, filename, &error, pos), + EPackages::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), _ => todo!("unhandled parse error {:?}", parse_problem), } @@ -3413,6 +3618,7 @@ fn to_effects_report<'a>( fn to_space_report<'a>( alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, filename: PathBuf, parse_problem: &roc_parse::parser::BadInputError, pos: Position, @@ -3421,7 +3627,7 @@ fn to_space_report<'a>( match parse_problem { BadInputError::HasTab => { - let region = Region::from_pos(pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); let doc = alloc.stack(vec![ alloc.reflow(r"I encountered a tab character"), @@ -3450,7 +3656,7 @@ enum Next<'a> { Other(Option), } -fn what_is_next<'a>(source_lines: &'a [&'a str], pos: Position) -> Next<'a> { +fn what_is_next<'a>(source_lines: &'a [&'a str], pos: LineColumn) -> Next<'a> { let row_index = pos.line as usize; let col_index = pos.column as usize; match source_lines.get(row_index) { @@ -3492,38 +3698,38 @@ pub fn starts_with_keyword(rest_of_line: &str, keyword: &str) -> bool { } } -fn next_line_starts_with_close_curly(source_lines: &[&str], pos: Position) -> Option { +fn next_line_starts_with_close_curly(source_lines: &[&str], pos: LineColumn) -> Option { next_line_starts_with_char(source_lines, pos, '}') } fn next_line_starts_with_close_parenthesis( source_lines: &[&str], - pos: Position, -) -> Option { + pos: LineColumn, +) -> Option { next_line_starts_with_char(source_lines, pos, ')') } fn next_line_starts_with_close_square_bracket( source_lines: &[&str], - pos: Position, -) -> Option { + pos: LineColumn, +) -> Option { next_line_starts_with_char(source_lines, pos, ']') } fn next_line_starts_with_char( source_lines: &[&str], - pos: Position, + pos: LineColumn, character: char, -) -> Option { +) -> Option { match source_lines.get(pos.line as usize + 1) { None => None, Some(line) => { let spaces_dropped = line.trim_start_matches(' '); match spaces_dropped.chars().next() { - Some(c) if c == character => Some(Position { + Some(c) if c == character => Some(LineColumn { line: pos.line + 1, - column: (line.len() - spaces_dropped.len()) as u16, + column: (line.len() - spaces_dropped.len()) as u32, }), _ => None, } @@ -3531,6 +3737,9 @@ fn next_line_starts_with_char( } } -fn to_keyword_region(pos: Position, keyword: &str) -> Region { - Region::new(pos, pos.bump_column(keyword.len() as u16)) +fn to_keyword_region(pos: LineColumn, keyword: &str) -> LineColumnRegion { + LineColumnRegion { + start: pos, + end: pos.bump_column(keyword.len() as u32), + } } diff --git a/reporting/src/error/type.rs b/reporting/src/error/type.rs index 75ff1f8962..4ac42f5fec 100644 --- a/reporting/src/error/type.rs +++ b/reporting/src/error/type.rs @@ -3,13 +3,12 @@ use roc_collections::all::{Index, MutSet, SendMap}; use roc_module::called_via::{BinOp, CalledVia}; use roc_module::ident::{Ident, IdentStr, Lowercase, TagName}; use roc_module::symbol::Symbol; -use roc_region::all::{Loc, Region}; +use roc_region::all::{LineInfo, Loc, Region}; use roc_solve::solve; use roc_types::pretty_print::{Parens, WILDCARD}; use roc_types::types::{Category, ErrorType, PatternCategory, Reason, RecordField, TypeExt}; use std::path::PathBuf; -use crate::internal_error; use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity}; use ven_pretty::DocAllocator; @@ -18,6 +17,7 @@ const ADD_ANNOTATIONS: &str = r#"Can more type annotations be added? Type annota pub fn type_problem<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, filename: PathBuf, problem: solve::TypeError, ) -> Option> { @@ -34,13 +34,14 @@ pub fn type_problem<'b>( match problem { BadExpr(region, category, found, expected) => Some(to_expr_report( - alloc, filename, region, category, found, expected, + alloc, lines, filename, region, category, found, expected, )), BadPattern(region, category, found, expected) => Some(to_pattern_report( - alloc, filename, region, category, found, expected, + alloc, lines, filename, region, category, found, expected, )), CircularType(region, symbol, overall_type) => Some(to_circular_report( alloc, + lines, filename, region, symbol, @@ -87,7 +88,7 @@ pub fn type_problem<'b>( found_arguments, alloc.reflow(" instead:"), ]), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.reflow("Are there missing parentheses?"), ]); @@ -99,16 +100,16 @@ pub fn type_problem<'b>( report(title, doc, filename) } - CyclicAlias(symbol, region, others) => { - let (doc, title) = cyclic_alias(alloc, symbol, region, others); - - report(title, doc, filename) + CyclicAlias(..) => { + // We'll also report cyclic aliases as a canonicalization problem, no need to + // re-report them. + None } SolvedTypeError => None, // Don't re-report cascading errors - see https://github.com/rtfeldman/roc/pull/1711 Shadowed(original_region, shadow) => { - let doc = report_shadowing(alloc, original_region, shadow); + let doc = report_shadowing(alloc, lines, original_region, shadow); let title = DUPLICATE_NAME.to_string(); report(title, doc, filename) @@ -122,6 +123,7 @@ pub fn type_problem<'b>( fn report_shadowing<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, original_region: Region, shadow: Loc, ) -> RocDocBuilder<'b> { @@ -132,15 +134,16 @@ fn report_shadowing<'b>( .text("The ") .append(alloc.ident(shadow.value)) .append(alloc.reflow(" name is first defined here:")), - alloc.region(original_region), + alloc.region(lines.convert_region(original_region)), alloc.reflow("But then it's defined a second time here:"), - alloc.region(shadow.region), + alloc.region(lines.convert_region(shadow.region)), alloc.reflow(line), ]) } pub fn cyclic_alias<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, symbol: Symbol, region: roc_region::all::Region, others: Vec, @@ -151,7 +154,7 @@ pub fn cyclic_alias<'b>( .reflow("The ") .append(alloc.symbol_unqualified(symbol)) .append(alloc.reflow(" alias is self-recursive in an invalid way:")), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.reflow("Recursion in aliases is only allowed if recursion happens behind a tag."), ]) } else { @@ -160,7 +163,7 @@ pub fn cyclic_alias<'b>( .reflow("The ") .append(alloc.symbol_unqualified(symbol)) .append(alloc.reflow(" alias is recursive in an invalid way:")), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc .reflow("The ") .append(alloc.symbol_unqualified(symbol)) @@ -186,6 +189,7 @@ pub fn cyclic_alias<'b>( #[allow(clippy::too_many_arguments)] fn report_mismatch<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, filename: PathBuf, category: &Category, found: ErrorType, @@ -198,9 +202,12 @@ fn report_mismatch<'b>( further_details: Option>, ) -> Report<'b> { let snippet = if let Some(highlight) = opt_highlight { - alloc.region_with_subregion(highlight, region) + alloc.region_with_subregion( + lines.convert_region(highlight), + lines.convert_region(region), + ) } else { - alloc.region(region) + alloc.region(lines.convert_region(region)) }; let lines = vec![ problem, @@ -227,6 +234,7 @@ fn report_mismatch<'b>( #[allow(clippy::too_many_arguments)] fn report_bad_type<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, filename: PathBuf, category: &Category, found: ErrorType, @@ -238,9 +246,12 @@ fn report_bad_type<'b>( further_details: RocDocBuilder<'b>, ) -> Report<'b> { let snippet = if let Some(highlight) = opt_highlight { - alloc.region_with_subregion(highlight, region) + alloc.region_with_subregion( + lines.convert_region(highlight), + lines.convert_region(region), + ) } else { - alloc.region(region) + alloc.region(lines.convert_region(region)) }; let lines = vec![ problem, @@ -285,6 +296,7 @@ fn lowercase_first(s: &str) -> String { fn to_expr_report<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, filename: PathBuf, expr_region: roc_region::all::Region, category: Category, @@ -308,7 +320,7 @@ fn to_expr_report<'b>( title: "TYPE MISMATCH".to_string(), doc: alloc.stack(vec![ alloc.text("This expression is used in an unexpected way:"), - alloc.region(expr_region), + alloc.region(lines.convert_region(expr_region)), comparison, ]), severity: Severity::RuntimeError, @@ -421,7 +433,10 @@ fn to_expr_report<'b>( // for typed bodies, include the line(s) with the signature let joined = roc_region::all::Region::span_across(&ann_region, &expr_region); - alloc.region_with_subregion(joined, expr_region) + alloc.region_with_subregion( + lines.convert_region(joined), + lines.convert_region(expr_region), + ) }, comparison, ]), @@ -440,6 +455,7 @@ fn to_expr_report<'b>( report_bad_type( alloc, + lines, filename, &category, found, @@ -478,6 +494,7 @@ fn to_expr_report<'b>( report_bad_type( alloc, + lines, filename, &category, found, @@ -515,6 +532,7 @@ fn to_expr_report<'b>( ]); report_bad_type( alloc, + lines, filename, &category, found, @@ -542,6 +560,7 @@ fn to_expr_report<'b>( } => match total_branches { 2 => report_mismatch( alloc, + lines, filename, &category, found, @@ -575,6 +594,7 @@ fn to_expr_report<'b>( ), _ => report_mismatch( alloc, + lines, filename, &category, found, @@ -599,6 +619,7 @@ fn to_expr_report<'b>( }, Reason::WhenBranch { index } => report_mismatch( alloc, + lines, filename, &category, found, @@ -637,6 +658,7 @@ fn to_expr_report<'b>( report_mismatch( alloc, + lines, filename, &category, found, @@ -651,6 +673,7 @@ fn to_expr_report<'b>( } Reason::RecordUpdateValue(field) => report_mismatch( alloc, + lines, filename, &category, found, @@ -685,6 +708,7 @@ fn to_expr_report<'b>( match diff.next().and_then(|k| Some((k, expected_fields.get(k)?))) { None => report_mismatch( alloc, + lines, filename, &category, found, @@ -719,7 +743,7 @@ fn to_expr_report<'b>( let doc = alloc.stack(vec![ header, - alloc.region(*field_region), + alloc.region(lines.convert_region(*field_region)), if suggestions.is_empty() { alloc.concat(vec![ alloc.reflow("In fact, "), @@ -764,6 +788,7 @@ fn to_expr_report<'b>( } _ => report_bad_type( alloc, + lines, filename, &category, found, @@ -798,7 +823,7 @@ fn to_expr_report<'b>( } )), ]), - alloc.region(expr_region), + alloc.region(lines.convert_region(expr_region)), alloc.reflow("Are there any missing commas? Or missing parentheses?"), ]; @@ -833,7 +858,7 @@ fn to_expr_report<'b>( arity )), ]), - alloc.region(expr_region), + alloc.region(lines.convert_region(expr_region)), alloc.reflow("Are there any missing commas? Or missing parentheses?"), ]; @@ -857,7 +882,7 @@ fn to_expr_report<'b>( arity )), ]), - alloc.region(expr_region), + alloc.region(lines.convert_region(expr_region)), alloc.reflow( "Roc does not allow functions to be partially applied. \ Use a closure to make partial application explicit.", @@ -883,6 +908,7 @@ fn to_expr_report<'b>( report_mismatch( alloc, + lines, filename, &category, found, @@ -903,6 +929,22 @@ fn to_expr_report<'b>( None, ) } + + Reason::NumericLiteralSuffix => report_mismatch( + alloc, + lines, + filename, + &category, + found, + expected_type, + region, + Some(expr_region), + alloc.text("This numeric literal is being used improperly:"), + alloc.text("Here the value is used as a:"), + alloc.text("But its suffix says it's a:"), + None, + ), + Reason::LowLevelOpArg { op, arg_index } => { panic!( "Compiler bug: argument #{} to low-level operation {:?} was the wrong type!", @@ -920,6 +962,7 @@ fn to_expr_report<'b>( foreign_symbol ); } + Reason::FloatLiteral | Reason::IntLiteral | Reason::NumLiteral => { unreachable!("I don't think these can be reached") } @@ -1230,6 +1273,7 @@ fn add_category<'b>( fn to_pattern_report<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, filename: PathBuf, expr_region: roc_region::all::Region, category: PatternCategory, @@ -1242,7 +1286,7 @@ fn to_pattern_report<'b>( PExpected::NoExpectation(expected_type) => { let doc = alloc.stack(vec![ alloc.text("This pattern is being used in an unexpected way:"), - alloc.region(expr_region), + alloc.region(lines.convert_region(expr_region)), pattern_type_comparison( alloc, found, @@ -1275,7 +1319,7 @@ fn to_pattern_report<'b>( .append(alloc.text(" argument to ")) .append(name.clone()) .append(alloc.text(" is weird:")), - alloc.region(region), + alloc.region(lines.convert_region(region)), pattern_type_comparison( alloc, found, @@ -1310,7 +1354,7 @@ fn to_pattern_report<'b>( .text("The 1st pattern in this ") .append(alloc.keyword("when")) .append(alloc.text(" is causing a mismatch:")), - alloc.region(region), + alloc.region(lines.convert_region(region)), pattern_type_comparison( alloc, found, @@ -1343,7 +1387,7 @@ fn to_pattern_report<'b>( .string(format!("The {} pattern in this ", index.ordinal())) .append(alloc.keyword("when")) .append(alloc.text(" does not match the previous ones:")), - alloc.region(region), + alloc.region(lines.convert_region(region)), pattern_type_comparison( alloc, found, @@ -1424,7 +1468,7 @@ fn add_pattern_category<'b>( Str => alloc.reflow(" strings:"), Num => alloc.reflow(" numbers:"), Int => alloc.reflow(" integers:"), - Float => alloc.reflow(" floats"), + Float => alloc.reflow(" floats:"), }; alloc.concat(vec![i_am_trying_to_match, rest]) @@ -1432,6 +1476,7 @@ fn add_pattern_category<'b>( fn to_circular_report<'b>( alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, filename: PathBuf, region: roc_region::all::Region, symbol: Symbol, @@ -1446,7 +1491,7 @@ fn to_circular_report<'b>( .reflow("I'm inferring a weird self-referential type for ") .append(alloc.symbol_unqualified(symbol)) .append(alloc.text(":")), - alloc.region(region), + alloc.region(lines.convert_region(region)), alloc.stack(vec![ alloc.reflow( "Here is my best effort at writing down the type. \ @@ -1735,6 +1780,15 @@ pub fn to_doc<'b>( ext_to_doc(alloc, ext), ) } + + Range(typ, range_types) => { + let typ = to_doc(alloc, parens, *typ); + let range_types = range_types + .into_iter() + .map(|arg| to_doc(alloc, Parens::Unnecessary, arg)) + .collect(); + report_text::range(alloc, typ, range_types) + } } } @@ -2589,6 +2643,29 @@ mod report_text { .append(rec_var) } } + + pub fn range<'b>( + alloc: &'b RocDocAllocator<'b>, + _encompassing_type: RocDocBuilder<'b>, + ranged_types: Vec>, + ) -> RocDocBuilder<'b> { + let mut doc = Vec::with_capacity(ranged_types.len() * 2); + + let last = ranged_types.len() - 1; + for (i, choice) in ranged_types.into_iter().enumerate() { + if i == last && i == 1 { + doc.push(alloc.reflow(" or ")); + } else if i == last && i > 1 { + doc.push(alloc.reflow(", or ")); + } else if i > 0 { + doc.push(alloc.reflow(", ")); + } + + doc.push(choice); + } + + alloc.concat(doc) + } } fn type_problem_to_pretty<'b>( @@ -2692,7 +2769,7 @@ fn type_problem_to_pretty<'b>( alloc.tip().append(line) } - (BadRigidVar(x, tipe), ExpectationContext::Annotation { on }) => { + (BadRigidVar(x, tipe), expectation) => { use ErrorType::*; let bad_rigid_var = |name: Lowercase, a_thing| { @@ -2706,17 +2783,23 @@ fn type_problem_to_pretty<'b>( }; let bad_double_wildcard = || { - alloc.tip().append(alloc.concat(vec![ + let mut hints_lines = vec![ alloc.reflow( "Any connection between types must use a named type variable, not a ", ), alloc.type_variable(WILDCARD.into()), - alloc.reflow("! Maybe the annotation "), - on, - alloc.reflow(" should have a named type variable in place of the "), - alloc.type_variable(WILDCARD.into()), - alloc.reflow("?"), - ])) + alloc.reflow("!"), + ]; + if let ExpectationContext::Annotation { on } = expectation { + hints_lines.append(&mut vec![ + alloc.reflow(" Maybe the annotation "), + on, + alloc.reflow(" should have a named type variable in place of the "), + alloc.type_variable(WILDCARD.into()), + alloc.reflow("?"), + ]); + } + alloc.tip().append(alloc.concat(hints_lines)) }; let bad_double_rigid = |a: Lowercase, b: Lowercase| { @@ -2750,11 +2833,9 @@ fn type_problem_to_pretty<'b>( alloc.reflow(" value"), ]), ), + Range(..) => bad_rigid_var(x, alloc.reflow("a range")), } } - (BadRigidVar(_, _), expectation_context) => { - internal_error!("I thought mismatches between rigid vars could only happen in the context of a type annotation, but here they're happening with a {:?}!", expectation_context) - } (IntFloat, _) => alloc.tip().append(alloc.concat(vec![ alloc.reflow("You can convert between "), diff --git a/reporting/src/lib.rs b/reporting/src/lib.rs index 1f7520b931..ff2791a5f8 100644 --- a/reporting/src/lib.rs +++ b/reporting/src/lib.rs @@ -4,35 +4,3 @@ pub mod error; pub mod report; - -/// `internal_error!` should be used whenever a compiler invariant is broken. -/// It is a wrapper around panic that tells the user to file a bug. -/// This should only be used in cases where there would be a compiler bug and the user can't fix it. -/// If there is simply an unimplemented feature, please use `unimplemented!` -/// If there is a user error, please use roc_reporting to print a nice error message. -#[macro_export] -macro_rules! internal_error { - ($($arg:tt)*) => ({ - eprintln!("An internal compiler expectation was broken."); - eprintln!("This is definitely a compiler bug."); - // TODO: update this to the new bug template. - eprintln!("Please file an issue here: https://github.com/rtfeldman/roc/issues/new/choose"); - #[allow(clippy::panic)] { - panic!($($arg)*); - } - }) -} - -/// `user_error!` should only ever be used temporarily. -/// It is a way to document locations where we do not yet have nice error reporting. -/// All cases of `user_error!` should eventually be replaced with pretty error printing using roc_reporting. -#[macro_export] -macro_rules! user_error { - ($($arg:tt)*) => ({ - eprintln!("We ran into an issue while compiling your code."); - eprintln!("Sadly, we don't havs a pretty error message for this case yet."); - eprintln!("If you can't figure out the problem from the context below, please reach out at: https://roc.zulipchat.com/"); - eprintln!($($arg)*); - std::process::exit(1); - }) -} diff --git a/reporting/src/report.rs b/reporting/src/report.rs index 94b9bb70ca..c74de2e0ff 100644 --- a/reporting/src/report.rs +++ b/reporting/src/report.rs @@ -1,6 +1,7 @@ use roc_module::ident::Ident; use roc_module::ident::{Lowercase, ModuleName, TagName, Uppercase}; use roc_module::symbol::{Interns, ModuleId, Symbol}; +use roc_region::all::LineColumnRegion; use std::fmt; use std::path::PathBuf; use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder, Render, RenderAnnotated}; @@ -391,9 +392,9 @@ impl<'a> RocDocAllocator<'a> { pub fn region_all_the_things( &'a self, - region: roc_region::all::Region, - sub_region1: roc_region::all::Region, - sub_region2: roc_region::all::Region, + region: LineColumnRegion, + sub_region1: LineColumnRegion, + sub_region2: LineColumnRegion, error_annotation: Annotation, ) -> DocBuilder<'a, Self, Annotation> { debug_assert!(region.contains(&sub_region1)); @@ -502,8 +503,8 @@ impl<'a> RocDocAllocator<'a> { pub fn region_with_subregion( &'a self, - region: roc_region::all::Region, - sub_region: roc_region::all::Region, + region: LineColumnRegion, + sub_region: LineColumnRegion, ) -> DocBuilder<'a, Self, Annotation> { // debug_assert!(region.contains(&sub_region)); @@ -588,13 +589,13 @@ impl<'a> RocDocAllocator<'a> { result } - pub fn region(&'a self, region: roc_region::all::Region) -> DocBuilder<'a, Self, Annotation> { + pub fn region(&'a self, region: LineColumnRegion) -> DocBuilder<'a, Self, Annotation> { self.region_with_subregion(region, region) } pub fn region_without_error( &'a self, - region: roc_region::all::Region, + region: LineColumnRegion, ) -> DocBuilder<'a, Self, Annotation> { let mut result = self.nil(); for i in region.start().line..=region.end().line { @@ -631,6 +632,39 @@ impl<'a> RocDocAllocator<'a> { self.text(format!("{}", ident.as_inline_str())) .annotate(Annotation::Symbol) } + + pub fn int_literal(&'a self, int: I) -> DocBuilder<'a, Self, Annotation> + where + I: ToString, + { + let s = int.to_string(); + + let is_negative = s.starts_with('-'); + + if s.len() < 7 + (is_negative as usize) { + // If the number is not at least in the millions, return it as-is. + return self.text(s); + } + + // Otherwise, let's add numeric separators to make it easier to read. + let mut result = String::with_capacity(s.len() + s.len() / 3); + for (idx, c) in s + .get((is_negative as usize)..) + .unwrap() + .chars() + .rev() + .enumerate() + { + if idx != 0 && idx % 3 == 0 { + result.push('_'); + } + result.push(c); + } + if is_negative { + result.push('-'); + } + self.text(result.chars().rev().collect::()) + } } #[derive(Copy, Clone)] diff --git a/reporting/tests/helpers/mod.rs b/reporting/tests/helpers/mod.rs index cd6cf8e80c..544c440164 100644 --- a/reporting/tests/helpers/mod.rs +++ b/reporting/tests/helpers/mod.rs @@ -11,6 +11,7 @@ use roc_collections::all::{ImMap, MutMap, SendSet}; use roc_constrain::expr::constrain_expr; use roc_constrain::module::{constrain_imported_values, Import}; use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds}; +use roc_parse::parser::{SourceError, SyntaxError}; use roc_problem::can::Problem; use roc_region::all::Loc; use roc_solve::solve; @@ -102,7 +103,7 @@ pub struct CanExprOut { #[derive(Debug)] pub struct ParseErrOut<'a> { - pub fail: roc_parse::parser::SyntaxError<'a>, + pub fail: SourceError<'a, SyntaxError<'a>>, pub home: ModuleId, pub interns: Interns, } diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index c696c3a0a1..7a7abd9be2 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -15,6 +15,7 @@ mod test_reporting { use roc_module::symbol::{Interns, ModuleId}; use roc_mono::ir::{Procs, Stmt, UpdateModeIds}; use roc_mono::layout::LayoutCache; + use roc_region::all::LineInfo; use roc_reporting::report::{ can_problem, mono_problem, parse_problem, type_problem, Report, Severity, BLUE_CODE, BOLD_CODE, CYAN_CODE, DEFAULT_PALETTE, GREEN_CODE, MAGENTA_CODE, RED_CODE, RESET_CODE, @@ -22,6 +23,7 @@ mod test_reporting { }; use roc_reporting::report::{RocDocAllocator, RocDocBuilder}; use roc_solve::solve; + use roc_test_utils::assert_multiline_str_eq; use roc_types::pretty_print::name_all_type_vars; use roc_types::subs::Subs; use std::path::PathBuf; @@ -94,8 +96,8 @@ mod test_reporting { let mut update_mode_ids = UpdateModeIds::new(); // Populate Procs and Subs, and get the low-level Expr from the canonical Expr - let ptr_bytes = 8; - let mut layout_cache = LayoutCache::new(ptr_bytes); + let target_info = roc_target::TargetInfo::default_x86_64(); + let mut layout_cache = LayoutCache::new(target_info); let mut mono_env = roc_mono::ir::Env { arena: &arena, subs: &mut subs, @@ -103,7 +105,7 @@ mod test_reporting { home, ident_ids: &mut ident_ids, update_mode_ids: &mut update_mode_ids, - ptr_bytes, + target_info, // call_specialization_counter=0 is reserved call_specialization_counter: 1, }; @@ -126,6 +128,7 @@ mod test_reporting { use ven_pretty::DocAllocator; let src_lines: Vec<&str> = src.split('\n').collect(); + let lines = LineInfo::new(src); let filename = filename_from_string(r"\code\proj\Main.roc"); @@ -139,8 +142,8 @@ mod test_reporting { let alloc = RocDocAllocator::new(&src_lines, home, &interns); - let problem = fail.into_parse_problem(filename.clone(), "", src.as_bytes()); - let doc = parse_problem(&alloc, filename, 0, problem); + let problem = fail.into_file_error(filename.clone()); + let doc = parse_problem(&alloc, &lines, filename, 0, problem); callback(doc.pretty(&alloc).append(alloc.line()), buf) } @@ -150,18 +153,20 @@ mod test_reporting { let alloc = RocDocAllocator::new(&src_lines, home, &interns); for problem in can_problems { - let report = can_problem(&alloc, filename.clone(), problem.clone()); + let report = can_problem(&alloc, &lines, filename.clone(), problem.clone()); reports.push(report); } for problem in type_problems { - if let Some(report) = type_problem(&alloc, filename.clone(), problem.clone()) { + if let Some(report) = + type_problem(&alloc, &lines, filename.clone(), problem.clone()) + { reports.push(report); } } for problem in mono_problems { - let report = mono_problem(&alloc, filename.clone(), problem.clone()); + let report = mono_problem(&alloc, &lines, filename.clone(), problem.clone()); reports.push(report); } @@ -192,6 +197,7 @@ mod test_reporting { let filename = filename_from_string(r"\code\proj\Main.roc"); let src_lines: Vec<&str> = src.split('\n').collect(); + let lines = LineInfo::new(src); match roc_parse::module::parse_header(arena, state) { Err(fail) => { @@ -201,12 +207,10 @@ mod test_reporting { let alloc = RocDocAllocator::new(&src_lines, home, &interns); use roc_parse::parser::SyntaxError; - let problem = SyntaxError::Header(fail).into_parse_problem( - filename.clone(), - "", - src.as_bytes(), - ); - let doc = parse_problem(&alloc, filename, 0, problem); + let problem = fail + .map_problem(SyntaxError::Header) + .into_file_error(filename.clone()); + let doc = parse_problem(&alloc, &lines, filename, 0, problem); callback(doc.pretty(&alloc).append(alloc.line()), buf) } @@ -233,7 +237,7 @@ mod test_reporting { } } - assert_eq!(buf, expected_rendering); + assert_multiline_str_eq!(expected_rendering, buf.as_str()); } fn report_header_problem_as(src: &str, expected_rendering: &str) { @@ -420,6 +424,16 @@ mod test_reporting { `Booly` is not used anywhere in your code. + 3│ Booly : [ Yes, No, Maybe ] + ^^^^^^^^^^^^^^^^^^^^^^^^^^ + + If you didn't intend on using `Booly` then remove it so future readers + of your code don't wonder why it is there. + + ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + + `Booly` is not used anywhere in your code. + 1│ Booly : [ Yes, No ] ^^^^^^^^^^^^^^^^^^^ @@ -572,14 +586,14 @@ mod test_reporting { indoc!( r#" ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── - + I cannot find a `true` value - + 1│ if true then 1 else 2 ^^^^ - + Did you mean one of these? - + True Str Num @@ -626,12 +640,12 @@ mod test_reporting { indoc!( r#" y = 9 - + box = \class, htmlChildren -> div [ class ] [] - + div = \_, _ -> 4 - + box "wizard" [] "# ), @@ -640,7 +654,7 @@ mod test_reporting { ── UNUSED ARGUMENT ───────────────────────────────────────────────────────────── `box` doesn't use `htmlChildren`. - + 3│ box = \class, htmlChildren -> ^^^^^^^^^^^^ @@ -664,39 +678,6 @@ mod test_reporting { ); } - // #[test] - // fn report_unused_import() { - // report_problem_as( - // indoc!( - // r#" - // interface Report - // exposes [ - // plainText, - // emText - // ] - // imports [ - // Symbol.{ Interns } - // ] - - // plainText = \str -> PlainText str - - // emText = \str -> EmText str - // "# - // ), - // indoc!( - // r#" - // Nothing from Symbol is used in this module. - - // 6│ imports [ - // 7│ Symbol.{ Interns } - // ^^^^^^ - // 8│ ] - - // Since Symbol isn't used, you don't need to import it."# - // ), - // ); - // } - #[test] fn report_value_color() { let src: &str = indoc!( @@ -1662,7 +1643,7 @@ mod test_reporting { But you are trying to use it as: - [ Foo a ]b + [ Foo a ] "# ), ) @@ -2481,20 +2462,26 @@ mod test_reporting { ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── - This pattern does not cover all the possibilities: + This expression is used in an unexpected way: 5│ (Left y) = x - ^^^^^^ + ^ - Other possibilities include: + This `x` value is a: - Right _ + [ Left I64, Right Bool ] - I would have to crash if I saw one of those! You can use a binding to - deconstruct a value if there is only ONE possibility. Use a `when` to - account for all possibilities. + But you are trying to use it as: + + [ Left a ] + + Tip: Seems like a tag typo. Maybe `Right` should be `Left`? + + Tip: Can more type annotations be added? Type annotations always help + me give more specific messages, and I think they could help a lot in + this case "# ), ) @@ -3389,15 +3376,15 @@ mod test_reporting { report_problem_as( indoc!( r#" - x = 9_223_372_036_854_775_807_000 + x = 170_141_183_460_469_231_731_687_303_715_884_105_728_000 - y = -9_223_372_036_854_775_807_000 + y = -170_141_183_460_469_231_731_687_303_715_884_105_728_000 - h = 0x8FFF_FFFF_FFFF_FFFF - l = -0x8FFF_FFFF_FFFF_FFFF + h = 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF + l = -0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF - minlit = -9_223_372_036_854_775_808 - maxlit = 9_223_372_036_854_775_807 + minlit = -170_141_183_460_469_231_731_687_303_715_884_105_728 + maxlit = 340_282_366_920_938_463_463_374_607_431_768_211_455 x + y + h + l + minlit + maxlit "# @@ -3408,11 +3395,11 @@ mod test_reporting { This integer literal is too big: - 1│ x = 9_223_372_036_854_775_807_000 - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 1│ x = 170_141_183_460_469_231_731_687_303_715_884_105_728_000 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Roc uses signed 64-bit integers, allowing values between - −9_223_372_036_854_775_808 and 9_223_372_036_854_775_807. + The largest number representable in Roc is the maximum U128 value, + 340_282_366_920_938_463_463_374_607_431_768_211_455. Tip: Learn more about number literals at TODO @@ -3420,11 +3407,11 @@ mod test_reporting { This integer literal is too small: - 3│ y = -9_223_372_036_854_775_807_000 - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 3│ y = -170_141_183_460_469_231_731_687_303_715_884_105_728_000 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Roc uses signed 64-bit integers, allowing values between - −9_223_372_036_854_775_808 and 9_223_372_036_854_775_807. + The smallest number representable in Roc is the minimum I128 value, + -170_141_183_460_469_231_731_687_303_715_884_105_728. Tip: Learn more about number literals at TODO @@ -3432,11 +3419,11 @@ mod test_reporting { This integer literal is too big: - 5│ h = 0x8FFF_FFFF_FFFF_FFFF - ^^^^^^^^^^^^^^^^^^^^^ + 5│ h = 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Roc uses signed 64-bit integers, allowing values between - −9_223_372_036_854_775_808 and 9_223_372_036_854_775_807. + The largest number representable in Roc is the maximum U128 value, + 340_282_366_920_938_463_463_374_607_431_768_211_455. Tip: Learn more about number literals at TODO @@ -3444,11 +3431,11 @@ mod test_reporting { This integer literal is too small: - 6│ l = -0x8FFF_FFFF_FFFF_FFFF - ^^^^^^^^^^^^^^^^^^^^^^ + 6│ l = -0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Roc uses signed 64-bit integers, allowing values between - −9_223_372_036_854_775_808 and 9_223_372_036_854_775_807. + The smallest number representable in Roc is the minimum I128 value, + -170_141_183_460_469_231_731_687_303_715_884_105_728. Tip: Learn more about number literals at TODO "# @@ -3526,7 +3513,8 @@ mod test_reporting { 1│ dec = 100A ^^^^ - Integer literals can only contain the digits 0-9. + Integer literals can only contain the digits + 0-9, or have an integer suffix. Tip: Learn more about number literals at TODO @@ -3538,7 +3526,7 @@ mod test_reporting { ^^^^^ Hexadecimal (base-16) integer literals can only contain the digits - 0-9, a-f and A-F. + 0-9, a-f and A-F, or have an integer suffix. Tip: Learn more about number literals at TODO @@ -3549,7 +3537,8 @@ mod test_reporting { 5│ oct = 0o9 ^^^ - Octal (base-8) integer literals can only contain the digits 0-7. + Octal (base-8) integer literals can only contain the digits + 0-7, or have an integer suffix. Tip: Learn more about number literals at TODO @@ -3560,7 +3549,8 @@ mod test_reporting { 7│ bin = 0b2 ^^^ - Binary (base-2) integer literals can only contain the digits 0 and 1. + Binary (base-2) integer literals can only contain the digits + 0 and 1, or have an integer suffix. Tip: Learn more about number literals at TODO "# @@ -3594,7 +3584,7 @@ mod test_reporting { ^^ Hexadecimal (base-16) integer literals must contain at least one of - the digits 0-9, a-f and A-F. + the digits 0-9, a-f and A-F, or have an integer suffix. Tip: Learn more about number literals at TODO @@ -3606,7 +3596,7 @@ mod test_reporting { ^^ Octal (base-8) integer literals must contain at least one of the - digits 0-7. + digits 0-7, or have an integer suffix. Tip: Learn more about number literals at TODO @@ -3618,7 +3608,7 @@ mod test_reporting { ^^ Binary (base-2) integer literals must contain at least one of the - digits 0 and 1. + digits 0 and 1, or have an integer suffix. Tip: Learn more about number literals at TODO "# @@ -3646,7 +3636,7 @@ mod test_reporting { ^^^^ Floating point literals can only contain the digits 0-9, or use - scientific notation 10e4 + scientific notation 10e4, or have a float suffix. Tip: Learn more about number literals at TODO "# @@ -5480,7 +5470,7 @@ mod test_reporting { ^^^^^ Floating point literals can only contain the digits 0-9, or use - scientific notation 10e4 + scientific notation 10e4, or have a float suffix. Tip: Learn more about number literals at TODO "# @@ -6113,16 +6103,16 @@ I need all branches in an `if` to have the same type! imports [Task] provides [ mainForHost ] effects fx.Effect - { - putChar : I64 -> Effect {}, - putLine : Str -> Effect {}, - getLine : Effect Str - } + { + putChar : I64 -> Effect {}, + putLine : Str -> Effect {}, + getLine : Effect Str + } "# ), indoc!( r#" - ── BAD REQUIRES RIGIDS ───────────────────────────────────────────────────────── + ── BAD REQUIRES ──────────────────────────────────────────────────────────────── I am partway through parsing a header, but I got stuck here: @@ -6130,10 +6120,10 @@ I need all branches in an `if` to have the same type! 2│ requires { main : Effect {} } ^ - I am expecting a list of rigids like `{}` or `{model=>Model}` next. A full + I am expecting a list of type names like `{}` or `{ Model }` next. A full `requires` definition looks like - requires {model=>Model, msg=>Msg} {main : Effect {}} + requires { Model, Msg } {main : Effect {}} "# ), ) @@ -6153,7 +6143,7 @@ I need all branches in an `if` to have the same type! r#" ── WEIRD EXPOSES ─────────────────────────────────────────────────────────────── - I am partway through parsing a exposes list, but I got stuck here: + I am partway through parsing an `exposes` list, but I got stuck here: 1│ interface Foobar 2│ exposes [ main, @Foo ] @@ -6984,4 +6974,1007 @@ I need all branches in an `if` to have the same type! ), ) } + + #[test] + fn mismatched_single_tag_arg() { + report_problem_as( + indoc!( + r#" + isEmpty = + \email -> + Email str = email + Str.isEmpty str + + isEmpty (Name "boo") + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + The 1st argument to `isEmpty` is not what I expect: + + 6│ isEmpty (Name "boo") + ^^^^^^^^^^ + + This `Name` global tag application has the type: + + [ Name Str ]a + + But `isEmpty` needs the 1st argument to be: + + [ Email Str ] + + Tip: Seems like a tag typo. Maybe `Name` should be `Email`? + + Tip: Can more type annotations be added? Type annotations always help + me give more specific messages, and I think they could help a lot in + this case + "# + ), + ) + } + + #[test] + fn issue_2326() { + report_problem_as( + indoc!( + r#" + C a b : a -> D a b + D a b : { a, b } + + f : C a Nat -> D a Nat + f = \c -> c 6 + f + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + The 1st argument to `c` is not what I expect: + + 5│ f = \c -> c 6 + ^ + + This argument is a number of type: + + Num a + + But `c` needs the 1st argument to be: + + a + + Tip: The type annotation uses the type variable `a` to say that this + definition can produce any type of value. But in the body I see that + it will only produce a `Num` value of a single specific type. Maybe + change the type annotation to be more specific? Maybe change the code + to be more general? + "# + ), + ) + } + + #[test] + fn issue_2380_annotations_only() { + report_problem_as( + indoc!( + r#" + F : F + a : F + a + "# + ), + indoc!( + r#" + ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── + + The `F` alias is self-recursive in an invalid way: + + 1│ F : F + ^ + + Recursion in aliases is only allowed if recursion happens behind a + tag. + "# + ), + ) + } + + #[test] + fn issue_2380_typed_body() { + report_problem_as( + indoc!( + r#" + F : F + a : F + a = 1 + a + "# + ), + indoc!( + r#" + ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── + + The `F` alias is self-recursive in an invalid way: + + 1│ F : F + ^ + + Recursion in aliases is only allowed if recursion happens behind a + tag. + "# + ), + ) + } + + #[test] + fn issue_2380_alias_with_vars() { + report_problem_as( + indoc!( + r#" + F a b : F a b + a : F Str Str + a + "# + ), + indoc!( + r#" + ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── + + The `F` alias is self-recursive in an invalid way: + + 1│ F a b : F a b + ^ + + Recursion in aliases is only allowed if recursion happens behind a + tag. + "# + ), + ) + } + + #[test] + fn issue_2167_record_field_optional_and_required_mismatch() { + report_problem_as( + indoc!( + r#" + Job : [ @Job { inputs : List Str } ] + job : { inputs ? List Str } -> Job + job = \{ inputs } -> + @Job { inputs } + + job { inputs: [ "build", "test" ] } + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + The 1st argument to `job` is weird: + + 3│ job = \{ inputs } -> + ^^^^^^^^^^ + + The argument is a pattern that matches record values of type: + + { inputs : List Str } + + But the annotation on `job` says the 1st argument should be: + + { inputs ? List Str } + + Tip: To extract the `.inputs` field it must be non-optional, but the + type says this field is optional. Learn more about optional fields at + TODO. + "# + ), + ) + } + + #[test] + fn unify_recursive_with_nonrecursive() { + report_problem_as( + indoc!( + r#" + Job : [ @Job { inputs : List Job } ] + + job : { inputs : List Str } -> Job + job = \{ inputs } -> + @Job { inputs } + + job { inputs: [ "build", "test" ] } + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + Something is off with the body of the `job` definition: + + 3│ job : { inputs : List Str } -> Job + 4│ job = \{ inputs } -> + 5│ @Job { inputs } + ^^^^^^^^^^^^^^^ + + This `@Job` private tag application has the type: + + [ @Job { inputs : List Str } ] + + But the type annotation on `job` says it should be: + + [ @Job { inputs : List a } ] as a + "# + ), + ) + } + + #[test] + fn nested_datatype() { + report_problem_as( + indoc!( + r#" + Nested a : [ Chain a (Nested (List a)), Term ] + + s : Nested Str + + s + "# + ), + indoc!( + r#" + ── NESTED DATATYPE ───────────────────────────────────────────────────────────── + + `Nested` is a nested datatype. Here is one recursive usage of it: + + 1│ Nested a : [ Chain a (Nested (List a)), Term ] + ^^^^^^^^^^^^^^^ + + But recursive usages of `Nested` must match its definition: + + 1│ Nested a : [ Chain a (Nested (List a)), Term ] + ^^^^^^^^ + + Nested datatypes are not supported in Roc. + + Hint: Consider rewriting the definition of `Nested` to use the recursive type with the same arguments. + "# + ), + ) + } + + #[test] + fn nested_datatype_inline() { + report_problem_as( + indoc!( + r#" + f : {} -> [ Chain a (Nested (List a)), Term ] as Nested a + + f + "# + ), + indoc!( + r#" + ── NESTED DATATYPE ───────────────────────────────────────────────────────────── + + `Nested` is a nested datatype. Here is one recursive usage of it: + + 1│ f : {} -> [ Chain a (Nested (List a)), Term ] as Nested a + ^^^^^^^^^^^^^^^ + + But recursive usages of `Nested` must match its definition: + + 1│ f : {} -> [ Chain a (Nested (List a)), Term ] as Nested a + ^^^^^^^^ + + Nested datatypes are not supported in Roc. + + Hint: Consider rewriting the definition of `Nested` to use the recursive type with the same arguments. + "# + ), + ) + } + + macro_rules! mismatched_suffix_tests { + ($($number:expr, $suffix:expr, $name:ident)*) => {$( + #[test] + fn $name() { + let number = $number.to_string(); + let mut typ = $suffix.to_string(); + typ.get_mut(0..1).unwrap().make_ascii_uppercase(); + let bad_type = if $suffix == "u8" { "I8" } else { "U8" }; + let carets = "^".repeat(number.len() + $suffix.len()); + let kind = match $suffix { + "dec"|"f32"|"f64" => "a float", + _ => "an integer", + }; + + report_problem_as( + &format!(indoc!( + r#" + use : {} -> U8 + use {}{} + "# + ), bad_type, number, $suffix), + &format!(indoc!( + r#" + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + The 1st argument to `use` is not what I expect: + + 2│ use {}{} + {} + + This argument is {} of type: + + {} + + But `use` needs the 1st argument to be: + + {} + "# + ), number, $suffix, carets, kind, typ, bad_type), + ) + } + )*} + } + + mismatched_suffix_tests! { + 1, "u8", mismatched_suffix_u8 + 1, "u16", mismatched_suffix_u16 + 1, "u32", mismatched_suffix_u32 + 1, "u64", mismatched_suffix_u64 + 1, "u128", mismatched_suffix_u128 + 1, "i8", mismatched_suffix_i8 + 1, "i16", mismatched_suffix_i16 + 1, "i32", mismatched_suffix_i32 + 1, "i64", mismatched_suffix_i64 + 1, "i128", mismatched_suffix_i128 + 1, "nat", mismatched_suffix_nat + 1, "dec", mismatched_suffix_dec + 1, "f32", mismatched_suffix_f32 + 1, "f64", mismatched_suffix_f64 + } + + macro_rules! mismatched_suffix_tests_in_pattern { + ($($number:expr, $suffix:expr, $name:ident)*) => {$( + #[test] + fn $name() { + let number = $number.to_string(); + let mut typ = $suffix.to_string(); + typ.get_mut(0..1).unwrap().make_ascii_uppercase(); + let bad_suffix = if $suffix == "u8" { "i8" } else { "u8" }; + let bad_type = if $suffix == "u8" { "I8" } else { "U8" }; + let carets = "^".repeat(number.len() + $suffix.len()); + let kind = match $suffix { + "dec"|"f32"|"f64" => "floats", + _ => "integers", + }; + + report_problem_as( + &format!(indoc!( + r#" + when {}{} is + {}{} -> 1 + _ -> 1 + "# + ), number, bad_suffix, number, $suffix), + &format!(indoc!( + r#" + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + The 1st pattern in this `when` is causing a mismatch: + + 2│ {}{} -> 1 + {} + + The first pattern is trying to match {}: + + {} + + But the expression between `when` and `is` has the type: + + {} + "# + ), number, $suffix, carets, kind, typ, bad_type), + ) + } + )*} + } + + mismatched_suffix_tests_in_pattern! { + 1, "u8", mismatched_suffix_u8_pattern + 1, "u16", mismatched_suffix_u16_pattern + 1, "u32", mismatched_suffix_u32_pattern + 1, "u64", mismatched_suffix_u64_pattern + 1, "u128", mismatched_suffix_u128_pattern + 1, "i8", mismatched_suffix_i8_pattern + 1, "i16", mismatched_suffix_i16_pattern + 1, "i32", mismatched_suffix_i32_pattern + 1, "i64", mismatched_suffix_i64_pattern + 1, "i128", mismatched_suffix_i128_pattern + 1, "nat", mismatched_suffix_nat_pattern + 1, "dec", mismatched_suffix_dec_pattern + 1, "f32", mismatched_suffix_f32_pattern + 1, "f64", mismatched_suffix_f64_pattern + } + + #[test] + fn bad_numeric_literal_suffix() { + report_problem_as( + indoc!( + r#" + 1u256 + "# + ), + // TODO: link to number suffixes + indoc!( + r#" + ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + + This integer literal contains an invalid digit: + + 1│ 1u256 + ^^^^^ + + Integer literals can only contain the digits + 0-9, or have an integer suffix. + + Tip: Learn more about number literals at TODO + "# + ), + ) + } + + #[test] + fn numer_literal_multi_suffix() { + report_problem_as( + indoc!( + r#" + 1u8u8 + "# + ), + // TODO: link to number suffixes + indoc!( + r#" + ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + + This integer literal contains an invalid digit: + + 1│ 1u8u8 + ^^^^^ + + Integer literals can only contain the digits + 0-9, or have an integer suffix. + + Tip: Learn more about number literals at TODO + "# + ), + ) + } + + #[test] + fn int_literal_has_float_suffix() { + report_problem_as( + indoc!( + r#" + 0b1f32 + "# + ), + indoc!( + r#" + ── CONFLICTING NUMBER SUFFIX ─────────────────────────────────────────────────── + + This number literal is an integer, but it has a float suffix: + + 1│ 0b1f32 + ^^^^^^ + "# + ), + ) + } + + #[test] + fn float_literal_has_int_suffix() { + report_problem_as( + indoc!( + r#" + 1.0u8 + "# + ), + indoc!( + r#" + ── CONFLICTING NUMBER SUFFIX ─────────────────────────────────────────────────── + + This number literal is a float, but it has an integer suffix: + + 1│ 1.0u8 + ^^^^^ + "# + ), + ) + } + + #[test] + fn u8_overflow() { + report_problem_as( + "256u8", + indoc!( + r#" + ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + + This integer literal overflows the type indicated by its suffix: + + 1│ 256u8 + ^^^^^ + + Tip: The suffix indicates this integer is a U8, whose maximum value is + 255. + "# + ), + ) + } + + #[test] + fn negative_u8() { + report_problem_as( + "-1u8", + indoc!( + r#" + ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + + This integer literal underflows the type indicated by its suffix: + + 1│ -1u8 + ^^^^ + + Tip: The suffix indicates this integer is a U8, whose minimum value is + 0. + "# + ), + ) + } + + #[test] + fn u16_overflow() { + report_problem_as( + "65536u16", + indoc!( + r#" + ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + + This integer literal overflows the type indicated by its suffix: + + 1│ 65536u16 + ^^^^^^^^ + + Tip: The suffix indicates this integer is a U16, whose maximum value + is 65535. + "# + ), + ) + } + + #[test] + fn negative_u16() { + report_problem_as( + "-1u16", + indoc!( + r#" + ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + + This integer literal underflows the type indicated by its suffix: + + 1│ -1u16 + ^^^^^ + + Tip: The suffix indicates this integer is a U16, whose minimum value + is 0. + "# + ), + ) + } + + #[test] + fn u32_overflow() { + report_problem_as( + "4_294_967_296u32", + indoc!( + r#" + ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + + This integer literal overflows the type indicated by its suffix: + + 1│ 4_294_967_296u32 + ^^^^^^^^^^^^^^^^ + + Tip: The suffix indicates this integer is a U32, whose maximum value + is 4_294_967_295. + "# + ), + ) + } + + #[test] + fn negative_u32() { + report_problem_as( + "-1u32", + indoc!( + r#" + ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + + This integer literal underflows the type indicated by its suffix: + + 1│ -1u32 + ^^^^^ + + Tip: The suffix indicates this integer is a U32, whose minimum value + is 0. + "# + ), + ) + } + + #[test] + fn u64_overflow() { + report_problem_as( + "18_446_744_073_709_551_616u64", + indoc!( + r#" + ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + + This integer literal overflows the type indicated by its suffix: + + 1│ 18_446_744_073_709_551_616u64 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Tip: The suffix indicates this integer is a U64, whose maximum value + is 18_446_744_073_709_551_615. + "# + ), + ) + } + + #[test] + fn negative_u64() { + report_problem_as( + "-1u64", + indoc!( + r#" + ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + + This integer literal underflows the type indicated by its suffix: + + 1│ -1u64 + ^^^^^ + + Tip: The suffix indicates this integer is a U64, whose minimum value + is 0. + "# + ), + ) + } + + #[test] + fn negative_u128() { + report_problem_as( + "-1u128", + indoc!( + r#" + ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + + This integer literal underflows the type indicated by its suffix: + + 1│ -1u128 + ^^^^^^ + + Tip: The suffix indicates this integer is a U128, whose minimum value + is 0. + "# + ), + ) + } + + #[test] + fn i8_overflow() { + report_problem_as( + "128i8", + indoc!( + r#" + ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + + This integer literal overflows the type indicated by its suffix: + + 1│ 128i8 + ^^^^^ + + Tip: The suffix indicates this integer is a I8, whose maximum value is + 127. + "# + ), + ) + } + + #[test] + fn i8_underflow() { + report_problem_as( + "-129i8", + indoc!( + r#" + ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + + This integer literal underflows the type indicated by its suffix: + + 1│ -129i8 + ^^^^^^ + + Tip: The suffix indicates this integer is a I8, whose minimum value is + -128. + "# + ), + ) + } + + #[test] + fn i16_overflow() { + report_problem_as( + "32768i16", + indoc!( + r#" + ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + + This integer literal overflows the type indicated by its suffix: + + 1│ 32768i16 + ^^^^^^^^ + + Tip: The suffix indicates this integer is a I16, whose maximum value + is 32767. + "# + ), + ) + } + + #[test] + fn i16_underflow() { + report_problem_as( + "-32769i16", + indoc!( + r#" + ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + + This integer literal underflows the type indicated by its suffix: + + 1│ -32769i16 + ^^^^^^^^^ + + Tip: The suffix indicates this integer is a I16, whose minimum value + is -32768. + "# + ), + ) + } + + #[test] + fn i32_overflow() { + report_problem_as( + "2_147_483_648i32", + indoc!( + r#" + ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + + This integer literal overflows the type indicated by its suffix: + + 1│ 2_147_483_648i32 + ^^^^^^^^^^^^^^^^ + + Tip: The suffix indicates this integer is a I32, whose maximum value + is 2_147_483_647. + "# + ), + ) + } + + #[test] + fn i32_underflow() { + report_problem_as( + "-2_147_483_649i32", + indoc!( + r#" + ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + + This integer literal underflows the type indicated by its suffix: + + 1│ -2_147_483_649i32 + ^^^^^^^^^^^^^^^^^ + + Tip: The suffix indicates this integer is a I32, whose minimum value + is -2_147_483_648. + "# + ), + ) + } + + #[test] + fn i64_overflow() { + report_problem_as( + "9_223_372_036_854_775_808i64", + indoc!( + r#" + ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + + This integer literal overflows the type indicated by its suffix: + + 1│ 9_223_372_036_854_775_808i64 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Tip: The suffix indicates this integer is a I64, whose maximum value + is 9_223_372_036_854_775_807. + "# + ), + ) + } + + #[test] + fn i64_underflow() { + report_problem_as( + "-9_223_372_036_854_775_809i64", + indoc!( + r#" + ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + + This integer literal underflows the type indicated by its suffix: + + 1│ -9_223_372_036_854_775_809i64 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Tip: The suffix indicates this integer is a I64, whose minimum value + is -9_223_372_036_854_775_808. + "# + ), + ) + } + + #[test] + fn i128_overflow() { + report_problem_as( + "170_141_183_460_469_231_731_687_303_715_884_105_728i128", + indoc!( + r#" + ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + + This integer literal overflows the type indicated by its suffix: + + 1│ 170_141_183_460_469_231_731_687_303_715_884_105_728i128 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Tip: The suffix indicates this integer is a I128, whose maximum value + is 170_141_183_460_469_231_731_687_303_715_884_105_727. + "# + ), + ) + } + + #[test] + fn list_get_negative_number() { + report_problem_as( + indoc!( + r#" + List.get [1,2,3] -1 + "# + ), + // TODO: this error message could be improved, e.g. something like "This argument can + // be used as ... because of its literal value" + indoc!( + r#" + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + The 2nd argument to `get` is not what I expect: + + 1│ List.get [1,2,3] -1 + ^^ + + This argument is a number of type: + + I8, I16, I32, I64, I128, F32, F64, or Dec + + But `get` needs the 2nd argument to be: + + Nat + "# + ), + ) + } + + #[test] + fn list_get_negative_number_indirect() { + report_problem_as( + indoc!( + r#" + a = -9_223_372_036_854 + List.get [1,2,3] a + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + The 2nd argument to `get` is not what I expect: + + 2│ List.get [1,2,3] a + ^ + + This `a` value is a: + + I64, I128, F32, F64, or Dec + + But `get` needs the 2nd argument to be: + + Nat + "# + ), + ) + } + + #[test] + fn list_get_negative_number_double_indirect() { + report_problem_as( + indoc!( + r#" + a = -9_223_372_036_854 + b = a + List.get [1,2,3] b + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + The 2nd argument to `get` is not what I expect: + + 3│ List.get [1,2,3] b + ^ + + This `b` value is a: + + I64, I128, F32, F64, or Dec + + But `get` needs the 2nd argument to be: + + Nat + "# + ), + ) + } + + #[test] + fn compare_unsigned_to_signed() { + report_problem_as( + indoc!( + r#" + when -1 is + 1u8 -> 1 + _ -> 1 + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + The 1st pattern in this `when` is causing a mismatch: + + 2│ 1u8 -> 1 + ^^^ + + The first pattern is trying to match integers: + + U8 + + But the expression between `when` and `is` has the type: + + I8, I16, I32, I64, I128, F32, F64, or Dec + "# + ), + ) + } } diff --git a/roc-for-elm-programmers.md b/roc-for-elm-programmers.md index 5d4ddb6d62..aa90d30258 100644 --- a/roc-for-elm-programmers.md +++ b/roc-for-elm-programmers.md @@ -1267,7 +1267,7 @@ So Roc does not use `number`, but rather uses `Num` - which works more like `Lis Either way, you get `+` being able to work on both integers and floats! Separately, there's also `Int a`, which is a type alias for `Num (Integer a)`, -and `Float a`, which is a type alias for `Num (Float a)`. These allow functions +and `Float a`, which is a type alias for `Num (FloatingPoint a)`. These allow functions that can work on any integer or any float. For example, `Num.bitwiseAnd : Int a, Int a -> Int a`. diff --git a/roc_std/src/lib.rs b/roc_std/src/lib.rs index 16a6d7196c..f633ed8f40 100644 --- a/roc_std/src/lib.rs +++ b/roc_std/src/lib.rs @@ -2,9 +2,10 @@ #![no_std] use core::convert::From; use core::ffi::c_void; +use core::fmt::{self, Display, Formatter}; use core::mem::{ManuallyDrop, MaybeUninit}; use core::ops::Drop; -use core::{fmt, mem, ptr, slice}; +use core::{mem, ptr, slice}; // A list of C functions that are being imported extern "C" { @@ -674,14 +675,30 @@ impl From<&str> for RocStr { } } +impl Display for RocStr { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.as_str().fmt(f) + } +} + impl fmt::Debug for RocStr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // RocStr { is_small_str: false, storage: Refcounted(3), elements: [ 1,2,3,4] } - f.debug_struct("RocStr") - .field("is_small_str", &self.is_small_str()) - .field("storage", &self.storage()) - .field("elements", &self.as_slice()) - .finish() + + match core::str::from_utf8(self.as_slice()) { + Ok(string) => f + .debug_struct("RocStr") + .field("is_small_str", &self.is_small_str()) + .field("storage", &self.storage()) + .field("string_contents", &string) + .finish(), + Err(_) => f + .debug_struct("RocStr") + .field("is_small_str", &self.is_small_str()) + .field("storage", &self.storage()) + .field("byte_contents", &self.as_slice()) + .finish(), + } } } diff --git a/shell.nix b/shell.nix index 9902d2bd73..7f85013d3b 100644 --- a/shell.nix +++ b/shell.nix @@ -3,6 +3,7 @@ let sources = import nix/sources.nix { }; pkgs = import sources.nixpkgs { }; + unstable-pkgs = import sources.nixpkgs-unstable { }; darwinInputs = with pkgs; lib.optionals stdenv.isDarwin (with pkgs.darwin.apple_sdk.frameworks; [ @@ -35,12 +36,8 @@ let zig = import ./nix/zig.nix { inherit pkgs; }; debugir = import ./nix/debugir.nix { inherit pkgs; }; - inputs = with pkgs; [ + inputs = (with pkgs; [ # build libraries - rustc - cargo - clippy - rustfmt cmake git python3 @@ -68,7 +65,13 @@ let # tools for development environment less - ]; + ]) ++ (with unstable-pkgs; [ + rustc + cargo + clippy + rustfmt + ]); + in pkgs.mkShell { buildInputs = inputs ++ darwinInputs ++ linuxInputs;