Merge remote-tracking branch 'origin/trunk' into list-str-capacity

This commit is contained in:
Folkert 2022-03-09 14:36:34 +01:00
commit 07063a8e18
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
458 changed files with 20371 additions and 8316 deletions

View file

@ -65,3 +65,6 @@ Mats Sigge <<mats.sigge@gmail.com>>
Drew Lazzeri <dlazzeri1@gmail.com>
Tom Dohrmann <erbse.13@gmx.de>
Elijah Schow <elijah.schow@gmail.com>
Derek Gustafson <degustaf@gmail.com>
Philippe Vinchon <p.vinchon@gmail.com>
Pierre-Henri Trivier <phtrivier@yahoo.fr>

View file

@ -1,7 +1,89 @@
# Building the Roc compiler from source
## Using Nix
## Installing LLVM, Zig, valgrind, and Python
### Install
Using [nix](https://nixos.org/download.html) is a quick way to get an environment bootstrapped with a single command.
Anyone having trouble installing the proper version of LLVM themselves might also prefer this method.
If you are running ArchLinux or a derivative like Manjaro, you'll need to run `sudo sysctl -w kernel.unprivileged_userns_clone=1` before installing nix.
Install nix:
`curl -L https://nixos.org/nix/install | sh`
You will need to start a fresh terminal session to use nix.
### Usage
Now with nix installed, you just need to run one command:
`nix-shell`
> This may not output anything for a little while. This is normal, hang in there. Also make sure you are in the roc project root.
> 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.
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
If you plan on using `nix-shell` regularly, check out [direnv](https://direnv.net/) and [lorri](https://github.com/nix-community/lorri). Whenever you `cd` into `roc/`, they will automatically load the Nix dependencies into your current shell, so you never have to run nix-shell directly!
### Editor
The editor is a :construction:WIP:construction: and not ready yet to replace your favorite editor, although if you want to try it out on nix, read on.
`cargo run edit` should work from NixOS, if you use a nix-shell from inside another OS, follow the instructions below.
#### Nvidia GPU
Outside of a nix shell, execute the following:
```
nix-channel --add https://github.com/guibou/nixGL/archive/main.tar.gz nixgl && nix-channel --update
nix-env -iA nixgl.auto.nixVulkanNvidia
```
Running the editor does not work with `nix-shell --pure`.
```
nix-shell
```
460.91.03 may be different for you, type nixVulkanNvidia and press tab to autocomplete for your version.
```
nixVulkanNvidia-460.91.03 cargo run edit
```
#### Integrated Intel Graphics
:exclamation: ** Our Nix setup currently cannot run the editor with integrated intel graphics, see #1856 ** :exclamation:
Outside of a nix shell, run:
```bash
git clone https://github.com/guibou/nixGL
cd nixGL
nix-env -f ./ -iA nixVulkanIntel
```
cd to the roc repo, and run (without --pure):
```
nix-shell
nixVulkanIntel cargo run edit
```
#### Other configs
Check the [nixGL repo](https://github.com/guibou/nixGL) for other graphics configurations.
## Troubleshooting
Create an issue if you run into problems not listed here.
That will help us improve this document for everyone who reads it in the future!
## Manual Install
To build the compiler, you need these installed:
@ -85,89 +167,6 @@ 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
Using [nix](https://nixos.org/download.html) is a quick way to get an environment bootstrapped with a single command.
Anyone having trouble installing the proper version of LLVM themselves might also prefer this method.
If you are running ArchLinux or a derivative like Manjaro, you'll need to run `sudo sysctl -w kernel.unprivileged_userns_clone=1` before installing nix.
Install nix:
`curl -L https://nixos.org/nix/install | sh`
You will need to start a fresh terminal session to use nix.
### Usage
Now with nix installed, you just need to run one command:
`nix-shell`
> This may not output anything for a little while. This is normal, hang in there. Also make sure you are in the roc project root.
> 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.
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
If you plan on using `nix-shell` regularly, check out [direnv](https://direnv.net/) and [lorri](https://github.com/nix-community/lorri). Whenever you `cd` into `roc/`, they will automatically load the Nix dependencies into your current shell, so you never have to run nix-shell directly!
### Editor
The editor is a WIP and not ready yet to replace your favorite editor, although if you want to try it out on nix, read on.
`cargo run edit` should work from NixOS, if you use a nix-shell from inside another OS, follow the instructions below.
#### Nvidia GPU
Outside of a nix shell, execute the following:
```
nix-channel --add https://github.com/guibou/nixGL/archive/main.tar.gz nixgl && nix-channel --update
nix-env -iA nixgl.auto.nixVulkanNvidia
```
Running the editor does not work with `nix-shell --pure`.
```
nix-shell
```
460.91.03 may be different for you, type nixVulkanNvidia and press tab to autocomplete for your version.
```
nixVulkanNvidia-460.91.03 cargo run edit
```
#### Integrated Intel Graphics
:exclamation: ** Our Nix setup currently cannot run the editor with integrated intel graphics, see #1856 ** :exclamation:
Outside of a nix shell, run:
```bash
git clone https://github.com/guibou/nixGL
cd nixGL
nix-env -f ./ -iA nixVulkanIntel
```
cd to the roc repo, and run (without --pure):
```
nix-shell
nixVulkanIntel cargo run edit
```
#### Other configs
Check the [nixGL repo](https://github.com/guibou/nixGL) for other graphics configurations.
## Troubleshooting
Create an issue if you run into problems not listed here.
That will help us improve this document for everyone who reads it in the future!
### LLVM installation on Linux
For a current list of all dependency versions and their names in apt, see the Earthfile.
@ -197,20 +196,24 @@ export CPPFLAGS="-I/usr/local/opt/llvm/include"
### LLVM installation on Windows
Installing LLVM's prebuilt binaries doesn't seem to be enough for the `llvm-sys` crate that Roc depends on, so I had to build LLVM from source
on Windows. After lots of help from [**@IanMacKenzie**](https://github.com/IanMacKenzie) (thank you, Ian!), here's what worked for me:
**Warning** While `cargo build` works on windows, linking roc programs does not yet, see issue #2608. This also means the repl, the editor and many tests will not work on windows.
Installing LLVM's prebuilt binaries doesn't seem to be enough for the `llvm-sys` crate that Roc depends on, so I had to follow the steps below:
1. I downloaded and installed [Build Tools for Visual Studio 2019](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=16) (a full Visual Studio install should work tool; the Build Tools are just the CLI tools, which is all I wanted)
1. In the installation configuration, under "additional components" I had to check both "C++ ATL for latest v142 build tools (x86 & x64)" and also "C++/CLI support for v142 build tools" [note: as of September 2021 this should no longer be necessary - the next time anyone tries this, please try it without this step and make a PR to delete this step if it's no longer needed!]
1. I launched the "x64 Native Tools Command Prompt for Visual Studio 2019" application (note: not the similarly-named "x86" one!)
1. Make sure [Python 2.7](https://www.python.org/) and [CMake 3.17](http://cmake.org/) are installed on your system.
1. I followed most of the steps under LLVM's [building from source instructions](https://github.com/llvm/llvm-project#getting-the-source-code-and-building-llvm) up to the `cmake -G ...` command, which didn't work for me. Instead, at that point I did the following step.
1. I ran `cmake -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release ../llvm` to generate a NMake makefile.
1. Once that completed, I ran `nmake` to build LLVM. (This took about 2 hours on my laptop.)
1. Finally, I set an environment variable `LLVM_SYS_100_PREFIX` to point to the `build` directory where I ran the `cmake` command.
1. I downloaded and installed [Build Tools for Visual Studio 2019](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=16) (a full Visual Studio install should work too; the Build Tools are just the CLI tools, which is all I wanted)
1. Download the custom LLVM 7z archive [here](https://github.com/PLC-lang/llvm-package-windows/releases/tag/v12.0.1).
1. [Download 7-zip](https://www.7-zip.org/) to be able to extract this archive.
1. Extract the 7z file to where you want to permanently keep the folder.
1. In powershell, set the `LLVM_SYS_120_PREFIX` environment variable (check [here](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_environment_variables?view=powershell-7.2#saving-changes-to-environment-variables) to make this a permanent environment variable):
```
[Environment]::SetEnvironmentVariable(
"Path",
[Environment]::GetEnvironmentVariable("Path", "User") + ";C:\Users\anton\Downloads\LLVM-12.0.1-win64\bin",
"User"
)
```
Once all that was done, `cargo` ran successfully for Roc!
Once all that was done, `cargo build` ran successfully for Roc!
### Build speed on WSL/WSL2

View file

@ -10,7 +10,15 @@ Check [Build from source](BUILDING_FROM_SOURCE.md) for instructions.
## Running Tests
To run all tests and checks as they are run on CI, [install earthly](https://earthly.dev/get-earthly) and run:
Most contributors execute the following commands befor pushing their code:
```
cargo test
cargo fmt --all -- --check
cargo clippy -- -D warnings
```
Execute `cargo fmt --all` to fix the formatting.
If you want to run all tests and checks as they are run on CI, [install earthly](https://earthly.dev/get-earthly) and run:
```
earthly +test-all
```

123
Cargo.lock generated
View file

@ -1193,6 +1193,38 @@ version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
[[package]]
name = "dunce"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "453440c271cf5577fd2a40e4942540cb7d0d2f85e27c8d07dd0023c925a67541"
[[package]]
name = "dynasm"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b1801e630bd336d0bbbdbf814de6cc749c9a400c7e3d995e6adfd455d0c83c"
dependencies = [
"bitflags",
"byteorder",
"lazy_static",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "dynasmrt"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d428afc93ad288f6dffc1fa5f4a78201ad2eec33c5a522e51c181009eb09061"
dependencies = [
"byteorder",
"dynasm",
"memmap2 0.5.3",
]
[[package]]
name = "either"
version = "1.6.1"
@ -1677,7 +1709,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46e977036f7f5139d580c7f19ad62df9cb8ebd8410bb569e73585226be80a86f"
dependencies = [
"lazy_static",
"static_assertions",
"static_assertions 1.1.0",
]
[[package]]
@ -2063,9 +2095,9 @@ dependencies = [
[[package]]
name = "memmap2"
version = "0.5.0"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4647a11b578fead29cdbb34d4adef8dd3dc35b876c9c6d5240d83f205abfe96e"
checksum = "057a3db23999c867821a7a59feb06a578fcb03685e983dff90daf9e7d24ac08f"
dependencies = [
"libc",
]
@ -3190,10 +3222,13 @@ name = "repl_test"
version = "0.1.0"
dependencies = [
"indoc",
"lazy_static",
"roc_cli",
"roc_repl_cli",
"roc_test_utils",
"strip-ansi-escapes",
"wasmer",
"wasmer-wasi",
]
[[package]]
@ -3228,6 +3263,16 @@ dependencies = [
"libc",
]
[[package]]
name = "roc_alias_analysis"
version = "0.1.0"
dependencies = [
"morphic_lib",
"roc_collections",
"roc_module",
"roc_mono",
]
[[package]]
name = "roc_ast"
version = "0.1.0"
@ -3251,6 +3296,7 @@ dependencies = [
"roc_unify",
"snafu",
"ven_graph",
"winapi",
]
[[package]]
@ -3288,6 +3334,7 @@ dependencies = [
name = "roc_builtins"
version = "0.1.0"
dependencies = [
"dunce",
"roc_collections",
"roc_module",
"roc_region",
@ -3304,11 +3351,13 @@ dependencies = [
"pretty_assertions",
"roc_builtins",
"roc_collections",
"roc_error_macros",
"roc_module",
"roc_parse",
"roc_problem",
"roc_region",
"roc_types",
"static_assertions 1.1.0",
"ven_graph",
]
@ -3378,9 +3427,11 @@ dependencies = [
name = "roc_constrain"
version = "0.1.0"
dependencies = [
"arrayvec 0.7.2",
"roc_builtins",
"roc_can",
"roc_collections",
"roc_error_macros",
"roc_module",
"roc_parse",
"roc_region",
@ -3464,6 +3515,16 @@ dependencies = [
name = "roc_error_macros"
version = "0.1.0"
[[package]]
name = "roc_exhaustive"
version = "0.1.0"
dependencies = [
"roc_collections",
"roc_module",
"roc_region",
"roc_std",
]
[[package]]
name = "roc_fmt"
version = "0.1.0"
@ -3510,6 +3571,7 @@ dependencies = [
"bumpalo",
"inkwell 0.1.0",
"morphic_lib",
"roc_alias_analysis",
"roc_builtins",
"roc_collections",
"roc_error_macros",
@ -3546,7 +3608,7 @@ dependencies = [
"bumpalo",
"clap 3.0.0-beta.5",
"iced-x86",
"memmap2 0.5.0",
"memmap2 0.5.3",
"object 0.26.2",
"roc_build",
"roc_collections",
@ -3572,6 +3634,7 @@ dependencies = [
"roc_can",
"roc_collections",
"roc_constrain",
"roc_error_macros",
"roc_module",
"roc_mono",
"roc_parse",
@ -3582,6 +3645,7 @@ dependencies = [
"roc_target",
"roc_types",
"roc_unify",
"strip-ansi-escapes",
"tempfile",
"ven_pretty",
]
@ -3597,7 +3661,7 @@ dependencies = [
"roc_ident",
"roc_region",
"snafu",
"static_assertions",
"static_assertions 1.1.0",
]
[[package]]
@ -3611,6 +3675,7 @@ dependencies = [
"roc_can",
"roc_collections",
"roc_error_macros",
"roc_exhaustive",
"roc_module",
"roc_problem",
"roc_region",
@ -3619,7 +3684,7 @@ dependencies = [
"roc_target",
"roc_types",
"roc_unify",
"static_assertions",
"static_assertions 1.1.0",
"ven_graph",
"ven_pretty",
]
@ -3655,7 +3720,7 @@ dependencies = [
name = "roc_region"
version = "0.1.0"
dependencies = [
"static_assertions",
"static_assertions 1.1.0",
]
[[package]]
@ -3674,6 +3739,7 @@ dependencies = [
"roc_mono",
"roc_parse",
"roc_repl_eval",
"roc_std",
"roc_target",
"roc_types",
"rustyline",
@ -3696,6 +3762,7 @@ dependencies = [
"roc_parse",
"roc_region",
"roc_reporting",
"roc_std",
"roc_target",
"roc_types",
]
@ -3705,6 +3772,7 @@ name = "roc_repl_wasm"
version = "0.1.0"
dependencies = [
"bumpalo",
"futures",
"js-sys",
"roc_builtins",
"roc_collections",
@ -3730,6 +3798,7 @@ dependencies = [
"roc_can",
"roc_collections",
"roc_constrain",
"roc_exhaustive",
"roc_module",
"roc_mono",
"roc_parse",
@ -3769,11 +3838,7 @@ dependencies = [
name = "roc_std"
version = "0.1.0"
dependencies = [
"indoc",
"libc",
"pretty_assertions",
"quickcheck",
"quickcheck_macros",
"static_assertions 0.1.1",
]
[[package]]
@ -3799,7 +3864,7 @@ dependencies = [
"roc_error_macros",
"roc_module",
"roc_region",
"static_assertions",
"static_assertions 1.1.0",
"ven_ena",
]
@ -3873,7 +3938,7 @@ checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088"
[[package]]
name = "rustyline"
version = "9.1.1"
source = "git+https://github.com/rtfeldman/rustyline?tag=v9.1.1#7053ae0fe0ee710d38ed5845dd979113382994dc"
source = "git+https://github.com/rtfeldman/rustyline?rev=e74333c#e74333c0d618896b88175bf06645108f996fe6d0"
dependencies = [
"bitflags",
"cfg-if 1.0.0",
@ -3896,7 +3961,7 @@ dependencies = [
[[package]]
name = "rustyline-derive"
version = "0.6.0"
source = "git+https://github.com/rtfeldman/rustyline?tag=v9.1.1#7053ae0fe0ee710d38ed5845dd979113382994dc"
source = "git+https://github.com/rtfeldman/rustyline?rev=e74333c#e74333c0d618896b88175bf06645108f996fe6d0"
dependencies = [
"quote",
"syn",
@ -4219,6 +4284,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "static_assertions"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f406d6ee68db6796e11ffd7b4d171864c58b7451e79ef9460ea33c287a1f89a7"
[[package]]
name = "static_assertions"
version = "1.1.0"
@ -4507,7 +4578,7 @@ checksum = "1f559b464de2e2bdabcac6a210d12e9b5a5973c251e102c44c585c71d51bd78e"
dependencies = [
"cfg-if 1.0.0",
"rand",
"static_assertions",
"static_assertions 1.1.0",
]
[[package]]
@ -4747,6 +4818,7 @@ dependencies = [
"thiserror",
"wasmer-compiler",
"wasmer-compiler-cranelift",
"wasmer-compiler-singlepass",
"wasmer-derive",
"wasmer-engine",
"wasmer-engine-dylib",
@ -4795,6 +4867,25 @@ dependencies = [
"wasmer-vm",
]
[[package]]
name = "wasmer-compiler-singlepass"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9429b9f7708c582d855b1787f09c7029ff23fb692550d4a1cc351c8ea84c3014"
dependencies = [
"byteorder",
"dynasm",
"dynasmrt",
"lazy_static",
"loupe",
"more-asserts",
"rayon",
"smallvec",
"wasmer-compiler",
"wasmer-types",
"wasmer-vm",
]
[[package]]
name = "wasmer-derive"
version = "2.0.0"

View file

@ -3,6 +3,7 @@ members = [
"compiler/ident",
"compiler/region",
"compiler/collections",
"compiler/exhaustive",
"compiler/module",
"compiler/parse",
"compiler/can",
@ -14,6 +15,7 @@ members = [
"compiler/solve",
"compiler/fmt",
"compiler/mono",
"compiler/alias_analysis",
"compiler/test_mono",
"compiler/load",
"compiler/gen_llvm",
@ -37,7 +39,6 @@ members = [
"repl_eval",
"repl_test",
"repl_wasm",
"roc_std",
"test_utils",
"utils",
"docs",
@ -49,6 +50,8 @@ exclude = [
# The tests will still correctly build them.
"cli_utils",
"compiler/test_mono_macros",
# `cargo build` would cause roc_std to be built with default features which errors on windows
"roc_std",
]
# Needed to be able to run `cargo run -p roc_cli --no-default-features` -
# see www/build.sh for more.

View file

@ -1,4 +1,4 @@
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
FROM rust:1.58.0-slim-bullseye # make sure to update rust-toolchain.toml and 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:
@ -33,12 +33,15 @@ install-zig-llvm-valgrind-clippy-rustfmt:
RUN rustup component add clippy
# rustfmt
RUN rustup component add rustfmt
# wasm repl
RUN rustup target add wasm32-unknown-unknown
RUN apt -y install libssl-dev
RUN OPENSSL_NO_VENDOR=1 cargo install wasm-pack
# criterion
RUN cargo install cargo-criterion
# editor
RUN apt -y install libxkbcommon-dev
# sccache
RUN apt -y install libssl-dev
RUN cargo install sccache
RUN sccache -V
ENV RUSTC_WRAPPER=/usr/local/cargo/bin/sccache
@ -86,8 +89,11 @@ test-rust:
# gen-wasm has some multithreading problems to do with the wasmer runtime. Run it single-threaded as a separate job
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo test --locked --release --package test_gen --no-default-features --features gen-wasm -- --test-threads=1 && sccache --show-stats
# repl_test: build the compiler for wasm target, then run the tests on native target
RUN --mount=type=cache,target=$SCCACHE_DIR \
repl_test/test_wasm.sh && sccache --show-stats
# run i386 (32-bit linux) cli tests
RUN echo "4" | cargo run --locked --release --features="target-x86" -- --backend=x86_32 examples/benchmarks/NQueens.roc
RUN echo "4" | cargo run --locked --release --features="target-x86" -- --target=x86_32 examples/benchmarks/NQueens.roc
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo test --locked --release --features with_sound --test cli_run i386 --features="i386-cli-run" && sccache --show-stats
@ -118,7 +124,7 @@ build-nightly-release:
RUN printf " on: " >> version.txt
RUN date >> version.txt
RUN RUSTFLAGS="-C target-cpu=x86-64" cargo build --features with_sound --release
RUN cd ./target/release && tar -czvf roc_linux_x86_64.tar.gz ./roc ../../LICENSE ../../LEGAL_DETAILS ../../examples/hello-world ../../examples/hello-rust ../../examples/hello-zig ../../compiler/builtins/bitcode/src/ ../../roc_std
RUN cd ./target/release && tar -czvf roc_linux_x86_64.tar.gz ./roc ../../LICENSE ../../LEGAL_DETAILS ../../examples/hello-world ../../compiler/builtins/bitcode/src/ ../../roc_std
SAVE ARTIFACT ./target/release/roc_linux_x86_64.tar.gz AS LOCAL roc_linux_x86_64.tar.gz
# compile everything needed for benchmarks and output a self-contained dir from which benchmarks can be run.

77
FAQ.md
View file

@ -1,18 +1,57 @@
# Frequently Asked Questions
## Is there syntax highlighting for Vim/Emacs/VS Code or a LSP?
Not currently. Although they will presumably exist someday, while Roc is in the early days there's actually a conscious
effort to focus on the Roc Editor *instead of* adding Roc support to other editors - specifically in order to give the Roc
Editor the best possible chance at kickstarting a virtuous cycle of plugin authorship.
This is an unusual approach, but there are more details in [this 2021 interview](https://youtu.be/ITrDd6-PbvY?t=212).
In the meantime, using CoffeeScript syntax highlighting for .roc files turns out to work surprisingly well!
## Why is there no way to specify "import everything this module exposes" in `imports`?
In [Elm](https://elm-lang.org), it's possible to import a module in a way that brings everything that module
exposes into scope. It can be convenient, but like all programming language features, it has downsides.
A minor reason Roc doesn't have this feature is that exposing everything can make it more difficult
outside the editor (e.g. on a website) to tell where something comes from, especially if multiple imports are
using this. ("I don't see `blah` defined in this module, so it must be coming from an import...but which of
these several import-exposing-everything modules could it be? I'll have to check all of them, or
download this code base and open it up in the editor so I can jump to definition!")
The main reason for this design, though, is compiler performance.
Currently, the name resolution step in compilation can be parallelized across modules, because it's possible to
tell if there's a naming error within a module using only the contents of that module. If "expose everything" is
allowed, then it's no longer clear whether anything is a naming error or not, until all the "expose everything"
modules have been processed, so we know exactly which names they expose. Because that feature doesn't exist in Roc,
all modules can do name resolution in parallel.
Of note, allowing this feature would only slow down modules that used it; modules that didn't use it would still be
parallelizable. However, when people find out ways to speed up their builds (in any language), advice starts to
circulate about how to unlock those speed boosts. If Roc had this feature, it's predictable that a commonly-accepted
piece of advice would eventually circulate: "don't use this feature because it slows down your builds."
If a feature exists in a language, but the common recommendation is never to use it, that's cause for reconsidering
whether the feature should be in the language at all. In the case of this feature, I think it's simpler if the
language doesn't have it; that way nobody has to learn (or spend time spreading the word) about the
performance-boosting advice not to use it.
## Why doesn't Roc have higher-kinded polymorphism or arbitrary-rank types?
_Since this is a FAQ answer, I'm going to assume familiarity with higher-kinded types and higher-rank types instead of including a primer on them._
A valuable aspect of Roc's type system is that it has [principal](https://en.wikipedia.org/wiki/Principal_type)
A valuable aspect of Roc's type system is that it has decidable [principal](https://en.wikipedia.org/wiki/Principal_type)
type inference. This means that:
* At compile time, Roc can correctly infer the types for every expression in a program, even if you don't annotate any of the types.
* This inference always infers the most general type possible; you couldn't possibly add a valid type annotation that would make the type more flexible than the one that Roc would infer if you deleted the annotation.
It's been proven that any type system which supports either [higher-kinded polymorphism](https://www.cl.cam.ac.uk/~jdy22/papers/lightweight-higher-kinded-polymorphism.pdf) or [arbitrary-rank types](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/putting.pdf) cannot have
It's been proven that any type system which supports either [higher-kinded polymorphism](https://www.cl.cam.ac.uk/~jdy22/papers/lightweight-higher-kinded-polymorphism.pdf) or [arbitrary-rank types](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/putting.pdf) cannot have decidable
principal type inference. With either of those features in the language, there will be situations where the compiler
reports an error that can only be fixed by the programmer adding a type annotation. This also means there would be
would be unable to infer a type—and you'd have to write a type annotation. This also means there would be
situations where the editor would not be able to reliably tell you the type of part of your program, unlike today
where it can accurately tell you the type of anything, even if you have no type annotations in your entire code base.
@ -84,7 +123,7 @@ arguing between the two sides of the divide. Again, I think it's completely reas
different preference, but given that language designers can only choose one of these options, I'm confident
I've made the right choice for Roc by designing it never to have higher-kinded polymorphism.
## Why does Roc's syntax and standard library differ from Elm's?
## Why do Roc's syntax and standard library differ from Elm's?
Roc is a direct descendant of [Elm](https://elm-lang.org/). However, there are some differences between the two languages.
@ -98,12 +137,14 @@ So why does Roc have the specific syntax changes it does? Here are some brief ex
* `#` instead of `--` for comments - this allows [hashbang](https://senthilnayagan.medium.com/shebang-hashbang-10966b8f28a8)s to work without needing special syntax. That isn't a use case Elm supports, but it is one Roc is designed to support.
* `{}` instead of `()` for the unit type - Elm has both, and they can both be used as a unit type. Since `{}` has other uses in the type system, but `()` doesn't, I consider it redundant and took it out.
* No tuples - I wanted to try simplifying the language and seeing how much we'd miss them. Anything that could be represented as a tuple can be represented with either a record or a single-tag union instead (e.g. `Pair x y = ...`), so is it really necessary to have a third syntax for representing a [product type](https://en.wikipedia.org/wiki/Product_type)?
* No tuples - I wanted to try simplifying the language and seeing how much we'd miss them. Anything that could be represented as a tuple can be represented with either a record or a single-tag union instead (e.g. `Pair x y = ...`), so is it really necessary to have a third syntax for representing a group of fields with potentially different types?
* `when`...`is` instead of `case`...`of` - I predict it will be easier for beginners to pick up, because usually the way I explain `case`...`of` to beginners is by saying the words "when" and "is" out loud - e.g. "when `color` is `Red`, it runs this first branch; when `color` is `Blue`, it runs this other branch..."
* `:` instead of `=` for record field names: I like `=` being reserved for definitions, and `:` is the most popular alternative.
* `:` instead of `=` for record field definitions (e.g. `{ foo: bar }` where Elm syntax would be `{ foo = bar }`): I like `=` being reserved for definitions, and `:` is the most popular alternative.
* Backpassing syntax - since Roc is designed to be used for use cases like command-line apps, shell scripts, and servers, I expect chained effects to come up a lot more often than they do in Elm. I think backpassing is nice for those use cases, similarly to how `do` notation is nice for them in Haskell.
* Tag unions instead of Elm's custom types (aka algebraic data types). This isn't just a syntactic change; tag unions are mainly in Roc because they can facilitate errors being accumulated across chained effects, which (as noted a moment ago) I expect to be a lot more common in Roc than in Elm. If you have tag unions, you don't really need a separate language feature for algebraic data types, since closed tag unions essentially work the same way - aside from not giving you a way to selectively expose variants or define phantom types. Roc's opaque types language feature covers those use cases instead.
* No `::` operator, or `::` pattern matching for lists. Both of these are for the same reason: an Elm `List` is a linked list, so both prepending to it and removing an element from the front are very cheap operations. In contrast, a Roc `List` is a flat array, so both prepending to it and removing an element from the front are among the most expensive operations you can possibly do with it! To get good performance, this usage pattern should be encouraged in Elm and discouraged in Roc. Since having special syntax would encourage it, it would not be good for Roc to have that syntax!
* No `<|` operator. In Elm, I almost exclusively found myself wanting to use this in conjunction with anonymous functions (e.g. `foo <| \bar -> ...`) or conditionals (e.g. `foo <| if bar then ...`). In Roc you can do both of these without the `<|`. That means the main remaining use for `<|` is to reduce parentheses, but I tend to think `|>` is better at that (or else the parens are fine), so after the other syntactic changes, I considered `<|` an unnecessary stylistic alternative to `|>` or parens.
* The `|>` operator passes the expression before the `|>` as the *first* argument to the function after the `|>` instead of as the last argument. See the section on currying for details on why this works this way.
* `:` instead of `type alias` - I like to avoid reserved keywords for terms that are desirable in userspace, so that people don't have to name things `typ` because `type` is a reserved keyword, or `clazz` because `class` is reserved. (I couldn't think of satisfactory alternatives for `as`, `when`, `is`, or `if` other than different reserved keywords. I could see an argument for `then`—and maybe even `is`—being replaced with a `->` or `=>` or something, but I don't anticipate missing either of those words much in userspace. `then` is used in JavaScript promises, but I think there are several better names for that function.)
* No underscores in variable names - I've seen Elm beginners reflexively use `snake_case` over `camelCase` and then need to un-learn the habit after the compiler accepted it. I'd rather have the compiler give feedback that this isn't the way to do it in Roc, and suggest a camelCase alternative. I've also seen underscores used for lazy naming, e.g. `foo` and then `foo_`. If lazy naming is the goal, `foo2` is just as concise as `foo_`, but `foo3` is more concise than `foo__`. So in a way, removing `_` is a forcing function for improved laziness. (Of course, more descriptive naming would be even better.)
* Trailing commas - I've seen people walk away (in some cases physically!) from Elm as soon as they saw the leading commas in collection literals. While I think they've made a mistake by not pushing past this aesthetic preference to give the language a chance, I also would prefer not put them in a position to make such a mistake in the first place. Secondarily, while I'm personally fine with either style, between the two I prefer the look of trailing commas.
@ -115,10 +156,15 @@ Roc also has a different standard library from Elm. Some of the differences come
* No `Basics` module. I wanted to have a simple rule of "all modules in the standard library are imported by default, and so are their exposed types," and that's it. Given that I wanted the comparison operators (e.g. `<`) to work only on numbers, it ended up that having `Num` and `Bool` modules meant that almost nothing would be left for a `Basics` equivalent in Roc except `identity` and `Never`. The Roc type `[]` (empty tag union) is equivalent to `Never`, so that wasn't necessary, and I generally think that `identity` is a good concept but a sign of an incomplete API whenever its use comes up in practice. For example, instead of calling `|> List.filterMap identity` I'd rather have access to a more self-descriptive function like `|> List.dropNothings`. With `Num` and `Bool`, and without `identity` and `Never`, there was nothing left in `Basics`.
* `Str` instead of `String` - after using the `str` type in Rust, I realized I had no issue whatsoever with the more concise name, especially since it was used in so many places (similar to `Msg` and `Cmd` in Elm) - so I decided to save a couple of letters.
* No function composition operators - I stopped using these in Elm so long ago, at one point I forgot they were in the language! See the FAQ entry on currying for details about why.
* No `Maybe`. If a function returns a potential error, I prefer `Result` with an error type that uses a no-payload tag to describe what went wrong. (For example, `List.first : List a -> Result a [ ListWasEmpty ]*` instead of `List.first : List a -> Maybe a`.) This is not only more self-descriptive, it also composes better with operations that have multiple ways to fail. Optional record fields can be handled using the explicit Optional Record Field language feature. To describe something that's neither an operation that can fail nor an optional field, I prefer using a more descriptive tag - e.g. for a nullable JSON decoder, instead of `nullable : Decoder a -> Decoder (Maybe a)`, making a self-documenting API like `nullable : Decoder a -> Decoder [ Null, NonNull a ]`. Joël's legendary [talk about Maybe](https://youtu.be/43eM4kNbb6c) is great, but the fact that a whole talk about such a simple type can be so useful speaks to how easy the type is to misuse. Imagine a 20-minute talk about `Result` - could it be anywhere near as hepful? On a historical note, it's conceivable that the creation of `Maybe` predated `Result`, and `Maybe` might have been thought of as a substitute for null pointers—as opposed to something that emerged organically based on specific motivating use cases after `Result` already existed.
* No `Char`. What most people think of as a "character" is a rendered glyph. However, rendered glyphs are comprised of [grapheme clusters](https://stackoverflow.com/a/27331885), which are a variable number of Unicode code points - and there's no upper bound on how many code points there can be in a single cluster. In a world of emoji, I think this makes `Char` error-prone and it's better to have `Str` be the only first-class unit. For convenience when working with unicode code points (e.g. for performance-critical tasks like parsing), the single-quote syntax is sugar for the corresponding `U32` code point - for example, writing `'鹏'` is exactly the same as writing `40527`. Like Rust, you get a compiler error if you put something in single quotes that's not a valid [Unicode scalar value](http://www.unicode.org/glossary/#unicode_scalar_value).
* No `Debug.log` - the editor can do a better job at this, or you can write `expect x != x` to see what `x` is when the expectation fails. Using the editor means your code doesn't change, and using `expect` gives a natural reminder to remove the debugging code before shipping: the build will fail.
* No `Debug.todo` - instead you can write a type annotation with no implementation below it; the type checker will treat it normally, but attempting to use the value will cause a runtime exception. This is a feature I've often wanted in Elm, because I like prototyping APIs by writing out the types only, but then when I want the compiler to type-check them for me, I end up having to add `Debug.todo` in various places.
* No `Maybe`. There are several reasons for this:
* If a function returns a potential error, I prefer `Result` with an error type that uses a no-payload tag to describe what went wrong. (For example, `List.first : List a -> Result a [ ListWasEmpty ]*` instead of `List.first : List a -> Maybe a`.) This is not only more self-descriptive, it also composes better with operations that have multiple ways to fail.
* Optional record fields can be handled using the explicit Optional Record Field language feature.
* To describe something that's neither an operation that can fail nor an optional field, I prefer using a more descriptive tag - e.g. for a nullable JSON decoder, instead of `nullable : Decoder a -> Decoder (Maybe a)`, making a self-documenting API like `nullable : Decoder a -> Decoder [ Null, NonNull a ]`.
* It's surprisingly easy to misuse - especially by overusing it when a different language feature (especially a custom tag union) would lead to nicer code. Joël's legendary [talk about Maybe](https://youtu.be/43eM4kNbb6c) is great, but the fact that a whole talk about such a simple type can be so useful speaks to how easy the type is to misuse. Imagine a 20-minute talk about `Result` - could it be anywhere near as hepful?
* On a historical note, it's conceivable that the creation of `Maybe` predated `Result`, and `Maybe` might have been thought of as a substitute for null pointers—as opposed to something that emerged organically based on specific motivating use cases after `Result` already existed.
## Why aren't Roc functions curried by default?
@ -128,18 +174,19 @@ typically what people mean when they say Roc isn't a curried language is that Ro
by default. For the rest of this section, I'll use "currying" as a shorthand for "functions that are curried
by default" for the sake of brevity.
As I see it, currying has one major upside and three major downsides. The upside:
As I see it, currying has one major upside and several major downsides. The upside:
* It makes function calls more concise in some cases.
The downsides:
* It lowers error message quality, because there can no longer be an error for "function called with too few arguments." (Calling a function with fewer arguments is always valid in curried functions; the error you get instead will unavoidably be some other sort of type mismatch, and it will be up to you to figure out that the real problem was that you forgot an argument.)
* It makes the `|>` operator more error-prone in some cases.
* It makes higher-order function calls need more parentheses in some cases.
* It significantly increases the language's learning curve.
* It significantly increases the language's learning curve. (More on this later.)
* It facilitates pointfree function composition. (More on why this is listed as a downside later.)
There's also a downside that it would make runtime performance of compiled progarms worse by default,
There's also a downside that it would make runtime performance of compiled programs worse by default,
but I assume it would be possible to optimize that away at the cost of slightly longer compile times.
I consider the one upside (conciseness in some places) extremely minor, and have almost never missed it in Roc.
@ -265,13 +312,3 @@ Currying facilitates the antipattern of pointfree function composition, which I
Stacking up all these downsides of currying against the one upside of making certain function calls more concise,
I concluded that it would be a mistake to have it in Roc.
## Is there syntax highlighting for Vim/Emacs/VS Code or a LSP?
Not currently. Although they will presumably exist someday, while Roc is in the early days there's actually a conscious
effort to focus on the Roc Editor *instead of* adding Roc support to other editors - specifically in order to give the Roc
Editor the best possible chance at kickstarting a virtuous cycle of plugin authorship.
This is an unusual approach, but there are more details in [this 2021 interview](https://youtu.be/ITrDd6-PbvY?t=212).
In the meantime, using CoffeeScript syntax highlighting for .roc files turns out to work surprisingly well!

View file

@ -515,3 +515,24 @@ See the License for the specific language governing permissions and
limitations under the License.
===========================================================
* iced - https://github.com/iced-rs/iced
Copyright 2019 Héctor Ramón, Iced contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -23,7 +23,7 @@ Many programs can however be compiled correctly. Check out [examples](examples)
Run examples as follows:
```
cargo run examples/hello-world/Hello.roc
cargo run examples/hello-world/helloWorld.roc
```
Some examples like `examples/benchmarks/NQueens.roc` require input after running.
For NQueens, input 10 in the terminal and press enter.

View file

@ -6,6 +6,10 @@ and more!
Enjoy!
## Getting started
Learn how to install roc on your machine [here](https://github.com/rtfeldman/roc#getting-started).
## Strings and Numbers
Lets start by getting acquainted with Rocs Read Eval Print Loop, or REPL for
@ -1619,8 +1623,7 @@ If you like, you can always annotate your functions as accepting open records. H
always be the nicest choice. For example, let's say you have a `User` type alias, like so:
```coffee
User :
{
User : {
email : Str,
firstName : Str,
lastName : Str,
@ -1657,8 +1660,7 @@ Since open records have a type variable (like `*` in `{ email : Str }*` or `a` i
type variable to the `User` type alias:
```coffee
User a :
{
User a : {
email : Str,
firstName : Str,
lastName : Str,

View file

@ -21,10 +21,14 @@ 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"
page_size = "0.4.2"
snafu = { version = "0.6.10", features = ["backtraces"] }
ven_graph = { path = "../vendor/pathfinding" }
libc = "0.2.106"
[dev-dependencies]
indoc = "1.0.3"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.9", features = ["memoryapi"]}

View file

@ -154,12 +154,18 @@ fn canonicalize_field<'a>(
let (loc_can_expr, output) =
expr_to_expr2(env, scope, &loc_expr.value, loc_expr.region);
Ok(CanonicalField::LabelAndValue {
match loc_can_expr {
Expr2::RuntimeError() => Ok(CanonicalField::InvalidLabelOnly {
label: label.value,
var: field_var,
}),
_ => Ok(CanonicalField::LabelAndValue {
label: label.value,
value_expr: loc_can_expr,
value_output: output,
var: field_var,
})
}),
}
}
OptionalValue(label, _, loc_expr) => Err(CanonicalizeFieldProblem::InvalidOptionalValue {

View file

@ -1,7 +1,7 @@
use bumpalo::{collections::Vec as BumpVec, Bump};
use roc_can::expected::{Expected, PExpected};
use roc_collections::all::{BumpMap, BumpMapDefault, Index, SendMap};
use roc_collections::all::{BumpMap, BumpMapDefault, HumanIndex, SendMap};
use roc_module::{
ident::{Lowercase, TagName},
symbol::Symbol,
@ -163,7 +163,7 @@ pub fn constrain_expr<'a>(
let elem_expected = Expected::ForReason(
Reason::ElemInList {
index: Index::zero_based(index),
index: HumanIndex::zero_based(index),
},
list_elem_type.shallow_clone(),
region,
@ -339,7 +339,7 @@ pub fn constrain_expr<'a>(
let reason = Reason::FnArg {
name: opt_symbol,
arg_index: Index::zero_based(index),
arg_index: HumanIndex::zero_based(index),
};
let expected_arg = Expected::ForReason(reason, arg_type.shallow_clone(), region);
@ -538,7 +538,7 @@ pub fn constrain_expr<'a>(
name.clone(),
arity,
AnnotationSource::TypedIfBranch {
index: Index::zero_based(index),
index: HumanIndex::zero_based(index),
num_branches,
region: ann_source.region(),
},
@ -559,7 +559,7 @@ pub fn constrain_expr<'a>(
name,
arity,
AnnotationSource::TypedIfBranch {
index: Index::zero_based(branches.len()),
index: HumanIndex::zero_based(branches.len()),
num_branches,
region: ann_source.region(),
},
@ -596,7 +596,7 @@ pub fn constrain_expr<'a>(
body,
Expected::ForReason(
Reason::IfBranch {
index: Index::zero_based(index),
index: HumanIndex::zero_based(index),
total_branches: branches.len(),
},
Type2::Variable(*expr_var),
@ -616,7 +616,7 @@ pub fn constrain_expr<'a>(
final_else_expr,
Expected::ForReason(
Reason::IfBranch {
index: Index::zero_based(branches.len()),
index: HumanIndex::zero_based(branches.len()),
total_branches: branches.len() + 1,
},
Type2::Variable(*expr_var),
@ -691,7 +691,7 @@ pub fn constrain_expr<'a>(
when_branch,
PExpected::ForReason(
PReason::WhenMatch {
index: Index::zero_based(index),
index: HumanIndex::zero_based(index),
},
cond_type.shallow_clone(),
pattern_region,
@ -700,7 +700,7 @@ pub fn constrain_expr<'a>(
name.clone(),
*arity,
AnnotationSource::TypedWhenBranch {
index: Index::zero_based(index),
index: HumanIndex::zero_based(index),
region: ann_source.region(),
},
typ.shallow_clone(),
@ -733,14 +733,14 @@ pub fn constrain_expr<'a>(
when_branch,
PExpected::ForReason(
PReason::WhenMatch {
index: Index::zero_based(index),
index: HumanIndex::zero_based(index),
},
cond_type.shallow_clone(),
pattern_region,
),
Expected::ForReason(
Reason::WhenBranch {
index: Index::zero_based(index),
index: HumanIndex::zero_based(index),
},
branch_type.shallow_clone(),
// TODO: when_branch.value.region,
@ -1065,7 +1065,7 @@ pub fn constrain_expr<'a>(
let reason = Reason::LowLevelOpArg {
op: *op,
arg_index: Index::zero_based(index),
arg_index: HumanIndex::zero_based(index),
};
let expected_arg =
Expected::ForReason(reason, arg_type.shallow_clone(), Region::zero());
@ -1474,6 +1474,15 @@ pub fn constrain_pattern<'a>(
));
}
CharacterLiteral(_) => {
state.constraints.push(Constraint::Pattern(
region,
PatternCategory::Character,
num_unsigned32(env.pool),
expected,
));
}
RecordDestructure {
whole_var,
ext_var,
@ -1672,7 +1681,7 @@ fn constrain_tag_pattern<'a>(
let expected = PExpected::ForReason(
PReason::TagArg {
tag_name: tag_name.clone(),
index: Index::zero_based(index),
index: HumanIndex::zero_based(index),
},
pattern_type,
region,
@ -1927,6 +1936,26 @@ fn _num_signed64(pool: &mut Pool) -> Type2 {
)
}
#[inline(always)]
fn num_unsigned32(pool: &mut Pool) -> Type2 {
let alias_content = Type2::TagUnion(
PoolVec::new(
std::iter::once((
TagName::Private(Symbol::NUM_UNSIGNED32),
PoolVec::empty(pool),
)),
pool,
),
pool.add(Type2::EmptyTagUnion),
);
Type2::Alias(
Symbol::NUM_UNSIGNED32,
PoolVec::empty(pool),
pool.add(alias_content),
)
}
#[inline(always)]
fn _num_integer(pool: &mut Pool, range: TypeId) -> Type2 {
let range_type = pool.get(range);

View file

@ -15,6 +15,24 @@ pub struct AST {
pub def_ids: Vec<DefId>,
}
impl AST {
pub fn insert_def_at_index(&mut self, new_def_id: DefId, index: usize) {
self.def_ids.insert(index, new_def_id);
}
// TODO print in tree shape, similar to linux tree command
pub fn ast_to_string(&self, pool: &Pool) -> String {
let mut full_ast_string = String::new();
for def_id in self.def_ids.iter() {
full_ast_string.push_str(&def2_to_string(*def_id, pool));
full_ast_string.push_str("\n\n");
}
full_ast_string
}
}
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum ASTNodeId {
ADefId(DefId),

View file

@ -13,9 +13,10 @@
// use crate::pattern::{bindings_from_patterns, canonicalize_pattern, Pattern};
// use crate::procedure::References;
use roc_collections::all::{default_hasher, ImMap, MutMap, MutSet, SendMap};
use roc_error_macros::todo_opaques;
use roc_module::ident::Lowercase;
use roc_module::symbol::Symbol;
use roc_parse::ast::{self, AliasHeader};
use roc_parse::ast::{self, TypeHeader};
use roc_parse::pattern::PatternType;
use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Loc, Region};
@ -199,7 +200,7 @@ fn to_pending_def<'a>(
}
roc_parse::ast::Def::Alias {
header: AliasHeader { name, vars },
header: TypeHeader { name, vars },
ann,
} => {
let region = Region::span_across(&name.region, &ann.region);
@ -260,6 +261,8 @@ fn to_pending_def<'a>(
}
}
Opaque { .. } => todo_opaques!(),
Expect(_) => todo!(),
SpaceBefore(sub_def, _) | SpaceAfter(sub_def, _) => {
@ -321,7 +324,7 @@ fn from_pending_alias<'a>(
for loc_lowercase in vars {
if !named_rigids.contains_key(&loc_lowercase.value) {
env.problem(Problem::PhantomTypeArgument {
alias: symbol,
typ: symbol,
variable_region: loc_lowercase.region,
variable_name: loc_lowercase.value.clone(),
});

View file

@ -148,6 +148,9 @@ fn expr2_to_string_helper(
&Expr2::Var { .. } => {
out_string.push_str(&format!("{:?}", expr2,));
}
Expr2::RuntimeError { .. } => {
out_string.push_str("RuntimeError\n");
}
other => todo!("Implement for {:?}", other),
}

View file

@ -50,6 +50,7 @@ pub fn expr_to_expr2<'a>(
region: Region,
) -> (Expr2, self::Output) {
use roc_parse::ast::Expr::*;
//dbg!("{:?}", parse_expr);
match parse_expr {
Float(string) => {

View file

@ -8,6 +8,7 @@ use roc_can::num::{
finish_parsing_base, finish_parsing_float, finish_parsing_num, ParsedNumResult,
};
use roc_collections::all::BumpMap;
use roc_error_macros::todo_opaques;
use roc_module::symbol::{Interns, Symbol};
use roc_parse::ast::{StrLiteral, StrSegment};
use roc_parse::pattern::PatternType;
@ -38,6 +39,7 @@ pub enum Pattern2 {
IntLiteral(IntVal), // 16B
FloatLiteral(FloatVal), // 16B
StrLiteral(PoolStr), // 8B
CharacterLiteral(char), // 4B
Underscore, // 0B
GlobalTag {
whole_var: Variable, // 4B
@ -248,6 +250,26 @@ pub fn to_pattern2<'a>(
ptype => unsupported_pattern(env, ptype, region),
},
SingleQuote(string) => match pattern_type {
WhenBranch => {
let mut it = string.chars().peekable();
if let Some(char) = it.next() {
if it.peek().is_none() {
Pattern2::CharacterLiteral(char)
} else {
// multiple chars is found
let problem = MalformedPatternProblem::MultipleCharsInSingleQuote;
malformed_pattern(env, problem, region)
}
} else {
// no characters found
let problem = MalformedPatternProblem::EmptySingleQuote;
malformed_pattern(env, problem, region)
}
}
ptype => unsupported_pattern(env, ptype, region),
},
GlobalTag(name) => {
// Canonicalize the tag's name.
Pattern2::GlobalTag {
@ -269,6 +291,8 @@ pub fn to_pattern2<'a>(
}
}
OpaqueRef(..) => todo_opaques!(),
Apply(tag, patterns) => {
let can_patterns = PoolVec::with_capacity(patterns.len() as u32, env.pool);
for (loc_pattern, node_id) in (*patterns).iter().zip(can_patterns.iter_node_ids()) {
@ -503,6 +527,7 @@ pub fn symbols_from_pattern(pool: &Pool, initial: &Pattern2) -> Vec<Symbol> {
| IntLiteral(_)
| FloatLiteral(_)
| StrLiteral(_)
| CharacterLiteral(_)
| Underscore
| MalformedPattern(_, _)
| Shadowed { .. }
@ -563,6 +588,7 @@ pub fn symbols_and_variables_from_pattern(
| IntLiteral(_)
| FloatLiteral(_)
| StrLiteral(_)
| CharacterLiteral(_)
| Underscore
| MalformedPattern(_, _)
| Shadowed { .. }

View file

@ -329,9 +329,9 @@ pub fn to_type2<'a>(
annotation: &roc_parse::ast::TypeAnnotation<'a>,
region: Region,
) -> Type2 {
use roc_parse::ast::AliasHeader;
use roc_parse::ast::Pattern;
use roc_parse::ast::TypeAnnotation::*;
use roc_parse::ast::TypeHeader;
match annotation {
Apply(module_name, ident, targs) => {
@ -455,7 +455,7 @@ pub fn to_type2<'a>(
As(
loc_inner,
_spaces,
AliasHeader {
TypeHeader {
name,
vars: loc_vars,
},

View file

@ -160,12 +160,17 @@ impl<'a> Env<'a> {
})
}
},
None => {
panic!(
"Module {} exists, but is not recorded in dep_idents",
module_name
)
}
None => Err(RuntimeError::ModuleNotImported {
module_name,
imported_modules: self
.dep_idents
.keys()
.filter_map(|module_id| self.module_ids.get_name(*module_id))
.map(|module_name| module_name.as_ref().into())
.collect(),
region,
module_exists: true,
}),
}
}
}
@ -177,6 +182,7 @@ impl<'a> Env<'a> {
.map(|string| string.as_ref().into())
.collect(),
region,
module_exists: false,
}),
}
}

View file

@ -43,7 +43,8 @@ fn to_type2(
var_store: &mut VarStore,
) -> Type2 {
match solved_type {
SolvedType::Alias(symbol, solved_type_variables, _todo, solved_actual) => {
// TODO(opaques): take opaques into account
SolvedType::Alias(symbol, solved_type_variables, _todo, solved_actual, _kind) => {
let type_variables = PoolVec::with_capacity(solved_type_variables.len() as u32, pool);
for (type_variable_node_id, (lowercase, solved_arg)) in type_variables

View file

@ -10,12 +10,10 @@
///
/// Pages also use the node value 0 (all 0 bits) to mark nodes as unoccupied.
/// This is important for performance.
use libc::{MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE};
use std::any::type_name;
use std::ffi::c_void;
use std::marker::PhantomData;
use std::mem::{align_of, size_of, MaybeUninit};
use std::ptr::null;
pub const NODE_BYTES: usize = 32;
@ -108,14 +106,32 @@ impl Pool {
// addresses from the OS which will be lazily translated into
// physical memory one 4096-byte page at a time, once we actually
// try to read or write in that page's address range.
#[cfg(unix)]
{
use libc::{MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE};
libc::mmap(
null::<c_void>() as *mut c_void,
std::ptr::null_mut(),
bytes_to_mmap,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
0,
0,
)
}
#[cfg(windows)]
{
use winapi::um::memoryapi::VirtualAlloc;
use winapi::um::winnt::PAGE_READWRITE;
use winapi::um::winnt::{MEM_COMMIT, MEM_RESERVE};
VirtualAlloc(
std::ptr::null_mut(),
bytes_to_mmap,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE,
)
}
} as *mut [MaybeUninit<u8>; NODE_BYTES];
// This is our actual capacity, in nodes.
@ -230,10 +246,24 @@ impl<T> std::ops::IndexMut<NodeId<T>> for Pool {
impl Drop for Pool {
fn drop(&mut self) {
unsafe {
#[cfg(unix)]
{
libc::munmap(
self.nodes as *mut c_void,
NODE_BYTES * self.capacity as usize,
);
}
#[cfg(windows)]
{
use winapi::um::memoryapi::VirtualFree;
use winapi::um::winnt::MEM_RELEASE;
VirtualFree(
self.nodes as *mut c_void,
NODE_BYTES * self.capacity as usize,
MEM_RELEASE,
);
}
}
}
}

View file

@ -27,7 +27,7 @@ pub fn load_module(src_file: &Path) -> LoadedModule {
Ok(x) => x,
Err(roc_load::file::LoadingProblem::FormattedReport(report)) => {
panic!(
"Failed to load module from src_file {:?}. Report: {:?}",
"Failed to load module from src_file {:?}. Report: {}",
src_file, report
);
}

View file

@ -25,7 +25,7 @@ pub fn parse_from_string<'a>(
) -> ASTResult<AST> {
let blank_line_indx = code_str
.find("\n\n")
.expect("I was expecting a double newline to split header and rest of code.");
.expect("I was expecting two newline chars to split header and rest of code.");
let header_str = &code_str[0..blank_line_indx];
let tail_str = &code_str[blank_line_indx..];

View file

@ -1,133 +0,0 @@
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_fmt::def::fmt_def;
use roc_fmt::module::fmt_module;
use roc_parse::ast::{Def, Module};
use roc_parse::module::module_defs;
use roc_parse::parser;
use roc_parse::parser::{Parser, SyntaxError};
use roc_region::all::Located;
use std::ffi::OsStr;
use std::path::Path;
use std::{fs, io};
#[derive(Debug)]
pub struct File<'a> {
path: &'a Path,
module_header: Module<'a>,
content: Vec<'a, Located<Def<'a>>>,
}
#[derive(Debug)]
pub enum ReadError<'a> {
Read(std::io::Error),
ParseDefs(SyntaxError<'a>),
ParseHeader(SyntaxError<'a>),
DoesntHaveRocExtension,
}
impl<'a> File<'a> {
pub fn read(path: &'a Path, arena: &'a Bump) -> Result<File<'a>, ReadError<'a>> {
if path.extension() != Some(OsStr::new("roc")) {
return Err(ReadError::DoesntHaveRocExtension);
}
let bytes = fs::read(path).map_err(ReadError::Read)?;
let allocation = arena.alloc(bytes);
let module_parse_state = parser::State::new(allocation);
let parsed_module = roc_parse::module::parse_header(arena, module_parse_state);
match parsed_module {
Ok((module, state)) => {
let parsed_defs = module_defs().parse(arena, state);
match parsed_defs {
Ok((_, defs, _)) => Ok(File {
path,
module_header: module,
content: defs,
}),
Err((_, error, _)) => Err(ReadError::ParseDefs(error)),
}
}
Err(error) => Err(ReadError::ParseHeader(SyntaxError::Header(error))),
}
}
pub fn fmt(&self) -> String {
let arena = Bump::new();
let mut formatted_file = String::new();
let mut module_header_buf = bumpalo::collections::String::new_in(&arena);
fmt_module(&mut module_header_buf, &self.module_header);
formatted_file.push_str(module_header_buf.as_str());
for def in &self.content {
let mut def_buf = bumpalo::collections::String::new_in(&arena);
fmt_def(&mut def_buf, &def.value, 0);
formatted_file.push_str(def_buf.as_str());
}
formatted_file
}
pub fn fmt_then_write_to(&self, write_path: &'a Path) -> io::Result<()> {
let formatted_file = self.fmt();
fs::write(write_path, formatted_file)
}
pub fn fmt_then_write_with_name(&self, new_name: &str) -> io::Result<()> {
self.fmt_then_write_to(
self.path
.with_file_name(new_name)
.with_extension("roc")
.as_path(),
)
}
pub fn fmt_then_write(&self) -> io::Result<()> {
self.fmt_then_write_to(self.path)
}
}
#[cfg(test)]
mod test_file {
use crate::lang::roc_file;
use bumpalo::Bump;
use std::path::Path;
#[test]
fn read_and_fmt_simple_roc_module() {
let simple_module_path = Path::new("./tests/modules/SimpleUnformatted.roc");
let arena = Bump::new();
let file = roc_file::File::read(simple_module_path, &arena)
.expect("Could not read SimpleUnformatted.roc in test_file test");
assert_eq!(
file.fmt(),
indoc!(
r#"
interface Simple
exposes [
v, x
]
imports []
v : Str
v = "Value!"
x : Int
x = 4"#
)
);
}
}

View file

@ -12,7 +12,8 @@ use roc_types::subs::{
SubsSlice, UnionTags, Variable, VariableSubsSlice,
};
use roc_types::types::{
gather_fields_unsorted_iter, Alias, Category, ErrorType, PatternCategory, RecordField,
gather_fields_unsorted_iter, Alias, AliasKind, Category, ErrorType, PatternCategory,
RecordField,
};
use roc_unify::unify::unify;
use roc_unify::unify::Mode;
@ -892,7 +893,9 @@ fn type_to_variable<'a>(
let arg_vars = AliasVariables::insert_into_subs(subs, arg_vars, []);
let alias_var = type_to_variable(arena, mempool, subs, rank, pools, cached, alias_type);
let content = Content::Alias(*symbol, arg_vars, alias_var);
// TODO(opaques): take opaques into account
let content = Content::Alias(*symbol, arg_vars, alias_var, AliasKind::Structural);
let result = register(subs, rank, pools, content);
@ -1384,7 +1387,7 @@ fn adjust_rank_content(
}
}
Alias(_, args, real_var) => {
Alias(_, args, real_var, _) => {
let mut rank = Rank::toplevel();
for var_index in args.variables() {
@ -1544,7 +1547,7 @@ fn instantiate_rigids_help(
subs.set(copy, make_descriptor(FlexVar(Some(name))));
}
Alias(_, args, real_type_var) => {
Alias(_, args, real_type_var, _) => {
for var_index in args.variables() {
let var = subs[var_index];
instantiate_rigids_help(subs, max_rank, pools, var);
@ -1794,7 +1797,7 @@ fn deep_copy_var_help(
copy
}
Alias(symbol, mut args, real_type_var) => {
Alias(symbol, mut args, real_type_var, kind) => {
let mut new_args = Vec::with_capacity(args.variables().len());
for var_index in args.variables() {
@ -1806,7 +1809,7 @@ fn deep_copy_var_help(
args.replace_variables(subs, new_args);
let new_real_type_var = deep_copy_var_help(subs, max_rank, pools, real_type_var);
let new_content = Alias(symbol, args, new_real_type_var);
let new_content = Alias(symbol, args, new_real_type_var, kind);
subs.set(copy, make_descriptor(new_content));

View file

@ -15,21 +15,24 @@ test = false
bench = false
[features]
default = ["target-aarch64", "target-x86_64", "target-wasm32", "editor"]
default = ["target-aarch64", "target-x86_64", "target-wasm32", "editor", "llvm"]
wasm32-cli-run = ["target-wasm32", "run-wasm32"]
i386-cli-run = ["target-x86"]
# TODO: change to roc_repl_cli/llvm once roc_repl can run without llvm.
llvm = ["roc_build/llvm", "roc_repl_cli"]
editor = ["roc_editor"]
run-wasm32 = ["wasmer", "wasmer-wasi"]
# Compiling for a different platform than the host can cause linker errors.
target-arm = ["roc_build/target-arm"]
target-aarch64 = ["roc_build/target-aarch64"]
target-x86 = ["roc_build/target-x86"]
target-x86_64 = ["roc_build/target-x86_64"]
target-wasm32 = ["roc_build/target-wasm32"]
target-arm = ["roc_build/target-arm", "roc_repl_cli/target-arm"]
target-aarch64 = ["roc_build/target-aarch64", "roc_repl_cli/target-aarch64"]
target-x86 = ["roc_build/target-x86", "roc_repl_cli/target-x86"]
target-x86_64 = ["roc_build/target-x86_64", "roc_repl_cli/target-x86_64"]
target-wasm32 = ["roc_build/target-wasm32", "roc_repl_cli/target-wasm32"]
target-all = [
"target-aarch64",
@ -50,27 +53,31 @@ roc_module = { path = "../compiler/module" }
roc_builtins = { path = "../compiler/builtins" }
roc_mono = { path = "../compiler/mono" }
roc_load = { path = "../compiler/load" }
roc_build = { path = "../compiler/build", default-features = false }
roc_build = { path = "../compiler/build" }
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"] }
roc_repl_cli = { path = "../repl_cli", optional = true }
clap = { version = "3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] }
const_format = "0.2.22"
bumpalo = { version = "3.8.0", features = ["collections"] }
mimalloc = { version = "0.1.26", default-features = false }
target-lexicon = "0.12.2"
tempfile = "3.2.0"
wasmer = { version = "2.0.0", optional = true, default-features = false, features = ["default-cranelift", "default-universal"] }
wasmer-wasi = { version = "2.0.0", optional = true }
# Wasmer singlepass compiler only works on x86_64.
[target.'cfg(target_arch = "x86_64")'.dependencies]
wasmer = { version = "2.0.0", optional = true, default-features = false, features = ["default-singlepass", "default-universal"] }
[target.'cfg(not(target_arch = "x86_64"))'.dependencies]
wasmer = { version = "2.0.0", optional = true, default-features = false, features = ["default-cranelift", "default-universal"] }
[dev-dependencies]
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" }
@ -79,6 +86,13 @@ serial_test = "0.5.1"
criterion = { git = "https://github.com/Anton-4/criterion.rs"}
cli_utils = { path = "../cli_utils" }
# Wasmer singlepass compiler only works on x86_64.
[target.'cfg(target_arch = "x86_64")'.dev-dependencies]
wasmer = { version = "2.0.0", default-features = false, features = ["default-singlepass", "default-universal"] }
[target.'cfg(not(target_arch = "x86_64"))'.dev-dependencies]
wasmer = { version = "2.0.0", default-features = false, features = ["default-cranelift", "default-universal"] }
[[bench]]
name = "time_bench"
harness = false

View file

@ -9,8 +9,8 @@ use roc_fmt::module::fmt_module;
use roc_fmt::Buf;
use roc_module::called_via::{BinOp, UnaryOp};
use roc_parse::ast::{
AliasHeader, AssignedField, Collection, Expr, Pattern, Spaced, StrLiteral, StrSegment, Tag,
TypeAnnotation, WhenBranch,
AssignedField, Collection, Expr, Pattern, Spaced, StrLiteral, StrSegment, Tag, TypeAnnotation,
TypeHeader, WhenBranch,
};
use roc_parse::header::{
AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
@ -25,7 +25,49 @@ use roc_parse::{
};
use roc_region::all::{Loc, Region};
fn flatten_directories(files: std::vec::Vec<PathBuf>) -> std::vec::Vec<PathBuf> {
let mut to_flatten = files;
let mut files = vec![];
while let Some(path) = to_flatten.pop() {
if path.is_dir() {
match path.read_dir() {
Ok(directory) => {
for item in directory {
match item {
Ok(file) => {
let file_path = file.path();
if file_path.is_dir() {
to_flatten.push(file_path);
} else if file_path.ends_with(".roc") {
files.push(file_path);
}
}
Err(error) => internal_error!(
"There was an error while trying to read a file from a directory: {:?}",
error
),
}
}
}
Err(error) => internal_error!(
"There was an error while trying to read the contents of a directory: {:?}",
error
),
}
} else {
files.push(path)
}
}
files
}
pub fn format(files: std::vec::Vec<PathBuf>, mode: FormatMode) -> Result<(), String> {
let files = flatten_directories(files);
for file in files {
let arena = Bump::new();
@ -138,6 +180,8 @@ fn fmt_all<'a>(arena: &'a Bump, buf: &mut Buf<'a>, ast: &'a Ast) {
for def in &ast.defs {
fmt_def(buf, arena.alloc(def.value), 0);
}
buf.fmt_end_of_file();
}
/// RemoveSpaces normalizes the ast to something that we _expect_ to be invariant under formatting.
@ -402,15 +446,25 @@ impl<'a> RemoveSpaces<'a> for Def<'a> {
Def::Annotation(a.remove_spaces(arena), b.remove_spaces(arena))
}
Def::Alias {
header: AliasHeader { name, vars },
header: TypeHeader { name, vars },
ann,
} => Def::Alias {
header: AliasHeader {
header: TypeHeader {
name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena),
},
ann: ann.remove_spaces(arena),
},
Def::Opaque {
header: TypeHeader { name, vars },
typ,
} => Def::Opaque {
header: TypeHeader {
name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena),
},
typ: typ.remove_spaces(arena),
},
Def::Body(a, b) => Def::Body(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
@ -514,6 +568,7 @@ impl<'a> RemoveSpaces<'a> for Expr<'a> {
Expr::Underscore(a) => Expr::Underscore(a),
Expr::GlobalTag(a) => Expr::GlobalTag(a),
Expr::PrivateTag(a) => Expr::PrivateTag(a),
Expr::OpaqueRef(a) => Expr::OpaqueRef(a),
Expr::Closure(a, b) => Expr::Closure(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
@ -554,6 +609,7 @@ impl<'a> RemoveSpaces<'a> for Expr<'a> {
Expr::PrecedenceConflict(a) => Expr::PrecedenceConflict(a),
Expr::SpaceBefore(a, _) => a.remove_spaces(arena),
Expr::SpaceAfter(a, _) => a.remove_spaces(arena),
Expr::SingleQuote(a) => Expr::Num(a),
}
}
}
@ -564,6 +620,7 @@ impl<'a> RemoveSpaces<'a> for Pattern<'a> {
Pattern::Identifier(a) => Pattern::Identifier(a),
Pattern::GlobalTag(a) => Pattern::GlobalTag(a),
Pattern::PrivateTag(a) => Pattern::PrivateTag(a),
Pattern::OpaqueRef(a) => Pattern::OpaqueRef(a),
Pattern::Apply(a, b) => Pattern::Apply(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
@ -595,6 +652,7 @@ impl<'a> RemoveSpaces<'a> for Pattern<'a> {
}
Pattern::SpaceBefore(a, _) => a.remove_spaces(arena),
Pattern::SpaceAfter(a, _) => a.remove_spaces(arena),
Pattern::SingleQuote(a) => Pattern::NumLiteral(a),
}
}
}

View file

@ -5,6 +5,7 @@ use build::{BuildOutcome, BuiltFile};
use bumpalo::Bump;
use clap::{App, AppSettings, Arg, ArgMatches};
use roc_build::link::LinkType;
use roc_error_macros::user_error;
use roc_load::file::LoadingProblem;
use roc_mono::ir::OptLevel;
use std::env;
@ -31,8 +32,9 @@ pub const CMD_FORMAT: &str = "format";
pub const FLAG_DEBUG: &str = "debug";
pub const FLAG_DEV: &str = "dev";
pub const FLAG_OPTIMIZE: &str = "optimize";
pub const FLAG_OPT_SIZE: &str = "opt-size";
pub const FLAG_LIB: &str = "lib";
pub const FLAG_BACKEND: &str = "backend";
pub const FLAG_TARGET: &str = "target";
pub const FLAG_TIME: &str = "time";
pub const FLAG_LINK: &str = "roc-linker";
pub const FLAG_PRECOMPILED: &str = "precompiled-host";
@ -40,7 +42,6 @@ pub const FLAG_VALGRIND: &str = "valgrind";
pub const FLAG_CHECK: &str = "check";
pub const ROC_FILE: &str = "ROC_FILE";
pub const ROC_DIR: &str = "ROC_DIR";
pub const BACKEND: &str = "BACKEND";
pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES";
pub const ARGS_FOR_APP: &str = "ARGS_FOR_APP";
@ -61,6 +62,12 @@ pub fn build_app<'a>() -> App<'a> {
.about("Optimize your compiled Roc program to run faster. (Optimization takes time to complete.)")
.required(false),
)
.arg(
Arg::new(FLAG_OPT_SIZE)
.long(FLAG_OPT_SIZE)
.about("Optimize your compiled Roc program to have a small binary size. (Optimization takes time to complete.)")
.required(false),
)
.arg(
Arg::new(FLAG_DEV)
.long(FLAG_DEV)
@ -68,12 +75,11 @@ pub fn build_app<'a>() -> App<'a> {
.required(false),
)
.arg(
Arg::new(FLAG_BACKEND)
.long(FLAG_BACKEND)
.about("Choose a different backend")
// .requires(BACKEND)
.default_value(Backend::default().as_str())
.possible_values(Backend::OPTIONS)
Arg::new(FLAG_TARGET)
.long(FLAG_TARGET)
.about("Choose a different target")
.default_value(Target::default().as_str())
.possible_values(Target::OPTIONS)
.required(false),
)
.arg(
@ -166,6 +172,12 @@ pub fn build_app<'a>() -> App<'a> {
.requires(ROC_FILE)
.required(false),
)
.arg(
Arg::new(FLAG_OPT_SIZE)
.long(FLAG_OPT_SIZE)
.about("Optimize your compiled Roc program to have a small binary size. (Optimization takes time to complete.)")
.required(false),
)
.arg(
Arg::new(FLAG_DEV)
.long(FLAG_DEV)
@ -198,12 +210,11 @@ pub fn build_app<'a>() -> App<'a> {
.required(false),
)
.arg(
Arg::new(FLAG_BACKEND)
.long(FLAG_BACKEND)
.about("Choose a different backend")
// .requires(BACKEND)
.default_value(Backend::default().as_str())
.possible_values(Backend::OPTIONS)
Arg::new(FLAG_TARGET)
.long(FLAG_TARGET)
.about("Choose a different target")
.default_value(Target::default().as_str())
.possible_values(Target::OPTIONS)
.required(false),
)
.arg(
@ -259,12 +270,12 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
use std::str::FromStr;
use BuildConfig::*;
let backend = match matches.value_of(FLAG_BACKEND) {
Some(name) => Backend::from_str(name).unwrap(),
None => Backend::default(),
let target = match matches.value_of(FLAG_TARGET) {
Some(name) => Target::from_str(name).unwrap(),
None => Target::default(),
};
let target = backend.to_triple();
let triple = target.to_triple();
let arena = Bump::new();
let filename = matches.value_of(ROC_FILE).unwrap();
@ -272,12 +283,14 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
let original_cwd = std::env::current_dir()?;
let opt_level = match (
matches.is_present(FLAG_OPTIMIZE),
matches.is_present(FLAG_OPT_SIZE),
matches.is_present(FLAG_DEV),
) {
(true, false) => OptLevel::Optimize,
(true, true) => panic!("development cannot be optimized!"),
(false, true) => OptLevel::Development,
(false, false) => OptLevel::Normal,
(true, false, false) => OptLevel::Optimize,
(false, true, false) => OptLevel::Size,
(false, false, true) => OptLevel::Development,
(false, false, false) => OptLevel::Normal,
_ => user_error!("build can be only one of `--dev`, `--optimize`, or `--opt-size`"),
};
let emit_debug_info = matches.is_present(FLAG_DEBUG);
let emit_timings = matches.is_present(FLAG_TIME);
@ -290,10 +303,10 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
let surgically_link = matches.is_present(FLAG_LINK);
let precompiled = matches.is_present(FLAG_PRECOMPILED);
if surgically_link && !roc_linker::supported(&link_type, &target) {
if surgically_link && !roc_linker::supported(&link_type, &triple) {
panic!(
"Link type, {:?}, with target, {}, not supported by roc linker",
link_type, target
link_type, triple
);
}
@ -322,7 +335,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
let target_valgrind = matches.is_present(FLAG_VALGRIND);
let res_binary_path = build_file(
&arena,
&target,
&triple,
src_dir,
path,
opt_level,
@ -361,7 +374,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
Ok(outcome.status_code())
}
BuildAndRun { roc_file_arg_index } => {
let mut cmd = match target.architecture {
let mut cmd = match triple.architecture {
Architecture::Wasm32 => {
// If possible, report the generated executable name relative to the current dir.
let generated_filename = binary_path
@ -382,7 +395,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
_ => Command::new(&binary_path),
};
if let Architecture::Wasm32 = target.architecture {
if let Architecture::Wasm32 = triple.architecture {
cmd.arg(binary_path);
}
@ -487,43 +500,43 @@ fn run_with_wasmer(_wasm_path: &std::path::Path, _args: &[String]) {
println!("Running wasm files not support");
}
enum Backend {
enum Target {
Host,
X86_32,
X86_64,
Wasm32,
}
impl Default for Backend {
impl Default for Target {
fn default() -> Self {
Backend::Host
Target::Host
}
}
impl Backend {
impl Target {
const fn as_str(&self) -> &'static str {
match self {
Backend::Host => "host",
Backend::X86_32 => "x86_32",
Backend::X86_64 => "x86_64",
Backend::Wasm32 => "wasm32",
Target::Host => "host",
Target::X86_32 => "x86_32",
Target::X86_64 => "x86_64",
Target::Wasm32 => "wasm32",
}
}
/// NOTE keep up to date!
const OPTIONS: &'static [&'static str] = &[
Backend::Host.as_str(),
Backend::X86_32.as_str(),
Backend::X86_64.as_str(),
Backend::Wasm32.as_str(),
Target::Host.as_str(),
Target::X86_32.as_str(),
Target::X86_64.as_str(),
Target::Wasm32.as_str(),
];
fn to_triple(&self) -> Triple {
let mut triple = Triple::unknown();
match self {
Backend::Host => Triple::host(),
Backend::X86_32 => {
Target::Host => Triple::host(),
Target::X86_32 => {
triple.architecture = Architecture::X86_32(X86_32Architecture::I386);
triple.binary_format = BinaryFormat::Elf;
@ -532,13 +545,13 @@ impl Backend {
triple
}
Backend::X86_64 => {
Target::X86_64 => {
triple.architecture = Architecture::X86_64;
triple.binary_format = BinaryFormat::Elf;
triple
}
Backend::Wasm32 => {
Target::Wasm32 => {
triple.architecture = Architecture::Wasm32;
triple.binary_format = BinaryFormat::Wasm;
@ -548,21 +561,21 @@ impl Backend {
}
}
impl std::fmt::Display for Backend {
impl std::fmt::Display for Target {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl std::str::FromStr for Backend {
impl std::str::FromStr for Target {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"host" => Ok(Backend::Host),
"x86_32" => Ok(Backend::X86_32),
"x86_64" => Ok(Backend::X86_64),
"wasm32" => Ok(Backend::Wasm32),
"host" => Ok(Target::Host),
"x86_32" => Ok(Target::X86_32),
"x86_64" => Ok(Target::X86_64),
"wasm32" => Ok(Target::Wasm32),
_ => Err(()),
}
}

View file

@ -63,11 +63,17 @@ fn main() -> io::Result<()> {
}
}
Some((CMD_REPL, _)) => {
#[cfg(feature = "llvm")]
{
roc_repl_cli::main()?;
// Exit 0 if the repl exited normally
Ok(0)
}
#[cfg(not(feature = "llvm"))]
todo!("enable roc repl without llvm");
}
Some((CMD_EDIT, matches)) => {
match matches
.values_of_os(DIRECTORY_OR_FILES)

View file

@ -12,8 +12,9 @@ extern crate indoc;
#[cfg(test)]
mod cli_run {
use cli_utils::helpers::{
example_file, examples_dir, extract_valgrind_errors, fixture_file, known_bad_file, run_cmd,
run_roc, run_with_valgrind, ValgrindError, ValgrindErrorXWhat,
example_file, examples_dir, extract_valgrind_errors, fixture_file, fixtures_dir,
known_bad_file, run_cmd, run_roc, run_with_valgrind, Out, ValgrindError,
ValgrindErrorXWhat,
};
use roc_test_utils::assert_multiline_str_eq;
use serial_test::serial;
@ -63,15 +64,15 @@ mod cli_run {
}
fn check_compile_error(file: &Path, flags: &[&str], expected: &str) {
let compile_out = run_roc(&[&["check", file.to_str().unwrap()], &flags[..]].concat());
let compile_out = run_roc(&[&["check", file.to_str().unwrap()], flags].concat());
let err = compile_out.stdout.trim();
let err = strip_colors(&err);
let err = strip_colors(err);
assert_multiline_str_eq!(err, expected.into());
}
fn check_format_check_as_expected(file: &Path, expects_success_exit_code: bool) {
let flags = &["--check"];
let out = run_roc(&[&["format", &file.to_str().unwrap()], &flags[..]].concat());
let out = run_roc(&[&["format", file.to_str().unwrap()], &flags[..]].concat());
if expects_success_exit_code {
assert!(out.status.success());
@ -80,6 +81,17 @@ mod cli_run {
}
}
fn build_example(file: &Path, flags: &[&str]) -> Out {
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags].concat());
if !compile_out.stderr.is_empty() {
panic!("roc build had stderr: {}", compile_out.stderr);
}
assert!(compile_out.status.success(), "bad status {:?}", compile_out);
compile_out
}
fn check_output_with_stdin(
file: &Path,
stdin: &[&str],
@ -96,12 +108,7 @@ mod cli_run {
all_flags.extend_from_slice(&["--valgrind"]);
}
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], &all_flags[..]].concat());
if !compile_out.stderr.is_empty() {
panic!("roc build had stderr: {}", compile_out.stderr);
}
assert!(compile_out.status.success(), "bad status {:?}", compile_out);
build_example(file, &all_flags[..]);
let out = if use_valgrind && ALLOW_VALGRIND {
let (valgrind_out, raw_xml) = if let Some(input_file) = input_file {
@ -187,7 +194,7 @@ mod cli_run {
) {
assert_eq!(input_file, None, "Wasm does not support input files");
let mut flags = flags.to_vec();
flags.push("--backend=wasm32");
flags.push("--target=wasm32");
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags.as_slice()].concat());
if !compile_out.stderr.is_empty() {
@ -227,17 +234,28 @@ mod cli_run {
let file_name = example_file(dir_name, example.filename);
match example.executable_filename {
"hello-web" => {
"helloWeb" => {
// this is a web webassembly example, but we don't test with JS at the moment
eprintln!("WARNING: skipping testing example {} because the test is broken right now!", example.filename);
return;
}
"hello-swift" => {
"helloSwift" => {
if cfg!(not(target_os = "macos")) {
eprintln!("WARNING: skipping testing example {} because it only works on MacOS.", example.filename);
return;
}
}
"hello-gui" => {
// Since this one requires opening a window, we do `roc build` on it but don't run it.
if cfg!(target_os = "linux") {
// The surgical linker can successfully link this on Linux, but the legacy linker errors!
build_example(&file_name, &["--optimize", "--roc-linker"]);
} else {
build_example(&file_name, &["--optimize"]);
}
return;
}
_ => {}
}
@ -306,56 +324,72 @@ mod cli_run {
// },
// ]
examples! {
hello_world:"hello-world" => Example {
filename: "Hello.roc",
executable_filename: "hello-world",
helloWorld:"hello-world" => Example {
filename: "helloWorld.roc",
executable_filename: "helloWorld",
stdin: &[],
input_file: None,
expected_ending:"Hello, World!\n",
use_valgrind: true,
},
hello_zig:"hello-zig" => Example {
filename: "Hello.roc",
executable_filename: "hello-world",
helloC:"hello-world/c-platform" => Example {
filename: "helloC.roc",
executable_filename: "helloC",
stdin: &[],
input_file: None,
expected_ending:"Hello, World!\n",
use_valgrind: true,
},
hello_rust:"hello-rust" => Example {
filename: "Hello.roc",
executable_filename: "hello-rust",
helloZig:"hello-world/zig-platform" => Example {
filename: "helloZig.roc",
executable_filename: "helloZig",
stdin: &[],
input_file: None,
expected_ending:"Hello, World!\n",
use_valgrind: true,
},
hello_swift:"hello-swift" => Example {
filename: "Hello.roc",
executable_filename: "hello-swift",
stdin: &[],
input_file: None,
expected_ending:"Hello Swift, meet Roc\n",
use_valgrind: true,
},
hello_web:"hello-web" => Example {
filename: "Hello.roc",
executable_filename: "hello-web",
helloRust:"hello-world/rust-platform" => Example {
filename: "helloRust.roc",
executable_filename: "helloRust",
stdin: &[],
input_file: None,
expected_ending:"Hello, World!\n",
use_valgrind: true,
},
fib:"fib" => Example {
filename: "Fib.roc",
executable_filename: "fib",
helloSwift:"hello-world/swift-platform" => Example {
filename: "helloSwift.roc",
executable_filename: "helloSwift",
stdin: &[],
input_file: None,
expected_ending:"Hello, World!\n",
use_valgrind: true,
},
helloWeb:"hello-world/web-platform" => Example {
filename: "helloWeb.roc",
executable_filename: "helloWeb",
stdin: &[],
input_file: None,
expected_ending:"Hello, World!\n",
use_valgrind: true,
},
fib:"algorithms" => Example {
filename: "fibonacci.roc",
executable_filename: "fibonacci",
stdin: &[],
input_file: None,
expected_ending:"55\n",
use_valgrind: true,
},
quicksort:"quicksort" => Example {
filename: "Quicksort.roc",
gui:"gui" => Example {
filename: "Hello.roc",
executable_filename: "hello-gui",
stdin: &[],
input_file: None,
expected_ending: "",
use_valgrind: false,
},
quicksort:"algorithms" => Example {
filename: "quicksort.roc",
executable_filename: "quicksort",
stdin: &[],
input_file: None,
@ -370,9 +404,9 @@ mod cli_run {
// expected_ending: "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
// use_valgrind: true,
// },
effect:"effect" => Example {
filename: "Main.roc",
executable_filename: "effect-example",
effects:"interactive" => Example {
filename: "effects.roc",
executable_filename: "effects",
stdin: &["hi there!"],
input_file: None,
expected_ending: "hi there!\nIt is known\n",
@ -386,7 +420,7 @@ mod cli_run {
// expected_ending: "",
// use_valgrind: true,
// },
cli:"cli" => Example {
cli:"interactive" => Example {
filename: "form.roc",
executable_filename: "form",
stdin: &["Giovanni\n", "Giorgio\n"],
@ -394,8 +428,8 @@ mod cli_run {
expected_ending: "Hi, Giovanni Giorgio! 👋\n",
use_valgrind: true,
},
tui:"tui" => Example {
filename: "Main.roc",
tui:"interactive" => Example {
filename: "tui.roc",
executable_filename: "tui",
stdin: &["foo\n"], // NOTE: adding more lines leads to memory leaks
input_file: None,
@ -539,7 +573,7 @@ mod cli_run {
&file_name,
benchmark.stdin,
benchmark.executable_filename,
&["--backend=x86_32"],
&["--target=x86_32"],
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
benchmark.expected_ending,
benchmark.use_valgrind,
@ -549,7 +583,7 @@ mod cli_run {
&file_name,
benchmark.stdin,
benchmark.executable_filename,
&["--backend=x86_32", "--optimize"],
&["--target=x86_32", "--optimize"],
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
benchmark.expected_ending,
benchmark.use_valgrind,
@ -680,6 +714,22 @@ mod cli_run {
if entry.file_type().unwrap().is_dir() {
let example_dir_name = entry.file_name().into_string().unwrap();
// TODO: Improve this with a more-dynamic approach. (Read all subdirectories?)
// Some hello-world examples live in nested directories
if example_dir_name == "hello-world" {
for sub_dir in [
"c-platform",
"rust-platform",
"swift-platform",
"web-platform",
"zig-platform",
] {
all_examples.remove(format!("{}/{}", example_dir_name, sub_dir).as_str()).unwrap_or_else(|| {
panic!("The example directory {}/{}/{} does not have any corresponding tests in cli_run. Please add one, so if it ever stops working, we'll know about it right away!", examples_dir, example_dir_name, sub_dir);
});
}
}
// We test benchmarks separately
if example_dir_name != "benchmarks" {
all_examples.remove(example_dir_name.as_str()).unwrap_or_else(|| {
@ -884,6 +934,15 @@ mod cli_run {
fn format_check_reformatting_needed() {
check_format_check_as_expected(&fixture_file("format", "NotFormatted.roc"), false);
}
#[test]
fn format_check_folders() {
// This fails, because "NotFormatted.roc" is present in this folder
check_format_check_as_expected(&fixtures_dir("format"), false);
// This doesn't fail, since only "Formatted.roc" is present in this folder
check_format_check_as_expected(&fixtures_dir("format/formatted_directory"), true);
}
}
#[allow(dead_code)]

View file

@ -0,0 +1,6 @@
app "formatted"
packages { pf: "platform" } imports []
provides [ main ] to pf
main : Str
main = Dep1.value1 {}

View file

@ -1,4 +1,4 @@
platform "examples/multi-module"
platform "multi-module"
requires {}{ main : Str }
exposes []
packages {}

View file

@ -61,15 +61,15 @@ pub export fn main() i32 {
// actually call roc to populate the callresult
const callresult = roc__mainForHost_1_exposed();
// end time
var ts2: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable;
// stdout the result
stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable;
callresult.deinit();
// end time
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);
stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable;

View file

@ -1,4 +1,4 @@
platform "examples/multi-dep-thunk"
platform "multi-dep-thunk"
requires {}{ main : Str }
exposes []
packages {}

View file

@ -60,15 +60,15 @@ pub export fn main() i32 {
// actually call roc to populate the callresult
const callresult = roc__mainForHost_1_exposed();
// end time
var ts2: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable;
// stdout the result
stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable;
callresult.deinit();
// end time
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);
stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable;

View file

@ -1 +1 @@
../../../examples/cli/platform
../../../examples/interactive/cli-platform

14
cli_utils/Cargo.lock generated
View file

@ -897,6 +897,12 @@ version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
[[package]]
name = "dunce"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "453440c271cf5577fd2a40e4942540cb7d0d2f85e27c8d07dd0023c925a67541"
[[package]]
name = "either"
version = "1.6.1"
@ -2504,6 +2510,7 @@ dependencies = [
name = "roc_builtins"
version = "0.1.0"
dependencies = [
"dunce",
"roc_collections",
"roc_module",
"roc_region",
@ -2518,6 +2525,7 @@ dependencies = [
"bumpalo",
"roc_builtins",
"roc_collections",
"roc_error_macros",
"roc_module",
"roc_parse",
"roc_problem",
@ -2587,6 +2595,7 @@ dependencies = [
"roc_builtins",
"roc_can",
"roc_collections",
"roc_error_macros",
"roc_module",
"roc_parse",
"roc_region",
@ -2687,6 +2696,7 @@ dependencies = [
"roc_mono",
"roc_problem",
"roc_region",
"roc_reporting",
"roc_solve",
"roc_target",
"roc_types",
@ -2760,6 +2770,7 @@ dependencies = [
"roc_can",
"roc_collections",
"roc_constrain",
"roc_error_macros",
"roc_module",
"roc_mono",
"roc_parse",
@ -2771,7 +2782,6 @@ dependencies = [
"roc_types",
"roc_unify",
"ven_pretty",
"wasm-bindgen",
]
[[package]]
@ -2880,7 +2890,6 @@ dependencies = [
"roc_reporting",
"roc_target",
"roc_types",
"wasm-bindgen",
]
[[package]]
@ -2913,7 +2922,6 @@ dependencies = [
"roc_region",
"roc_types",
"roc_unify",
"wasm-bindgen",
]
[[package]]

View file

@ -20,4 +20,6 @@ serde = { version = "1.0.130", features = ["derive"] }
serde-xml-rs = "0.5.1"
strip-ansi-escapes = "0.1.1"
tempfile = "3.2.0"
[target.'cfg(unix)'.dependencies]
rlimit = "0.6.2"

View file

@ -1,11 +1,12 @@
use crate::helpers::{example_file, run_cmd, run_roc};
use criterion::{black_box, measurement::Measurement, BenchmarkGroup};
use rlimit::{setrlimit, Resource};
use std::path::Path;
use std::{path::Path, thread};
const CFOLD_STACK_SIZE: usize = 8192 * 100000;
fn exec_bench_w_input<T: Measurement>(
file: &Path,
stdin_str: &str,
stdin_str: &'static str,
executable_filename: &str,
expected_ending: &str,
bench_group_opt: Option<&mut BenchmarkGroup<T>>,
@ -31,7 +32,7 @@ fn exec_bench_w_input<T: Measurement>(
fn check_cmd_output(
file: &Path,
stdin_str: &str,
stdin_str: &'static str,
executable_filename: &str,
expected_ending: &str,
) {
@ -41,11 +42,16 @@ fn check_cmd_output(
.unwrap()
.to_string();
if cmd_str.contains("cfold") {
increase_stack_limit();
}
let out = if cmd_str.contains("cfold") {
let child = thread::Builder::new()
.stack_size(CFOLD_STACK_SIZE)
.spawn(move || run_cmd(&cmd_str, &[stdin_str], &[]))
.unwrap();
let out = run_cmd(&cmd_str, &[stdin_str], &[]);
child.join().unwrap()
} else {
run_cmd(&cmd_str, &[stdin_str], &[])
};
if !&out.stdout.ends_with(expected_ending) {
panic!(
@ -69,7 +75,20 @@ fn bench_cmd<T: Measurement>(
.to_string();
if cmd_str.contains("cfold") {
increase_stack_limit();
#[cfg(unix)]
use rlimit::{setrlimit, Resource};
#[cfg(unix)]
setrlimit(
Resource::STACK,
CFOLD_STACK_SIZE as u64,
CFOLD_STACK_SIZE as u64,
)
.expect("Failed to increase stack limit.");
#[cfg(windows)]
println!("Skipping the cfold benchmark on windows, I can't adjust the stack size and use criterion at the same time.");
#[cfg(windows)]
return;
}
if let Some(bench_group) = bench_group_opt {
@ -85,12 +104,6 @@ fn bench_cmd<T: Measurement>(
}
}
fn increase_stack_limit() {
let new_stack_limit = 8192 * 100000;
setrlimit(Resource::STACK, new_stack_limit, new_stack_limit)
.expect("Failed to increase stack limit.");
}
pub fn bench_nqueens<T: Measurement>(bench_group_opt: Option<&mut BenchmarkGroup<T>>) {
exec_bench_w_input(
&example_file("benchmarks", "NQueens.roc"),

View file

@ -3,3 +3,4 @@ pub mod markup;
pub mod markup_error;
pub mod slow_pool;
pub mod syntax_highlight;
pub mod underline_style;

View file

@ -55,8 +55,13 @@ pub enum Attribute {
HighlightStart { highlight_start: HighlightStart },
HighlightEnd { highlight_end: HighlightEnd },
UnderlineStart { underline_start: UnderlineStart },
UnderlineEnd { underline_end: UnderlineEnd },
Underline { underline_spec: UnderlineSpec },
}
#[derive(Debug)]
pub enum UnderlineSpec {
Partial { start: usize, end: usize },
Full,
}
#[derive(Debug, Default)]

View file

@ -64,13 +64,12 @@ pub fn expr2_to_markup<'a>(
Expr2::Str(text) => {
let content = format!("\"{}\"", text.as_str(env.pool));
new_markup_node(
with_indent(indent_level, &content),
ast_node_id,
HighlightStyle::String,
mark_node_pool,
indent_level,
)
string_mark_node(&content, indent_level, ast_node_id, mark_node_pool)
}
Expr2::SmallStr(array_str) => {
let content = format!("\"{}\"", array_str.as_str());
string_mark_node(&content, indent_level, ast_node_id, mark_node_pool)
}
Expr2::GlobalTag { name, .. } => new_markup_node(
with_indent(indent_level, &get_string(env, name)),
@ -387,3 +386,18 @@ fn with_indent(indent_level: usize, some_str: &str) -> String {
full_string
}
fn string_mark_node(
content: &str,
indent_level: usize,
ast_node_id: ASTNodeId,
mark_node_pool: &mut SlowPool,
) -> MarkNodeId {
new_markup_node(
with_indent(indent_level, content),
ast_node_id,
HighlightStyle::String,
mark_node_pool,
indent_level,
)
}

View file

@ -468,3 +468,34 @@ pub fn join_mark_nodes_commas(
mark_nodes.into_iter().interleave(join_nodes).collect()
}
pub fn mark_nodes_to_string(markup_node_ids: &[MarkNodeId], mark_node_pool: &SlowPool) -> String {
let mut all_code_string = String::new();
for mark_node_id in markup_node_ids.iter() {
node_to_string_w_children(*mark_node_id, &mut all_code_string, mark_node_pool)
}
all_code_string
}
pub fn node_to_string_w_children(
node_id: MarkNodeId,
str_buffer: &mut String,
mark_node_pool: &SlowPool,
) {
let node = mark_node_pool.get(node_id);
if node.is_nested() {
for child_id in node.get_children_ids() {
node_to_string_w_children(child_id, str_buffer, mark_node_pool);
}
for _ in 0..node.get_newlines_at_end() {
str_buffer.push('\n')
}
} else {
let node_content_str = node.get_full_content();
str_buffer.push_str(&node_content_str);
}
}

View file

@ -0,0 +1,20 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::colors::{from_hsb, RgbaTup};
#[derive(Hash, Eq, PartialEq, Copy, Clone, Debug, Deserialize, Serialize)]
pub enum UnderlineStyle {
Error,
Warning,
}
pub fn default_underline_color_map() -> HashMap<UnderlineStyle, RgbaTup> {
let mut underline_colors = HashMap::new();
underline_colors.insert(UnderlineStyle::Error, from_hsb(0, 50, 75));
underline_colors.insert(UnderlineStyle::Warning, from_hsb(60, 50, 75));
underline_colors
}

View file

@ -0,0 +1,13 @@
[package]
authors = ["The Roc Contributors"]
edition = "2018"
license = "UPL-1.0"
name = "roc_alias_analysis"
version = "0.1.0"
[dependencies]
morphic_lib = {path = "../../vendor/morphic_lib"}
roc_collections = {path = "../collections"}
roc_module = {path = "../module"}
roc_mono = {path = "../mono"}

View file

@ -8,11 +8,11 @@ use roc_collections::all::{MutMap, MutSet};
use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol;
use crate::ir::{
use roc_mono::ir::{
Call, CallType, Expr, HigherOrderLowLevel, HostExposedLayouts, ListLiteralElement, Literal,
ModifyRc, OptLevel, Proc, Stmt,
};
use crate::layout::{Builtin, Layout, RawFunctionLayout, UnionLayout};
use roc_mono::layout::{Builtin, Layout, RawFunctionLayout, UnionLayout};
// just using one module for now
pub const MOD_APP: ModName = ModName(b"UserApp");
@ -110,7 +110,7 @@ fn bytes_as_ascii(bytes: &[u8]) -> String {
pub fn spec_program<'a, I>(
opt_level: OptLevel,
entry_point: crate::ir::EntryPoint<'a>,
entry_point: roc_mono::ir::EntryPoint<'a>,
procs: I,
) -> Result<morphic_lib::Solutions>
where
@ -245,7 +245,7 @@ where
match opt_level {
OptLevel::Development | OptLevel::Normal => morphic_lib::solve_trivial(program),
OptLevel::Optimize => morphic_lib::solve(program),
OptLevel::Optimize | OptLevel::Size => morphic_lib::solve(program),
}
}
@ -266,7 +266,7 @@ fn terrible_hack(builder: &mut FuncDefBuilder, block: BlockId, type_id: TypeId)
}
fn build_entry_point(
layout: crate::ir::ProcLayout,
layout: roc_mono::ir::ProcLayout,
func_name: FuncName,
host_exposed_functions: &[([u8; SIZE], &[Layout])],
) -> Result<FuncDef> {
@ -308,7 +308,7 @@ fn build_entry_point(
let block = builder.add_block();
let type_id = layout_spec(&mut builder, &Layout::Struct(layouts))?;
let type_id = layout_spec(&mut builder, &Layout::struct_no_name_order(layouts))?;
let argument = builder.add_unknown_with(block, &[], type_id)?;
@ -349,7 +349,10 @@ fn proc_spec<'a>(proc: &Proc<'a>) -> Result<(FuncDef, MutSet<UnionLayout<'a>>)>
let value_id = stmt_spec(&mut builder, &mut env, block, &proc.ret_layout, &proc.body)?;
let root = BlockExpr(block, value_id);
let arg_type_id = layout_spec(&mut builder, &Layout::Struct(&argument_layouts))?;
let arg_type_id = layout_spec(
&mut builder,
&Layout::struct_no_name_order(&argument_layouts),
)?;
let ret_type_id = layout_spec(&mut builder, &proc.ret_layout)?;
let spec = builder.build(arg_type_id, ret_type_id, root)?;
@ -360,7 +363,7 @@ fn proc_spec<'a>(proc: &Proc<'a>) -> Result<(FuncDef, MutSet<UnionLayout<'a>>)>
#[derive(Default)]
struct Env<'a> {
symbols: MutMap<Symbol, ValueId>,
join_points: MutMap<crate::ir::JoinPointId, morphic_lib::ContinuationId>,
join_points: MutMap<roc_mono::ir::JoinPointId, morphic_lib::ContinuationId>,
type_names: MutSet<UnionLayout<'a>>,
}
@ -708,7 +711,7 @@ fn call_spec(
passed_function,
..
}) => {
use crate::low_level::HigherOrder::*;
use roc_mono::low_level::HigherOrder::*;
let array = passed_function.specialization_id.to_bytes();
let spec_var = CalleeSpecVar(&array);
@ -1135,7 +1138,7 @@ fn call_spec(
// ListFindUnsafe returns { value: v, found: Bool=Int1 }
let output_layouts = vec![argument_layouts[0], Layout::Builtin(Builtin::Bool)];
let output_layout = Layout::Struct(&output_layouts);
let output_layout = Layout::struct_no_name_order(&output_layouts);
let output_type = layout_spec(builder, &output_layout)?;
let loop_body = |builder: &mut FuncDefBuilder, block, output| {
@ -1193,7 +1196,7 @@ fn lowlevel_spec(
block: BlockId,
layout: &Layout,
op: &LowLevel,
update_mode: crate::ir::UpdateModeId,
update_mode: roc_mono::ir::UpdateModeId,
arguments: &[Symbol],
) -> Result<ValueId> {
use LowLevel::*;
@ -1255,22 +1258,21 @@ fn lowlevel_spec(
builder.add_bag_get(block, bag)
}
ListSet => {
ListReplaceUnsafe => {
let list = env.symbols[&arguments[0]];
let to_insert = env.symbols[&arguments[2]];
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
// decrement the overwritten element
let overwritten = builder.add_bag_get(block, bag)?;
let _unit = builder.add_recursive_touch(block, overwritten)?;
let _unit = builder.add_update(block, update_mode_var, cell)?;
let _unit1 = builder.add_touch(block, cell)?;
let _unit2 = builder.add_update(block, update_mode_var, cell)?;
builder.add_bag_insert(block, bag, to_insert)?;
with_new_heap_cell(builder, block, bag)
let old_value = builder.add_bag_get(block, bag)?;
let new_list = with_new_heap_cell(builder, block, bag)?;
builder.add_make_tuple(block, &[new_list, old_value])
}
ListSwap => {
let list = env.symbols[&arguments[0]];
@ -1672,7 +1674,9 @@ fn layout_spec_help(
match layout {
Builtin(builtin) => builtin_spec(builder, builtin, when_recursive),
Struct(fields) => build_recursive_tuple_type(builder, fields, when_recursive),
Struct { field_layouts, .. } => {
build_recursive_tuple_type(builder, field_layouts, when_recursive)
}
LambdaSet(lambda_set) => layout_spec_help(
builder,
&lambda_set.runtime_representation(),

View file

@ -24,7 +24,7 @@ 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 }
roc_reporting = { path = "../../reporting" }
roc_std = { path = "../../roc_std" }
roc_std = { path = "../../roc_std", default-features = false }
bumpalo = { version = "3.8.0", features = ["collections"] }
libloading = "0.7.1"
tempfile = "3.2.0"
@ -35,7 +35,6 @@ target-lexicon = "0.12.2"
serde_json = "1.0.69"
[features]
default = ["llvm", "target-aarch64", "target-x86_64", "target-wasm32"]
target-arm = []
target-aarch64 = ["roc_gen_dev/target-aarch64"]
target-x86 = []

View file

@ -8,7 +8,7 @@ use std::collections::HashMap;
use std::env;
use std::io;
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Output};
use std::process::{self, Child, Command, Output};
use target_lexicon::{Architecture, OperatingSystem, Triple};
fn zig_executable() -> String {
@ -46,6 +46,10 @@ pub fn link(
operating_system: OperatingSystem::Darwin,
..
} => link_macos(target, output_path, input_paths, link_type),
Triple {
operating_system: OperatingSystem::Windows,
..
} => link_windows(target, output_path, input_paths, link_type),
_ => panic!("TODO gracefully handle unsupported target: {:?}", target),
}
}
@ -137,6 +141,8 @@ pub fn build_zig_host_native(
if matches!(opt_level, OptLevel::Optimize) {
command.args(&["-O", "ReleaseSafe"]);
} else if matches!(opt_level, OptLevel::Size) {
command.args(&["-O", "ReleaseSmall"]);
}
command.output().unwrap()
}
@ -231,6 +237,8 @@ pub fn build_zig_host_native(
]);
if matches!(opt_level, OptLevel::Optimize) {
command.args(&["-O", "ReleaseSafe"]);
} else if matches!(opt_level, OptLevel::Size) {
command.args(&["-O", "ReleaseSmall"]);
}
command.output().unwrap()
}
@ -282,6 +290,8 @@ pub fn build_zig_host_wasm32(
]);
if matches!(opt_level, OptLevel::Optimize) {
command.args(&["-O", "ReleaseSafe"]);
} else if matches!(opt_level, OptLevel::Size) {
command.args(&["-O", "ReleaseSmall"]);
}
command.output().unwrap()
}
@ -317,7 +327,9 @@ pub fn build_c_host_native(
command.args(&["-fPIC", "-c"]);
}
if matches!(opt_level, OptLevel::Optimize) {
command.arg("-O2");
command.arg("-O3");
} else if matches!(opt_level, OptLevel::Size) {
command.arg("-Os");
}
command.output().unwrap()
}
@ -351,6 +363,8 @@ pub fn build_swift_host_native(
if matches!(opt_level, OptLevel::Optimize) {
command.arg("-O");
} else if matches!(opt_level, OptLevel::Size) {
command.arg("-Osize");
}
command.output().unwrap()
@ -456,18 +470,18 @@ pub fn rebuild_host(
} else if cargo_host_src.exists() {
// Compile and link Cargo.toml, if it exists
let cargo_dir = host_input_path.parent().unwrap();
let cargo_out_dir =
cargo_dir
.join("target")
.join(if matches!(opt_level, OptLevel::Optimize) {
let cargo_out_dir = cargo_dir.join("target").join(
if matches!(opt_level, OptLevel::Optimize | OptLevel::Size) {
"release"
} else {
"debug"
});
},
);
let mut command = Command::new("cargo");
command.arg("build").current_dir(cargo_dir);
if matches!(opt_level, OptLevel::Optimize) {
// Rust doesn't expose size without editing the cargo.toml. Instead just use release.
if matches!(opt_level, OptLevel::Optimize | OptLevel::Size) {
command.arg("--release");
}
let source_file = if shared_lib_path.is_some() {
@ -533,6 +547,8 @@ pub fn rebuild_host(
]);
if matches!(opt_level, OptLevel::Optimize) {
command.arg("-O");
} else if matches!(opt_level, OptLevel::Size) {
command.arg("-C opt-level=s");
}
let output = command.output().unwrap();
@ -636,6 +652,33 @@ fn library_path<const N: usize>(segments: [&str; N]) -> Option<PathBuf> {
}
}
/// Given a list of library directories and the name of a library, find the 1st match
///
/// The provided list of library directories should be in the form of a list of
/// directories, where each directory is represented by a series of path segments, like
///
/// ["/usr", "lib"]
///
/// Each directory will be checked for a file with the provided filename, and the first
/// match will be returned.
///
/// If there are no matches, [`None`] will be returned.
fn look_for_library(lib_dirs: &[&[&str]], lib_filename: &str) -> Option<PathBuf> {
lib_dirs
.iter()
.map(|lib_dir| {
lib_dir.iter().fold(PathBuf::new(), |mut path, segment| {
path.push(segment);
path
})
})
.map(|mut path| {
path.push(lib_filename);
path
})
.find(|path| path.exists())
}
fn link_linux(
target: &Triple,
output_path: PathBuf,
@ -670,28 +713,75 @@ fn link_linux(
));
}
let libcrt_path =
// Some things we'll need to build a list of dirs to check for libraries
let maybe_nix_path = nix_path_opt();
let usr_lib_arch = ["/usr", "lib", &architecture];
let lib_arch = ["/lib", &architecture];
let nix_path_segments;
let lib_dirs_if_nix: [&[&str]; 5];
let lib_dirs_if_nonix: [&[&str]; 4];
// Build the aformentioned list
let lib_dirs: &[&[&str]] =
// give preference to nix_path if it's defined, this prevents bugs
if let Some(nix_path) = nix_path_opt() {
library_path([&nix_path])
.unwrap()
if let Some(nix_path) = &maybe_nix_path {
nix_path_segments = [nix_path.as_str()];
lib_dirs_if_nix = [
&nix_path_segments,
&usr_lib_arch,
&lib_arch,
&["/usr", "lib"],
&["/usr", "lib64"],
];
&lib_dirs_if_nix
} else {
library_path(["/usr", "lib", &architecture])
.or_else(|| library_path(["/usr", "lib"]))
.unwrap()
lib_dirs_if_nonix = [
&usr_lib_arch,
&lib_arch,
&["/usr", "lib"],
&["/usr", "lib64"],
];
&lib_dirs_if_nonix
};
// Look for the libraries we'll need
let libgcc_name = "libgcc_s.so.1";
let libgcc_path =
// give preference to nix_path if it's defined, this prevents bugs
if let Some(nix_path) = nix_path_opt() {
library_path([&nix_path, libgcc_name])
.unwrap()
} else {
library_path(["/lib", &architecture, libgcc_name])
.or_else(|| library_path(["/usr", "lib", &architecture, libgcc_name]))
.or_else(|| library_path(["/usr", "lib", libgcc_name]))
.unwrap()
let libgcc_path = look_for_library(lib_dirs, libgcc_name);
let crti_name = "crti.o";
let crti_path = look_for_library(lib_dirs, crti_name);
let crtn_name = "crtn.o";
let crtn_path = look_for_library(lib_dirs, crtn_name);
let scrt1_name = "Scrt1.o";
let scrt1_path = look_for_library(lib_dirs, scrt1_name);
// Unwrap all the paths at once so we can inform the user of all missing libs at once
let (libgcc_path, crti_path, crtn_path, scrt1_path) =
match (libgcc_path, crti_path, crtn_path, scrt1_path) {
(Some(libgcc), Some(crti), Some(crtn), Some(scrt1)) => (libgcc, crti, crtn, scrt1),
(maybe_gcc, maybe_crti, maybe_crtn, maybe_scrt1) => {
if maybe_gcc.is_none() {
eprintln!("Couldn't find libgcc_s.so.1!");
eprintln!("You may need to install libgcc\n");
}
if maybe_crti.is_none() | maybe_crtn.is_none() | maybe_scrt1.is_none() {
eprintln!("Couldn't find the glibc development files!");
eprintln!("We need the objects crti.o, crtn.o, and Scrt1.o");
eprintln!("You may need to install the glibc development package");
eprintln!("(probably called glibc-dev or glibc-devel)\n");
}
let dirs = lib_dirs
.iter()
.map(|segments| segments.join("/"))
.collect::<Vec<String>>()
.join("\n");
eprintln!("We looked in the following directories:\n{}", dirs);
process::exit(1);
}
};
let ld_linux = match target.architecture {
@ -717,7 +807,7 @@ fn link_linux(
LinkType::Executable => (
// Presumably this S stands for Static, since if we include Scrt1.o
// in the linking for dynamic builds, linking fails.
vec![libcrt_path.join("Scrt1.o").to_str().unwrap().to_string()],
vec![scrt1_path.to_string_lossy().into_owned()],
output_path,
),
LinkType::Dylib => {
@ -749,8 +839,6 @@ fn link_linux(
let env_path = env::var("PATH").unwrap_or_else(|_| "".to_string());
init_arch(target);
// NOTE: order of arguments to `ld` matters here!
// The `-l` flags should go after the `.o` arguments
@ -772,8 +860,8 @@ fn link_linux(
"-A",
arch_str(target),
"-pie",
libcrt_path.join("crti.o").to_str().unwrap(),
libcrt_path.join("crtn.o").to_str().unwrap(),
&*crti_path.to_string_lossy(),
&*crtn_path.to_string_lossy(),
])
.args(&base_args)
.args(&["-dynamic-linker", ld_linux])
@ -850,6 +938,18 @@ fn link_macos(
ld_command.arg(format!("-L{}/swift", sdk_path));
};
let roc_link_flags = match env::var("ROC_LINK_FLAGS") {
Ok(flags) => {
println!("⚠️ CAUTION: The ROC_LINK_FLAGS environment variable is a temporary workaround, and will no longer do anything once surgical linking lands! If you're concerned about what this means for your use case, please ask about it on Zulip.");
flags
}
Err(_) => "".to_string(),
};
for roc_link_flag in roc_link_flags.split_whitespace() {
ld_command.arg(roc_link_flag.to_string());
}
ld_command.args(&[
// Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496392274
// for discussion and further references
@ -953,6 +1053,15 @@ fn link_wasm32(
Ok((child, output_path))
}
fn link_windows(
_target: &Triple,
_output_path: PathBuf,
_input_paths: &[&str],
_link_type: LinkType,
) -> io::Result<(Child, PathBuf)> {
todo!("Add windows support to the surgical linker. See issue #2608.")
}
#[cfg(feature = "llvm")]
pub fn module_to_dylib(
module: &inkwell::module::Module,
@ -1010,13 +1119,3 @@ fn validate_output(file_name: &str, cmd_name: &str, output: Output) {
}
}
}
#[cfg(feature = "llvm")]
fn init_arch(target: &Triple) {
crate::target::init_arch(target);
}
#[cfg(not(feature = "llvm"))]
fn init_arch(_target: &Triple) {
panic!("Tried to initialize LLVM when crate was not built with `feature = \"llvm\"` enabled");
}

View file

@ -179,7 +179,7 @@ pub fn gen_from_mono_module(
_emit_debug_info: bool,
) -> CodeGenTiming {
match opt_level {
OptLevel::Optimize => {
OptLevel::Optimize | OptLevel::Size => {
todo!("Return this error message in a better way: optimized builds not supported without llvm backend");
}
OptLevel::Normal | OptLevel::Development => {
@ -199,7 +199,7 @@ pub fn gen_from_mono_module(
emit_debug_info: bool,
) -> CodeGenTiming {
match opt_level {
OptLevel::Normal | OptLevel::Optimize => gen_from_mono_module_llvm(
OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => gen_from_mono_module_llvm(
arena,
loaded,
roc_file_path,

View file

@ -41,6 +41,11 @@ pub fn target_triple_str(target: &Triple) -> &'static str {
operating_system: OperatingSystem::Darwin,
..
} => "x86_64-unknown-darwin10",
Triple {
architecture: Architecture::X86_64,
operating_system: OperatingSystem::Windows,
..
} => "x86_64-pc-windows-gnu",
_ => panic!("TODO gracefully handle unsupported target: {:?}", target),
}
}
@ -114,6 +119,8 @@ pub fn target_machine(
pub fn convert_opt_level(level: OptLevel) -> OptimizationLevel {
match level {
OptLevel::Development | OptLevel::Normal => OptimizationLevel::None,
// Default is O2/Os. If we want Oz, we have to explicitly turn of loop vectorization as well.
OptLevel::Size => OptimizationLevel::Default,
OptLevel::Optimize => OptimizationLevel::Aggressive,
}
}

View file

@ -11,3 +11,7 @@ roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_types = { path = "../types" }
roc_target = { path = "../roc_target" }
[build-dependencies]
# dunce can be removed once ziglang/zig#5109 is fixed
dunce = "1.0.2"

View file

@ -7,7 +7,7 @@ To add a builtin:
2. Make sure the function is public with the `pub` keyword and uses the C calling convention. This is really easy, just add `pub` and `callconv(.C)` to the function declaration like so: `pub fn atan(num: f64) callconv(.C) f64 { ... }`
3. In `src/main.zig`, export the function. This is also organized by module. For example, for a `Num` function find the `Num` section and add: `comptime { exportNumFn(num.atan, "atan"); }`. The first argument is the function, the second is the name of it in LLVM.
4. In `compiler/builtins/src/bitcode.rs`, add a constant for the new function. This is how we use it in Rust. Once again, this is organized by module, so just find the relevant area and add your new function.
5. You can now your function in Rust using `call_bitcode_fn` in `llvm/src/build.rs`!
5. You can now use your function in Rust using `call_bitcode_fn` in `llvm/src/build.rs`!
## How it works
@ -28,7 +28,7 @@ There will be two directories like `roc_builtins-[some random characters]`, look
> The bitcode is a bunch of bytes that aren't particularly human-readable.
> If you want to take a look at the human-readable LLVM IR, look at
> `target/debug/build/roc_builtins-[some random characters]/out/builtins.ll`
> `compiler/builtins/bitcode/builtins.ll`
## Calling bitcode functions

View file

@ -26,21 +26,19 @@ pub const RocDec = extern struct {
return .{ .num = num * one_point_zero_i128 };
}
// TODO: There's got to be a better way to do this other than converting to Str
pub fn fromF64(num: f64) ?RocDec {
var digit_bytes: [19]u8 = undefined; // 19 = max f64 digits + '.' + '-'
var result: f64 = num * comptime @intToFloat(f64, one_point_zero_i128);
var fbs = std.io.fixedBufferStream(digit_bytes[0..]);
std.fmt.formatFloatDecimal(num, .{}, fbs.writer()) catch
return null;
var dec = RocDec.fromStr(RocStr.init(&digit_bytes, fbs.pos));
if (dec) |d| {
return d;
} else {
if (result > comptime @intToFloat(f64, math.maxInt(i128))) {
return null;
}
if (result < comptime @intToFloat(f64, math.minInt(i128))) {
return null;
}
var ret: RocDec = .{ .num = @floatToInt(i128, result) };
return ret;
}
pub fn fromStr(roc_str: RocStr) ?RocDec {
@ -729,6 +727,11 @@ test "fromF64" {
try expectEqual(RocDec{ .num = 25500000000000000000 }, dec.?);
}
test "fromF64 overflow" {
var dec = RocDec.fromF64(1e308);
try expectEqual(dec, null);
}
test "fromStr: empty" {
var roc_str = RocStr.init("", 0);
var dec = RocDec.fromStr(roc_str);

View file

@ -1259,95 +1259,56 @@ pub fn listConcat(list_a: RocList, list_b: RocList, alignment: u32, element_widt
return output;
}
pub fn listSetInPlace(
bytes: ?[*]u8,
pub fn listReplaceInPlace(
list: RocList,
index: usize,
element: Opaque,
element_width: usize,
dec: Dec,
) callconv(.C) ?[*]u8 {
out_element: ?[*]u8,
) callconv(.C) RocList {
// INVARIANT: bounds checking happens on the roc side
//
// at the time of writing, the function is implemented roughly as
// `if inBounds then LowLevelListGet input index item else input`
// `if inBounds then LowLevelListReplace input index item else input`
// so we don't do a bounds check here. Hence, the list is also non-empty,
// because inserting into an empty list is always out of bounds
return listSetInPlaceHelp(bytes, index, element, element_width, dec);
return listReplaceInPlaceHelp(list, index, element, element_width, out_element);
}
pub fn listSet(
bytes: ?[*]u8,
length: usize,
pub fn listReplace(
list: RocList,
alignment: u32,
index: usize,
element: Opaque,
element_width: usize,
dec: Dec,
) callconv(.C) ?[*]u8 {
out_element: ?[*]u8,
) callconv(.C) RocList {
// INVARIANT: bounds checking happens on the roc side
//
// at the time of writing, the function is implemented roughly as
// `if inBounds then LowLevelListGet input index item else input`
// `if inBounds then LowLevelListReplace input index item else input`
// so we don't do a bounds check here. Hence, the list is also non-empty,
// because inserting into an empty list is always out of bounds
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(@alignOf(usize), bytes));
if ((ptr - 1)[0] == utils.REFCOUNT_ONE) {
return listSetInPlaceHelp(bytes, index, element, element_width, dec);
} else {
return listSetImmutable(bytes, length, alignment, index, element, element_width, dec);
}
return listReplaceInPlaceHelp(list.makeUnique(alignment, element_width), index, element, element_width, out_element);
}
inline fn listSetInPlaceHelp(
bytes: ?[*]u8,
inline fn listReplaceInPlaceHelp(
list: RocList,
index: usize,
element: Opaque,
element_width: usize,
dec: Dec,
) ?[*]u8 {
out_element: ?[*]u8,
) RocList {
// the element we will replace
var element_at_index = (bytes orelse undefined) + (index * element_width);
var element_at_index = (list.bytes orelse undefined) + (index * element_width);
// decrement its refcount
dec(element_at_index);
// copy out the old element
@memcpy(out_element orelse undefined, element_at_index, element_width);
// copy in the new element
@memcpy(element_at_index, element orelse undefined, element_width);
return bytes;
}
inline fn listSetImmutable(
old_bytes: ?[*]u8,
length: usize,
alignment: u32,
index: usize,
element: Opaque,
element_width: usize,
dec: Dec,
) ?[*]u8 {
const data_bytes = length * element_width;
var new_bytes = utils.allocateWithRefcount(data_bytes, alignment);
@memcpy(new_bytes, old_bytes orelse undefined, data_bytes);
// the element we will replace
var element_at_index = new_bytes + (index * element_width);
// decrement its refcount
dec(element_at_index);
// copy in the new element
@memcpy(element_at_index, element orelse undefined, element_width);
// consume RC token of original
utils.decref(old_bytes, data_bytes, alignment);
//return list;
return new_bytes;
return list;
}
pub fn listFindUnsafe(

View file

@ -49,8 +49,8 @@ comptime {
exportListFn(list.listConcat, "concat");
exportListFn(list.listSublist, "sublist");
exportListFn(list.listDropAt, "drop_at");
exportListFn(list.listSet, "set");
exportListFn(list.listSetInPlace, "set_in_place");
exportListFn(list.listReplace, "replace");
exportListFn(list.listReplaceInPlace, "replace_in_place");
exportListFn(list.listSwap, "swap");
exportListFn(list.listAny, "any");
exportListFn(list.listAll, "all");
@ -98,6 +98,14 @@ comptime {
num.exportDivCeil(T, ROC_BUILTINS ++ "." ++ NUM ++ ".div_ceil.");
}
inline for (INTEGERS) |FROM| {
inline for (INTEGERS) |TO| {
// We're exporting more than we need here, but that's okay.
num.exportToIntCheckingMax(FROM, TO, ROC_BUILTINS ++ "." ++ NUM ++ ".int_to_" ++ @typeName(TO) ++ "_checking_max.");
num.exportToIntCheckingMaxAndMin(FROM, TO, ROC_BUILTINS ++ "." ++ NUM ++ ".int_to_" ++ @typeName(TO) ++ "_checking_max_and_min.");
}
}
inline for (FLOATS) |T| {
num.exportAsin(T, ROC_BUILTINS ++ "." ++ NUM ++ ".asin.");
num.exportAcos(T, ROC_BUILTINS ++ "." ++ NUM ++ ".acos.");

View file

@ -108,6 +108,39 @@ pub fn exportDivCeil(comptime T: type, comptime name: []const u8) void {
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn ToIntCheckedResult(comptime T: type) type {
// On the Roc side we sort by alignment; putting the errorcode last
// always works out (no number with smaller alignment than 1).
return extern struct {
value: T,
out_of_bounds: bool,
};
}
pub fn exportToIntCheckingMax(comptime From: type, comptime To: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(input: From) callconv(.C) ToIntCheckedResult(To) {
if (input > std.math.maxInt(To)) {
return .{ .out_of_bounds = true, .value = 0 };
}
return .{ .out_of_bounds = false, .value = @intCast(To, input) };
}
}.func;
@export(f, .{ .name = name ++ @typeName(From), .linkage = .Strong });
}
pub fn exportToIntCheckingMaxAndMin(comptime From: type, comptime To: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(input: From) callconv(.C) ToIntCheckedResult(To) {
if (input > std.math.maxInt(To) or input < std.math.minInt(To)) {
return .{ .out_of_bounds = true, .value = 0 };
}
return .{ .out_of_bounds = false, .value = @intCast(To, input) };
}
}.func;
@export(f, .{ .name = name ++ @typeName(From), .linkage = .Strong });
}
pub fn bytesToU16C(arg: RocList, position: usize) callconv(.C) u16 {
return @call(.{ .modifier = always_inline }, bytesToU16, .{ arg, position });
}

View file

@ -30,7 +30,8 @@ fn main() {
}
// "." is relative to where "build.rs" is
let build_script_dir_path = fs::canonicalize(Path::new(".")).unwrap();
// dunce can be removed once ziglang/zig#5109 is fixed
let build_script_dir_path = dunce::canonicalize(Path::new(".")).unwrap();
let bitcode_path = build_script_dir_path.join("bitcode");
// LLVM .bc FILES
@ -54,12 +55,17 @@ fn main() {
);
// OBJECT FILES
#[cfg(windows)]
const BUILTINS_HOST_FILE: &str = "builtins-host.obj";
#[cfg(not(windows))]
const BUILTINS_HOST_FILE: &str = "builtins-host.o";
generate_object_file(
&bitcode_path,
"BUILTINS_HOST_O",
"object",
"builtins-host.o",
BUILTINS_HOST_FILE,
);
generate_object_file(
@ -108,8 +114,8 @@ fn generate_object_file(
println!("Moving zig object `{}` to: {}", zig_object, dest_obj);
// we store this .o file in rust's `target` folder
run_command(&bitcode_path, "mv", &[src_obj, dest_obj]);
// we store this .o file in rust's `target` folder (for wasm we need to leave a copy here too)
fs::copy(src_obj, dest_obj).expect("Failed to copy object file.");
}
}

View file

@ -100,6 +100,26 @@ interface Num
subWrap,
sqrt,
tan,
toI8,
toI8Checked,
toI16,
toI16Checked,
toI32,
toI32Checked,
toI64,
toI64Checked,
toI128,
toI128Checked,
toU8,
toU8Checked,
toU16,
toU16Checked,
toU32,
toU32Checked,
toU64,
toU64Checked,
toU128,
toU128Checked,
toFloat,
toStr
]
@ -592,6 +612,35 @@ mulCheckOverflow : Num a, Num a -> Result (Num a) [ Overflow ]*
## Convert
## Convert any [Int] to a specifically-sized [Int], without checking validity.
## These are unchecked bitwise operations,
## so if the source number is outside the target range, then these will
## effectively modulo-wrap around the target range to reach a valid value.
toI8 : Int * -> I8
toI16 : Int * -> I16
toI32 : Int * -> I32
toI64 : Int * -> I64
toI128 : Int * -> I128
toU8 : Int * -> U8
toU16 : Int * -> U16
toU32 : Int * -> U32
toU64 : Int * -> U64
toU128 : Int * -> U128
## Convert any [Int] to a specifically-sized [Int], after checking validity.
## These are checked bitwise operations,
## so if the source number is outside the target range, then these will
## return `Err OutOfBounds`.
toI8Checked : Int * -> Result I8 [ OutOfBounds ]*
toI16Checked : Int * -> Result I16 [ OutOfBounds ]*
toI32Checked : Int * -> Result I32 [ OutOfBounds ]*
toI64Checked : Int * -> Result I64 [ OutOfBounds ]*
toI128Checked : Int * -> Result I128 [ OutOfBounds ]*
toU8Checked : Int * -> Result U8 [ OutOfBounds ]*
toU16Checked : Int * -> Result U16 [ OutOfBounds ]*
toU32Checked : Int * -> Result U32 [ OutOfBounds ]*
toU64Checked : Int * -> Result U64 [ OutOfBounds ]*
toU128Checked : Int * -> Result U128 [ OutOfBounds ]*
## Convert a number to a [Str].
##
## This is the same as calling `Num.format {}` - so for more details on

View file

@ -392,27 +392,19 @@ toUtf32Le : Str -> List U8
# Parsing
## If the string begins with a valid [extended grapheme cluster](http://www.unicode.org/glossary/#extended_grapheme_cluster),
## return it along with the rest of the string after that grapheme.
## If the bytes begin with a valid [extended grapheme cluster](http://www.unicode.org/glossary/#extended_grapheme_cluster)
## encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8), return it along with the number of bytes it took up.
##
## If the string does not begin with a full grapheme, for example because it was
## empty, return `Err`.
parseGrapheme : Str -> Result { val : Str, rest : Str } [ Expected [ Grapheme ]* Str ]*
## If the bytes do not begin with a valid grapheme, for example because the list was
## empty or began with an invalid grapheme, return `Err`.
parseUtf8Grapheme : List U8 -> Result { grapheme : Str, bytesParsed: Nat } [ InvalidGrapheme ]*
## If the string begins with a valid [Unicode code point](http://www.unicode.org/glossary/#code_point),
## return it along with the rest of the string after that code point.
## If the bytes begin with a valid [Unicode code point](http://www.unicode.org/glossary/#code_point)
## encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8), return it along with the number of bytes it took up.
##
## If the string does not begin with a valid code point, for example because it was
## empty, return `Err`.
parseCodePt : Str -> Result { val : U32, rest : Str } [ Expected [ CodePt ]* Str ]*
## If the first string begins with the second, return whatever comes
## after the second.
chomp : Str, Str -> Result Str [ Expected [ ExactStr Str ]* Str ]*
## If the string begins with a [Unicode code point](http://www.unicode.org/glossary/#code_point)
## equal to the given [U32], return whatever comes after that code point.
chompCodePt : Str, U32 -> Result Str [ Expected [ ExactCodePt U32 ]* Str ]*
## If the string does not begin with a valid code point, for example because the list was
## empty or began with an invalid code point, return an `Err`.
parseUtf8CodePt : List U8 -> Result { codePt : U32, bytesParsed: Nat } [ InvalidCodePt ]*
## If the string represents a valid [U8] number, return that number.
##

View file

@ -12,7 +12,7 @@ pub const BUILTINS_WASM32_OBJ_PATH: &str = env!(
"Env var BUILTINS_WASM32_O not found. Is there a problem with the build script?"
);
#[derive(Debug, Default)]
#[derive(Debug, Default, Copy, Clone)]
pub struct IntrinsicName {
pub options: [&'static str; 14],
}
@ -159,6 +159,21 @@ impl IntWidth {
_ => None,
}
}
pub const fn type_name(&self) -> &'static str {
match self {
Self::I8 => "i8",
Self::I16 => "i16",
Self::I32 => "i32",
Self::I64 => "i64",
Self::I128 => "i128",
Self::U8 => "u8",
Self::U16 => "u16",
Self::U32 => "u32",
Self::U64 => "u64",
Self::U128 => "u128",
}
}
}
impl Index<DecWidth> for IntrinsicName {
@ -214,11 +229,12 @@ macro_rules! float_intrinsic {
}
#[macro_export]
macro_rules! int_intrinsic {
macro_rules! llvm_int_intrinsic {
($signed_name:literal, $unsigned_name:literal) => {{
let mut output = IntrinsicName::default();
// The indeces align with the `Index` impl for `IntrinsicName`.
// LLVM uses the same types for both signed and unsigned integers.
output.options[4] = concat!($unsigned_name, ".i8");
output.options[5] = concat!($unsigned_name, ".i16");
output.options[6] = concat!($unsigned_name, ".i32");
@ -239,6 +255,28 @@ macro_rules! int_intrinsic {
};
}
#[macro_export]
macro_rules! int_intrinsic {
($name:expr) => {{
let mut output = IntrinsicName::default();
// The indices align with the `Index` impl for `IntrinsicName`.
output.options[4] = concat!($name, ".u8");
output.options[5] = concat!($name, ".u16");
output.options[6] = concat!($name, ".u32");
output.options[7] = concat!($name, ".u64");
output.options[8] = concat!($name, ".u128");
output.options[9] = concat!($name, ".i8");
output.options[10] = concat!($name, ".i16");
output.options[11] = concat!($name, ".i32");
output.options[12] = concat!($name, ".i64");
output.options[13] = concat!($name, ".i128");
output
}};
}
pub const NUM_ASIN: IntrinsicName = float_intrinsic!("roc_builtins.num.asin");
pub const NUM_ACOS: IntrinsicName = float_intrinsic!("roc_builtins.num.acos");
pub const NUM_ATAN: IntrinsicName = float_intrinsic!("roc_builtins.num.atan");
@ -316,8 +354,8 @@ pub const LIST_RANGE: &str = "roc_builtins.list.range";
pub const LIST_REVERSE: &str = "roc_builtins.list.reverse";
pub const LIST_SORT_WITH: &str = "roc_builtins.list.sort_with";
pub const LIST_CONCAT: &str = "roc_builtins.list.concat";
pub const LIST_SET: &str = "roc_builtins.list.set";
pub const LIST_SET_IN_PLACE: &str = "roc_builtins.list.set_in_place";
pub const LIST_REPLACE: &str = "roc_builtins.list.replace";
pub const LIST_REPLACE_IN_PLACE: &str = "roc_builtins.list.replace_in_place";
pub const LIST_ANY: &str = "roc_builtins.list.any";
pub const LIST_ALL: &str = "roc_builtins.list.all";
pub const LIST_FIND_UNSAFE: &str = "roc_builtins.list.find_unsafe";
@ -339,3 +377,50 @@ pub const UTILS_DECREF_CHECK_NULL: &str = "roc_builtins.utils.decref_check_null"
pub const UTILS_EXPECT_FAILED: &str = "roc_builtins.expect.expect_failed";
pub const UTILS_GET_EXPECT_FAILURES: &str = "roc_builtins.expect.get_expect_failures";
pub const UTILS_DEINIT_FAILURES: &str = "roc_builtins.expect.deinit_failures";
#[derive(Debug, Default)]
pub struct IntToIntrinsicName {
pub options: [IntrinsicName; 10],
}
impl IntToIntrinsicName {
pub const fn default() -> Self {
Self {
options: [IntrinsicName::default(); 10],
}
}
}
impl Index<IntWidth> for IntToIntrinsicName {
type Output = IntrinsicName;
fn index(&self, index: IntWidth) -> &Self::Output {
&self.options[index as usize]
}
}
#[macro_export]
macro_rules! int_to_int_intrinsic {
($name_prefix:literal, $name_suffix:literal) => {{
let mut output = IntToIntrinsicName::default();
output.options[0] = int_intrinsic!(concat!($name_prefix, "u8", $name_suffix));
output.options[1] = int_intrinsic!(concat!($name_prefix, "u16", $name_suffix));
output.options[2] = int_intrinsic!(concat!($name_prefix, "u32", $name_suffix));
output.options[3] = int_intrinsic!(concat!($name_prefix, "u64", $name_suffix));
output.options[4] = int_intrinsic!(concat!($name_prefix, "u128", $name_suffix));
output.options[5] = int_intrinsic!(concat!($name_prefix, "i8", $name_suffix));
output.options[6] = int_intrinsic!(concat!($name_prefix, "i16", $name_suffix));
output.options[7] = int_intrinsic!(concat!($name_prefix, "i32", $name_suffix));
output.options[8] = int_intrinsic!(concat!($name_prefix, "i64", $name_suffix));
output.options[9] = int_intrinsic!(concat!($name_prefix, "i128", $name_suffix));
output
}};
}
pub const NUM_INT_TO_INT_CHECKING_MAX: IntToIntrinsicName =
int_to_int_intrinsic!("roc_builtins.num.int_to_", "_checking_max");
pub const NUM_INT_TO_INT_CHECKING_MAX_AND_MIN: IntToIntrinsicName =
int_to_int_intrinsic!("roc_builtins.num.int_to_", "_checking_max_and_min");

View file

@ -445,6 +445,156 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// maxI128 : I128
add_type!(Symbol::NUM_MAX_I128, i128_type());
// toI8 : Int * -> I8
add_top_level_function_type!(
Symbol::NUM_TO_I8,
vec![int_type(flex(TVAR1))],
Box::new(i8_type()),
);
let out_of_bounds = SolvedType::TagUnion(
vec![(TagName::Global("OutOfBounds".into()), vec![])],
Box::new(SolvedType::Wildcard),
);
// toI8Checked : Int * -> Result I8 [ OutOfBounds ]*
add_top_level_function_type!(
Symbol::NUM_TO_I8_CHECKED,
vec![int_type(flex(TVAR1))],
Box::new(result_type(i8_type(), out_of_bounds.clone())),
);
// toI16 : Int * -> I16
add_top_level_function_type!(
Symbol::NUM_TO_I16,
vec![int_type(flex(TVAR1))],
Box::new(i16_type()),
);
// toI16Checked : Int * -> Result I16 [ OutOfBounds ]*
add_top_level_function_type!(
Symbol::NUM_TO_I16_CHECKED,
vec![int_type(flex(TVAR1))],
Box::new(result_type(i16_type(), out_of_bounds.clone())),
);
// toI32 : Int * -> I32
add_top_level_function_type!(
Symbol::NUM_TO_I32,
vec![int_type(flex(TVAR1))],
Box::new(i32_type()),
);
// toI32Checked : Int * -> Result I32 [ OutOfBounds ]*
add_top_level_function_type!(
Symbol::NUM_TO_I32_CHECKED,
vec![int_type(flex(TVAR1))],
Box::new(result_type(i32_type(), out_of_bounds.clone())),
);
// toI64 : Int * -> I64
add_top_level_function_type!(
Symbol::NUM_TO_I64,
vec![int_type(flex(TVAR1))],
Box::new(i64_type()),
);
// toI64Checked : Int * -> Result I64 [ OutOfBounds ]*
add_top_level_function_type!(
Symbol::NUM_TO_I64_CHECKED,
vec![int_type(flex(TVAR1))],
Box::new(result_type(i64_type(), out_of_bounds.clone())),
);
// toI128 : Int * -> I128
add_top_level_function_type!(
Symbol::NUM_TO_I128,
vec![int_type(flex(TVAR1))],
Box::new(i128_type()),
);
// toI128Checked : Int * -> Result I128 [ OutOfBounds ]*
add_top_level_function_type!(
Symbol::NUM_TO_I128_CHECKED,
vec![int_type(flex(TVAR1))],
Box::new(result_type(i128_type(), out_of_bounds)),
);
// toU8 : Int * -> U8
add_top_level_function_type!(
Symbol::NUM_TO_U8,
vec![int_type(flex(TVAR1))],
Box::new(u8_type()),
);
let out_of_bounds = SolvedType::TagUnion(
vec![(TagName::Global("OutOfBounds".into()), vec![])],
Box::new(SolvedType::Wildcard),
);
// toU8Checked : Int * -> Result U8 [ OutOfBounds ]*
add_top_level_function_type!(
Symbol::NUM_TO_U8_CHECKED,
vec![int_type(flex(TVAR1))],
Box::new(result_type(u8_type(), out_of_bounds.clone())),
);
// toU16 : Int * -> U16
add_top_level_function_type!(
Symbol::NUM_TO_U16,
vec![int_type(flex(TVAR1))],
Box::new(u16_type()),
);
// toU16Checked : Int * -> Result U16 [ OutOfBounds ]*
add_top_level_function_type!(
Symbol::NUM_TO_U16_CHECKED,
vec![int_type(flex(TVAR1))],
Box::new(result_type(u16_type(), out_of_bounds.clone())),
);
// toU32 : Int * -> U32
add_top_level_function_type!(
Symbol::NUM_TO_U32,
vec![int_type(flex(TVAR1))],
Box::new(u32_type()),
);
// toU32Checked : Int * -> Result U32 [ OutOfBounds ]*
add_top_level_function_type!(
Symbol::NUM_TO_U32_CHECKED,
vec![int_type(flex(TVAR1))],
Box::new(result_type(u32_type(), out_of_bounds.clone())),
);
// toU64 : Int * -> U64
add_top_level_function_type!(
Symbol::NUM_TO_U64,
vec![int_type(flex(TVAR1))],
Box::new(u64_type()),
);
// toU64Checked : Int * -> Result U64 [ OutOfBounds ]*
add_top_level_function_type!(
Symbol::NUM_TO_U64_CHECKED,
vec![int_type(flex(TVAR1))],
Box::new(result_type(u64_type(), out_of_bounds.clone())),
);
// toU128 : Int * -> U128
add_top_level_function_type!(
Symbol::NUM_TO_U128,
vec![int_type(flex(TVAR1))],
Box::new(u128_type()),
);
// toU128Checked : Int * -> Result U128 [ OutOfBounds ]*
add_top_level_function_type!(
Symbol::NUM_TO_U128_CHECKED,
vec![int_type(flex(TVAR1))],
Box::new(result_type(u128_type(), out_of_bounds)),
);
// toStr : Num a -> Str
add_top_level_function_type!(
Symbol::NUM_TO_STR,
@ -906,6 +1056,19 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(result_type(flex(TVAR1), list_was_empty.clone())),
);
// replace : List elem, Nat, elem -> { list: List elem, value: elem }
add_top_level_function_type!(
Symbol::LIST_REPLACE,
vec![list_type(flex(TVAR1)), nat_type(), flex(TVAR1)],
Box::new(SolvedType::Record {
fields: vec![
("list".into(), RecordField::Required(list_type(flex(TVAR1)))),
("value".into(), RecordField::Required(flex(TVAR1))),
],
ext: Box::new(SolvedType::EmptyRecord),
}),
);
// set : List elem, Nat, elem -> List elem
add_top_level_function_type!(
Symbol::LIST_SET,

View file

@ -7,6 +7,7 @@ edition = "2018"
[dependencies]
roc_collections = { path = "../collections" }
roc_error_macros = { path = "../../error_macros" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_parse = { path = "../parse" }
@ -15,6 +16,7 @@ roc_types = { path = "../types" }
roc_builtins = { path = "../builtins" }
ven_graph = { path = "../../vendor/pathfinding" }
bumpalo = { version = "3.8.0", features = ["collections"] }
static_assertions = "1.1.0"
[dev-dependencies]
pretty_assertions = "1.0.0"

View file

@ -3,10 +3,10 @@ use crate::scope::Scope;
use roc_collections::all::{ImMap, MutMap, MutSet, SendMap};
use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_parse::ast::{AliasHeader, AssignedField, Pattern, Tag, TypeAnnotation};
use roc_parse::ast::{AssignedField, Pattern, Tag, TypeAnnotation, TypeHeader};
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::{Alias, LambdaSet, Problem, RecordField, Type};
use roc_types::types::{Alias, AliasKind, LambdaSet, Problem, RecordField, Type};
#[derive(Clone, Debug, PartialEq)]
pub struct Annotation {
@ -29,6 +29,8 @@ pub struct IntroducedVariables {
// but a variable can only have one name. Therefore
// `ftv : SendMap<Variable, Lowercase>`.
pub wildcards: Vec<Variable>,
pub lambda_sets: Vec<Variable>,
pub inferred: Vec<Variable>,
pub var_by_name: SendMap<Lowercase, Variable>,
pub name_by_var: SendMap<Variable, Lowercase>,
pub host_exposed_aliases: MutMap<Symbol, Variable>,
@ -44,12 +46,22 @@ impl IntroducedVariables {
self.wildcards.push(var);
}
pub fn insert_inferred(&mut self, var: Variable) {
self.inferred.push(var);
}
fn insert_lambda_set(&mut self, var: Variable) {
self.lambda_sets.push(var);
}
pub fn insert_host_exposed_alias(&mut self, symbol: Symbol, var: Variable) {
self.host_exposed_aliases.insert(symbol, var);
}
pub fn union(&mut self, other: &Self) {
self.wildcards.extend(other.wildcards.iter().cloned());
self.lambda_sets.extend(other.lambda_sets.iter().cloned());
self.inferred.extend(other.inferred.iter().cloned());
self.var_by_name.extend(other.var_by_name.clone());
self.name_by_var.extend(other.name_by_var.clone());
self.host_exposed_aliases
@ -130,13 +142,20 @@ fn make_apply_symbol(
// it was imported but it doesn't expose this ident.
env.problem(roc_problem::can::Problem::RuntimeError(problem));
Err(Type::Erroneous(Problem::UnrecognizedIdent((*ident).into())))
// A failed import should have already been reported through
// roc_can::env::Env::qualified_lookup's checks
Err(Type::Erroneous(Problem::SolvedTypeError))
}
}
}
}
pub fn find_alias_symbols(
/// Retrieves all symbols in an annotations that reference a type definition, that is either an
/// alias or an opaque type.
///
/// For example, in `[ A Age U8, B Str {} ]`, there are three type definition references - `Age`,
/// `U8`, and `Str`.
pub fn find_type_def_symbols(
module_id: ModuleId,
ident_ids: &mut IdentIds,
initial_annotation: &roc_parse::ast::TypeAnnotation,
@ -273,7 +292,9 @@ fn can_annotation_help(
references,
);
let closure = Type::Variable(var_store.fresh());
let lambda_set = var_store.fresh();
introduced_variables.insert_lambda_set(lambda_set);
let closure = Type::Variable(lambda_set);
Type::Function(args, Box::new(closure), Box::new(ret))
}
@ -305,9 +326,6 @@ fn can_annotation_help(
match scope.lookup_alias(symbol) {
Some(alias) => {
// use a known alias
let mut actual = alias.typ.clone();
let mut substitutions = ImMap::default();
let mut vars = Vec::new();
if alias.type_variables.len() != args.len() {
let error = Type::Erroneous(Problem::BadTypeArguments {
@ -319,42 +337,22 @@ fn can_annotation_help(
return error;
}
for (loc_var, arg_ann) in alias.type_variables.iter().zip(args.into_iter()) {
let name = loc_var.value.0.clone();
let var = loc_var.value.1;
substitutions.insert(var, arg_ann.clone());
vars.push((name.clone(), arg_ann));
}
// make sure the recursion variable is freshly instantiated
if let Type::RecursiveTagUnion(rvar, _, _) = &mut actual {
let new = var_store.fresh();
substitutions.insert(*rvar, Type::Variable(new));
*rvar = new;
}
// make sure hidden variables are freshly instantiated
let mut lambda_set_variables =
Vec::with_capacity(alias.lambda_set_variables.len());
for typ in alias.lambda_set_variables.iter() {
if let Type::Variable(var) = typ.0 {
let fresh = var_store.fresh();
substitutions.insert(var, Type::Variable(fresh));
lambda_set_variables.push(LambdaSet(Type::Variable(fresh)));
} else {
unreachable!("at this point there should be only vars in there");
}
}
// instantiate variables
actual.substitute(&substitutions);
let (type_arguments, lambda_set_variables, actual) =
instantiate_and_freshen_alias_type(
var_store,
introduced_variables,
&alias.type_variables,
args,
&alias.lambda_set_variables,
alias.typ.clone(),
);
Type::Alias {
symbol,
type_arguments: vars,
type_arguments,
lambda_set_variables,
actual: Box::new(actual),
kind: alias.kind,
}
}
None => Type::Apply(symbol, args, region),
@ -377,8 +375,7 @@ fn can_annotation_help(
As(
loc_inner,
_spaces,
alias_header
@ AliasHeader {
alias_header @ TypeHeader {
name,
vars: loc_vars,
},
@ -488,7 +485,13 @@ fn can_annotation_help(
hidden_variables.remove(&loc_var.value.1);
}
scope.add_alias(symbol, region, lowercase_vars, alias_actual);
scope.add_alias(
symbol,
region,
lowercase_vars,
alias_actual,
AliasKind::Structural, // aliases in "as" are never opaque
);
let alias = scope.lookup_alias(symbol).unwrap();
local_aliases.insert(symbol, alias.clone());
@ -511,24 +514,22 @@ fn can_annotation_help(
type_arguments: vars,
lambda_set_variables: alias.lambda_set_variables.clone(),
actual: Box::new(alias.typ.clone()),
kind: alias.kind,
}
}
}
Record { fields, ext } => {
let ext_type = match ext {
Some(loc_ann) => can_annotation_help(
let ext_type = can_extension_type(
env,
&loc_ann.value,
region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
),
None => Type::EmptyRec,
};
ext,
roc_problem::can::ExtensionTypeKind::Record,
);
if fields.is_empty() {
match ext {
@ -557,19 +558,16 @@ fn can_annotation_help(
}
}
TagUnion { tags, ext, .. } => {
let ext_type = match ext {
Some(loc_ann) => can_annotation_help(
let ext_type = can_extension_type(
env,
&loc_ann.value,
loc_ann.region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
),
None => Type::EmptyTagUnion,
};
ext,
roc_problem::can::ExtensionTypeKind::TagUnion,
);
if tags.is_empty() {
match ext {
@ -623,6 +621,9 @@ fn can_annotation_help(
// Inference variables aren't bound to a rigid or a wildcard, so all we have to do is
// make a fresh unconstrained variable, and let the type solver fill it in for us 🤠
let var = var_store.fresh();
introduced_variables.insert_inferred(var);
Type::Variable(var)
}
Malformed(string) => {
@ -637,6 +638,144 @@ fn can_annotation_help(
}
}
#[allow(clippy::too_many_arguments)]
fn can_extension_type<'a>(
env: &mut Env,
scope: &mut Scope,
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
local_aliases: &mut SendMap<Symbol, Alias>,
references: &mut MutSet<Symbol>,
opt_ext: &Option<&Loc<TypeAnnotation<'a>>>,
ext_problem_kind: roc_problem::can::ExtensionTypeKind,
) -> Type {
fn valid_record_ext_type(typ: &Type) -> bool {
// Include erroneous types so that we don't overreport errors.
matches!(
typ,
Type::EmptyRec | Type::Record(..) | Type::Variable(..) | Type::Erroneous(..)
)
}
fn valid_tag_ext_type(typ: &Type) -> bool {
matches!(
typ,
Type::EmptyTagUnion | Type::TagUnion(..) | Type::Variable(..) | Type::Erroneous(..)
)
}
use roc_problem::can::ExtensionTypeKind;
let (empty_ext_type, valid_extension_type): (_, fn(&Type) -> bool) = match ext_problem_kind {
ExtensionTypeKind::Record => (Type::EmptyRec, valid_record_ext_type),
ExtensionTypeKind::TagUnion => (Type::EmptyTagUnion, valid_tag_ext_type),
};
match opt_ext {
Some(loc_ann) => {
let ext_type = can_annotation_help(
env,
&loc_ann.value,
loc_ann.region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
);
if valid_extension_type(ext_type.shallow_dealias()) {
ext_type
} else {
// Report an error but mark the extension variable to be inferred
// so that we're as permissive as possible.
//
// THEORY: invalid extension types can appear in this position. Otherwise
// they would be caught as errors during unification.
env.problem(roc_problem::can::Problem::InvalidExtensionType {
region: loc_ann.region,
kind: ext_problem_kind,
});
let var = var_store.fresh();
introduced_variables.insert_inferred(var);
Type::Variable(var)
}
}
None => empty_ext_type,
}
}
pub fn instantiate_and_freshen_alias_type(
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
type_variables: &[Loc<(Lowercase, Variable)>],
type_arguments: Vec<Type>,
lambda_set_variables: &[LambdaSet],
mut actual_type: Type,
) -> (Vec<(Lowercase, Type)>, Vec<LambdaSet>, Type) {
let mut substitutions = ImMap::default();
let mut type_var_to_arg = Vec::new();
for (loc_var, arg_ann) in type_variables.iter().zip(type_arguments.into_iter()) {
let name = loc_var.value.0.clone();
let var = loc_var.value.1;
substitutions.insert(var, arg_ann.clone());
type_var_to_arg.push((name.clone(), arg_ann));
}
// make sure the recursion variable is freshly instantiated
if let Type::RecursiveTagUnion(rvar, _, _) = &mut actual_type {
let new = var_store.fresh();
substitutions.insert(*rvar, Type::Variable(new));
*rvar = new;
}
// make sure hidden variables are freshly instantiated
let mut new_lambda_set_variables = Vec::with_capacity(lambda_set_variables.len());
for typ in lambda_set_variables.iter() {
if let Type::Variable(var) = typ.0 {
let fresh = var_store.fresh();
substitutions.insert(var, Type::Variable(fresh));
introduced_variables.insert_lambda_set(fresh);
new_lambda_set_variables.push(LambdaSet(Type::Variable(fresh)));
} else {
unreachable!("at this point there should be only vars in there");
}
}
// instantiate variables
actual_type.substitute(&substitutions);
(type_var_to_arg, new_lambda_set_variables, actual_type)
}
pub fn freshen_opaque_def(
var_store: &mut VarStore,
opaque: &Alias,
) -> (Vec<(Lowercase, Type)>, Vec<LambdaSet>, Type) {
debug_assert!(opaque.kind == AliasKind::Opaque);
let fresh_arguments = opaque
.type_variables
.iter()
.map(|_| Type::Variable(var_store.fresh()))
.collect();
// TODO this gets ignored; is that a problem
let mut introduced_variables = IntroducedVariables::default();
instantiate_and_freshen_alias_type(
var_store,
&mut introduced_variables,
&opaque.type_variables,
fresh_arguments,
&opaque.lambda_set_variables,
opaque.typ.clone(),
)
}
fn insertion_sort_by<T, F>(arr: &mut [T], mut compare: F)
where
F: FnMut(&T, &T) -> std::cmp::Ordering,

View file

@ -57,6 +57,7 @@ pub fn builtin_dependencies(symbol: Symbol) -> &'static [Symbol] {
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],
Symbol::LIST_SET => &[Symbol::LIST_REPLACE],
_ => &[],
}
}
@ -102,6 +103,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
STR_TO_I8 => str_to_num,
LIST_LEN => list_len,
LIST_GET => list_get,
LIST_REPLACE => list_replace,
LIST_SET => list_set,
LIST_APPEND => list_append,
LIST_FIRST => list_first,
@ -242,6 +244,26 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
NUM_MAX_U64=> num_max_u64,
NUM_MIN_I128=> num_min_i128,
NUM_MAX_I128=> num_max_i128,
NUM_TO_I8 => num_to_i8,
NUM_TO_I8_CHECKED => num_to_i8_checked,
NUM_TO_I16 => num_to_i16,
NUM_TO_I16_CHECKED => num_to_i16_checked,
NUM_TO_I32 => num_to_i32,
NUM_TO_I32_CHECKED => num_to_i32_checked,
NUM_TO_I64 => num_to_i64,
NUM_TO_I64_CHECKED => num_to_i64_checked,
NUM_TO_I128 => num_to_i128,
NUM_TO_I128_CHECKED => num_to_i128_checked,
NUM_TO_U8 => num_to_u8,
NUM_TO_U8_CHECKED => num_to_u8_checked,
NUM_TO_U16 => num_to_u16,
NUM_TO_U16_CHECKED => num_to_u16_checked,
NUM_TO_U32 => num_to_u32,
NUM_TO_U32_CHECKED => num_to_u32_checked,
NUM_TO_U64 => num_to_u64,
NUM_TO_U64_CHECKED => num_to_u64_checked,
NUM_TO_U128 => num_to_u128,
NUM_TO_U128_CHECKED => num_to_u128_checked,
NUM_TO_STR => num_to_str,
RESULT_MAP => result_map,
RESULT_MAP_ERR => result_map_err,
@ -390,6 +412,174 @@ fn lowlevel_5(symbol: Symbol, op: LowLevel, var_store: &mut VarStore) -> Def {
)
}
// Num.toI8 : Int * -> I8
fn num_to_i8(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to IntCast
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
// Num.toI16 : Int * -> I16
fn num_to_i16(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to IntCast
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
// Num.toI32 : Int * -> I32
fn num_to_i32(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to IntCast
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
// Num.toI64 : Int * -> I64
fn num_to_i64(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to IntCast
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
// Num.toI128 : Int * -> I128
fn num_to_i128(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to IntCast
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
// Num.toU8 : Int * -> U8
fn num_to_u8(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to IntCast
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
// Num.toU16 : Int * -> U16
fn num_to_u16(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to IntCast
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
// Num.toU32 : Int * -> U32
fn num_to_u32(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to IntCast
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
// Num.toU64 : Int * -> U64
fn num_to_u64(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to IntCast
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
// Num.toU128 : Int * -> U128
fn num_to_u128(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to IntCast
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
fn to_num_checked(symbol: Symbol, var_store: &mut VarStore, lowlevel: LowLevel) -> Def {
let bool_var = var_store.fresh();
let num_var_1 = var_store.fresh();
let num_var_2 = var_store.fresh();
let ret_var = var_store.fresh();
let record_var = var_store.fresh();
// let arg_2 = RunLowLevel NumToXXXChecked arg_1
// if arg_2.b then
// Err OutOfBounds
// else
// Ok arg_2.a
//
// "a" and "b" because the lowlevel return value looks like { converted_val: XXX, out_of_bounds: bool },
// and codegen will sort by alignment, so "a" will be the first key, etc.
let cont = If {
branch_var: ret_var,
cond_var: bool_var,
branches: vec![(
// if-condition
no_region(
// arg_2.b
Access {
record_var,
ext_var: var_store.fresh(),
field: "b".into(),
field_var: var_store.fresh(),
loc_expr: Box::new(no_region(Var(Symbol::ARG_2))),
},
),
// out of bounds!
no_region(tag(
"Err",
vec![tag("OutOfBounds", Vec::new(), var_store)],
var_store,
)),
)],
final_else: Box::new(
// all is well
no_region(
// Ok arg_2.a
tag(
"Ok",
vec![
// arg_2.a
Access {
record_var,
ext_var: var_store.fresh(),
field: "a".into(),
field_var: num_var_2,
loc_expr: Box::new(no_region(Var(Symbol::ARG_2))),
},
],
var_store,
),
),
),
};
// arg_2 = RunLowLevel NumToXXXChecked arg_1
let def = crate::def::Def {
loc_pattern: no_region(Pattern::Identifier(Symbol::ARG_2)),
loc_expr: no_region(RunLowLevel {
op: lowlevel,
args: vec![(num_var_1, Var(Symbol::ARG_1))],
ret_var: record_var,
}),
expr_var: record_var,
pattern_vars: SendMap::default(),
annotation: None,
};
let body = LetNonRec(Box::new(def), Box::new(no_region(cont)), ret_var);
defn(
symbol,
vec![(num_var_1, Symbol::ARG_1)],
var_store,
body,
ret_var,
)
}
macro_rules! num_to_checked {
($($fn:ident)*) => {$(
// Num.toXXXChecked : Int * -> Result XXX [ OutOfBounds ]*
fn $fn(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Use the generic `NumToIntChecked`; we'll figure out exactly what layout(s) we need
// during code generation after types are resolved.
to_num_checked(symbol, var_store, LowLevel::NumToIntChecked)
}
)*}
}
num_to_checked! {
num_to_i8_checked
num_to_i16_checked
num_to_i32_checked
num_to_i64_checked
num_to_i128_checked
num_to_u8_checked
num_to_u16_checked
num_to_u32_checked
num_to_u64_checked
num_to_u128_checked
}
// Num.toStr : Num a -> Str
fn num_to_str(symbol: Symbol, var_store: &mut VarStore) -> Def {
let num_var = var_store.fresh();
@ -2115,6 +2305,91 @@ fn list_get(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// List.replace : List elem, Nat, elem -> { list: List elem, value: elem }
fn list_replace(symbol: Symbol, var_store: &mut VarStore) -> Def {
let arg_list = Symbol::ARG_1;
let arg_index = Symbol::ARG_2;
let arg_elem = Symbol::ARG_3;
let bool_var = var_store.fresh();
let len_var = var_store.fresh();
let elem_var = var_store.fresh();
let list_arg_var = var_store.fresh();
let ret_record_var = var_store.fresh();
let ret_result_var = var_store.fresh();
let list_field = Field {
var: list_arg_var,
region: Region::zero(),
loc_expr: Box::new(Loc::at_zero(Expr::Var(arg_list))),
};
let value_field = Field {
var: elem_var,
region: Region::zero(),
loc_expr: Box::new(Loc::at_zero(Expr::Var(arg_elem))),
};
// Perform a bounds check. If it passes, run LowLevel::ListReplaceUnsafe.
// Otherwise, return the list unmodified.
let body = If {
cond_var: bool_var,
branch_var: ret_result_var,
branches: vec![(
// if-condition
no_region(
// index < List.len list
RunLowLevel {
op: LowLevel::NumLt,
args: vec![
(len_var, Var(arg_index)),
(
len_var,
RunLowLevel {
op: LowLevel::ListLen,
args: vec![(list_arg_var, Var(arg_list))],
ret_var: len_var,
},
),
],
ret_var: bool_var,
},
),
// then-branch
no_region(
// List.replaceUnsafe list index elem
RunLowLevel {
op: LowLevel::ListReplaceUnsafe,
args: vec![
(list_arg_var, Var(arg_list)),
(len_var, Var(arg_index)),
(elem_var, Var(arg_elem)),
],
ret_var: ret_record_var,
},
),
)],
final_else: Box::new(
// else-branch
no_region(record(
vec![("list".into(), list_field), ("value".into(), value_field)],
var_store,
)),
),
};
defn(
symbol,
vec![
(list_arg_var, Symbol::ARG_1),
(len_var, Symbol::ARG_2),
(elem_var, Symbol::ARG_3),
],
var_store,
body,
ret_result_var,
)
}
/// List.set : List elem, Nat, elem -> List elem
///
/// List.set :
@ -2129,9 +2404,27 @@ fn list_set(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh();
let len_var = var_store.fresh();
let elem_var = var_store.fresh();
let replace_record_var = var_store.fresh();
let list_arg_var = var_store.fresh(); // Uniqueness type Attr differs between
let list_ret_var = var_store.fresh(); // the arg list and the returned list
let replace_function = (
var_store.fresh(),
Loc::at_zero(Expr::Var(Symbol::LIST_REPLACE)),
var_store.fresh(),
replace_record_var,
);
let replace_call = Expr::Call(
Box::new(replace_function),
vec![
(list_arg_var, Loc::at_zero(Var(arg_list))),
(len_var, Loc::at_zero(Var(arg_index))),
(elem_var, Loc::at_zero(Var(arg_elem))),
],
CalledVia::Space,
);
// Perform a bounds check. If it passes, run LowLevel::ListSet.
// Otherwise, return the list unmodified.
let body = If {
@ -2158,18 +2451,16 @@ fn list_set(symbol: Symbol, var_store: &mut VarStore) -> Def {
},
),
// then-branch
no_region(
// List.setUnsafe list index
RunLowLevel {
op: LowLevel::ListSet,
args: vec![
(list_arg_var, Var(arg_list)),
(len_var, Var(arg_index)),
(elem_var, Var(arg_elem)),
],
ret_var: list_ret_var,
},
),
no_region(Access {
record_var: replace_record_var,
ext_var: var_store.fresh(),
field_var: list_ret_var,
loc_expr: Box::new(no_region(
// List.replaceUnsafe list index elem
replace_call,
)),
field: "list".into(),
}),
)],
final_else: Box::new(
// else-branch

View file

@ -1,175 +1,505 @@
use crate::expected::{Expected, PExpected};
use roc_collections::all::{MutSet, SendMap};
use roc_module::{ident::TagName, symbol::Symbol};
use roc_collections::soa::{Index, Slice};
use roc_module::ident::TagName;
use roc_module::symbol::Symbol;
use roc_region::all::{Loc, Region};
use roc_types::subs::Variable;
use roc_types::types::{Category, PatternCategory, Type};
use roc_types::{subs::Variable, types::VariableDetail};
/// A presence constraint is an additive constraint that defines the lower bound
/// of a type. For example, `Present(t1, IncludesTag(A, []))` means that the
/// type `t1` must contain at least the tag `A`. The additive nature of these
/// constraints makes them behaviorally different from unification-based constraints.
#[derive(Debug, Clone, PartialEq)]
pub enum PresenceConstraint {
IncludesTag(TagName, Vec<Type>),
IsOpen,
Pattern(Region, PatternCategory, PExpected<Type>),
#[derive(Debug)]
pub struct Constraints {
pub constraints: Vec<Constraint>,
pub types: Vec<Type>,
pub variables: Vec<Variable>,
pub loc_symbols: Vec<(Symbol, Region)>,
pub let_constraints: Vec<LetConstraint>,
pub categories: Vec<Category>,
pub pattern_categories: Vec<PatternCategory>,
pub expectations: Vec<Expected<Type>>,
pub pattern_expectations: Vec<PExpected<Type>>,
pub includes_tags: Vec<IncludesTag>,
pub strings: Vec<&'static str>,
}
impl Default for Constraints {
fn default() -> Self {
Self::new()
}
}
impl Constraints {
pub fn new() -> Self {
let constraints = Vec::new();
let mut types = Vec::new();
let variables = Vec::new();
let loc_symbols = Vec::new();
let let_constraints = Vec::new();
let mut categories = Vec::with_capacity(16);
let mut pattern_categories = Vec::with_capacity(16);
let expectations = Vec::new();
let pattern_expectations = Vec::new();
let includes_tags = Vec::new();
let strings = Vec::new();
types.extend([Type::EmptyRec, Type::EmptyTagUnion]);
categories.extend([
Category::Record,
Category::ForeignCall,
Category::OpaqueArg,
Category::Lambda,
Category::ClosureSize,
Category::StrInterpolation,
Category::If,
Category::When,
Category::Float,
Category::Int,
Category::Num,
Category::List,
Category::Str,
Category::Character,
]);
pattern_categories.extend([
PatternCategory::Record,
PatternCategory::EmptyRecord,
PatternCategory::PatternGuard,
PatternCategory::PatternDefault,
PatternCategory::Set,
PatternCategory::Map,
PatternCategory::Str,
PatternCategory::Num,
PatternCategory::Int,
PatternCategory::Float,
PatternCategory::Character,
]);
Self {
constraints,
types,
variables,
loc_symbols,
let_constraints,
categories,
pattern_categories,
expectations,
pattern_expectations,
includes_tags,
strings,
}
}
pub const EMPTY_RECORD: Index<Type> = Index::new(0);
pub const EMPTY_TAG_UNION: Index<Type> = Index::new(1);
pub const CATEGORY_RECORD: Index<Category> = Index::new(0);
pub const CATEGORY_FOREIGNCALL: Index<Category> = Index::new(1);
pub const CATEGORY_OPAQUEARG: Index<Category> = Index::new(2);
pub const CATEGORY_LAMBDA: Index<Category> = Index::new(3);
pub const CATEGORY_CLOSURESIZE: Index<Category> = Index::new(4);
pub const CATEGORY_STRINTERPOLATION: Index<Category> = Index::new(5);
pub const CATEGORY_IF: Index<Category> = Index::new(6);
pub const CATEGORY_WHEN: Index<Category> = Index::new(7);
pub const CATEGORY_FLOAT: Index<Category> = Index::new(8);
pub const CATEGORY_INT: Index<Category> = Index::new(9);
pub const CATEGORY_NUM: Index<Category> = Index::new(10);
pub const CATEGORY_LIST: Index<Category> = Index::new(11);
pub const CATEGORY_STR: Index<Category> = Index::new(12);
pub const CATEGORY_CHARACTER: Index<Category> = Index::new(13);
pub const PCATEGORY_RECORD: Index<PatternCategory> = Index::new(0);
pub const PCATEGORY_EMPTYRECORD: Index<PatternCategory> = Index::new(1);
pub const PCATEGORY_PATTERNGUARD: Index<PatternCategory> = Index::new(2);
pub const PCATEGORY_PATTERNDEFAULT: Index<PatternCategory> = Index::new(3);
pub const PCATEGORY_SET: Index<PatternCategory> = Index::new(4);
pub const PCATEGORY_MAP: Index<PatternCategory> = Index::new(5);
pub const PCATEGORY_STR: Index<PatternCategory> = Index::new(6);
pub const PCATEGORY_NUM: Index<PatternCategory> = Index::new(7);
pub const PCATEGORY_INT: Index<PatternCategory> = Index::new(8);
pub const PCATEGORY_FLOAT: Index<PatternCategory> = Index::new(9);
pub const PCATEGORY_CHARACTER: Index<PatternCategory> = Index::new(10);
#[inline(always)]
pub fn push_type(&mut self, typ: Type) -> Index<Type> {
match typ {
Type::EmptyRec => Self::EMPTY_RECORD,
Type::EmptyTagUnion => Self::EMPTY_TAG_UNION,
other => Index::push_new(&mut self.types, other),
}
}
#[inline(always)]
pub fn push_expected_type(&mut self, expected: Expected<Type>) -> Index<Expected<Type>> {
Index::push_new(&mut self.expectations, expected)
}
#[inline(always)]
pub fn push_category(&mut self, category: Category) -> Index<Category> {
match category {
Category::Record => Self::CATEGORY_RECORD,
Category::ForeignCall => Self::CATEGORY_FOREIGNCALL,
Category::OpaqueArg => Self::CATEGORY_OPAQUEARG,
Category::Lambda => Self::CATEGORY_LAMBDA,
Category::ClosureSize => Self::CATEGORY_CLOSURESIZE,
Category::StrInterpolation => Self::CATEGORY_STRINTERPOLATION,
Category::If => Self::CATEGORY_IF,
Category::When => Self::CATEGORY_WHEN,
Category::Float => Self::CATEGORY_FLOAT,
Category::Int => Self::CATEGORY_INT,
Category::Num => Self::CATEGORY_NUM,
Category::List => Self::CATEGORY_LIST,
Category::Str => Self::CATEGORY_STR,
Category::Character => Self::CATEGORY_CHARACTER,
other => Index::push_new(&mut self.categories, other),
}
}
#[inline(always)]
pub fn push_pattern_category(&mut self, category: PatternCategory) -> Index<PatternCategory> {
match category {
PatternCategory::Record => Self::PCATEGORY_RECORD,
PatternCategory::EmptyRecord => Self::PCATEGORY_EMPTYRECORD,
PatternCategory::PatternGuard => Self::PCATEGORY_PATTERNGUARD,
PatternCategory::PatternDefault => Self::PCATEGORY_PATTERNDEFAULT,
PatternCategory::Set => Self::PCATEGORY_SET,
PatternCategory::Map => Self::PCATEGORY_MAP,
PatternCategory::Str => Self::PCATEGORY_STR,
PatternCategory::Num => Self::PCATEGORY_NUM,
PatternCategory::Int => Self::PCATEGORY_INT,
PatternCategory::Float => Self::PCATEGORY_FLOAT,
PatternCategory::Character => Self::PCATEGORY_CHARACTER,
other => Index::push_new(&mut self.pattern_categories, other),
}
}
#[inline(always)]
pub fn equal_types(
&mut self,
typ: Type,
expected: Expected<Type>,
category: Category,
region: Region,
) -> Constraint {
let type_index = Index::push_new(&mut self.types, typ);
let expected_index = Index::push_new(&mut self.expectations, expected);
let category_index = Self::push_category(self, category);
Constraint::Eq(type_index, expected_index, category_index, region)
}
pub fn equal_pattern_types(
&mut self,
typ: Type,
expected: PExpected<Type>,
category: PatternCategory,
region: Region,
) -> Constraint {
let type_index = Index::push_new(&mut self.types, typ);
let expected_index = Index::push_new(&mut self.pattern_expectations, expected);
let category_index = Self::push_pattern_category(self, category);
Constraint::Pattern(type_index, expected_index, category_index, region)
}
pub fn pattern_presence(
&mut self,
typ: Type,
expected: PExpected<Type>,
category: PatternCategory,
region: Region,
) -> Constraint {
let type_index = Index::push_new(&mut self.types, typ);
let expected_index = Index::push_new(&mut self.pattern_expectations, expected);
let category_index = Index::push_new(&mut self.pattern_categories, category);
Constraint::PatternPresence(type_index, expected_index, category_index, region)
}
pub fn is_open_type(&mut self, typ: Type) -> Constraint {
let type_index = Index::push_new(&mut self.types, typ);
Constraint::IsOpenType(type_index)
}
pub fn includes_tag<I>(
&mut self,
typ: Type,
tag_name: TagName,
types: I,
category: PatternCategory,
region: Region,
) -> Constraint
where
I: IntoIterator<Item = Type>,
{
let type_index = Index::push_new(&mut self.types, typ);
let category_index = Index::push_new(&mut self.pattern_categories, category);
let types_slice = Slice::extend_new(&mut self.types, types);
let includes_tag = IncludesTag {
type_index,
tag_name,
types: types_slice,
pattern_category: category_index,
region,
};
let includes_tag_index = Index::push_new(&mut self.includes_tags, includes_tag);
Constraint::IncludesTag(includes_tag_index)
}
fn variable_slice<I>(&mut self, it: I) -> Slice<Variable>
where
I: IntoIterator<Item = Variable>,
{
let start = self.variables.len();
self.variables.extend(it);
let length = self.variables.len() - start;
Slice::new(start as _, length as _)
}
fn def_types_slice<I>(&mut self, it: I) -> DefTypes
where
I: IntoIterator<Item = (Symbol, Loc<Type>)>,
I::IntoIter: ExactSizeIterator,
{
let it = it.into_iter();
let types_start = self.types.len();
let loc_symbols_start = self.loc_symbols.len();
// because we have an ExactSizeIterator, we can reserve space here
let length = it.len();
self.types.reserve(length);
self.loc_symbols.reserve(length);
for (symbol, loc_type) in it {
let Loc { region, value } = loc_type;
self.types.push(value);
self.loc_symbols.push((symbol, region));
}
DefTypes {
types: Slice::new(types_start as _, length as _),
loc_symbols: Slice::new(loc_symbols_start as _, length as _),
}
}
#[inline(always)]
pub fn exists<I>(&mut self, flex_vars: I, defs_constraint: Constraint) -> Constraint
where
I: IntoIterator<Item = Variable>,
{
let defs_and_ret_constraint = Index::new(self.constraints.len() as _);
self.constraints.push(defs_constraint);
self.constraints.push(Constraint::True);
let let_contraint = LetConstraint {
rigid_vars: Slice::default(),
flex_vars: self.variable_slice(flex_vars),
def_types: DefTypes::default(),
defs_and_ret_constraint,
};
let let_index = Index::new(self.let_constraints.len() as _);
self.let_constraints.push(let_contraint);
Constraint::Let(let_index)
}
#[inline(always)]
pub fn exists_many<I, C>(&mut self, flex_vars: I, defs_constraint: C) -> Constraint
where
I: IntoIterator<Item = Variable>,
C: IntoIterator<Item = Constraint>,
C::IntoIter: ExactSizeIterator,
{
let defs_constraint = self.and_constraint(defs_constraint);
let defs_and_ret_constraint = Index::new(self.constraints.len() as _);
self.constraints.push(defs_constraint);
self.constraints.push(Constraint::True);
let let_contraint = LetConstraint {
rigid_vars: Slice::default(),
flex_vars: self.variable_slice(flex_vars),
def_types: DefTypes::default(),
defs_and_ret_constraint,
};
let let_index = Index::new(self.let_constraints.len() as _);
self.let_constraints.push(let_contraint);
Constraint::Let(let_index)
}
#[inline(always)]
pub fn let_constraint<I1, I2, I3>(
&mut self,
rigid_vars: I1,
flex_vars: I2,
def_types: I3,
defs_constraint: Constraint,
ret_constraint: Constraint,
) -> Constraint
where
I1: IntoIterator<Item = Variable>,
I2: IntoIterator<Item = Variable>,
I3: IntoIterator<Item = (Symbol, Loc<Type>)>,
I3::IntoIter: ExactSizeIterator,
{
let defs_and_ret_constraint = Index::new(self.constraints.len() as _);
self.constraints.push(defs_constraint);
self.constraints.push(ret_constraint);
let let_contraint = LetConstraint {
rigid_vars: self.variable_slice(rigid_vars),
flex_vars: self.variable_slice(flex_vars),
def_types: self.def_types_slice(def_types),
defs_and_ret_constraint,
};
let let_index = Index::new(self.let_constraints.len() as _);
self.let_constraints.push(let_contraint);
Constraint::Let(let_index)
}
#[inline(always)]
pub fn and_constraint<I>(&mut self, constraints: I) -> Constraint
where
I: IntoIterator<Item = Constraint>,
I::IntoIter: ExactSizeIterator,
{
let mut it = constraints.into_iter();
match it.len() {
0 => Constraint::True,
1 => it.next().unwrap(),
_ => {
let start = self.constraints.len() as u32;
self.constraints.extend(it);
let end = self.constraints.len() as u32;
let slice = Slice::new(start, (end - start) as u16);
Constraint::And(slice)
}
}
}
pub fn lookup(
&mut self,
symbol: Symbol,
expected: Expected<Type>,
region: Region,
) -> Constraint {
Constraint::Lookup(
symbol,
Index::push_new(&mut self.expectations, expected),
region,
)
}
pub fn contains_save_the_environment(&self, constraint: &Constraint) -> bool {
match constraint {
Constraint::Eq(..) => false,
Constraint::Store(..) => false,
Constraint::Lookup(..) => false,
Constraint::Pattern(..) => false,
Constraint::True => false,
Constraint::SaveTheEnvironment => true,
Constraint::Let(index) => {
let let_constraint = &self.let_constraints[index.index()];
let offset = let_constraint.defs_and_ret_constraint.index();
let defs_constraint = &self.constraints[offset];
let ret_constraint = &self.constraints[offset + 1];
self.contains_save_the_environment(defs_constraint)
|| self.contains_save_the_environment(ret_constraint)
}
Constraint::And(slice) => {
let constraints = &self.constraints[slice.indices()];
constraints
.iter()
.any(|c| self.contains_save_the_environment(c))
}
Constraint::IsOpenType(_) => false,
Constraint::IncludesTag(_) => false,
Constraint::PatternPresence(_, _, _, _) => false,
}
}
pub fn store(
&mut self,
typ: Type,
variable: Variable,
filename: &'static str,
line_number: u32,
) -> Constraint {
let type_index = Index::push_new(&mut self.types, typ);
let string_index = Index::push_new(&mut self.strings, filename);
Constraint::Store(type_index, variable, string_index, line_number)
}
}
static_assertions::assert_eq_size!([u8; 3 * 8], Constraint);
#[derive(Debug, Clone, PartialEq)]
pub enum Constraint {
Eq(Type, Expected<Type>, Category, Region),
Store(Type, Variable, &'static str, u32),
Lookup(Symbol, Expected<Type>, Region),
Pattern(Region, PatternCategory, Type, PExpected<Type>),
Eq(Index<Type>, Index<Expected<Type>>, Index<Category>, Region),
Store(Index<Type>, Variable, Index<&'static str>, u32),
Lookup(Symbol, Index<Expected<Type>>, Region),
Pattern(
Index<Type>,
Index<PExpected<Type>>,
Index<PatternCategory>,
Region,
),
True, // Used for things that always unify, e.g. blanks and runtime errors
SaveTheEnvironment,
Let(Box<LetConstraint>),
And(Vec<Constraint>),
Present(Type, PresenceConstraint),
Let(Index<LetConstraint>),
And(Slice<Constraint>),
/// Presence constraints
IsOpenType(Index<Type>), // Theory; always applied to a variable? if yes the use that
IncludesTag(Index<IncludesTag>),
PatternPresence(
Index<Type>,
Index<PExpected<Type>>,
Index<PatternCategory>,
Region,
),
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct DefTypes {
pub types: Slice<Type>,
pub loc_symbols: Slice<(Symbol, Region)>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct LetConstraint {
pub rigid_vars: Vec<Variable>,
pub flex_vars: Vec<Variable>,
pub def_types: SendMap<Symbol, Loc<Type>>,
pub defs_constraint: Constraint,
pub ret_constraint: Constraint,
pub rigid_vars: Slice<Variable>,
pub flex_vars: Slice<Variable>,
pub def_types: DefTypes,
pub defs_and_ret_constraint: Index<(Constraint, Constraint)>,
}
// VALIDATE
#[derive(Default, Clone)]
struct Declared {
pub rigid_vars: MutSet<Variable>,
pub flex_vars: MutSet<Variable>,
}
impl Constraint {
pub fn validate(&self) -> bool {
let mut unbound = Default::default();
validate_help(self, &Declared::default(), &mut unbound);
if !unbound.type_variables.is_empty() {
panic!("found unbound type variables {:?}", &unbound.type_variables);
}
if !unbound.lambda_set_variables.is_empty() {
panic!(
"found unbound lambda set variables {:?}",
&unbound.lambda_set_variables
);
}
if !unbound.recursion_variables.is_empty() {
panic!(
"found unbound recursion variables {:?}",
&unbound.recursion_variables
);
}
true
}
pub fn contains_save_the_environment(&self) -> bool {
match self {
Constraint::Eq(_, _, _, _) => false,
Constraint::Store(_, _, _, _) => false,
Constraint::Lookup(_, _, _) => false,
Constraint::Pattern(_, _, _, _) => false,
Constraint::True => false,
Constraint::SaveTheEnvironment => true,
Constraint::Let(boxed) => {
boxed.ret_constraint.contains_save_the_environment()
|| boxed.defs_constraint.contains_save_the_environment()
}
Constraint::And(cs) => cs.iter().any(|c| c.contains_save_the_environment()),
Constraint::Present(_, _) => false,
}
}
}
fn subtract(declared: &Declared, detail: &VariableDetail, accum: &mut VariableDetail) {
for var in &detail.type_variables {
if !(declared.rigid_vars.contains(var) || declared.flex_vars.contains(var)) {
accum.type_variables.insert(*var);
}
}
// lambda set variables are always flex
for var in &detail.lambda_set_variables {
if declared.rigid_vars.contains(var) {
panic!("lambda set variable {:?} is declared as rigid", var);
}
if !declared.flex_vars.contains(var) {
accum.lambda_set_variables.push(*var);
}
}
// recursion vars should be always rigid
for var in &detail.recursion_variables {
if declared.flex_vars.contains(var) {
panic!("recursion variable {:?} is declared as flex", var);
}
if !declared.rigid_vars.contains(var) {
accum.recursion_variables.insert(*var);
}
}
}
fn validate_help(constraint: &Constraint, declared: &Declared, accum: &mut VariableDetail) {
use Constraint::*;
match constraint {
True | SaveTheEnvironment | Lookup(_, _, _) => { /* nothing */ }
Store(typ, var, _, _) => {
subtract(declared, &typ.variables_detail(), accum);
if !declared.flex_vars.contains(var) {
accum.type_variables.insert(*var);
}
}
Constraint::Eq(typ, expected, _, _) => {
subtract(declared, &typ.variables_detail(), accum);
subtract(declared, &expected.get_type_ref().variables_detail(), accum);
}
Constraint::Pattern(_, _, typ, expected) => {
subtract(declared, &typ.variables_detail(), accum);
subtract(declared, &expected.get_type_ref().variables_detail(), accum);
}
Constraint::Let(letcon) => {
let mut declared = declared.clone();
declared
.rigid_vars
.extend(letcon.rigid_vars.iter().copied());
declared.flex_vars.extend(letcon.flex_vars.iter().copied());
validate_help(&letcon.defs_constraint, &declared, accum);
validate_help(&letcon.ret_constraint, &declared, accum);
}
Constraint::And(inner) => {
for c in inner {
validate_help(c, declared, accum);
}
}
Constraint::Present(typ, constr) => {
subtract(declared, &typ.variables_detail(), accum);
match constr {
PresenceConstraint::IncludesTag(_, tys) => {
for ty in tys {
subtract(declared, &ty.variables_detail(), accum);
}
}
PresenceConstraint::IsOpen => {}
PresenceConstraint::Pattern(_, _, expected) => {
subtract(declared, &typ.variables_detail(), accum);
subtract(declared, &expected.get_type_ref().variables_detail(), accum);
}
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct IncludesTag {
pub type_index: Index<Type>,
pub tag_name: TagName,
pub types: Slice<Type>,
pub pattern_category: Index<PatternCategory>,
pub region: Region,
}

View file

@ -15,11 +15,12 @@ use roc_collections::all::{default_hasher, ImMap, ImSet, MutMap, MutSet, SendMap
use roc_module::ident::Lowercase;
use roc_module::symbol::Symbol;
use roc_parse::ast;
use roc_parse::ast::AliasHeader;
use roc_parse::ast::TypeHeader;
use roc_parse::pattern::PatternType;
use roc_problem::can::{CycleEntry, Problem, RuntimeError};
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::AliasKind;
use roc_types::types::{Alias, Type};
use std::collections::HashMap;
use std::fmt::Debug;
@ -73,17 +74,18 @@ enum PendingDef<'a> {
&'a Loc<ast::Expr<'a>>,
),
/// A type alias, e.g. `Ints : List Int`
/// A structural or opaque type alias, e.g. `Ints : List Int` or `Age := U32` respectively.
Alias {
name: Loc<Symbol>,
vars: Vec<Loc<Lowercase>>,
ann: &'a Loc<ast::TypeAnnotation<'a>>,
kind: AliasKind,
},
/// An invalid alias, that is ignored in the rest of the pipeline
/// e.g. a shadowed alias, or a definition like `MyAlias 1 : Int`
/// with an incorrect pattern
InvalidAlias,
InvalidAlias { kind: AliasKind },
}
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
@ -108,18 +110,20 @@ impl Declaration {
}
}
/// Returns a topologically sorted sequence of alias names
fn sort_aliases_before_introduction(mut alias_symbols: MutMap<Symbol, Vec<Symbol>>) -> Vec<Symbol> {
let defined_symbols: Vec<Symbol> = alias_symbols.keys().copied().collect();
/// Returns a topologically sorted sequence of alias/opaque names
fn sort_type_defs_before_introduction(
mut referenced_symbols: MutMap<Symbol, Vec<Symbol>>,
) -> Vec<Symbol> {
let defined_symbols: Vec<Symbol> = referenced_symbols.keys().copied().collect();
// find the strongly connected components and their relations
let sccs = {
// only retain symbols from the current alias_defs
for v in alias_symbols.iter_mut() {
// only retain symbols from the current set of defined symbols; the rest come from other modules
for v in referenced_symbols.iter_mut() {
v.1.retain(|x| defined_symbols.iter().any(|s| s == x));
}
let all_successors_with_self = |symbol: &Symbol| alias_symbols[symbol].iter().copied();
let all_successors_with_self = |symbol: &Symbol| referenced_symbols[symbol].iter().copied();
strongly_connected_components(&defined_symbols, all_successors_with_self)
};
@ -139,7 +143,7 @@ fn sort_aliases_before_introduction(mut alias_symbols: MutMap<Symbol, Vec<Symbol
for (index, group) in sccs.iter().enumerate() {
for s in group {
let reachable = &alias_symbols[s];
let reachable = &referenced_symbols[s];
for r in reachable {
let new_index = symbol_to_group_index[r];
@ -224,7 +228,10 @@ pub fn canonicalize_defs<'a>(
.map(|t| t.0),
)
}
PendingDef::Alias { .. } | PendingDef::InvalidAlias => {}
// Type definitions aren't value definitions, so we don't need to do
// anything for them here.
PendingDef::Alias { .. } | PendingDef::InvalidAlias { .. } => {}
}
}
// Record the ast::Expr for later. We'll do another pass through these
@ -245,25 +252,34 @@ pub fn canonicalize_defs<'a>(
let mut value_defs = Vec::new();
let mut alias_defs = MutMap::default();
let mut alias_symbols = MutMap::default();
let mut referenced_type_symbols = MutMap::default();
for pending_def in pending.into_iter() {
match pending_def {
PendingDef::Alias { name, vars, ann } => {
let symbols =
crate::annotation::find_alias_symbols(env.home, &mut env.ident_ids, &ann.value);
PendingDef::Alias {
name,
vars,
ann,
kind,
} => {
let referenced_symbols = crate::annotation::find_type_def_symbols(
env.home,
&mut env.ident_ids,
&ann.value,
);
alias_symbols.insert(name.value, symbols);
alias_defs.insert(name.value, (name, vars, ann));
referenced_type_symbols.insert(name.value, referenced_symbols);
alias_defs.insert(name.value, (name, vars, ann, kind));
}
other => value_defs.push(other),
}
}
let sorted = sort_aliases_before_introduction(alias_symbols);
let sorted = sort_type_defs_before_introduction(referenced_type_symbols);
for alias_name in sorted {
let (name, vars, ann) = alias_defs.remove(&alias_name).unwrap();
for type_name in sorted {
let (name, vars, ann, kind) = alias_defs.remove(&type_name).unwrap();
let symbol = name.value;
let can_ann = canonicalize_annotation(env, &mut scope, &ann.value, ann.region, var_store);
@ -271,7 +287,7 @@ pub fn canonicalize_defs<'a>(
// Record all the annotation's references in output.references.lookups
for symbol in can_ann.references {
output.references.lookups.insert(symbol);
output.references.referenced_aliases.insert(symbol);
output.references.referenced_type_defs.insert(symbol);
}
let mut can_vars: Vec<Loc<(Lowercase, Variable)>> = Vec::with_capacity(vars.len());
@ -282,7 +298,7 @@ pub fn canonicalize_defs<'a>(
.introduced_variables
.var_by_name(&loc_lowercase.value)
{
// This is a valid lowercase rigid var for the alias.
// This is a valid lowercase rigid var for the type def.
can_vars.push(Loc {
value: (loc_lowercase.value.clone(), *var),
region: loc_lowercase.region,
@ -291,7 +307,7 @@ pub fn canonicalize_defs<'a>(
is_phantom = true;
env.problems.push(Problem::PhantomTypeArgument {
alias: symbol,
typ: symbol,
variable_region: loc_lowercase.region,
variable_name: loc_lowercase.value.clone(),
});
@ -303,7 +319,13 @@ pub fn canonicalize_defs<'a>(
continue;
}
let alias = create_alias(symbol, name.region, can_vars.clone(), can_ann.typ.clone());
let alias = create_alias(
symbol,
name.region,
can_vars.clone(),
can_ann.typ.clone(),
kind,
);
aliases.insert(symbol, alias.clone());
}
@ -316,6 +338,7 @@ pub fn canonicalize_defs<'a>(
alias.region,
alias.type_variables.clone(),
alias.typ.clone(),
alias.kind,
);
}
@ -376,7 +399,7 @@ pub fn sort_can_defs(
) -> (Result<Vec<Declaration>, RuntimeError>, Output) {
let CanDefs {
refs_by_symbol,
can_defs_by_symbol,
mut can_defs_by_symbol,
aliases,
} = defs;
@ -560,7 +583,7 @@ pub fn sort_can_defs(
&group,
&env.closures,
&mut all_successors_with_self,
&can_defs_by_symbol,
&mut can_defs_by_symbol,
&mut declarations,
);
}
@ -694,7 +717,7 @@ pub fn sort_can_defs(
group,
&env.closures,
&mut all_successors_with_self,
&can_defs_by_symbol,
&mut can_defs_by_symbol,
&mut declarations,
);
}
@ -716,7 +739,7 @@ fn group_to_declaration(
group: &[Symbol],
closures: &MutMap<Symbol, References>,
successors: &mut dyn FnMut(&Symbol) -> ImSet<Symbol>,
can_defs_by_symbol: &MutMap<Symbol, Def>,
can_defs_by_symbol: &mut MutMap<Symbol, Def>,
declarations: &mut Vec<Declaration>,
) {
use Declaration::*;
@ -736,15 +759,14 @@ fn group_to_declaration(
// Can bind multiple symbols. When not incorrectly recursive (which is guaranteed in this function),
// normally `someDef` would be inserted twice. We use the region of the pattern as a unique key
// for a definition, so every definition is only inserted (thus typechecked and emitted) once
let mut seen_pattern_regions: ImSet<Region> = ImSet::default();
let mut seen_pattern_regions: Vec<Region> = Vec::with_capacity(2);
for cycle in strongly_connected_components(group, filtered_successors) {
if cycle.len() == 1 {
let symbol = &cycle[0];
if let Some(can_def) = can_defs_by_symbol.get(symbol) {
let mut new_def = can_def.clone();
match can_defs_by_symbol.remove(symbol) {
Some(mut new_def) => {
// Determine recursivity of closures that are not tail-recursive
if let Closure(ClosureData {
recursive: recursive @ Recursive::NotRecursive,
@ -757,22 +779,24 @@ fn group_to_declaration(
let is_recursive = successors(symbol).contains(symbol);
if !seen_pattern_regions.contains(&new_def.loc_pattern.region) {
seen_pattern_regions.push(new_def.loc_pattern.region);
if is_recursive {
declarations.push(DeclareRec(vec![new_def.clone()]));
declarations.push(DeclareRec(vec![new_def]));
} else {
declarations.push(Declare(new_def.clone()));
declarations.push(Declare(new_def));
}
seen_pattern_regions.insert(new_def.loc_pattern.region);
}
}
None => roc_error_macros::internal_error!("def not available {:?}", symbol),
}
} else {
let mut can_defs = Vec::new();
// Topological sort gives us the reverse of the sorting we want!
for symbol in cycle.into_iter().rev() {
if let Some(can_def) = can_defs_by_symbol.get(&symbol) {
let mut new_def = can_def.clone();
match can_defs_by_symbol.remove(&symbol) {
Some(mut new_def) => {
// Determine recursivity of closures that are not tail-recursive
if let Closure(ClosureData {
recursive: recursive @ Recursive::NotRecursive,
@ -783,10 +807,12 @@ fn group_to_declaration(
}
if !seen_pattern_regions.contains(&new_def.loc_pattern.region) {
can_defs.push(new_def.clone());
}
seen_pattern_regions.push(new_def.loc_pattern.region);
seen_pattern_regions.insert(new_def.loc_pattern.region);
can_defs.push(new_def);
}
}
None => roc_error_macros::internal_error!("def not available {:?}", symbol),
}
}
@ -812,6 +838,14 @@ fn pattern_to_vars_by_symbol(
}
}
UnwrappedOpaque {
argument, opaque, ..
} => {
let (var, nested) = &**argument;
pattern_to_vars_by_symbol(vars_by_symbol, &nested.value, *var);
vars_by_symbol.insert(*opaque, expr_var);
}
RecordDestructure { destructs, .. } => {
for destruct in destructs {
vars_by_symbol.insert(destruct.value.symbol, destruct.value.var);
@ -822,9 +856,37 @@ fn pattern_to_vars_by_symbol(
| IntLiteral(..)
| FloatLiteral(..)
| StrLiteral(_)
| SingleQuote(_)
| Underscore
| MalformedPattern(_, _)
| UnsupportedPattern(_) => {}
| UnsupportedPattern(_)
| OpaqueNotInScope(..) => {}
}
}
fn single_can_def(
loc_can_pattern: Loc<Pattern>,
loc_can_expr: Loc<Expr>,
expr_var: Variable,
opt_loc_annotation: Option<Loc<crate::annotation::Annotation>>,
pattern_vars: SendMap<Symbol, Variable>,
) -> Def {
let def_annotation = opt_loc_annotation.map(|loc_annotation| Annotation {
signature: loc_annotation.value.typ,
introduced_variables: loc_annotation.value.introduced_variables,
aliases: loc_annotation.value.aliases,
region: loc_annotation.region,
});
Def {
expr_var,
loc_pattern: loc_can_pattern,
loc_expr: Loc {
region: loc_can_expr.region,
value: loc_can_expr.value,
},
pattern_vars,
annotation: def_annotation,
}
}
@ -851,25 +913,25 @@ fn canonicalize_pending_def<'a>(
AnnotationOnly(_, loc_can_pattern, loc_ann) => {
// annotation sans body cannot introduce new rigids that are visible in other annotations
// but the rigids can show up in type error messages, so still register them
let ann =
let type_annotation =
canonicalize_annotation(env, scope, &loc_ann.value, loc_ann.region, var_store);
// Record all the annotation's references in output.references.lookups
for symbol in ann.references {
output.references.lookups.insert(symbol);
output.references.referenced_aliases.insert(symbol);
for symbol in type_annotation.references.iter() {
output.references.lookups.insert(*symbol);
output.references.referenced_type_defs.insert(*symbol);
}
aliases.extend(ann.aliases.clone());
aliases.extend(type_annotation.aliases.clone());
output.introduced_variables.union(&ann.introduced_variables);
output
.introduced_variables
.union(&type_annotation.introduced_variables);
pattern_to_vars_by_symbol(&mut vars_by_symbol, &loc_can_pattern.value, expr_var);
let typ = ann.typ;
let arity = typ.arity();
let arity = type_annotation.typ.arity();
let problem = match &loc_can_pattern.value {
Pattern::Identifier(symbol) => RuntimeError::NoImplementationNamed {
@ -927,6 +989,16 @@ fn canonicalize_pending_def<'a>(
}
};
if let Pattern::Identifier(symbol) = loc_can_pattern.value {
let def = single_can_def(
loc_can_pattern,
loc_can_expr,
expr_var,
Some(Loc::at(loc_ann.region, type_annotation)),
vars_by_symbol.clone(),
);
can_defs_by_symbol.insert(symbol, def);
} else {
for (_, (symbol, _)) in scope.idents() {
if !vars_by_symbol.contains_key(symbol) {
continue;
@ -947,37 +1019,39 @@ fn canonicalize_pending_def<'a>(
},
pattern_vars: vars_by_symbol.clone(),
annotation: Some(Annotation {
signature: typ.clone(),
signature: type_annotation.typ.clone(),
introduced_variables: output.introduced_variables.clone(),
aliases: ann.aliases.clone(),
aliases: type_annotation.aliases.clone(),
region: loc_ann.region,
}),
},
);
}
}
}
Alias { .. } => unreachable!("Aliases are handled in a separate pass"),
InvalidAlias => {
// invalid aliases (shadowed, incorrect patterns) get ignored
InvalidAlias { .. } => {
// invalid aliases and opaques (shadowed, incorrect patterns) get ignored
}
TypedBody(loc_pattern, loc_can_pattern, loc_ann, loc_expr) => {
let ann =
TypedBody(_loc_pattern, loc_can_pattern, loc_ann, loc_expr) => {
let type_annotation =
canonicalize_annotation(env, scope, &loc_ann.value, loc_ann.region, var_store);
// Record all the annotation's references in output.references.lookups
for symbol in ann.references {
output.references.lookups.insert(symbol);
output.references.referenced_aliases.insert(symbol);
for symbol in type_annotation.references.iter() {
output.references.lookups.insert(*symbol);
output.references.referenced_type_defs.insert(*symbol);
}
let typ = ann.typ;
for (symbol, alias) in ann.aliases.clone() {
for (symbol, alias) in type_annotation.aliases.clone() {
aliases.insert(symbol, alias);
}
output.introduced_variables.union(&ann.introduced_variables);
output
.introduced_variables
.union(&type_annotation.introduced_variables);
// bookkeeping for tail-call detection. If we're assigning to an
// identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called.
@ -1004,42 +1078,31 @@ fn canonicalize_pending_def<'a>(
// reset the tailcallable_symbol
env.tailcallable_symbol = outer_identifier;
// see below: a closure needs a fresh References!
let mut is_closure = false;
// First, make sure we are actually assigning an identifier instead of (for example) a tag.
//
// If we're assigning (UserId userId) = ... then this is certainly not a closure declaration,
// which also implies it's not a self tail call!
//
// Only defs of the form (foo = ...) can be closure declarations or self tail calls.
if let (
&ast::Pattern::Identifier(_name),
&Pattern::Identifier(ref defined_symbol),
&Closure(ClosureData {
if let Pattern::Identifier(symbol) = loc_can_pattern.value {
if let Closure(ClosureData {
function_type,
closure_type,
closure_ext_var,
return_type,
name: ref symbol,
name: ref closure_name,
ref arguments,
loc_body: ref body,
ref captured_symbols,
..
}),
) = (
&loc_pattern.value,
&loc_can_pattern.value,
&loc_can_expr.value,
) {
is_closure = true;
}) = loc_can_expr.value
{
// Since everywhere in the code it'll be referred to by its defined name,
// remove its generated name from the closure map. (We'll re-insert it later.)
let references = env.closures.remove(symbol).unwrap_or_else(|| {
let references = env.closures.remove(closure_name).unwrap_or_else(|| {
panic!(
"Tried to remove symbol {:?} from procedures, but it was not found: {:?}",
symbol, env.closures
closure_name, env.closures
)
});
@ -1047,52 +1110,59 @@ fn canonicalize_pending_def<'a>(
// closures don't have a name, and therefore pick a fresh symbol. But in this
// case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...`
// and we want to reference it by that name.
env.closures.insert(*defined_symbol, references);
env.closures.insert(symbol, references);
// The closure is self tail recursive iff it tail calls itself (by defined name).
let is_recursive = match can_output.tail_call {
Some(ref symbol) if symbol == defined_symbol => Recursive::TailRecursive,
Some(tail_symbol) if tail_symbol == symbol => Recursive::TailRecursive,
_ => Recursive::NotRecursive,
};
// Recursion doesn't count as referencing. (If it did, all recursive functions
// would result in circular def errors!)
refs_by_symbol
.entry(*defined_symbol)
.and_modify(|(_, refs)| {
refs.lookups = refs.lookups.without(defined_symbol);
refs_by_symbol.entry(symbol).and_modify(|(_, refs)| {
refs.lookups = refs.lookups.without(&symbol);
});
// renamed_closure_def = Some(&defined_symbol);
// renamed_closure_def = Some(&symbol);
loc_can_expr.value = Closure(ClosureData {
function_type,
closure_type,
closure_ext_var,
return_type,
name: *defined_symbol,
name: symbol,
captured_symbols: captured_symbols.clone(),
recursive: is_recursive,
arguments: arguments.clone(),
loc_body: body.clone(),
});
// Functions' references don't count in defs.
// See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its
// parent commit for the bug this fixed!
let refs = References::new();
refs_by_symbol.insert(symbol, (loc_can_pattern.region, refs));
} else {
let refs = can_output.references;
refs_by_symbol.insert(symbol, (loc_ann.region, refs));
}
// Store the referenced locals in the refs_by_symbol map, so we can later figure out
// which defined names reference each other.
let def = single_can_def(
loc_can_pattern,
loc_can_expr,
expr_var,
Some(Loc::at(loc_ann.region, type_annotation)),
vars_by_symbol.clone(),
);
can_defs_by_symbol.insert(symbol, def);
} else {
for (_, (symbol, region)) in scope.idents() {
if !vars_by_symbol.contains_key(symbol) {
continue;
}
let refs =
// Functions' references don't count in defs.
// See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its
// parent commit for the bug this fixed!
if is_closure {
References::new()
} else {
can_output.references.clone()
};
let refs = can_output.references.clone();
refs_by_symbol.insert(*symbol, (*region, refs));
@ -1109,15 +1179,16 @@ fn canonicalize_pending_def<'a>(
},
pattern_vars: vars_by_symbol.clone(),
annotation: Some(Annotation {
signature: typ.clone(),
introduced_variables: output.introduced_variables.clone(),
aliases: ann.aliases.clone(),
signature: type_annotation.typ.clone(),
introduced_variables: type_annotation.introduced_variables.clone(),
aliases: type_annotation.aliases.clone(),
region: loc_ann.region,
}),
},
);
}
}
}
// If we have a pattern, then the def has a body (that is, it's not a
// standalone annotation), so we need to canonicalize the pattern and expr.
Body(loc_pattern, loc_can_pattern, loc_expr) => {
@ -1147,42 +1218,31 @@ fn canonicalize_pending_def<'a>(
// reset the tailcallable_symbol
env.tailcallable_symbol = outer_identifier;
// see below: a closure needs a fresh References!
let mut is_closure = false;
// First, make sure we are actually assigning an identifier instead of (for example) a tag.
//
// If we're assigning (UserId userId) = ... then this is certainly not a closure declaration,
// which also implies it's not a self tail call!
//
// Only defs of the form (foo = ...) can be closure declarations or self tail calls.
if let (
&ast::Pattern::Identifier(_name),
&Pattern::Identifier(ref defined_symbol),
&Closure(ClosureData {
if let Pattern::Identifier(symbol) = loc_can_pattern.value {
if let Closure(ClosureData {
function_type,
closure_type,
closure_ext_var,
return_type,
name: ref symbol,
name: ref closure_name,
ref arguments,
loc_body: ref body,
ref captured_symbols,
..
}),
) = (
&loc_pattern.value,
&loc_can_pattern.value,
&loc_can_expr.value,
) {
is_closure = true;
}) = loc_can_expr.value
{
// Since everywhere in the code it'll be referred to by its defined name,
// remove its generated name from the closure map. (We'll re-insert it later.)
let references = env.closures.remove(symbol).unwrap_or_else(|| {
let references = env.closures.remove(closure_name).unwrap_or_else(|| {
panic!(
"Tried to remove symbol {:?} from procedures, but it was not found: {:?}",
symbol, env.closures
closure_name, env.closures
)
});
@ -1190,20 +1250,18 @@ fn canonicalize_pending_def<'a>(
// closures don't have a name, and therefore pick a fresh symbol. But in this
// case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...`
// and we want to reference it by that name.
env.closures.insert(*defined_symbol, references);
env.closures.insert(symbol, references);
// The closure is self tail recursive iff it tail calls itself (by defined name).
let is_recursive = match can_output.tail_call {
Some(ref symbol) if symbol == defined_symbol => Recursive::TailRecursive,
Some(tail_symbol) if tail_symbol == symbol => Recursive::TailRecursive,
_ => Recursive::NotRecursive,
};
// Recursion doesn't count as referencing. (If it did, all recursive functions
// would result in circular def errors!)
refs_by_symbol
.entry(*defined_symbol)
.and_modify(|(_, refs)| {
refs.lookups = refs.lookups.without(defined_symbol);
refs_by_symbol.entry(symbol).and_modify(|(_, refs)| {
refs.lookups = refs.lookups.without(&symbol);
});
loc_can_expr.value = Closure(ClosureData {
@ -1211,27 +1269,36 @@ fn canonicalize_pending_def<'a>(
closure_type,
closure_ext_var,
return_type,
name: *defined_symbol,
name: symbol,
captured_symbols: captured_symbols.clone(),
recursive: is_recursive,
arguments: arguments.clone(),
loc_body: body.clone(),
});
}
// Store the referenced locals in the refs_by_symbol map, so we can later figure out
// which defined names reference each other.
for (symbol, region) in bindings_from_patterns(std::iter::once(&loc_can_pattern)) {
let refs =
// Functions' references don't count in defs.
// See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its
// parent commit for the bug this fixed!
if is_closure {
References::new()
let refs = References::new();
refs_by_symbol.insert(symbol, (loc_pattern.region, refs));
} else {
can_output.references.clone()
};
let refs = can_output.references.clone();
refs_by_symbol.insert(symbol, (loc_pattern.region, refs));
}
let def = single_can_def(
loc_can_pattern,
loc_can_expr,
expr_var,
None,
vars_by_symbol.clone(),
);
can_defs_by_symbol.insert(symbol, def);
} else {
// Store the referenced locals in the refs_by_symbol map, so we can later figure out
// which defined names reference each other.
for (symbol, region) in bindings_from_patterns(std::iter::once(&loc_can_pattern)) {
let refs = can_output.references.clone();
refs_by_symbol.insert(symbol, (region, refs));
can_defs_by_symbol.insert(
@ -1250,6 +1317,7 @@ fn canonicalize_pending_def<'a>(
},
);
}
}
output.union(can_output);
}
@ -1442,10 +1510,19 @@ fn to_pending_def<'a>(
}
Alias {
header: AliasHeader { name, vars },
header: TypeHeader { name, vars },
ann,
..
}
| Opaque {
header: TypeHeader { name, vars },
typ: ann,
} => {
let kind = if matches!(def, Alias { .. }) {
AliasKind::Structural
} else {
AliasKind::Opaque
};
let region = Region::span_across(&name.region, &ann.region);
match scope.introduce(
@ -1470,27 +1547,33 @@ fn to_pending_def<'a>(
}
_ => {
// any other pattern in this position is a syntax error.
env.problems.push(Problem::InvalidAliasRigid {
let problem = Problem::InvalidAliasRigid {
alias_name: symbol,
region: loc_var.region,
});
};
env.problems.push(problem);
return Some((Output::default(), PendingDef::InvalidAlias));
}
}
}
Some((
return Some((
Output::default(),
PendingDef::Alias {
name: Loc {
PendingDef::InvalidAlias { kind },
));
}
}
}
let name = Loc {
region: name.region,
value: symbol,
},
};
let pending_def = PendingDef::Alias {
name,
vars: can_rigids,
ann,
},
))
kind,
};
Some((Output::default(), pending_def))
}
Err((original_region, loc_shadowed_symbol, _new_symbol)) => {
@ -1499,7 +1582,7 @@ fn to_pending_def<'a>(
shadow: loc_shadowed_symbol,
});
Some((Output::default(), PendingDef::InvalidAlias))
Some((Output::default(), PendingDef::InvalidAlias { kind }))
}
}
}

View file

@ -10,7 +10,7 @@ use roc_module::ident::TagName;
use roc_module::symbol::Symbol;
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::Type;
use roc_types::types::{AliasKind, Type};
#[derive(Default, Clone, Copy)]
pub(crate) struct HostedGeneratedFunctions {
@ -1140,6 +1140,7 @@ fn build_effect_loop(
closure_var,
))],
actual: Box::new(actual),
kind: AliasKind::Structural,
}
};
@ -1579,6 +1580,7 @@ fn build_effect_alias(
type_arguments: vec![(a_name.into(), Type::Variable(a_var))],
lambda_set_variables: vec![roc_types::types::LambdaSet(Type::Variable(closure_var))],
actual: Box::new(actual),
kind: AliasKind::Structural,
}
}

View file

@ -124,12 +124,17 @@ impl<'a> Env<'a> {
})
}
},
None => {
panic!(
"Module {} exists, but is not recorded in dep_idents",
module_name
)
}
None => Err(RuntimeError::ModuleNotImported {
module_name,
imported_modules: self
.dep_idents
.keys()
.filter_map(|module_id| self.module_ids.get_name(*module_id))
.map(|module_name| module_name.as_ref().into())
.collect(),
region,
module_exists: true,
}),
}
}
}
@ -141,6 +146,7 @@ impl<'a> Env<'a> {
.map(|string| string.as_ref().into())
.collect(),
region,
module_exists: false,
}),
}
}

View file

@ -1,4 +1,4 @@
use crate::annotation::IntroducedVariables;
use crate::annotation::{freshen_opaque_def, IntroducedVariables};
use crate::builtins::builtin_defs_map;
use crate::def::{can_defs_with_return, Def};
use crate::env::Env;
@ -19,7 +19,7 @@ use roc_parse::pattern::PatternType::*;
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 roc_types::types::{Alias, LambdaSet, Type};
use std::fmt::{Debug, Display};
use std::{char, u32};
@ -73,6 +73,7 @@ pub enum Expr {
Int(Variable, Variable, Box<str>, IntValue, IntBound),
Float(Variable, Variable, Box<str>, f64, FloatBound),
Str(Box<str>),
SingleQuote(char),
List {
elem_var: Variable,
loc_elems: Vec<Loc<Expr>>,
@ -137,17 +138,7 @@ pub enum Expr {
field: Lowercase,
},
/// field accessor as a function, e.g. (.foo) expr
Accessor {
/// accessors are desugared to closures; they need to have a name
/// so the closure can have a correct lambda set
name: Symbol,
function_var: Variable,
record_var: Variable,
closure_ext_var: Variable,
ext_var: Variable,
field_var: Variable,
field: Lowercase,
},
Accessor(AccessorData),
Update {
record_var: Variable,
@ -172,6 +163,31 @@ pub enum Expr {
arguments: Vec<(Variable, Loc<Expr>)>,
},
/// A wrapping of an opaque type, like `$Age 21`
// TODO(opaques): $->@ above when opaques land
OpaqueRef {
opaque_var: Variable,
name: Symbol,
argument: Box<(Variable, Loc<Expr>)>,
// The following help us link this opaque reference to the type specified by its
// definition, which we then use during constraint generation. For example
// suppose we have
//
// Id n := [ Id U64 n ]
// @Id "sasha"
//
// Then `opaque` is "Id", `argument` is "sasha", but this is not enough for us to
// infer the type of the expression as "Id Str" - we need to link the specialized type of
// the variable "n".
// That's what `specialized_def_type` and `type_arguments` are for; they are specialized
// for the expression from the opaque definition. `type_arguments` is something like
// [(n, fresh1)], and `specialized_def_type` becomes "[ Id U64 fresh1 ]".
specialized_def_type: Box<Type>,
type_arguments: Vec<(Lowercase, Type)>,
lambda_set_variables: Vec<LambdaSet>,
},
// Test
Expect(Box<Loc<Expr>>, Box<Loc<Expr>>),
@ -191,6 +207,70 @@ pub struct ClosureData {
pub loc_body: Box<Loc<Expr>>,
}
/// A record accessor like `.foo`, which is equivalent to `\r -> r.foo`
/// Accessors are desugared to closures; they need to have a name
/// so the closure can have a correct lambda set.
///
/// We distinguish them from closures so we can have better error messages
/// during constraint generation.
#[derive(Clone, Debug, PartialEq)]
pub struct AccessorData {
pub name: Symbol,
pub function_var: Variable,
pub record_var: Variable,
pub closure_var: Variable,
pub closure_ext_var: Variable,
pub ext_var: Variable,
pub field_var: Variable,
pub field: Lowercase,
}
impl AccessorData {
pub fn to_closure_data(self, record_symbol: Symbol) -> ClosureData {
let AccessorData {
name,
function_var,
record_var,
closure_var,
closure_ext_var,
ext_var,
field_var,
field,
} = self;
// IDEA: convert accessor from
//
// .foo
//
// into
//
// (\r -> r.foo)
let body = Expr::Access {
record_var,
ext_var,
field_var,
loc_expr: Box::new(Loc::at_zero(Expr::Var(record_symbol))),
field,
};
let loc_body = Loc::at_zero(body);
let arguments = vec![(record_var, Loc::at_zero(Pattern::Identifier(record_symbol)))];
ClosureData {
function_type: function_var,
closure_type: closure_var,
closure_ext_var,
return_type: field_var,
name,
captured_symbols: vec![],
recursive: Recursive::NotRecursive,
arguments,
loc_body: Box::new(loc_body),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Field {
pub var: Variable,
@ -318,6 +398,28 @@ pub fn canonicalize_expr<'a>(
}
}
ast::Expr::Str(literal) => flatten_str_literal(env, var_store, scope, literal),
ast::Expr::SingleQuote(string) => {
let mut it = string.chars().peekable();
if let Some(char) = it.next() {
if it.peek().is_none() {
(Expr::SingleQuote(char), Output::default())
} else {
// multiple chars is found
let error = roc_problem::can::RuntimeError::MultipleCharsInSingleQuote(region);
let answer = Expr::RuntimeError(error);
(answer, Output::default())
}
} else {
// no characters found
let error = roc_problem::can::RuntimeError::EmptySingleQuote(region);
let answer = Expr::RuntimeError(error);
(answer, Output::default())
}
}
ast::Expr::List(loc_elems) => {
if loc_elems.is_empty() {
(
@ -360,29 +462,65 @@ pub fn canonicalize_expr<'a>(
// (foo) bar baz
let fn_region = loc_fn.region;
// Canonicalize the function expression and its arguments
let (fn_expr, mut output) =
canonicalize_expr(env, var_store, scope, fn_region, &loc_fn.value);
// The function's return type
let mut args = Vec::new();
let mut outputs = Vec::new();
let mut output = Output::default();
for loc_arg in loc_args.iter() {
let (arg_expr, arg_out) =
canonicalize_expr(env, var_store, scope, loc_arg.region, &loc_arg.value);
args.push((var_store.fresh(), arg_expr));
outputs.push(arg_out);
output.references = output.references.union(arg_out.references);
}
if let ast::Expr::OpaqueRef(name) = loc_fn.value {
// We treat opaques specially, since an opaque can wrap exactly one argument.
debug_assert!(!args.is_empty());
if args.len() > 1 {
let problem =
roc_problem::can::RuntimeError::OpaqueAppliedToMultipleArgs(region);
env.problem(Problem::RuntimeError(problem.clone()));
(RuntimeError(problem), output)
} else {
match scope.lookup_opaque_ref(name, loc_fn.region) {
Err(runtime_error) => {
env.problem(Problem::RuntimeError(runtime_error.clone()));
(RuntimeError(runtime_error), output)
}
Ok((name, opaque_def)) => {
let argument = Box::new(args.pop().unwrap());
output.references.referenced_type_defs.insert(name);
output.references.lookups.insert(name);
let (type_arguments, lambda_set_variables, specialized_def_type) =
freshen_opaque_def(var_store, opaque_def);
let opaque_ref = OpaqueRef {
opaque_var: var_store.fresh(),
name,
argument,
specialized_def_type: Box::new(specialized_def_type),
type_arguments,
lambda_set_variables,
};
(opaque_ref, output)
}
}
}
} else {
// Canonicalize the function expression and its arguments
let (fn_expr, fn_expr_output) =
canonicalize_expr(env, var_store, scope, fn_region, &loc_fn.value);
output.union(fn_expr_output);
// Default: We're not tail-calling a symbol (by name), we're tail-calling a function value.
output.tail_call = None;
for arg_out in outputs {
output.references = output.references.union(arg_out.references);
}
let expr = match fn_expr.value {
Var(symbol) => {
output.references.calls.insert(symbol);
@ -447,6 +585,7 @@ pub fn canonicalize_expr<'a>(
(expr, output)
}
}
ast::Expr::Var { module_name, ident } => {
canonicalize_lookup(env, scope, module_name, ident, region)
}
@ -545,7 +684,7 @@ pub fn canonicalize_expr<'a>(
output.union(new_output);
// filter out aliases
captured_symbols.retain(|s| !output.references.referenced_aliases.contains(s));
captured_symbols.retain(|s| !output.references.referenced_type_defs.contains(s));
// filter out functions that don't close over anything
captured_symbols.retain(|s| !output.non_closures.contains(s));
@ -650,15 +789,16 @@ pub fn canonicalize_expr<'a>(
)
}
ast::Expr::AccessorFunction(field) => (
Accessor {
Accessor(AccessorData {
name: env.gen_unique_symbol(),
function_var: var_store.fresh(),
record_var: var_store.fresh(),
ext_var: var_store.fresh(),
closure_var: var_store.fresh(),
closure_ext_var: var_store.fresh(),
field_var: var_store.fresh(),
field: (*field).into(),
},
}),
Output::default(),
),
ast::Expr::GlobalTag(tag) => {
@ -696,6 +836,16 @@ pub fn canonicalize_expr<'a>(
Output::default(),
)
}
ast::Expr::OpaqueRef(opaque_ref) => {
// If we're here, the opaque reference is definitely not wrapping an argument - wrapped
// arguments are handled in the Apply branch.
let problem = roc_problem::can::RuntimeError::OpaqueNotApplied(Loc::at(
region,
(*opaque_ref).into(),
));
env.problem(Problem::RuntimeError(problem.clone()));
(RuntimeError(problem), Output::default())
}
ast::Expr::Expect(condition, continuation) => {
let mut output = Output::default();
@ -1245,6 +1395,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
| other @ Int(..)
| other @ Float(..)
| other @ Str { .. }
| other @ SingleQuote(_)
| other @ RuntimeError(_)
| other @ EmptyRecord
| other @ Accessor { .. }
@ -1473,6 +1624,30 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
);
}
OpaqueRef {
opaque_var,
name,
argument,
specialized_def_type,
type_arguments,
lambda_set_variables,
} => {
let (var, loc_expr) = *argument;
let argument = Box::new((
var,
loc_expr.map_owned(|expr| inline_calls(var_store, scope, expr)),
));
OpaqueRef {
opaque_var,
name,
argument,
specialized_def_type,
type_arguments,
lambda_set_variables,
}
}
ZeroArgumentTag {
closure_name,
variant_var,

View file

@ -16,7 +16,7 @@ use roc_parse::pattern::PatternType;
use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::{Alias, Type};
use roc_types::types::{Alias, AliasKind, Type};
#[derive(Debug)]
pub struct Module {
@ -86,7 +86,13 @@ pub fn canonicalize_module_defs<'a>(
let num_deps = dep_idents.len();
for (name, alias) in aliases.into_iter() {
scope.add_alias(name, alias.region, alias.type_variables, alias.typ);
scope.add_alias(
name,
alias.region,
alias.type_variables,
alias.typ,
alias.kind,
);
}
struct Hosted {
@ -131,6 +137,7 @@ pub fn canonicalize_module_defs<'a>(
Region::zero(),
vec![Loc::at_zero(("a".into(), a_var))],
actual,
AliasKind::Structural,
);
}
@ -541,6 +548,10 @@ fn fix_values_captured_in_closure_pattern(
fix_values_captured_in_closure_pattern(&mut loc_arg.value, no_capture_symbols);
}
}
UnwrappedOpaque { argument, .. } => {
let (_, loc_arg) = &mut **argument;
fix_values_captured_in_closure_pattern(&mut loc_arg.value, no_capture_symbols);
}
RecordDestructure { destructs, .. } => {
for loc_destruct in destructs.iter_mut() {
use crate::pattern::DestructType::*;
@ -561,10 +572,12 @@ fn fix_values_captured_in_closure_pattern(
| IntLiteral(..)
| FloatLiteral(..)
| StrLiteral(_)
| SingleQuote(_)
| Underscore
| Shadowed(..)
| MalformedPattern(_, _)
| UnsupportedPattern(_) => (),
| UnsupportedPattern(_)
| OpaqueNotInScope(..) => (),
}
}
@ -617,6 +630,7 @@ fn fix_values_captured_in_closure_expr(
| Int(..)
| Float(..)
| Str(_)
| SingleQuote(_)
| Var(_)
| EmptyRecord
| RuntimeError(_)
@ -691,5 +705,9 @@ fn fix_values_captured_in_closure_expr(
fix_values_captured_in_closure_expr(&mut loc_arg.value, no_capture_symbols);
}
}
OpaqueRef { argument, .. } => {
let (_, loc_arg) = &mut **argument;
fix_values_captured_in_closure_expr(&mut loc_arg.value, no_capture_symbols);
}
}
}

View file

@ -95,6 +95,7 @@ pub fn desugar_def<'a>(arena: &'a Bump, def: &'a Def<'a>) -> Def<'a> {
Body(loc_pattern, loc_expr) => Body(loc_pattern, desugar_expr(arena, loc_expr)),
SpaceBefore(def, _) | SpaceAfter(def, _) => desugar_def(arena, def),
alias @ Alias { .. } => *alias,
opaque @ Opaque { .. } => *opaque,
ann @ Annotation(_, _) => *ann,
AnnotatedBody {
ann_pattern,
@ -125,6 +126,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
| Num(..)
| NonBase10Int { .. }
| Str(_)
| SingleQuote(_)
| AccessorFunction(_)
| Var { .. }
| Underscore { .. }
@ -132,7 +134,8 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
| MalformedClosure
| PrecedenceConflict { .. }
| GlobalTag(_)
| PrivateTag(_) => loc_expr,
| PrivateTag(_)
| OpaqueRef(_) => loc_expr,
Access(sub_expr, paths) => {
let region = loc_expr.region;
@ -170,7 +173,10 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
}),
RecordUpdate { fields, update } => {
// NOTE the `update` field is always a `Var { .. }` and does not need to be desugared
// NOTE the `update` field is always a `Var { .. }`, we only desugar it to get rid of
// any spaces before/after
let new_update = desugar_expr(arena, update);
let new_fields = fields.map_items(arena, |field| {
let value = desugar_field(arena, &field.value);
Loc {
@ -182,7 +188,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
arena.alloc(Loc {
region: loc_expr.region,
value: RecordUpdate {
update: *update,
update: new_update,
fields: new_fields,
},
})
@ -415,7 +421,8 @@ fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) {
Or => (ModuleName::BOOL, "or"),
Pizza => unreachable!("Cannot desugar the |> operator"),
Assignment => unreachable!("Cannot desugar the = operator"),
HasType => unreachable!("Cannot desugar the : operator"),
IsAliasType => unreachable!("Cannot desugar the : operator"),
IsOpaqueType => unreachable!("Cannot desugar the := operator"),
Backpassing => unreachable!("Cannot desugar the <- operator"),
}
}

View file

@ -1,3 +1,4 @@
use crate::annotation::freshen_opaque_def;
use crate::env::Env;
use crate::expr::{canonicalize_expr, unescape_char, Expr, IntValue, Output};
use crate::num::{
@ -12,6 +13,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};
use roc_types::types::{LambdaSet, Type};
/// A pattern, including possible problems (e.g. shadowing) so that
/// codegen can generate a runtime error if this pattern is reached.
@ -24,6 +26,30 @@ pub enum Pattern {
tag_name: TagName,
arguments: Vec<(Variable, Loc<Pattern>)>,
},
UnwrappedOpaque {
whole_var: Variable,
opaque: Symbol,
argument: Box<(Variable, Loc<Pattern>)>,
// The following help us link this opaque reference to the type specified by its
// definition, which we then use during constraint generation. For example
// suppose we have
//
// Id n := [ Id U64 n ]
// strToBool : Str -> Bool
//
// f = \@Id who -> strToBool who
//
// Then `opaque` is "Id", `argument` is "who", but this is not enough for us to
// infer the type of the expression as "Id Str" - we need to link the specialized type of
// the variable "n".
// That's what `specialized_def_type` and `type_arguments` are for; they are specialized
// for the expression from the opaque definition. `type_arguments` is something like
// [(n, fresh1)], and `specialized_def_type` becomes "[ Id U64 fresh1 ]".
specialized_def_type: Box<Type>,
type_arguments: Vec<(Lowercase, Type)>,
lambda_set_variables: Vec<LambdaSet>,
},
RecordDestructure {
whole_var: Variable,
ext_var: Variable,
@ -33,10 +59,12 @@ pub enum Pattern {
IntLiteral(Variable, Variable, Box<str>, IntValue, IntBound),
FloatLiteral(Variable, Variable, Box<str>, f64, FloatBound),
StrLiteral(Box<str>),
SingleQuote(char),
Underscore,
// Runtime Exceptions
Shadowed(Region, Loc<Ident>, Symbol),
OpaqueNotInScope(Loc<Ident>),
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
UnsupportedPattern(Region),
// parse error patterns
@ -78,6 +106,13 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
symbols_from_pattern_help(&nested.value, symbols);
}
}
UnwrappedOpaque {
opaque, argument, ..
} => {
symbols.push(*opaque);
let (_, nested) = &**argument;
symbols_from_pattern_help(&nested.value, symbols);
}
RecordDestructure { destructs, .. } => {
for destruct in destructs {
// when a record field has a pattern guard, only symbols in the guard are introduced
@ -93,9 +128,11 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
| IntLiteral(..)
| FloatLiteral(..)
| StrLiteral(_)
| SingleQuote(_)
| Underscore
| MalformedPattern(_, _)
| UnsupportedPattern(_) => {}
| UnsupportedPattern(_)
| OpaqueNotInScope(..) => {}
}
}
@ -153,17 +190,15 @@ pub fn canonicalize_pattern<'a>(
arguments: vec![],
}
}
Apply(tag, patterns) => {
let tag_name = match tag.value {
GlobalTag(name) => TagName::Global(name.into()),
PrivateTag(name) => {
let ident_id = env.ident_ids.get_or_insert(&name.into());
TagName::Private(Symbol::new(env.home, ident_id))
OpaqueRef(name) => {
// If this opaque ref had an argument, we would be in the "Apply" branch.
let loc_name = Loc::at(region, (*name).into());
env.problem(Problem::RuntimeError(RuntimeError::OpaqueNotApplied(
loc_name,
)));
Pattern::UnsupportedPattern(region)
}
_ => unreachable!("Other patterns cannot be applied"),
};
Apply(tag, patterns) => {
let mut can_patterns = Vec::with_capacity(patterns.len());
for loc_pattern in *patterns {
let (new_output, can_pattern) = canonicalize_pattern(
@ -180,6 +215,9 @@ pub fn canonicalize_pattern<'a>(
can_patterns.push((var_store.fresh(), can_pattern));
}
match tag.value {
GlobalTag(name) => {
let tag_name = TagName::Global(name.into());
Pattern::AppliedTag {
whole_var: var_store.fresh(),
ext_var: var_store.fresh(),
@ -187,6 +225,56 @@ pub fn canonicalize_pattern<'a>(
arguments: can_patterns,
}
}
PrivateTag(name) => {
let ident_id = env.ident_ids.get_or_insert(&name.into());
let tag_name = TagName::Private(Symbol::new(env.home, ident_id));
Pattern::AppliedTag {
whole_var: var_store.fresh(),
ext_var: var_store.fresh(),
tag_name,
arguments: can_patterns,
}
}
OpaqueRef(name) => match scope.lookup_opaque_ref(name, tag.region) {
Ok((opaque, opaque_def)) => {
debug_assert!(!can_patterns.is_empty());
if can_patterns.len() > 1 {
env.problem(Problem::RuntimeError(
RuntimeError::OpaqueAppliedToMultipleArgs(region),
));
Pattern::UnsupportedPattern(region)
} else {
let argument = Box::new(can_patterns.pop().unwrap());
let (type_arguments, lambda_set_variables, specialized_def_type) =
freshen_opaque_def(var_store, opaque_def);
output.references.referenced_type_defs.insert(opaque);
output.references.lookups.insert(opaque);
Pattern::UnwrappedOpaque {
whole_var: var_store.fresh(),
opaque,
argument,
specialized_def_type: Box::new(specialized_def_type),
type_arguments,
lambda_set_variables,
}
}
}
Err(runtime_error) => {
env.problem(Problem::RuntimeError(runtime_error));
Pattern::OpaqueNotInScope(Loc::at(tag.region, name.into()))
}
},
_ => unreachable!("Other patterns cannot be applied"),
}
}
&FloatLiteral(str) => match pattern_type {
WhenBranch => match finish_parsing_float(str) {
@ -272,6 +360,23 @@ pub fn canonicalize_pattern<'a>(
ptype => unsupported_pattern(env, ptype, region),
},
SingleQuote(string) => {
let mut it = string.chars().peekable();
if let Some(char) = it.next() {
if it.peek().is_none() {
Pattern::SingleQuote(char)
} else {
// multiple chars is found
let problem = MalformedPatternProblem::MultipleCharsInSingleQuote;
malformed_pattern(env, problem, region)
}
} else {
// no characters found
let problem = MalformedPatternProblem::EmptySingleQuote;
malformed_pattern(env, problem, region)
}
}
SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) => {
return canonicalize_pattern(env, var_store, scope, pattern_type, sub_pattern, region)
}
@ -500,6 +605,13 @@ fn add_bindings_from_patterns(
add_bindings_from_patterns(&loc_arg.region, &loc_arg.value, answer);
}
}
UnwrappedOpaque {
argument, opaque, ..
} => {
let (_, loc_arg) = &**argument;
add_bindings_from_patterns(&loc_arg.region, &loc_arg.value, answer);
answer.push((*opaque, *region));
}
RecordDestructure { destructs, .. } => {
for Loc {
region,
@ -513,9 +625,11 @@ fn add_bindings_from_patterns(
| IntLiteral(..)
| FloatLiteral(..)
| StrLiteral(_)
| SingleQuote(_)
| Underscore
| MalformedPattern(_, _)
| UnsupportedPattern(_) => (),
| UnsupportedPattern(_)
| OpaqueNotInScope(..) => (),
}
}

View file

@ -46,7 +46,8 @@ impl Procedure {
pub struct References {
pub bound_symbols: ImSet<Symbol>,
pub lookups: ImSet<Symbol>,
pub referenced_aliases: ImSet<Symbol>,
/// Aliases or opaque types referenced
pub referenced_type_defs: ImSet<Symbol>,
pub calls: ImSet<Symbol>,
}
@ -59,7 +60,7 @@ impl References {
self.lookups = self.lookups.union(other.lookups);
self.calls = self.calls.union(other.calls);
self.bound_symbols = self.bound_symbols.union(other.bound_symbols);
self.referenced_aliases = self.referenced_aliases.union(other.referenced_aliases);
self.referenced_type_defs = self.referenced_type_defs.union(other.referenced_type_defs);
self
}
@ -68,7 +69,7 @@ impl References {
self.lookups.extend(other.lookups);
self.calls.extend(other.calls);
self.bound_symbols.extend(other.bound_symbols);
self.referenced_aliases.extend(other.referenced_aliases);
self.referenced_type_defs.extend(other.referenced_type_defs);
}
pub fn has_lookup(&self, symbol: Symbol) -> bool {

View file

@ -4,7 +4,7 @@ use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_problem::can::RuntimeError;
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::{Alias, Type};
use roc_types::types::{Alias, AliasKind, Type};
#[derive(Clone, Debug, PartialEq)]
pub struct Scope {
@ -50,6 +50,8 @@ impl Scope {
lambda_set_variables: Vec::new(),
recursion_variables: MutSet::default(),
type_variables: variables,
// TODO(opaques): replace when opaques are included in the stdlib
kind: AliasKind::Structural,
};
aliases.insert(symbol, alias);
@ -100,6 +102,76 @@ impl Scope {
self.aliases.get(&symbol)
}
/// Check if there is an opaque type alias referenced by `opaque_ref` referenced in the
/// current scope. E.g. `$Age` must reference an opaque `Age` declared in this module, not any
/// other!
// TODO(opaques): $->@ in the above comment
pub fn lookup_opaque_ref(
&self,
opaque_ref: &str,
lookup_region: Region,
) -> Result<(Symbol, &Alias), RuntimeError> {
debug_assert!(opaque_ref.starts_with('$'));
let opaque = opaque_ref[1..].into();
match self.idents.get(&opaque) {
// TODO: is it worth caching any of these results?
Some((symbol, decl_region)) => {
if symbol.module_id() != self.home {
// The reference is to an opaque type declared in another module - this is
// illegal, as opaque types can only be wrapped/unwrapped in the scope they're
// declared.
return Err(RuntimeError::OpaqueOutsideScope {
opaque,
referenced_region: lookup_region,
imported_region: *decl_region,
});
}
match self.aliases.get(symbol) {
None => Err(self.opaque_not_defined_error(opaque, lookup_region, None)),
Some(alias) => match alias.kind {
// The reference is to a proper alias like `Age : U32`, not an opaque type!
AliasKind::Structural => Err(self.opaque_not_defined_error(
opaque,
lookup_region,
Some(alias.header_region()),
)),
// All is good
AliasKind::Opaque => Ok((*symbol, alias)),
},
}
}
None => Err(self.opaque_not_defined_error(opaque, lookup_region, None)),
}
}
fn opaque_not_defined_error(
&self,
opaque: Ident,
lookup_region: Region,
opt_defined_alias: Option<Region>,
) -> RuntimeError {
let opaques_in_scope = self
.idents()
.filter(|(_, (sym, _))| {
self.aliases
.get(sym)
.map(|alias| alias.kind)
.unwrap_or(AliasKind::Structural)
== AliasKind::Opaque
})
.map(|(v, _)| v.as_ref().into())
.collect();
RuntimeError::OpaqueNotDefined {
usage: Loc::at(lookup_region, opaque),
opaques_in_scope,
opt_defined_alias,
}
}
/// Introduce a new ident to scope.
///
/// Returns Err if this would shadow an existing ident, including the
@ -180,8 +252,9 @@ impl Scope {
region: Region,
vars: Vec<Loc<(Lowercase, Variable)>>,
typ: Type,
kind: AliasKind,
) {
let alias = create_alias(name, region, vars, typ);
let alias = create_alias(name, region, vars, typ, kind);
self.aliases.insert(name, alias);
}
@ -195,6 +268,7 @@ pub fn create_alias(
region: Region,
vars: Vec<Loc<(Lowercase, Variable)>>,
typ: Type,
kind: AliasKind,
) -> Alias {
let roc_types::types::VariableDetail {
type_variables,
@ -230,5 +304,6 @@ pub fn create_alias(
lambda_set_variables,
recursion_variables,
typ,
kind,
}
}

View file

@ -1063,6 +1063,22 @@ mod test_can {
assert_eq!(problems, Vec::new());
}
#[test]
fn issue_2534() {
let src = indoc!(
r#"
x = { a: 1 }
{
x & a: 2
}
"#
);
let arena = Bump::new();
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
assert_eq!(problems, Vec::new());
}
//#[test]
//fn closing_over_locals() {
// // "local" should be used, because the closure used it.

View file

@ -161,13 +161,13 @@ where
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Index(usize);
pub struct HumanIndex(usize);
impl Index {
pub const FIRST: Self = Index(0);
impl HumanIndex {
pub const FIRST: Self = HumanIndex(0);
pub fn zero_based(i: usize) -> Self {
Index(i)
HumanIndex(i)
}
pub fn to_zero_based(self) -> usize {
@ -175,7 +175,7 @@ impl Index {
}
pub fn one_based(i: usize) -> Self {
Index(i - 1)
HumanIndex(i - 1)
}
pub fn ordinal(self) -> std::string::String {

View file

@ -3,3 +3,4 @@
#![allow(clippy::large_enum_variant)]
pub mod all;
pub mod soa;

View file

@ -0,0 +1,119 @@
use std::usize;
#[derive(PartialEq, Eq)]
pub struct Index<T> {
index: u32,
_marker: std::marker::PhantomData<T>,
}
impl<T> Clone for Index<T> {
fn clone(&self) -> Self {
Self {
index: self.index,
_marker: self._marker,
}
}
}
impl<T> Copy for Index<T> {}
impl<T> std::fmt::Debug for Index<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Index({})", self.index)
}
}
impl<T> Index<T> {
pub const fn new(index: u32) -> Self {
Self {
index,
_marker: std::marker::PhantomData,
}
}
pub const fn index(&self) -> usize {
self.index as usize
}
pub fn push_new(vector: &mut Vec<T>, value: T) -> Index<T> {
let index = Self::new(vector.len() as _);
vector.push(value);
index
}
}
#[derive(PartialEq, Eq)]
pub struct Slice<T> {
start: u32,
length: u16,
_marker: std::marker::PhantomData<T>,
}
impl<T> Clone for Slice<T> {
fn clone(&self) -> Self {
Self {
start: self.start,
length: self.length,
_marker: self._marker,
}
}
}
impl<T> Copy for Slice<T> {}
impl<T> std::fmt::Debug for Slice<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Slice(start = {}, length = {})", self.start, self.length)
}
}
impl<T> Default for Slice<T> {
fn default() -> Self {
Self {
start: Default::default(),
length: Default::default(),
_marker: Default::default(),
}
}
}
impl<T> Slice<T> {
pub const fn new(start: u32, length: u16) -> Self {
Self {
start,
length,
_marker: std::marker::PhantomData,
}
}
pub fn extend_new<I>(vector: &mut Vec<T>, values: I) -> Slice<T>
where
I: IntoIterator<Item = T>,
{
let start = vector.len() as u32;
vector.extend(values);
let end = vector.len() as u32;
Self::new(start, (end - start) as u16)
}
pub const fn len(&self) -> usize {
self.length as _
}
pub const fn is_empty(&self) -> bool {
self.length == 0
}
pub const fn indices(&self) -> std::ops::Range<usize> {
self.start as usize..(self.start as usize + self.length as usize)
}
pub fn into_iter(&self) -> impl Iterator<Item = Index<T>> {
self.indices().map(|i| Index::new(i as _))
}
}

View file

@ -7,9 +7,11 @@ edition = "2018"
[dependencies]
roc_collections = { path = "../collections" }
roc_error_macros = { path = "../../error_macros" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_parse = { path = "../parse" }
roc_types = { path = "../types" }
roc_can = { path = "../can" }
roc_builtins = { path = "../builtins" }
arrayvec = "0.7.2"

View file

@ -1,19 +1,20 @@
use roc_can::constraint::Constraint::{self, *};
use roc_can::constraint::LetConstraint;
use arrayvec::ArrayVec;
use roc_can::constraint::{Constraint, Constraints};
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;
use roc_region::all::Region;
use roc_types::subs::Variable;
use roc_types::types::Category;
use roc_types::types::Reason;
use roc_types::types::Type::{self, *};
use roc_types::types::{AliasKind, Category};
#[must_use]
#[inline(always)]
pub fn add_numeric_bound_constr(
constrs: &mut Vec<Constraint>,
constraints: &mut Constraints,
num_constraints: &mut impl Extend<Constraint>,
num_type: Type,
bound: impl TypedNumericBound,
region: Region,
@ -27,12 +28,12 @@ pub fn add_numeric_bound_constr(
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,
));
let expected = Expected::ForReason(Reason::NumericLiteralSuffix, actual_type, region);
let because_suffix =
constraints.equal_types(total_num_type.clone(), expected, category, region);
num_constraints.extend([because_suffix]);
total_num_type
}
_ => RangedNumber(Box::new(total_num_type), range),
@ -41,6 +42,7 @@ pub fn add_numeric_bound_constr(
#[inline(always)]
pub fn int_literal(
constraints: &mut Constraints,
num_var: Variable,
precision_var: Variable,
expected: Expected<Type>,
@ -49,31 +51,35 @@ pub fn int_literal(
) -> Constraint {
let reason = Reason::IntLiteral;
let mut constrs = Vec::with_capacity(3);
// Always add the bound first; this improves the resolved type quality in case it's an alias
// like "U8".
// Always add the bound first; this improves the resolved type quality in case it's an alias like "U8".
let mut constrs = ArrayVec::<_, 3>::new();
let num_type = add_numeric_bound_constr(
constraints,
&mut constrs,
Variable(num_var),
bound,
region,
Category::Num,
);
constrs.extend(vec![
Eq(
constrs.extend([
constraints.equal_types(
num_type.clone(),
ForReason(reason, num_int(Type::Variable(precision_var)), region),
Category::Int,
region,
),
Eq(num_type, expected, Category::Int, region),
constraints.equal_types(num_type, expected, Category::Int, region),
]);
exists(vec![num_var], And(constrs))
// TODO the precision_var is not part of the exists here; for float it is. Which is correct?
let and_constraint = constraints.and_constraint(constrs);
constraints.exists([num_var], and_constraint)
}
#[inline(always)]
pub fn float_literal(
constraints: &mut Constraints,
num_var: Variable,
precision_var: Variable,
expected: Expected<Type>,
@ -82,29 +88,33 @@ pub fn float_literal(
) -> Constraint {
let reason = Reason::FloatLiteral;
let mut constrs = Vec::with_capacity(3);
let mut constrs = ArrayVec::<_, 3>::new();
let num_type = add_numeric_bound_constr(
constraints,
&mut constrs,
Variable(num_var),
bound,
region,
Category::Float,
);
constrs.extend(vec![
Eq(
constrs.extend([
constraints.equal_types(
num_type.clone(),
ForReason(reason, num_float(Type::Variable(precision_var)), region),
Category::Float,
region,
),
Eq(num_type, expected, Category::Float, region),
constraints.equal_types(num_type, expected, Category::Float, region),
]);
exists(vec![num_var, precision_var], And(constrs))
let and_constraint = constraints.and_constraint(constrs);
constraints.exists([num_var, precision_var], and_constraint)
}
#[inline(always)]
pub fn num_literal(
constraints: &mut Constraints,
num_var: Variable,
expected: Expected<Type>,
region: Region,
@ -112,23 +122,20 @@ pub fn num_literal(
) -> 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)]);
let mut constrs = ArrayVec::<_, 2>::new();
let num_type = add_numeric_bound_constr(
constraints,
&mut constrs,
open_number_type,
bound,
region,
Category::Num,
);
exists(vec![num_var], And(constrs))
}
constrs.extend([constraints.equal_types(num_type, expected, Category::Num, region)]);
#[inline(always)]
pub fn exists(flex_vars: Vec<Variable>, constraint: Constraint) -> Constraint {
Let(Box::new(LetConstraint {
rigid_vars: Vec::new(),
flex_vars,
def_types: SendMap::default(),
defs_constraint: constraint,
ret_constraint: Constraint::True,
}))
let and_constraint = constraints.and_constraint(constrs);
constraints.exists([num_var], and_constraint)
}
#[inline(always)]
@ -162,6 +169,8 @@ fn builtin_alias(
type_arguments,
actual,
lambda_set_variables: vec![],
// TODO(opaques): revisit later
kind: AliasKind::Structural,
}
}
@ -191,6 +200,21 @@ pub fn num_floatingpoint(range: Type) -> Type {
)
}
#[inline(always)]
pub fn num_u32() -> Type {
builtin_alias(Symbol::NUM_U32, vec![], Box::new(num_int(num_unsigned32())))
}
#[inline(always)]
fn num_unsigned32() -> Type {
let alias_content = Type::TagUnion(
vec![(TagName::Private(Symbol::NUM_AT_UNSIGNED32), vec![])],
Box::new(Type::EmptyTagUnion),
);
builtin_alias(Symbol::NUM_UNSIGNED32, vec![], Box::new(alias_content))
}
#[inline(always)]
pub fn num_binary64() -> Type {
let alias_content = Type::TagUnion(

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,5 @@
use crate::expr::constrain_decls;
use roc_builtins::std::StdLib;
use roc_can::constraint::{Constraint, LetConstraint};
use roc_can::constraint::{Constraint, Constraints};
use roc_can::def::Declaration;
use roc_collections::all::{MutMap, MutSet, SendMap};
use roc_module::symbol::{ModuleId, Symbol};
@ -17,13 +16,12 @@ pub enum ExposedModuleTypes {
Valid(MutMap<Symbol, SolvedType>, MutMap<Symbol, Alias>),
}
pub struct ConstrainedModule {
pub unused_imports: MutMap<ModuleId, Region>,
pub constraint: Constraint,
}
pub fn constrain_module(declarations: &[Declaration], home: ModuleId) -> Constraint {
constrain_decls(home, declarations)
pub fn constrain_module(
constraints: &mut Constraints,
declarations: &[Declaration],
home: ModuleId,
) -> Constraint {
crate::expr::constrain_decls(constraints, home, declarations)
}
#[derive(Debug, Clone)]
@ -33,11 +31,11 @@ pub struct Import {
}
pub fn constrain_imported_values(
constraints: &mut Constraints,
imports: Vec<Import>,
body_con: Constraint,
var_store: &mut VarStore,
) -> (Vec<Variable>, Constraint) {
use Constraint::*;
let mut def_types = SendMap::default();
let mut rigid_vars = Vec::new();
@ -47,7 +45,7 @@ pub fn constrain_imported_values(
// an imported symbol can be either an alias or a value
match import.solved_type {
SolvedType::Alias(symbol, _, _, _) if symbol == loc_symbol.value => {
SolvedType::Alias(symbol, _, _, _, _) if symbol == loc_symbol.value => {
// do nothing, in the future the alias definitions should not be in the list of imported values
}
_ => {
@ -84,24 +82,19 @@ pub fn constrain_imported_values(
(
rigid_vars.clone(),
Let(Box::new(LetConstraint {
rigid_vars,
flex_vars: Vec::new(),
def_types,
defs_constraint: True,
ret_constraint: body_con,
})),
constraints.let_constraint(rigid_vars, [], def_types, Constraint::True, body_con),
)
}
/// Run pre_constrain_imports to get imported_symbols and imported_aliases.
pub fn constrain_imports(
constraints: &mut Constraints,
imported_symbols: Vec<Import>,
constraint: Constraint,
var_store: &mut VarStore,
) -> Constraint {
let (_introduced_rigids, constraint) =
constrain_imported_values(imported_symbols, constraint, var_store);
constrain_imported_values(constraints, imported_symbols, constraint, var_store);
// TODO determine what to do with those rigids
// for var in introduced_rigids {

View file

@ -1,15 +1,15 @@
use crate::builtins;
use crate::expr::{constrain_expr, Env};
use roc_can::constraint::{Constraint, PresenceConstraint};
use roc_can::constraint::{Constraint, Constraints};
use roc_can::expected::{Expected, PExpected};
use roc_can::pattern::Pattern::{self, *};
use roc_can::pattern::{DestructType, RecordDestruct};
use roc_collections::all::{Index, SendMap};
use roc_collections::all::{HumanIndex, SendMap};
use roc_module::ident::Lowercase;
use roc_module::symbol::Symbol;
use roc_region::all::{Loc, Region};
use roc_types::subs::Variable;
use roc_types::types::{Category, PReason, PatternCategory, Reason, RecordField, Type};
use roc_types::types::{AliasKind, Category, PReason, PatternCategory, Reason, RecordField, Type};
#[derive(Default)]
pub struct PatternState {
@ -27,7 +27,7 @@ pub struct PatternState {
/// definition has an annotation, we instead now add `x => Int`.
pub fn headers_from_annotation(
pattern: &Pattern,
annotation: &Loc<Type>,
annotation: &Loc<&Type>,
) -> Option<SendMap<Symbol, Loc<Type>>> {
let mut headers = SendMap::default();
// Check that the annotation structurally agrees with the pattern, preventing e.g. `{ x, y } : Int`
@ -44,20 +44,23 @@ pub fn headers_from_annotation(
fn headers_from_annotation_help(
pattern: &Pattern,
annotation: &Loc<Type>,
annotation: &Loc<&Type>,
headers: &mut SendMap<Symbol, Loc<Type>>,
) -> bool {
match pattern {
Identifier(symbol) | Shadowed(_, _, symbol) => {
headers.insert(*symbol, annotation.clone());
let typ = Loc::at(annotation.region, annotation.value.clone());
headers.insert(*symbol, typ);
true
}
Underscore
| MalformedPattern(_, _)
| UnsupportedPattern(_)
| OpaqueNotInScope(..)
| NumLiteral(..)
| IntLiteral(..)
| FloatLiteral(..)
| SingleQuote(_)
| StrLiteral(_) => true,
RecordDestructure { destructs, .. } => match annotation.value.shallow_dealias() {
@ -104,7 +107,7 @@ fn headers_from_annotation_help(
.all(|(arg_pattern, arg_type)| {
headers_from_annotation_help(
&arg_pattern.1.value,
&Loc::at(annotation.region, arg_type.clone()),
&Loc::at(annotation.region, arg_type),
headers,
)
})
@ -114,23 +117,37 @@ fn headers_from_annotation_help(
}
_ => false,
},
}
}
fn make_pattern_constraint(
region: Region,
category: PatternCategory,
actual: Type,
expected: PExpected<Type>,
presence_con: bool,
) -> Constraint {
if presence_con {
Constraint::Present(
UnwrappedOpaque {
whole_var: _,
opaque,
argument,
specialized_def_type: _,
type_arguments: pat_type_arguments,
lambda_set_variables: pat_lambda_set_variables,
} => match &annotation.value {
Type::Alias {
symbol,
kind: AliasKind::Opaque,
actual,
PresenceConstraint::Pattern(region, category, expected),
type_arguments,
lambda_set_variables,
} if symbol == opaque
&& type_arguments.len() == pat_type_arguments.len()
&& lambda_set_variables.len() == pat_lambda_set_variables.len() =>
{
let typ = Loc::at(annotation.region, annotation.value.clone());
headers.insert(*opaque, typ);
let (_, argument_pat) = &**argument;
headers_from_annotation_help(
&argument_pat.value,
&Loc::at(annotation.region, actual),
headers,
)
} else {
Constraint::Pattern(region, category, actual, expected)
}
_ => false,
},
}
}
@ -138,37 +155,33 @@ fn make_pattern_constraint(
/// initialize the Vecs in PatternState using with_capacity
/// based on its knowledge of their lengths.
pub fn constrain_pattern(
constraints: &mut Constraints,
env: &Env,
pattern: &Pattern,
region: Region,
expected: PExpected<Type>,
state: &mut PatternState,
destruct_position: bool,
) {
match pattern {
Underscore if destruct_position => {
Underscore => {
// This is an underscore in a position where we destruct a variable,
// like a when expression:
// when x is
// A -> ""
// _ -> ""
// so, we know that "x" (in this case, a tag union) must be open.
state.constraints.push(Constraint::Present(
expected.get_type(),
PresenceConstraint::IsOpen,
));
state
.constraints
.push(constraints.is_open_type(expected.get_type()));
}
Underscore | UnsupportedPattern(_) | MalformedPattern(_, _) => {
// Neither the _ pattern nor erroneous ones add any constraints.
UnsupportedPattern(_) | MalformedPattern(_, _) | OpaqueNotInScope(..) => {
// Erroneous patterns don't add any constraints.
}
Identifier(symbol) | Shadowed(_, _, symbol) => {
if destruct_position {
state.constraints.push(Constraint::Present(
expected.get_type_ref().clone(),
PresenceConstraint::IsOpen,
));
}
state
.constraints
.push(constraints.is_open_type(expected.get_type_ref().clone()));
state.headers.insert(
*symbol,
Loc {
@ -184,6 +197,7 @@ pub fn constrain_pattern(
let num_type = builtins::num_num(Type::Variable(var));
let num_type = builtins::add_numeric_bound_constr(
constraints,
&mut state.constraints,
num_type,
bound,
@ -191,11 +205,11 @@ pub fn constrain_pattern(
Category::Num,
);
state.constraints.push(Constraint::Pattern(
region,
PatternCategory::Num,
state.constraints.push(constraints.equal_pattern_types(
num_type,
expected,
PatternCategory::Num,
region,
));
}
@ -203,6 +217,7 @@ pub fn constrain_pattern(
// 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(
constraints,
&mut state.constraints,
Type::Variable(num_var),
bound,
@ -213,7 +228,7 @@ pub fn constrain_pattern(
// 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(
state.constraints.push(constraints.equal_types(
num_type, // TODO check me if something breaks!
Expected::NoExpectation(int_type),
Category::Int,
@ -221,11 +236,11 @@ pub fn constrain_pattern(
));
// Also constrain the pattern against the num var, again to reuse aliases if they're present.
state.constraints.push(Constraint::Pattern(
region,
PatternCategory::Int,
state.constraints.push(constraints.equal_pattern_types(
Type::Variable(num_var),
expected,
PatternCategory::Int,
region,
));
}
@ -233,6 +248,7 @@ pub fn constrain_pattern(
// 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(
constraints,
&mut state.constraints,
Type::Variable(num_var),
bound,
@ -243,7 +259,7 @@ pub fn constrain_pattern(
// 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(
state.constraints.push(constraints.equal_types(
num_type.clone(), // TODO check me if something breaks!
Expected::NoExpectation(float_type),
Category::Float,
@ -251,20 +267,29 @@ pub fn constrain_pattern(
));
// Also constrain the pattern against the num var, again to reuse aliases if they're present.
state.constraints.push(Constraint::Pattern(
region,
PatternCategory::Float,
state.constraints.push(constraints.equal_pattern_types(
num_type, // TODO check me if something breaks!
expected,
PatternCategory::Float,
region,
));
}
StrLiteral(_) => {
state.constraints.push(Constraint::Pattern(
region,
PatternCategory::Str,
state.constraints.push(constraints.equal_pattern_types(
builtins::str_type(),
expected,
PatternCategory::Str,
region,
));
}
SingleQuote(_) => {
state.constraints.push(constraints.equal_pattern_types(
builtins::num_u32(),
expected,
PatternCategory::Character,
region,
));
}
@ -301,41 +326,39 @@ pub fn constrain_pattern(
let field_type = match typ {
DestructType::Guard(guard_var, loc_guard) => {
state.constraints.push(make_pattern_constraint(
region,
PatternCategory::PatternGuard,
state.constraints.push(constraints.pattern_presence(
Type::Variable(*guard_var),
PExpected::ForReason(
PReason::PatternGuard,
pat_type.clone(),
loc_guard.region,
),
destruct_position,
PatternCategory::PatternGuard,
region,
));
state.vars.push(*guard_var);
constrain_pattern(
constraints,
env,
&loc_guard.value,
loc_guard.region,
expected,
state,
destruct_position,
);
RecordField::Demanded(pat_type)
}
DestructType::Optional(expr_var, loc_expr) => {
state.constraints.push(make_pattern_constraint(
region,
PatternCategory::PatternDefault,
state.constraints.push(constraints.pattern_presence(
Type::Variable(*expr_var),
PExpected::ForReason(
PReason::OptionalField,
pat_type.clone(),
loc_expr.region,
),
destruct_position,
PatternCategory::PatternDefault,
region,
));
state.vars.push(*expr_var);
@ -346,8 +369,13 @@ pub fn constrain_pattern(
loc_expr.region,
);
let expr_con =
constrain_expr(env, loc_expr.region, &loc_expr.value, expr_expected);
let expr_con = constrain_expr(
constraints,
env,
loc_expr.region,
&loc_expr.value,
expr_expected,
);
state.constraints.push(expr_con);
RecordField::Optional(pat_type)
@ -365,19 +393,18 @@ pub fn constrain_pattern(
let record_type = Type::Record(field_types, Box::new(ext_type));
let whole_con = Constraint::Eq(
let whole_con = constraints.equal_types(
Type::Variable(*whole_var),
Expected::NoExpectation(record_type),
Category::Storage(std::file!(), std::line!()),
region,
);
let record_con = make_pattern_constraint(
region,
PatternCategory::Record,
let record_con = constraints.pattern_presence(
Type::Variable(*whole_var),
expected,
destruct_position,
PatternCategory::Record,
region,
);
state.constraints.push(whole_con);
@ -399,44 +426,36 @@ pub fn constrain_pattern(
let expected = PExpected::ForReason(
PReason::TagArg {
tag_name: tag_name.clone(),
index: Index::zero_based(index),
index: HumanIndex::zero_based(index),
},
pattern_type,
region,
);
constrain_pattern(
constraints,
env,
&loc_pattern.value,
loc_pattern.region,
expected,
state,
destruct_position,
);
}
let whole_con = if destruct_position {
Constraint::Present(
expected.clone().get_type(),
PresenceConstraint::IncludesTag(tag_name.clone(), argument_types.clone()),
)
} else {
Constraint::Eq(
Type::Variable(*whole_var),
Expected::NoExpectation(Type::TagUnion(
vec![(tag_name.clone(), argument_types)],
Box::new(Type::Variable(*ext_var)),
)),
Category::Storage(std::file!(), std::line!()),
region,
)
};
let pat_category = PatternCategory::Ctor(tag_name.clone());
let tag_con = make_pattern_constraint(
let whole_con = constraints.includes_tag(
expected.clone().get_type(),
tag_name.clone(),
argument_types.clone(),
pat_category.clone(),
region,
PatternCategory::Ctor(tag_name.clone()),
);
let tag_con = constraints.pattern_presence(
Type::Variable(*whole_var),
expected,
destruct_position,
pat_category,
region,
);
state.vars.push(*whole_var);
@ -444,5 +463,80 @@ pub fn constrain_pattern(
state.constraints.push(whole_con);
state.constraints.push(tag_con);
}
UnwrappedOpaque {
whole_var,
opaque,
argument,
specialized_def_type,
type_arguments,
lambda_set_variables,
} => {
// Suppose we are constraining the pattern \@Id who, where Id n := [ Id U64 n ]
let (arg_pattern_var, loc_arg_pattern) = &**argument;
let arg_pattern_type = Type::Variable(*arg_pattern_var);
let opaque_type = Type::Alias {
symbol: *opaque,
type_arguments: type_arguments.clone(),
lambda_set_variables: lambda_set_variables.clone(),
actual: Box::new(arg_pattern_type.clone()),
kind: AliasKind::Opaque,
};
// First, add a constraint for the argument "who"
let arg_pattern_expected = PExpected::NoExpectation(arg_pattern_type.clone());
constrain_pattern(
constraints,
env,
&loc_arg_pattern.value,
loc_arg_pattern.region,
arg_pattern_expected,
state,
);
// Next, link `whole_var` to the opaque type of "@Id who"
let whole_con = constraints.equal_types(
Type::Variable(*whole_var),
Expected::NoExpectation(opaque_type),
Category::Storage(std::file!(), std::line!()),
region,
);
// Link the entire wrapped opaque type (with the now-constrained argument) to the type
// variables of the opaque type
// TODO: better expectation here
let link_type_variables_con = constraints.equal_types(
(**specialized_def_type).clone(),
Expected::NoExpectation(arg_pattern_type),
Category::OpaqueWrap(*opaque),
loc_arg_pattern.region,
);
// Next, link `whole_var` (the type of "@Id who") to the expected type
let opaque_pattern_con = constraints.pattern_presence(
Type::Variable(*whole_var),
expected,
PatternCategory::Opaque(*opaque),
region,
);
state
.vars
.extend_from_slice(&[*arg_pattern_var, *whole_var]);
// Also add the fresh variables we created for the type argument and lambda sets
state.vars.extend(type_arguments.iter().map(|(_, t)| {
t.expect_variable("all type arguments should be fresh variables here")
}));
state.vars.extend(lambda_set_variables.iter().map(|v| {
v.0.expect_variable("all lambda sets should be fresh variables here")
}));
state.constraints.extend_from_slice(&[
whole_con,
link_type_variables_con,
opaque_pattern_con,
]);
}
}
}

View file

@ -0,0 +1,12 @@
[package]
name = "roc_exhaustive"
version = "0.1.0"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2018"
[dependencies]
roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_std = { path = "../../roc_std", default-features = false }

View file

@ -0,0 +1,443 @@
//! Exhaustiveness checking, based on "Warning for pattern matching" (Luc Maranget, 2007).
//! http://moscova.inria.fr/~maranget/papers/warn/warn.pdf
use roc_collections::all::{HumanIndex, MutMap};
use roc_module::ident::{Lowercase, TagIdIntType, TagName};
use roc_region::all::Region;
use roc_std::RocDec;
use self::Pattern::*;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Union {
pub alternatives: Vec<Ctor>,
pub render_as: RenderAs,
}
impl Union {
pub fn newtype_wrapper(tag_name: TagName, arity: usize) -> Self {
let alternatives = vec![Ctor {
name: tag_name,
tag_id: TagId(0),
arity,
}];
Union {
alternatives,
render_as: RenderAs::Tag,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum RenderAs {
Tag,
Opaque,
Record(Vec<Lowercase>),
Guard,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)]
pub struct TagId(pub TagIdIntType);
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Ctor {
pub name: TagName,
pub tag_id: TagId,
pub arity: usize,
}
#[derive(Clone, Debug, PartialEq)]
pub enum Pattern {
Anything,
Literal(Literal),
Ctor(Union, TagId, std::vec::Vec<Pattern>),
}
#[derive(Clone, Debug, PartialEq)]
pub enum Literal {
Int(i128),
U128(u128),
Bit(bool),
Byte(u8),
Float(u64),
Decimal(RocDec),
Str(Box<str>),
}
/// Error
#[derive(Clone, Debug, PartialEq)]
pub enum Error {
Incomplete(Region, Context, Vec<Pattern>),
Redundant {
overall_region: Region,
branch_region: Region,
index: HumanIndex,
},
}
#[derive(Clone, Debug, PartialEq)]
pub enum Context {
BadArg,
BadDestruct,
BadCase,
}
#[derive(Clone, Debug, PartialEq)]
pub enum Guard {
HasGuard,
NoGuard,
}
/// Check
pub fn check(
region: Region,
context: Context,
matrix: Vec<Vec<Pattern>>,
) -> Result<(), Vec<Error>> {
let mut errors = Vec::new();
let bad_patterns = is_exhaustive(&matrix, 1);
if !bad_patterns.is_empty() {
// TODO i suspect this is like a concat in in practice? code below can panic
// if this debug_assert! ever fails, the theory is disproven
debug_assert!(bad_patterns.iter().map(|v| v.len()).sum::<usize>() == bad_patterns.len());
let heads = bad_patterns.into_iter().map(|mut v| v.remove(0)).collect();
errors.push(Error::Incomplete(region, context, heads));
return Err(errors);
}
Ok(())
}
/// EXHAUSTIVE PATTERNS
/// INVARIANTS:
///
/// The initial rows "matrix" are all of length 1
/// The initial count of items per row "n" is also 1
/// The resulting rows are examples of missing patterns
fn is_exhaustive(matrix: &RefPatternMatrix, n: usize) -> PatternMatrix {
if matrix.is_empty() {
vec![std::iter::repeat(Anything).take(n).collect()]
} else if n == 0 {
vec![]
} else {
let ctors = collect_ctors(matrix);
let num_seen = ctors.len();
if num_seen == 0 {
let new_matrix: Vec<_> = matrix
.iter()
.filter_map(|row| specialize_row_by_anything(row))
.collect();
let mut rest = is_exhaustive(&new_matrix, n - 1);
for row in rest.iter_mut() {
row.push(Anything);
}
rest
} else {
let alts = ctors.iter().next().unwrap().1;
let alt_list = &alts.alternatives;
let num_alts = alt_list.len();
if num_seen < num_alts {
let new_matrix: Vec<_> = matrix
.iter()
.filter_map(|row| specialize_row_by_anything(row))
.collect();
let rest: Vec<Vec<Pattern>> = is_exhaustive(&new_matrix, n - 1);
let last: _ = alt_list
.iter()
.filter_map(|r| is_missing(alts.clone(), &ctors, r));
let mut result = Vec::new();
for last_option in last {
for mut row in rest.clone() {
row.push(last_option.clone());
result.push(row);
}
}
result
} else {
let is_alt_exhaustive = |Ctor { arity, tag_id, .. }| {
let new_matrix: Vec<_> = matrix
.iter()
.filter_map(|r| specialize_row_by_ctor(tag_id, arity, r))
.collect();
let rest: Vec<Vec<Pattern>> = is_exhaustive(&new_matrix, arity + n - 1);
let mut result = Vec::with_capacity(rest.len());
for row in rest {
result.push(recover_ctor(alts.clone(), tag_id, arity, row));
}
result
};
alt_list
.iter()
.cloned()
.map(is_alt_exhaustive)
.flatten()
.collect()
}
}
}
}
fn is_missing<T>(union: Union, ctors: &MutMap<TagId, T>, ctor: &Ctor) -> Option<Pattern> {
let Ctor { arity, tag_id, .. } = ctor;
if ctors.contains_key(tag_id) {
None
} else {
let anythings = std::iter::repeat(Anything).take(*arity).collect();
Some(Pattern::Ctor(union, *tag_id, anythings))
}
}
fn recover_ctor(
union: Union,
tag_id: TagId,
arity: usize,
mut patterns: Vec<Pattern>,
) -> Vec<Pattern> {
let mut rest = patterns.split_off(arity);
let args = patterns;
rest.push(Ctor(union, tag_id, args));
rest
}
/// Check if a new row "vector" is useful given previous rows "matrix"
pub fn is_useful(mut old_matrix: PatternMatrix, mut vector: Row) -> bool {
let mut matrix = Vec::with_capacity(old_matrix.len());
// this loop ping-pongs the rows between old_matrix and matrix
'outer: loop {
match vector.pop() {
_ if old_matrix.is_empty() => {
// No rows are the same as the new vector! The vector is useful!
break true;
}
None => {
// There is nothing left in the new vector, but we still have
// rows that match the same things. This is not a useful vector!
break false;
}
Some(first_pattern) => {
// NOTE: if there are bugs in this code, look at the ordering of the row/matrix
match first_pattern {
// keep checking rows that start with this Ctor or Anything
Ctor(_, id, args) => {
specialize_row_by_ctor2(id, args.len(), &mut old_matrix, &mut matrix);
std::mem::swap(&mut old_matrix, &mut matrix);
vector.extend(args);
}
Anything => {
// check if all alternatives appear in matrix
match is_complete(&old_matrix) {
Complete::No => {
// This Anything is useful because some Ctors are missing.
// But what if a previous row has an Anything?
// If so, this one is not useful.
for mut row in old_matrix.drain(..) {
if let Some(Anything) = row.pop() {
matrix.push(row);
}
}
std::mem::swap(&mut old_matrix, &mut matrix);
}
Complete::Yes(alternatives) => {
// All Ctors are covered, so this Anything is not needed for any
// of those. But what if some of those Ctors have subpatterns
// that make them less general? If so, this actually is useful!
for alternative in alternatives {
let Ctor { arity, tag_id, .. } = alternative;
let mut old_matrix = old_matrix.clone();
let mut matrix = vec![];
specialize_row_by_ctor2(
tag_id,
arity,
&mut old_matrix,
&mut matrix,
);
let mut vector = vector.clone();
vector.extend(std::iter::repeat(Anything).take(arity));
if is_useful(matrix, vector) {
break 'outer true;
}
}
break false;
}
}
}
Literal(literal) => {
// keep checking rows that start with this Literal or Anything
for mut row in old_matrix.drain(..) {
let head = row.pop();
let patterns = row;
match head {
Some(Literal(lit)) => {
if lit == literal {
matrix.push(patterns);
} else {
// do nothing
}
}
Some(Anything) => matrix.push(patterns),
Some(Ctor(_, _, _)) => panic!(
r#"Compiler bug! After type checking, constructors and literals should never align in pattern match exhaustiveness checks."#
),
None => panic!(
"Compiler error! Empty matrices should not get specialized."
),
}
}
std::mem::swap(&mut old_matrix, &mut matrix);
}
}
}
}
}
}
/// INVARIANT: (length row == N) ==> (length result == arity + N - 1)
fn specialize_row_by_ctor2(
tag_id: TagId,
arity: usize,
old_matrix: &mut PatternMatrix,
matrix: &mut PatternMatrix,
) {
for mut row in old_matrix.drain(..) {
let head = row.pop();
let mut patterns = row;
match head {
Some(Ctor(_, id, args)) =>
if id == tag_id {
patterns.extend(args);
matrix.push(patterns);
} else {
// do nothing
}
Some(Anything) => {
// TODO order!
patterns.extend(std::iter::repeat(Anything).take(arity));
matrix.push(patterns);
}
Some(Literal(_)) => panic!( "Compiler bug! After type checking, constructors and literal should never align in pattern match exhaustiveness checks."),
None => panic!("Compiler error! Empty matrices should not get specialized."),
}
}
}
/// INVARIANT: (length row == N) ==> (length result == arity + N - 1)
fn specialize_row_by_ctor(tag_id: TagId, arity: usize, row: &RefRow) -> Option<Row> {
let mut row = row.to_vec();
let head = row.pop();
let patterns = row;
match head {
Some(Ctor(_, id, args)) => {
if id == tag_id {
// TODO order!
let mut new_patterns = Vec::new();
new_patterns.extend(args);
new_patterns.extend(patterns);
Some(new_patterns)
} else {
None
}
}
Some(Anything) => {
// TODO order!
let new_patterns = std::iter::repeat(Anything)
.take(arity)
.chain(patterns)
.collect();
Some(new_patterns)
}
Some(Literal(_)) => unreachable!(
r#"Compiler bug! After type checking, a constructor can never align with a literal: that should be a type error!"#
),
None => panic!("Compiler error! Empty matrices should not get specialized."),
}
}
/// INVARIANT: (length row == N) ==> (length result == N-1)
fn specialize_row_by_anything(row: &RefRow) -> Option<Row> {
let mut row = row.to_vec();
match row.pop() {
Some(Anything) => Some(row),
_ => None,
}
}
/// ALL CONSTRUCTORS ARE PRESENT?
pub enum Complete {
Yes(Vec<Ctor>),
No,
}
fn is_complete(matrix: &RefPatternMatrix) -> Complete {
let ctors = collect_ctors(matrix);
let length = ctors.len();
let mut it = ctors.into_iter();
match it.next() {
None => Complete::No,
Some((_, Union { alternatives, .. })) => {
if length == alternatives.len() {
Complete::Yes(alternatives)
} else {
Complete::No
}
}
}
}
/// COLLECT CTORS
type RefPatternMatrix = [Vec<Pattern>];
type PatternMatrix = Vec<Vec<Pattern>>;
type RefRow = [Pattern];
type Row = Vec<Pattern>;
fn collect_ctors(matrix: &RefPatternMatrix) -> MutMap<TagId, Union> {
let mut ctors = MutMap::default();
for row in matrix {
if let Some(Ctor(union, id, _)) = row.get(row.len() - 1) {
ctors.insert(*id, union.clone());
}
}
ctors
}

View file

@ -3,7 +3,7 @@ use crate::{
spaces::{fmt_comments_only, fmt_spaces, NewlineAt, INDENT},
Buf,
};
use roc_parse::ast::{AliasHeader, AssignedField, Collection, Expr, Tag, TypeAnnotation};
use roc_parse::ast::{AssignedField, Collection, Expr, Tag, TypeAnnotation, TypeHeader};
use roc_parse::ident::UppercaseIdent;
use roc_region::all::Loc;
@ -35,7 +35,7 @@ pub enum Parens {
/// we also want to show newlines. By default the formatter
/// takes care of inserting newlines, but sometimes the user's
/// newlines are taken into account.
#[derive(PartialEq, Eq, Clone, Copy)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum Newlines {
No,
Yes,
@ -276,7 +276,7 @@ impl<'a> Formattable for TypeAnnotation<'a> {
}
}
As(lhs, _spaces, AliasHeader { name, vars }) => {
As(lhs, _spaces, TypeHeader { name, vars }) => {
// TODO use _spaces?
lhs.value
.format_with_options(buf, Parens::InFunctionType, Newlines::No, indent);
@ -293,6 +293,9 @@ impl<'a> Formattable for TypeAnnotation<'a> {
SpaceBefore(ann, spaces) => {
buf.newline();
buf.indent(indent);
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
ann.format_with_options(buf, parens, Newlines::No, indent)
}

View file

@ -49,7 +49,6 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
);
buf.newline();
buf.indent(braces_indent);
buf.push(end);
} else {
// is_multiline == false
// there is no comment to add
@ -67,7 +66,7 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
if !items.is_empty() {
buf.spaces(1);
}
}
buf.push(end);
}
}

View file

@ -2,7 +2,7 @@ use crate::annotation::{Formattable, Newlines, Parens};
use crate::pattern::fmt_pattern;
use crate::spaces::{fmt_spaces, INDENT};
use crate::Buf;
use roc_parse::ast::{AliasHeader, Def, Expr, Pattern};
use roc_parse::ast::{Def, Expr, Pattern, TypeHeader};
use roc_region::all::Loc;
/// A Located formattable value is also formattable
@ -12,6 +12,7 @@ impl<'a> Formattable for Def<'a> {
match self {
Alias { ann, .. } => ann.is_multiline(),
Opaque { typ, .. } => typ.is_multiline(),
Annotation(loc_pattern, loc_annotation) => {
loc_pattern.is_multiline() || loc_annotation.is_multiline()
}
@ -58,8 +59,12 @@ impl<'a> Formattable for Def<'a> {
}
}
Alias {
header: AliasHeader { name, vars },
header: TypeHeader { name, vars },
ann,
}
| Opaque {
header: TypeHeader { name, vars },
typ: ann,
} => {
buf.indent(indent);
buf.push_str(name.value);
@ -69,7 +74,11 @@ impl<'a> Formattable for Def<'a> {
fmt_pattern(buf, &var.value, indent, Parens::NotNeeded);
}
buf.push_str(" :");
buf.push_str(match self {
Alias { .. } => " :",
Opaque { .. } => " :=",
_ => unreachable!(),
});
buf.spaces(1);
ann.format(buf, indent + INDENT)

View file

@ -30,6 +30,7 @@ impl<'a> Formattable for Expr<'a> {
Float(..)
| Num(..)
| NonBase10Int { .. }
| SingleQuote(_)
| Access(_, _)
| AccessorFunction(_)
| Var { .. }
@ -37,7 +38,8 @@ impl<'a> Formattable for Expr<'a> {
| MalformedIdent(_, _)
| MalformedClosure
| GlobalTag(_)
| PrivateTag(_) => false,
| PrivateTag(_)
| OpaqueRef(_) => false,
// These expressions always have newlines
Defs(_, _) | When(_, _) => true,
@ -141,11 +143,17 @@ impl<'a> Formattable for Expr<'a> {
} else {
buf.indent(indent);
buf.push('(');
let next_indent = if starts_with_newline(sub_expr) {
indent + INDENT
} else {
indent
};
sub_expr.format_with_options(
buf,
Parens::NotNeeded,
Newlines::Yes,
indent + INDENT,
next_indent,
);
buf.indent(indent);
buf.push(')');
@ -204,10 +212,15 @@ impl<'a> Formattable for Expr<'a> {
buf.indent(indent);
buf.push_str(string);
}
GlobalTag(string) | PrivateTag(string) => {
GlobalTag(string) | PrivateTag(string) | OpaqueRef(string) => {
buf.indent(indent);
buf.push_str(string)
}
SingleQuote(string) => {
buf.push('\'');
buf.push_str(string);
buf.push('\'');
}
&NonBase10Int {
base,
string,
@ -297,6 +310,30 @@ impl<'a> Formattable for Expr<'a> {
}
}
fn starts_with_newline(expr: &Expr) -> bool {
use roc_parse::ast::Expr::*;
match expr {
SpaceBefore(_, comment_or_newline) => {
if !comment_or_newline.is_empty() {
// safe because we check the length before
comment_or_newline.get(0).unwrap().is_newline()
} else {
false
}
}
SpaceAfter(_, comment_or_newline) => {
if !(**comment_or_newline).is_empty() {
// safe because we check the length before
comment_or_newline.get(0).unwrap().is_newline()
} else {
false
}
}
_ => false,
}
}
fn format_str_segment<'a, 'buf>(seg: &StrSegment<'a>, buf: &mut Buf<'buf>, indent: u16) {
use StrSegment::*;
@ -347,7 +384,8 @@ fn push_op(buf: &mut Buf, op: BinOp) {
called_via::BinOp::Or => buf.push_str("||"),
called_via::BinOp::Pizza => buf.push_str("|>"),
called_via::BinOp::Assignment => unreachable!(),
called_via::BinOp::HasType => unreachable!(),
called_via::BinOp::IsAliasType => unreachable!(),
called_via::BinOp::IsOpaqueType => unreachable!(),
called_via::BinOp::Backpassing => unreachable!(),
}
}
@ -432,7 +470,13 @@ fn fmt_bin_ops<'a, 'buf>(
buf.spaces(1);
}
loc_right_side.format_with_options(buf, apply_needs_parens, Newlines::Yes, indent);
let next_indent = if is_multiline {
indent + INDENT
} else {
indent
};
loc_right_side.format_with_options(buf, apply_needs_parens, Newlines::Yes, next_indent);
}
fn empty_line_before_expr<'a>(expr: &'a Expr<'a>) -> bool {
@ -1067,7 +1111,11 @@ fn sub_expr_requests_parens(expr: &Expr<'_>) -> bool {
| BinOp::GreaterThanOrEq
| BinOp::And
| BinOp::Or => true,
BinOp::Pizza | BinOp::Assignment | BinOp::HasType | BinOp::Backpassing => false,
BinOp::Pizza
| BinOp::Assignment
| BinOp::IsAliasType
| BinOp::IsOpaqueType
| BinOp::Backpassing => false,
})
}
Expr::If(_, _) => true,

View file

@ -55,7 +55,6 @@ impl<'a> Buf<'a> {
pub fn push_str_allow_spaces(&mut self, s: &str) {
debug_assert!(!self.beginning_of_line);
debug_assert!(!s.contains('\n'));
self.flush_spaces();
@ -91,4 +90,116 @@ impl<'a> Buf<'a> {
self.spaces_to_flush = 0;
}
}
/// Ensures the text ends in a newline with no whitespace preceding it.
pub fn fmt_end_of_file(&mut self) {
fmt_text_eof(&mut self.text)
}
}
/// Ensures the text ends in a newline with no whitespace preceding it.
fn fmt_text_eof(text: &mut bumpalo::collections::String<'_>) {
let mut chars_rev = text.chars().rev();
let mut last_whitespace = None;
let mut last_whitespace_index = text.len();
// Keep going until we either run out of characters or encounter one
// that isn't whitespace.
loop {
match chars_rev.next() {
Some(ch) if ch.is_whitespace() => {
last_whitespace = Some(ch);
last_whitespace_index -= 1;
}
_ => {
break;
}
}
}
match last_whitespace {
Some('\n') => {
// There may have been more whitespace after this newline; remove it!
text.truncate(last_whitespace_index + '\n'.len_utf8());
}
Some(_) => {
// There's some whitespace at the end of this file, but the first
// whitespace char after the last non-whitespace char isn't a newline.
// So replace that whitespace char (and everything after it) with a newline.
text.replace_range(last_whitespace_index.., "\n");
}
None => {
debug_assert!(last_whitespace_index == text.len());
debug_assert!(!text.ends_with(char::is_whitespace));
// This doesn't end in whitespace at all, so add a newline.
text.push('\n');
}
}
}
#[test]
fn eof_text_ends_with_newline() {
use bumpalo::{collections::String, Bump};
let arena = Bump::new();
let input = "This should be a newline:\n";
let mut text = String::from_str_in(input, &arena);
fmt_text_eof(&mut text);
// This should be unchanged!
assert_eq!(text.as_str(), input);
}
#[test]
fn eof_text_ends_with_whitespace() {
use bumpalo::{collections::String, Bump};
let arena = Bump::new();
let input = "This should be a newline: \t";
let mut text = String::from_str_in(input, &arena);
fmt_text_eof(&mut text);
assert_eq!(text.as_str(), "This should be a newline:\n");
}
#[test]
fn eof_text_ends_with_whitespace_then_newline() {
use bumpalo::{collections::String, Bump};
let arena = Bump::new();
let input = "This should be a newline: \n";
let mut text = String::from_str_in(input, &arena);
fmt_text_eof(&mut text);
assert_eq!(text.as_str(), "This should be a newline:\n");
}
#[test]
fn eof_text_ends_with_no_whitespace() {
use bumpalo::{collections::String, Bump};
let arena = Bump::new();
let input = "This should be a newline:";
let mut text = String::from_str_in(input, &arena);
fmt_text_eof(&mut text);
assert_eq!(text.as_str(), "This should be a newline:\n");
}
#[test]
fn eof_text_is_empty() {
use bumpalo::{collections::String, Bump};
let arena = Bump::new();
let input = "";
let mut text = String::from_str_in(input, &arena);
fmt_text_eof(&mut text);
assert_eq!(text.as_str(), "\n");
}

View file

@ -30,11 +30,13 @@ impl<'a> Formattable for Pattern<'a> {
Pattern::Identifier(_)
| Pattern::GlobalTag(_)
| Pattern::PrivateTag(_)
| Pattern::OpaqueRef(_)
| Pattern::Apply(_, _)
| Pattern::NumLiteral(..)
| Pattern::NonBase10Literal { .. }
| Pattern::FloatLiteral(..)
| Pattern::StrLiteral(_)
| Pattern::SingleQuote(_)
| Pattern::Underscore(_)
| Pattern::Malformed(_)
| Pattern::MalformedIdent(_, _)
@ -56,7 +58,7 @@ impl<'a> Formattable for Pattern<'a> {
buf.indent(indent);
buf.push_str(string)
}
GlobalTag(name) | PrivateTag(name) => {
GlobalTag(name) | PrivateTag(name) | OpaqueRef(name) => {
buf.indent(indent);
buf.push_str(name);
}
@ -146,6 +148,11 @@ impl<'a> Formattable for Pattern<'a> {
StrLiteral(literal) => {
todo!("Format string literal: {:?}", literal);
}
SingleQuote(string) => {
buf.push('\'');
buf.push_str(string);
buf.push('\'');
}
Underscore(name) => {
buf.indent(indent);
buf.push('_');

View file

@ -2582,6 +2582,30 @@ mod test_fmt {
));
}
#[test]
fn apply_lambda() {
expr_formats_same(indoc!(
r#"
List.map
xs
(\i ->
i + length)
"#
));
}
#[test]
fn pipline_apply_lambda() {
expr_formats_same(indoc!(
r#"
shout
|> List.map
xs
(\i -> i)
"#
));
}
// MODULES
#[test]
@ -2984,6 +3008,18 @@ mod test_fmt {
));
}
#[test]
fn multiline_higher_order_function() {
expr_formats_same(indoc!(
r#"
foo :
(Str -> Bool) -> Bool
42
"#
));
}
#[test]
/// Test that everything under examples/ is formatted correctly
/// If this test fails on your diff, it probably means you need to re-format the examples.
@ -3002,7 +3038,7 @@ mod test_fmt {
for entry in walkdir::WalkDir::new(&root) {
let entry = entry.unwrap();
let path = entry.path();
if path.extension() == Some(&std::ffi::OsStr::new("roc")) {
if path.extension() == Some(std::ffi::OsStr::new("roc")) {
count += 1;
let src = std::fs::read_to_string(path).unwrap();
println!("Now trying to format {}", path.display());

View file

@ -30,7 +30,7 @@ packed_struct = "0.10.0"
[dev-dependencies]
roc_can = { path = "../can" }
roc_parse = { path = "../parse" }
roc_std = { path = "../../roc_std" }
roc_std = { path = "../../roc_std", default-features = false }
bumpalo = { version = "3.8.0", features = ["collections"] }
[features]

View file

@ -66,10 +66,12 @@ impl RegTrait for AArch64FloatReg {
}
}
#[derive(Copy, Clone)]
pub struct AArch64Assembler {}
// AArch64Call may need to eventually be split by OS,
// but I think with how we use it, they may all be the same.
#[derive(Copy, Clone)]
pub struct AArch64Call {}
const STACK_ALIGNMENT: u8 = 16;
@ -281,7 +283,8 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg, AArch64Assembler> for AArch64C
AArch64Assembler,
AArch64Call,
>,
_args: &'a [Symbol],
_dst: &Symbol,
_args: &[Symbol],
_arg_layouts: &[Layout<'a>],
_ret_layout: &Layout<'a>,
) {
@ -480,6 +483,70 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
}
}
#[inline(always)]
fn mov_reg64_mem64_offset32(
buf: &mut Vec<'_, u8>,
dst: AArch64GeneralReg,
src: AArch64GeneralReg,
offset: i32,
) {
if offset < 0 {
todo!("negative mem offsets for AArch64");
} else if offset < (0xFFF << 8) {
debug_assert!(offset % 8 == 0);
ldr_reg64_imm12(buf, dst, src, (offset as u16) >> 3);
} else {
todo!("mem offsets over 32k for AArch64");
}
}
#[inline(always)]
fn mov_mem64_offset32_reg64(
buf: &mut Vec<'_, u8>,
dst: AArch64GeneralReg,
offset: i32,
src: AArch64GeneralReg,
) {
if offset < 0 {
todo!("negative mem offsets for AArch64");
} else if offset < (0xFFF << 8) {
debug_assert!(offset % 8 == 0);
str_reg64_imm12(buf, src, dst, (offset as u16) >> 3);
} else {
todo!("mem offsets over 32k for AArch64");
}
}
#[inline(always)]
fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, offset: i32, size: u8) {
debug_assert!(size <= 8);
if size == 8 {
Self::mov_reg64_base32(buf, dst, offset);
} else if size == 4 {
todo!("sign extending 4 byte values");
} else if size == 2 {
todo!("sign extending 2 byte values");
} else if size == 1 {
todo!("sign extending 1 byte values");
} else {
internal_error!("Invalid size for sign extension: {}", size);
}
}
#[inline(always)]
fn movzx_reg64_base32(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, offset: i32, size: u8) {
debug_assert!(size <= 8);
if size == 8 {
Self::mov_reg64_base32(buf, dst, offset);
} else if size == 4 {
todo!("zero extending 4 byte values");
} else if size == 2 {
todo!("zero extending 2 byte values");
} else if size == 1 {
todo!("zero extending 1 byte values");
} else {
internal_error!("Invalid size for zero extension: {}", size);
}
}
#[inline(always)]
fn mov_freg64_stack32(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _offset: i32) {
todo!("loading floating point reg from stack for AArch64");
@ -606,6 +673,16 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
todo!("registers to float for AArch64");
}
#[inline(always)]
fn lte_reg64_reg64_reg64(
_buf: &mut Vec<'_, u8>,
_dst: AArch64GeneralReg,
_src1: AArch64GeneralReg,
_src2: AArch64GeneralReg,
) {
todo!("registers less than or equal for AArch64");
}
#[inline(always)]
fn gte_reg64_reg64_reg64(
_buf: &mut Vec<'_, u8>,

View file

@ -1,12 +1,15 @@
use crate::{single_register_floats, single_register_integers, Backend, Env, Relocation};
use crate::{
single_register_floats, single_register_int_builtins, single_register_integers, Backend, Env,
Relocation,
};
use bumpalo::collections::Vec;
use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
use roc_collections::all::MutMap;
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_mono::layout::{Builtin, Layout, TagIdIntType, UnionLayout};
use roc_target::TargetInfo;
use std::marker::PhantomData;
@ -16,8 +19,10 @@ pub(crate) mod x86_64;
use storage::StorageManager;
// TODO: on all number functions double check and deal with over/underflow.
pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait, ASM: Assembler<GeneralReg, FloatReg>>:
Sized
Sized + Copy
{
const BASE_PTR_REG: GeneralReg;
const STACK_PTR_REG: GeneralReg;
@ -72,7 +77,8 @@ pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait, ASM: Assembler<Gene
fn store_args<'a>(
buf: &mut Vec<'a, u8>,
storage_manager: &mut StorageManager<'a, GeneralReg, FloatReg, ASM, Self>,
args: &'a [Symbol],
dst: &Symbol,
args: &[Symbol],
arg_layouts: &[Layout<'a>],
// ret_layout is needed because if it is a complex type, we pass a pointer as the first arg.
ret_layout: &Layout<'a>,
@ -103,7 +109,7 @@ pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait, ASM: Assembler<Gene
/// Thus, some backends will need to use mulitiple instructions to preform a single one of this calls.
/// Generally, I prefer explicit sources, as opposed to dst being one of the sources. Ex: `x = x + y` would be `add x, x, y` instead of `add x, y`.
/// dst should always come before sources.
pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized {
pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
fn abs_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg);
fn abs_freg64_freg64(
buf: &mut Vec<'_, u8>,
@ -167,6 +173,26 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized {
fn mov_base32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg);
fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg);
fn mov_reg64_mem64_offset32(
buf: &mut Vec<'_, u8>,
dst: GeneralReg,
src: GeneralReg,
offset: i32,
);
fn mov_mem64_offset32_reg64(
buf: &mut Vec<'_, u8>,
dst: GeneralReg,
offset: i32,
src: GeneralReg,
);
/// Sign extends the data at `offset` with `size` as it copies it to `dst`
/// size must be less than or equal to 8.
fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32, size: u8);
/// Zero extends the data at `offset` with `size` as it copies it to `dst`
/// size must be less than or equal to 8.
fn movzx_reg64_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32, size: u8);
fn mov_freg64_stack32(buf: &mut Vec<'_, u8>, dst: FloatReg, offset: i32);
fn mov_reg64_stack32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32);
fn mov_stack32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg);
@ -217,6 +243,13 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized {
fn to_float_freg64_freg32(buf: &mut Vec<'_, u8>, dst: FloatReg, src: FloatReg);
fn lte_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
dst: GeneralReg,
src1: GeneralReg,
src2: GeneralReg,
);
fn gte_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
dst: GeneralReg,
@ -256,7 +289,7 @@ pub struct Backend64Bit<
free_map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>,
literal_map: MutMap<Symbol, (*const Literal<'a>, *const Layout<'a>)>,
join_map: MutMap<JoinPointId, u64>,
join_map: MutMap<JoinPointId, Vec<'a, (u64, u64)>>,
storage_manager: StorageManager<'a, GeneralReg, FloatReg, ASM, CC>,
}
@ -328,7 +361,6 @@ impl<
self.join_map.clear();
self.free_map.clear();
self.buf.clear();
self.helper_proc_symbols.clear();
self.storage_manager.reset();
}
@ -462,7 +494,7 @@ impl<
&mut self,
dst: &Symbol,
fn_name: String,
args: &'a [Symbol],
args: &[Symbol],
arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>,
) {
@ -479,6 +511,7 @@ impl<
CC::store_args(
&mut self.buf,
&mut self.storage_manager,
dst,
args,
arg_layouts,
ret_layout,
@ -523,17 +556,20 @@ impl<
.storage_manager
.load_to_general_reg(&mut self.buf, cond_symbol);
let mut base_storage = self.storage_manager.clone();
let mut max_branch_stack_size = 0;
let mut ret_jumps = bumpalo::vec![in self.env.arena];
let mut tmp = bumpalo::vec![in self.env.arena];
for (val, branch_info, stmt) in branches.iter() {
for (val, _branch_info, stmt) in branches.iter() {
// TODO: look into branch info and if it matters here.
tmp.clear();
if let BranchInfo::None = branch_info {
// Create jump to next branch if not cond_sym not equal to value.
// Create jump to next branch if cond_sym not equal to value.
// Since we don't know the offset yet, set it to 0 and overwrite later.
let jne_location = self.buf.len();
let start_offset = ASM::jne_reg64_imm64_imm32(&mut self.buf, cond_reg, *val, 0);
// Build all statements in this branch.
// Build all statements in this branch. Using storage as from before any branch.
self.storage_manager = base_storage.clone();
self.build_stmt(stmt, ret_layout);
// Build unconditional jump to the end of this switch.
@ -549,12 +585,16 @@ impl<
for (i, byte) in tmp.iter().enumerate() {
self.buf[jne_location + i] = *byte;
}
} else {
todo!("Switch: branch info, {:?}", branch_info);
// Update important storage information to avoid overwrites.
max_branch_stack_size =
std::cmp::max(max_branch_stack_size, self.storage_manager.stack_size());
base_storage.update_fn_call_stack_size(self.storage_manager.fn_call_stack_size());
}
}
let (branch_info, stmt) = default_branch;
if let BranchInfo::None = branch_info {
self.storage_manager = base_storage;
self.storage_manager
.update_stack_size(max_branch_stack_size);
let (_branch_info, stmt) = default_branch;
self.build_stmt(stmt, ret_layout);
// Update all return jumps to jump past the default case.
@ -567,9 +607,6 @@ impl<
ret_offset as u64,
);
}
} else {
todo!("Switch: branch info, {:?}", branch_info);
}
}
fn build_join(
@ -580,36 +617,41 @@ impl<
remainder: &'a Stmt<'a>,
ret_layout: &Layout<'a>,
) {
// Free everything to the stack to make sure they don't get messed up when looping back to this point.
// TODO: look into a nicer solution.
self.storage_manager.free_all_to_stack(&mut self.buf);
// Ensure all the joinpoint parameters have storage locations.
// On jumps to the joinpoint, we will overwrite those locations as a way to "pass parameters" to the joinpoint.
self.storage_manager
.setup_joinpoint(&mut self.buf, id, parameters);
// Create jump to remaining.
let jmp_location = self.buf.len();
let start_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678);
self.join_map.insert(*id, bumpalo::vec![in self.env.arena]);
// Build remainder of function first. It is what gets run and jumps to join.
self.build_stmt(remainder, ret_layout);
let join_location = self.buf.len() as u64;
// Build all statements in body.
self.join_map.insert(*id, self.buf.len() as u64);
self.build_stmt(body, ret_layout);
// Overwrite the original jump with the correct offset.
// Overwrite the all jumps to the joinpoint with the correct offset.
let mut tmp = bumpalo::vec![in self.env.arena];
self.update_jmp_imm32_offset(
&mut tmp,
jmp_location as u64,
start_offset as u64,
self.buf.len() as u64,
);
// Build remainder of function.
self.build_stmt(remainder, ret_layout)
for (jmp_location, start_offset) in self
.join_map
.remove(id)
.unwrap_or_else(|| internal_error!("join point not defined"))
{
tmp.clear();
self.update_jmp_imm32_offset(&mut tmp, jmp_location, start_offset, join_location);
}
}
fn build_jump(
&mut self,
id: &JoinPointId,
args: &'a [Symbol],
args: &[Symbol],
arg_layouts: &[Layout<'a>],
_ret_layout: &Layout<'a>,
) {
@ -619,15 +661,8 @@ impl<
let jmp_location = self.buf.len();
let start_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678);
if let Some(offset) = self.join_map.get(id) {
let offset = *offset;
let mut tmp = bumpalo::vec![in self.env.arena];
self.update_jmp_imm32_offset(
&mut tmp,
jmp_location as u64,
start_offset as u64,
offset,
);
if let Some(vec) = self.join_map.get_mut(id) {
vec.push((jmp_location as u64, start_offset as u64))
} else {
internal_error!("Jump: unknown point specified to jump to: {:?}", id);
}
@ -716,7 +751,7 @@ impl<
fn build_eq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &Layout<'a>) {
match arg_layout {
Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => {
Layout::Builtin(single_register_int_builtins!()) => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self
.storage_manager
@ -823,6 +858,28 @@ impl<
}
}
fn build_num_lte(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
arg_layout: &Layout<'a>,
) {
match arg_layout {
Layout::Builtin(single_register_int_builtins!()) => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self
.storage_manager
.load_to_general_reg(&mut self.buf, src1);
let src2_reg = self
.storage_manager
.load_to_general_reg(&mut self.buf, src2);
ASM::lte_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
}
x => todo!("NumLte: layout, {:?}", x),
}
}
fn build_num_gte(
&mut self,
dst: &Symbol,
@ -831,7 +888,7 @@ impl<
arg_layout: &Layout<'a>,
) {
match arg_layout {
Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => {
Layout::Builtin(single_register_int_builtins!()) => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self
.storage_manager
@ -845,13 +902,173 @@ impl<
}
}
fn build_list_len(&mut self, dst: &Symbol, list: &Symbol) {
self.storage_manager.list_len(&mut self.buf, dst, list);
}
fn build_list_get_unsafe(
&mut self,
dst: &Symbol,
list: &Symbol,
index: &Symbol,
ret_layout: &Layout<'a>,
) {
let (base_offset, _) = self.storage_manager.stack_offset_and_size(list);
let index_reg = self
.storage_manager
.load_to_general_reg(&mut self.buf, index);
let ret_stack_size = ret_layout.stack_size(self.storage_manager.target_info());
// TODO: This can be optimized with smarter instructions.
// Also can probably be moved into storage manager at least partly.
self.storage_manager.with_tmp_general_reg(
&mut self.buf,
|storage_manager, buf, list_ptr| {
ASM::mov_reg64_base32(buf, list_ptr, base_offset as i32);
storage_manager.with_tmp_general_reg(buf, |storage_manager, buf, tmp| {
ASM::mov_reg64_imm64(buf, tmp, ret_stack_size as i64);
ASM::imul_reg64_reg64_reg64(buf, tmp, tmp, index_reg);
ASM::add_reg64_reg64_reg64(buf, tmp, tmp, list_ptr);
match ret_layout {
single_register_integers!() if ret_stack_size == 8 => {
let dst_reg = storage_manager.claim_general_reg(buf, dst);
ASM::mov_reg64_mem64_offset32(buf, dst_reg, tmp, 0);
}
x => internal_error!("Loading list element with layout: {:?}", x),
}
});
},
);
}
fn build_list_replace_unsafe(
&mut self,
dst: &Symbol,
args: &'a [Symbol],
arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>,
) {
// We want to delegate to the zig builtin, but it takes some extra parameters.
// Firstly, it takes the alignment of the list.
// Secondly, it takes the stack size of an element.
// Thirdly, it takes a pointer that it will write the output element to.
let list = args[0];
let list_layout = arg_layouts[0];
let index = args[1];
let index_layout = arg_layouts[1];
let elem = args[2];
let elem_layout = arg_layouts[2];
let u32_layout = &Layout::Builtin(Builtin::Int(IntWidth::U32));
let list_alignment = list_layout.alignment_bytes(self.storage_manager.target_info());
self.load_literal(
&Symbol::DEV_TMP,
u32_layout,
&Literal::Int(list_alignment as i128),
);
// Have to pass the input element by pointer, so put it on the stack and load it's address.
self.storage_manager
.ensure_symbol_on_stack(&mut self.buf, &elem);
let u64_layout = &Layout::Builtin(Builtin::Int(IntWidth::U64));
let (new_elem_offset, _) = self.storage_manager.stack_offset_and_size(&elem);
// Load address of output element into register.
let reg = self
.storage_manager
.claim_general_reg(&mut self.buf, &Symbol::DEV_TMP2);
ASM::add_reg64_reg64_imm32(&mut self.buf, reg, CC::BASE_PTR_REG, new_elem_offset);
// Load the elements size.
let elem_stack_size = elem_layout.stack_size(self.storage_manager.target_info());
self.load_literal(
&Symbol::DEV_TMP3,
u64_layout,
&Literal::Int(elem_stack_size as i128),
);
// Setup the return location.
let base_offset = self.storage_manager.claim_stack_area(
dst,
ret_layout.stack_size(self.storage_manager.target_info()),
);
let ret_fields = if let Layout::Struct { field_layouts, .. } = ret_layout {
field_layouts
} else {
internal_error!(
"Expected replace to return a struct instead found: {:?}",
ret_layout
)
};
// Only return list and old element.
debug_assert_eq!(ret_fields.len(), 2);
let (out_list_offset, out_elem_offset) = if ret_fields[0] == elem_layout {
(
base_offset + ret_fields[0].stack_size(self.storage_manager.target_info()) as i32,
base_offset,
)
} else {
(
base_offset,
base_offset + ret_fields[0].stack_size(self.storage_manager.target_info()) as i32,
)
};
// Load address of output element into register.
let reg = self
.storage_manager
.claim_general_reg(&mut self.buf, &Symbol::DEV_TMP4);
ASM::add_reg64_reg64_imm32(&mut self.buf, reg, CC::BASE_PTR_REG, out_elem_offset);
let lowlevel_args = bumpalo::vec![
in self.env.arena;
list,
Symbol::DEV_TMP,
index,
Symbol::DEV_TMP2,
Symbol::DEV_TMP3,
Symbol::DEV_TMP4,
];
let lowlevel_arg_layouts = bumpalo::vec![
in self.env.arena;
list_layout,
*u32_layout,
index_layout,
*u64_layout,
*u64_layout,
*u64_layout,
];
self.build_fn_call(
&Symbol::DEV_TMP5,
bitcode::LIST_REPLACE.to_string(),
&lowlevel_args,
&lowlevel_arg_layouts,
&list_layout,
);
self.free_symbol(&Symbol::DEV_TMP);
self.free_symbol(&Symbol::DEV_TMP2);
self.free_symbol(&Symbol::DEV_TMP3);
self.free_symbol(&Symbol::DEV_TMP4);
// Copy from list to the output record.
self.storage_manager.copy_symbol_to_stack_offset(
&mut self.buf,
out_list_offset,
&Symbol::DEV_TMP5,
&list_layout,
);
self.free_symbol(&Symbol::DEV_TMP5);
}
fn build_ptr_cast(&mut self, dst: &Symbol, src: &Symbol) {
// We may not strictly need an instruction here.
// What's important is to load the value, and for src and dest to have different Layouts.
// This is used for pointer math in refcounting and for pointer equality
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src_reg = self.storage_manager.load_to_general_reg(&mut self.buf, src);
ASM::mov_reg64_reg64(&mut self.buf, dst_reg, src_reg);
self.storage_manager
.ensure_symbol_on_stack(&mut self.buf, src);
let (offset, _) = self.storage_manager.stack_offset_and_size(src);
ASM::add_reg64_reg64_imm32(&mut self.buf, dst_reg, CC::BASE_PTR_REG, offset);
}
fn create_struct(&mut self, sym: &Symbol, layout: &Layout<'a>, fields: &'a [Symbol]) {
@ -870,6 +1087,43 @@ impl<
.load_field_at_index(sym, structure, index, field_layouts);
}
fn load_union_at_index(
&mut self,
sym: &Symbol,
structure: &Symbol,
tag_id: TagIdIntType,
index: u64,
union_layout: &UnionLayout<'a>,
) {
match union_layout {
UnionLayout::NonRecursive(tag_layouts) | UnionLayout::Recursive(tag_layouts) => {
self.storage_manager.load_field_at_index(
sym,
structure,
index,
tag_layouts[tag_id as usize],
);
}
x => todo!("loading from union type: {:?}", x),
}
}
fn get_tag_id(&mut self, sym: &Symbol, structure: &Symbol, union_layout: &UnionLayout<'a>) {
self.storage_manager
.load_union_tag_id(&mut self.buf, sym, structure, union_layout);
}
fn tag(
&mut self,
sym: &Symbol,
fields: &'a [Symbol],
union_layout: &UnionLayout<'a>,
tag_id: TagIdIntType,
) {
self.storage_manager
.create_union(&mut self.buf, sym, union_layout, fields, tag_id)
}
fn load_literal(&mut self, sym: &Symbol, layout: &Layout<'a>, lit: &Literal<'a>) {
match (lit, layout) {
(
@ -994,11 +1248,23 @@ impl<
}
#[macro_export]
macro_rules! single_register_integers {
macro_rules! sign_extended_int_builtins {
() => {
Layout::Builtin(
Builtin::Bool
| Builtin::Int(
Builtin::Int(IntWidth::I8 | IntWidth::I16 | IntWidth::I32 | IntWidth::I64 | IntWidth::I128)
};
}
#[macro_export]
macro_rules! zero_extended_int_builtins {
() => {
Builtin::Int(IntWidth::U8 | IntWidth::U16 | IntWidth::U32 | IntWidth::U64 | IntWidth::U128)
};
}
#[macro_export]
macro_rules! single_register_int_builtins {
() => {
Builtin::Int(
IntWidth::I8
| IntWidth::I16
| IntWidth::I32
@ -1007,8 +1273,14 @@ macro_rules! single_register_integers {
| IntWidth::U16
| IntWidth::U32
| IntWidth::U64,
),
) | Layout::RecursivePointer
)
};
}
#[macro_export]
macro_rules! single_register_integers {
() => {
Layout::Builtin(Builtin::Bool | single_register_int_builtins!()) | Layout::RecursivePointer
};
}

View file

@ -1,6 +1,7 @@
use crate::{
generic64::{Assembler, CallConv, RegTrait},
single_register_floats, single_register_integers, single_register_layouts, Env,
sign_extended_int_builtins, single_register_floats, single_register_int_builtins,
single_register_integers, single_register_layouts, Env,
};
use bumpalo::collections::Vec;
use roc_builtins::bitcode::{FloatWidth, IntWidth};
@ -9,7 +10,7 @@ use roc_error_macros::internal_error;
use roc_module::symbol::Symbol;
use roc_mono::{
ir::{JoinPointId, Param},
layout::{Builtin, Layout},
layout::{Builtin, Layout, TagIdIntType, UnionLayout},
};
use roc_target::TargetInfo;
use std::cmp::max;
@ -48,6 +49,9 @@ enum StackStorage<GeneralReg: RegTrait, FloatReg: RegTrait> {
base_offset: i32,
// Size on the stack in bytes.
size: u32,
// Whether or not the data is need to be sign extended on load.
// If not, it must be zero extended.
sign_extend: bool,
},
/// Complex data (lists, unions, structs, str) stored on the stack.
/// Note, this is also used for referencing a value within a struct/union.
@ -72,6 +76,7 @@ enum Storage<GeneralReg: RegTrait, FloatReg: RegTrait> {
NoData,
}
#[derive(Clone)]
pub struct StorageManager<
'a,
GeneralReg: RegTrait,
@ -177,6 +182,10 @@ impl<
self.fn_call_stack_size = 0;
}
pub fn target_info(&self) -> TargetInfo {
self.target_info
}
pub fn stack_size(&self) -> u32 {
self.stack_size
}
@ -323,20 +332,22 @@ impl<
);
reg
}
Stack(ReferencedPrimitive { base_offset, size })
if base_offset % 8 == 0 && size == 8 =>
{
// The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
Stack(ReferencedPrimitive {
base_offset,
size,
sign_extend,
}) => {
let reg = self.get_general_reg(buf);
ASM::mov_reg64_base32(buf, reg, base_offset);
if sign_extend {
ASM::movsx_reg64_base32(buf, reg, base_offset, size as u8);
} else {
ASM::movzx_reg64_base32(buf, reg, base_offset, size as u8);
}
self.general_used_regs.push((reg, *sym));
self.symbol_storage_map.insert(*sym, Reg(General(reg)));
self.free_reference(sym);
reg
}
Stack(ReferencedPrimitive { .. }) => {
todo!("loading referenced primitives")
}
Stack(Complex { .. }) => {
internal_error!("Cannot load large values into general registers: {}", sym)
}
@ -385,9 +396,9 @@ impl<
);
reg
}
Stack(ReferencedPrimitive { base_offset, size })
if base_offset % 8 == 0 && size == 8 =>
{
Stack(ReferencedPrimitive {
base_offset, size, ..
}) if base_offset % 8 == 0 && size == 8 => {
// The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
let reg = self.get_float_reg(buf);
ASM::mov_freg64_base32(buf, reg, base_offset);
@ -444,9 +455,9 @@ impl<
debug_assert_eq!(base_offset % 8, 0);
ASM::mov_reg64_base32(buf, reg, *base_offset);
}
Stack(ReferencedPrimitive { base_offset, size })
if base_offset % 8 == 0 && *size == 8 =>
{
Stack(ReferencedPrimitive {
base_offset, size, ..
}) if base_offset % 8 == 0 && *size == 8 => {
// The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
ASM::mov_reg64_base32(buf, reg, *base_offset);
}
@ -493,9 +504,9 @@ impl<
debug_assert_eq!(base_offset % 8, 0);
ASM::mov_freg64_base32(buf, reg, *base_offset);
}
Stack(ReferencedPrimitive { base_offset, size })
if base_offset % 8 == 0 && *size == 8 =>
{
Stack(ReferencedPrimitive {
base_offset, size, ..
}) if base_offset % 8 == 0 && *size == 8 => {
// The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
ASM::mov_freg64_base32(buf, reg, *base_offset);
}
@ -522,11 +533,7 @@ impl<
) {
debug_assert!(index < field_layouts.len() as u64);
// This must be removed and reinserted for ownership and mutability reasons.
let owned_data = if let Some(owned_data) = self.allocation_map.remove(structure) {
owned_data
} else {
internal_error!("Unknown symbol: {}", structure);
};
let owned_data = self.remove_allocation_for_sym(structure);
self.allocation_map
.insert(*structure, Rc::clone(&owned_data));
match self.get_storage_for_sym(structure) {
@ -538,15 +545,19 @@ impl<
data_offset += field_size as i32;
}
debug_assert!(data_offset < base_offset + size as i32);
self.allocation_map.insert(*sym, owned_data);
let layout = field_layouts[index as usize];
let size = layout.stack_size(self.target_info);
self.allocation_map.insert(*sym, owned_data);
self.symbol_storage_map.insert(
*sym,
Stack(if is_primitive(&layout) {
ReferencedPrimitive {
base_offset: data_offset,
size,
sign_extend: matches!(
layout,
Layout::Builtin(sign_extended_int_builtins!())
),
}
} else {
Complex {
@ -565,6 +576,57 @@ impl<
}
}
pub fn load_union_tag_id(
&mut self,
_buf: &mut Vec<'a, u8>,
sym: &Symbol,
structure: &Symbol,
union_layout: &UnionLayout<'a>,
) {
// This must be removed and reinserted for ownership and mutability reasons.
let owned_data = self.remove_allocation_for_sym(structure);
self.allocation_map
.insert(*structure, Rc::clone(&owned_data));
match union_layout {
UnionLayout::NonRecursive(_) => {
let (union_offset, _) = self.stack_offset_and_size(structure);
let (data_size, data_alignment) =
union_layout.data_size_and_alignment(self.target_info);
let id_offset = data_size - data_alignment;
let id_builtin = union_layout.tag_id_builtin();
let size = id_builtin.stack_size(self.target_info);
self.allocation_map.insert(*sym, owned_data);
self.symbol_storage_map.insert(
*sym,
Stack(ReferencedPrimitive {
base_offset: union_offset + id_offset as i32,
size,
sign_extend: matches!(id_builtin, sign_extended_int_builtins!()),
}),
);
}
x => todo!("getting tag id of union with layout ({:?})", x),
}
}
// Loads the dst to be the later 64 bits of a list (its length).
pub fn list_len(&mut self, _buf: &mut Vec<'a, u8>, dst: &Symbol, list: &Symbol) {
let owned_data = self.remove_allocation_for_sym(list);
self.allocation_map.insert(*list, Rc::clone(&owned_data));
self.allocation_map.insert(*dst, owned_data);
let (list_offset, _) = self.stack_offset_and_size(list);
self.symbol_storage_map.insert(
*dst,
Stack(ReferencedPrimitive {
base_offset: list_offset + 8,
size: 8,
sign_extend: false,
}),
);
}
/// Creates a struct on the stack, moving the data in fields into the struct.
pub fn create_struct(
&mut self,
@ -580,7 +642,7 @@ impl<
}
let base_offset = self.claim_stack_area(sym, struct_size);
if let Layout::Struct(field_layouts) = layout {
if let Layout::Struct { field_layouts, .. } = layout {
let mut current_offset = base_offset;
for (field, field_layout) in fields.iter().zip(field_layouts.iter()) {
self.copy_symbol_to_stack_offset(buf, current_offset, field, field_layout);
@ -594,11 +656,66 @@ impl<
}
}
/// Creates a union on the stack, moving the data in fields into the union and tagging it.
pub fn create_union(
&mut self,
buf: &mut Vec<'a, u8>,
sym: &Symbol,
union_layout: &UnionLayout<'a>,
fields: &'a [Symbol],
tag_id: TagIdIntType,
) {
match union_layout {
UnionLayout::NonRecursive(field_layouts) => {
let (data_size, data_alignment) =
union_layout.data_size_and_alignment(self.target_info);
let id_offset = data_size - data_alignment;
if data_alignment < 8 || data_alignment % 8 != 0 {
todo!("small/unaligned tagging");
}
let base_offset = self.claim_stack_area(sym, data_size);
let mut current_offset = base_offset;
for (field, field_layout) in
fields.iter().zip(field_layouts[tag_id as usize].iter())
{
self.copy_symbol_to_stack_offset(buf, current_offset, field, field_layout);
let field_size = field_layout.stack_size(self.target_info);
current_offset += field_size as i32;
}
self.with_tmp_general_reg(buf, |_symbol_storage, buf, reg| {
ASM::mov_reg64_imm64(buf, reg, tag_id as i64);
debug_assert!((base_offset + id_offset as i32) % 8 == 0);
ASM::mov_base32_reg64(buf, base_offset + id_offset as i32, reg);
});
}
x => todo!("creating unions with layout: {:?}", x),
}
}
/// Copies a complex symbol on the stack to the arg pointer.
pub fn copy_symbol_to_arg_pointer(
&mut self,
buf: &mut Vec<'a, u8>,
sym: &Symbol,
_layout: &Layout<'a>,
) {
let ret_reg = self.load_to_general_reg(buf, &Symbol::RET_POINTER);
let (base_offset, size) = self.stack_offset_and_size(sym);
debug_assert!(base_offset % 8 == 0);
debug_assert!(size % 8 == 0);
self.with_tmp_general_reg(buf, |_storage_manager, buf, tmp_reg| {
for i in (0..size as i32).step_by(8) {
ASM::mov_reg64_base32(buf, tmp_reg, base_offset + i);
ASM::mov_mem64_offset32_reg64(buf, ret_reg, i, tmp_reg);
}
});
}
/// Copies a symbol to the specified stack offset. This is used for things like filling structs.
/// The offset is not guarenteed to be perfectly aligned, it follows Roc's alignment plan.
/// This means that, for example 2 I32s might be back to back on the stack.
/// Always interact with the stack using aligned 64bit movement.
fn copy_symbol_to_stack_offset(
pub fn copy_symbol_to_stack_offset(
&mut self,
buf: &mut Vec<'a, u8>,
to_offset: i32,
@ -616,32 +733,33 @@ impl<
let reg = self.load_to_float_reg(buf, sym);
ASM::mov_base32_freg64(buf, to_offset, reg);
}
// Layout::Struct(_) if layout.safe_to_memcpy() => {
// // self.storage_manager.with_tmp_float_reg(&mut self.buf, |buf, storage, )
// // if let Some(SymbolStorage::Base {
// // offset: from_offset,
// // size,
// // ..
// // }) = self.symbol_storage_map.get(sym)
// // {
// // debug_assert_eq!(
// // *size,
// // layout.stack_size(self.target_info),
// // "expected struct to have same size as data being stored in it"
// // );
// // for i in 0..layout.stack_size(self.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);
// // }
// todo!()
// } else {
// internal_error!("unknown struct: {:?}", sym);
// }
// }
Layout::Builtin(Builtin::Str | Builtin::List(_)) => {
let (from_offset, _) = self.stack_offset_and_size(sym);
self.with_tmp_general_reg(buf, |_storage_manager, buf, reg| {
ASM::mov_reg64_base32(buf, reg, from_offset);
ASM::mov_base32_reg64(buf, to_offset, reg);
ASM::mov_reg64_base32(buf, reg, from_offset + 8);
ASM::mov_base32_reg64(buf, to_offset + 8, reg);
});
}
_ if layout.stack_size(self.target_info) == 0 => {}
_ if layout.safe_to_memcpy() && layout.stack_size(self.target_info) > 8 => {
let (from_offset, size) = self.stack_offset_and_size(sym);
debug_assert!(from_offset % 8 == 0);
debug_assert!(size % 8 == 0);
debug_assert_eq!(size, layout.stack_size(self.target_info));
self.with_tmp_general_reg(buf, |_storage_manager, buf, reg| {
for i in (0..size as i32).step_by(8) {
ASM::mov_reg64_base32(buf, reg, from_offset + i);
ASM::mov_base32_reg64(buf, to_offset + i, reg);
}
});
}
x => todo!("copying data to the stack with layout, {:?}", x),
}
}
#[allow(dead_code)]
/// Ensures that a register is free. If it is not free, data will be moved to make it free.
fn ensure_reg_free(
&mut self,
@ -690,6 +808,58 @@ impl<
}
}
pub fn ensure_symbol_on_stack(&mut self, buf: &mut Vec<'a, u8>, sym: &Symbol) {
match self.remove_storage_for_sym(sym) {
Reg(reg_storage) => {
let base_offset = self.claim_stack_size(8);
match reg_storage {
General(reg) => ASM::mov_base32_reg64(buf, base_offset, reg),
Float(reg) => ASM::mov_base32_freg64(buf, base_offset, reg),
}
self.symbol_storage_map.insert(
*sym,
Stack(Primitive {
base_offset,
reg: Some(reg_storage),
}),
);
}
x => {
self.symbol_storage_map.insert(*sym, x);
}
}
}
/// Frees all symbols to the stack setuping up a clean slate.
pub fn free_all_to_stack(&mut self, buf: &mut Vec<'a, u8>) {
let mut free_list = bumpalo::vec![in self.env.arena];
for (sym, storage) in self.symbol_storage_map.iter() {
match storage {
Reg(reg_storage)
| Stack(Primitive {
reg: Some(reg_storage),
..
}) => {
free_list.push((*sym, *reg_storage));
}
_ => {}
}
}
for (sym, reg_storage) in free_list {
match reg_storage {
General(reg) => {
self.general_free_regs.push(reg);
self.general_used_regs.retain(|(r, _)| *r != reg);
}
Float(reg) => {
self.float_free_regs.push(reg);
self.float_used_regs.retain(|(r, _)| *r != reg);
}
}
self.free_to_stack(buf, &sym, reg_storage);
}
}
/// Frees `wanted_reg` which is currently owned by `sym` by making sure the value is loaded on the stack.
/// Note, used and free regs are expected to be updated outside of this function.
fn free_to_stack(
@ -739,9 +909,12 @@ impl<
pub fn stack_offset_and_size(&self, sym: &Symbol) -> (i32, u32) {
match self.get_storage_for_sym(sym) {
Stack(Primitive { base_offset, .. }) => (*base_offset, 8),
Stack(ReferencedPrimitive { base_offset, size } | Complex { base_offset, size }) => {
(*base_offset, *size)
Stack(
ReferencedPrimitive {
base_offset, size, ..
}
| Complex { base_offset, size },
) => (*base_offset, *size),
storage => {
internal_error!(
"Data not on the stack for sym ({}) with storage ({:?})",
@ -775,12 +948,33 @@ impl<
reg: None,
}),
);
self.allocation_map.insert(*sym, Rc::new((base_offset, 8)));
}
/// Specifies a complex is loaded at the specific base offset.
pub fn complex_stack_arg(&mut self, sym: &Symbol, base_offset: i32, size: u32) {
self.symbol_storage_map
.insert(*sym, Stack(Complex { base_offset, size }));
self.allocation_map
.insert(*sym, Rc::new((base_offset, size)));
}
/// Specifies a no data exists.
pub fn no_data_arg(&mut self, sym: &Symbol) {
self.symbol_storage_map.insert(*sym, NoData);
}
/// Loads the arg pointer symbol to the specified general reg.
pub fn ret_pointer_arg(&mut self, reg: GeneralReg) {
self.symbol_storage_map
.insert(Symbol::RET_POINTER, Reg(General(reg)));
self.general_free_regs.retain(|x| *x != reg);
self.general_used_regs.push((reg, Symbol::RET_POINTER));
}
/// updates the stack size to the max of its current value and the tmp size needed.
pub fn update_stack_size(&mut self, tmp_size: u32) {
self.stack_size = max(self.stack_size, tmp_size);
}
/// updates the function call stack size to the max of its current value and the size need for this call.
@ -794,7 +988,7 @@ impl<
/// Later jumps to the join point can overwrite the stored locations to pass parameters.
pub fn setup_joinpoint(
&mut self,
buf: &mut Vec<'a, u8>,
_buf: &mut Vec<'a, u8>,
id: &JoinPointId,
params: &'a [Param<'a>],
) {
@ -812,12 +1006,19 @@ impl<
todo!("joinpoints with borrowed parameters");
}
// Claim a location for every join point parameter to be loaded at.
// Put everything on the stack for simplicity.
match layout {
single_register_integers!() => {
self.claim_general_reg(buf, symbol);
}
single_register_floats!() => {
self.claim_float_reg(buf, symbol);
single_register_layouts!() => {
let base_offset = self.claim_stack_size(8);
self.symbol_storage_map.insert(
*symbol,
Stack(Primitive {
base_offset,
reg: None,
}),
);
self.allocation_map
.insert(*symbol, Rc::new((base_offset, 8)));
}
_ => {
let stack_size = layout.stack_size(self.target_info);
@ -839,7 +1040,7 @@ impl<
&mut self,
buf: &mut Vec<'a, u8>,
id: &JoinPointId,
args: &'a [Symbol],
args: &[Symbol],
arg_layouts: &[Layout<'a>],
) {
// TODO: remove was use here and for current_storage to deal with borrow checker.
@ -856,28 +1057,45 @@ impl<
continue;
}
match wanted_storage {
Reg(General(reg)) => {
// Ensure the reg is free, if not free it.
self.ensure_reg_free(buf, General(*reg));
// Copy the value over to the reg.
self.load_to_specified_general_reg(buf, sym, *reg)
Reg(_) => {
internal_error!("Register storage is not allowed for jumping to joinpoint")
}
Reg(Float(reg)) => {
// Ensure the reg is free, if not free it.
self.ensure_reg_free(buf, Float(*reg));
// Copy the value over to the reg.
self.load_to_specified_float_reg(buf, sym, *reg)
}
Stack(ReferencedPrimitive { base_offset, .. } | Complex { base_offset, .. }) => {
Stack(Complex { base_offset, .. }) => {
// TODO: This might be better not to call.
// Maybe we want a more memcpy like method to directly get called here.
// That would also be capable of asserting the size.
// Maybe copy stack to stack or something.
self.copy_symbol_to_stack_offset(buf, *base_offset, sym, layout);
}
Stack(Primitive {
base_offset,
reg: None,
}) => match layout {
single_register_integers!() => {
let reg = self.load_to_general_reg(buf, sym);
ASM::mov_base32_reg64(buf, *base_offset, reg);
}
single_register_floats!() => {
let reg = self.load_to_float_reg(buf, sym);
ASM::mov_base32_freg64(buf, *base_offset, reg);
}
_ => {
internal_error!(
"cannot load non-primitive layout ({:?}) to primitive stack location",
layout
);
}
},
NoData => {}
Stack(Primitive { .. }) => {
internal_error!("Primitive stack storage is not allowed for jumping")
Stack(Primitive { reg: Some(_), .. }) => {
internal_error!(
"primitives with register storage are not allowed for jumping to joinpoint"
)
}
Stack(ReferencedPrimitive { .. }) => {
internal_error!(
"referenced primitive stack storage is not allowed for jumping to joinpoint"
)
}
}
}
@ -973,11 +1191,7 @@ impl<
/// Frees an reference and release an allocation if it is no longer used.
fn free_reference(&mut self, sym: &Symbol) {
let owned_data = if let Some(owned_data) = self.allocation_map.remove(sym) {
owned_data
} else {
internal_error!("Unknown symbol: {:?}", sym);
};
let owned_data = self.remove_allocation_for_sym(sym);
if Rc::strong_count(&owned_data) == 1 {
self.free_stack_chunk(owned_data.0, owned_data.1);
}
@ -1060,7 +1274,26 @@ impl<
}
}
/// Gets a value from storage. They index symbol must be defined.
#[allow(dead_code)]
/// Gets the allocated area for a symbol. The index symbol must be defined.
fn get_allocation_for_sym(&self, sym: &Symbol) -> &Rc<(i32, u32)> {
if let Some(allocation) = self.allocation_map.get(sym) {
allocation
} else {
internal_error!("Unknown symbol: {:?}", sym);
}
}
/// Removes and returns the allocated area for a symbol. They index symbol must be defined.
fn remove_allocation_for_sym(&mut self, sym: &Symbol) -> Rc<(i32, u32)> {
if let Some(allocation) = self.allocation_map.remove(sym) {
allocation
} else {
internal_error!("Unknown symbol: {:?}", sym);
}
}
/// Gets a value from storage. The index symbol must be defined.
fn get_storage_for_sym(&self, sym: &Symbol) -> &Storage<GeneralReg, FloatReg> {
if let Some(storage) = self.symbol_storage_map.get(sym) {
storage

View file

@ -1,6 +1,7 @@
use crate::generic64::{storage::StorageManager, Assembler, CallConv, RegTrait};
use crate::{
single_register_floats, single_register_integers, single_register_layouts, Relocation,
single_register_floats, single_register_int_builtins, single_register_integers,
single_register_layouts, Relocation,
};
use bumpalo::collections::Vec;
use roc_builtins::bitcode::{FloatWidth, IntWidth};
@ -63,8 +64,11 @@ impl RegTrait for X86_64FloatReg {
}
}
#[derive(Copy, Clone)]
pub struct X86_64Assembler {}
#[derive(Copy, Clone)]
pub struct X86_64WindowsFastcall {}
#[derive(Copy, Clone)]
pub struct X86_64SystemV {}
const STACK_ALIGNMENT: u8 = 16;
@ -215,6 +219,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
general_i += 1;
}
for (layout, sym) in args.iter() {
let stack_size = layout.stack_size(TARGET_INFO);
match layout {
single_register_integers!() => {
if general_i < Self::GENERAL_PARAM_REGS.len() {
@ -247,7 +252,14 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
todo!("loading lists and strings args on the stack");
}
}
x if x.stack_size(TARGET_INFO) == 0 => {}
_ if stack_size == 0 => {
storage_manager.no_data_arg(sym);
}
_ if stack_size > 16 => {
// TODO: Double check this.
storage_manager.complex_stack_arg(sym, arg_offset, stack_size);
arg_offset += stack_size as i32;
}
x => {
todo!("Loading args with layout {:?}", x);
}
@ -265,19 +277,28 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
X86_64Assembler,
X86_64SystemV,
>,
args: &'a [Symbol],
dst: &Symbol,
args: &[Symbol],
arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>,
) {
let mut tmp_stack_offset = Self::SHADOW_SPACE_SIZE as i32;
if Self::returns_via_arg_pointer(ret_layout) {
// Save space on the stack for the arg we will return.
storage_manager
.claim_stack_area(&Symbol::RET_POINTER, ret_layout.stack_size(TARGET_INFO));
todo!("claim first parama reg for the address");
}
let mut general_i = 0;
let mut float_i = 0;
if Self::returns_via_arg_pointer(ret_layout) {
// Save space on the stack for the result we will be return.
let base_offset =
storage_manager.claim_stack_area(dst, ret_layout.stack_size(TARGET_INFO));
// Set the first reg to the address base + offset.
let ret_reg = Self::GENERAL_PARAM_REGS[general_i];
general_i += 1;
X86_64Assembler::add_reg64_reg64_imm32(
buf,
ret_reg,
X86_64GeneralReg::RBP,
base_offset,
);
}
for (sym, layout) in args.iter().zip(arg_layouts.iter()) {
match layout {
single_register_integers!() => {
@ -326,7 +347,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
tmp_stack_offset += 8;
}
}
Layout::Builtin(Builtin::Str) => {
Layout::Builtin(Builtin::Str | Builtin::List(_)) => {
if general_i + 1 < Self::GENERAL_PARAM_REGS.len() {
let (base_offset, _size) = storage_manager.stack_offset_and_size(sym);
debug_assert_eq!(base_offset % 8, 0);
@ -346,6 +367,19 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
}
}
x if x.stack_size(TARGET_INFO) == 0 => {}
x if x.stack_size(TARGET_INFO) > 16 => {
// TODO: Double check this.
// Just copy onto the stack.
let (base_offset, size) = storage_manager.stack_offset_and_size(sym);
debug_assert_eq!(base_offset % 8, 0);
storage_manager.with_tmp_general_reg(buf, |_storage_manager, buf, reg| {
for i in (0..size as i32).step_by(8) {
X86_64Assembler::mov_reg64_base32(buf, reg, base_offset + i);
X86_64Assembler::mov_stack32_reg64(buf, tmp_stack_offset + i, reg);
}
});
tmp_stack_offset += size as i32;
}
x => {
todo!("calling with arg type, {:?}", x);
}
@ -381,7 +415,42 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
);
}
x if x.stack_size(TARGET_INFO) == 0 => {}
x => todo!("returning complex type, {:?}", x),
x if !Self::returns_via_arg_pointer(x) => {
let (base_offset, size) = storage_manager.stack_offset_and_size(sym);
debug_assert_eq!(base_offset % 8, 0);
if size <= 8 {
X86_64Assembler::mov_reg64_base32(
buf,
Self::GENERAL_RETURN_REGS[0],
base_offset,
);
} else if size <= 16 {
X86_64Assembler::mov_reg64_base32(
buf,
Self::GENERAL_RETURN_REGS[0],
base_offset,
);
X86_64Assembler::mov_reg64_base32(
buf,
Self::GENERAL_RETURN_REGS[1],
base_offset + 8,
);
} else {
internal_error!(
"types that don't return via arg pointer must be less than 16 bytes"
);
}
}
_ => {
// This is a large type returned via the arg pointer.
storage_manager.copy_symbol_to_arg_pointer(buf, sym, layout);
// Also set the return reg to the arg pointer.
storage_manager.load_to_specified_general_reg(
buf,
&Symbol::RET_POINTER,
Self::GENERAL_RETURN_REGS[0],
);
}
}
}
@ -407,7 +476,29 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
X86_64Assembler::mov_base32_reg64(buf, offset + 8, Self::GENERAL_RETURN_REGS[1]);
}
x if x.stack_size(TARGET_INFO) == 0 => {}
x => todo!("receiving complex return type, {:?}", x),
x if !Self::returns_via_arg_pointer(x) => {
let size = layout.stack_size(TARGET_INFO);
let offset = storage_manager.claim_stack_area(sym, size);
if size <= 8 {
X86_64Assembler::mov_base32_reg64(buf, offset, Self::GENERAL_RETURN_REGS[0]);
} else if size <= 16 {
X86_64Assembler::mov_base32_reg64(buf, offset, Self::GENERAL_RETURN_REGS[0]);
X86_64Assembler::mov_base32_reg64(
buf,
offset + 8,
Self::GENERAL_RETURN_REGS[1],
);
} else {
internal_error!(
"types that don't return via arg pointer must be less than 16 bytes"
);
}
}
_ => {
// This should have been recieved via an arg pointer.
// That means the value is already loaded onto the stack area we allocated before the call.
// Nothing to do.
}
}
}
}
@ -612,15 +703,15 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Windo
X86_64Assembler,
X86_64WindowsFastcall,
>,
args: &'a [Symbol],
dst: &Symbol,
args: &[Symbol],
arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>,
) {
let mut tmp_stack_offset = Self::SHADOW_SPACE_SIZE as i32;
if Self::returns_via_arg_pointer(ret_layout) {
// Save space on the stack for the arg we will return.
storage_manager
.claim_stack_area(&Symbol::RET_POINTER, ret_layout.stack_size(TARGET_INFO));
storage_manager.claim_stack_area(dst, ret_layout.stack_size(TARGET_INFO));
todo!("claim first parama reg for the address");
}
for (i, (sym, layout)) in args.iter().zip(arg_layouts.iter()).enumerate() {
@ -669,7 +760,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Windo
tmp_stack_offset += 8;
}
}
Layout::Builtin(Builtin::Str) => {
Layout::Builtin(Builtin::Str | Builtin::List(_)) => {
// I think this just needs to be passed on the stack, so not a huge deal.
todo!("Passing str args with Windows fast call");
}
@ -988,6 +1079,56 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
mov_base64_offset32_reg64(buf, X86_64GeneralReg::RBP, offset, src)
}
#[inline(always)]
fn mov_reg64_mem64_offset32(
buf: &mut Vec<'_, u8>,
dst: X86_64GeneralReg,
src: X86_64GeneralReg,
offset: i32,
) {
mov_reg64_base64_offset32(buf, dst, src, offset)
}
#[inline(always)]
fn mov_mem64_offset32_reg64(
buf: &mut Vec<'_, u8>,
dst: X86_64GeneralReg,
offset: i32,
src: X86_64GeneralReg,
) {
mov_base64_offset32_reg64(buf, dst, offset, src)
}
#[inline(always)]
fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32, size: u8) {
debug_assert!(size <= 8);
if size == 8 {
Self::mov_reg64_base32(buf, dst, offset);
} else if size == 4 {
todo!("sign extending 4 byte values");
} else if size == 2 {
todo!("sign extending 2 byte values");
} else if size == 1 {
todo!("sign extending 1 byte values");
} else {
internal_error!("Invalid size for sign extension: {}", size);
}
}
#[inline(always)]
fn movzx_reg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32, size: u8) {
debug_assert!(size <= 8);
if size == 8 {
Self::mov_reg64_base32(buf, dst, offset);
} else if size == 4 {
todo!("zero extending 4 byte values");
} else if size == 2 {
todo!("zero extending 2 byte values");
} else if size == 1 {
movzx_reg64_base8_offset32(buf, dst, X86_64GeneralReg::RBP, offset);
} else {
internal_error!("Invalid size for zero extension: {}", size);
}
}
#[inline(always)]
fn mov_freg64_stack32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, offset: i32) {
movsd_freg64_base64_offset32(buf, dst, X86_64GeneralReg::RSP, offset)
@ -1091,6 +1232,17 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
cvtsi2sd_freg64_reg64(buf, dst, src);
}
#[inline(always)]
fn lte_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
dst: X86_64GeneralReg,
src1: X86_64GeneralReg,
src2: X86_64GeneralReg,
) {
cmp_reg64_reg64(buf, src1, src2);
setle_reg64(buf, dst);
}
#[inline(always)]
fn gte_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
@ -1378,6 +1530,27 @@ fn mov_reg64_base64_offset32(
buf.extend(&offset.to_le_bytes());
}
/// `MOVZX r64,r/m8` -> Move r/m8 with zero extention to r64, where m8 references a base + offset.
#[inline(always)]
fn movzx_reg64_base8_offset32(
buf: &mut Vec<'_, u8>,
dst: X86_64GeneralReg,
base: X86_64GeneralReg,
offset: i32,
) {
let rex = add_rm_extension(base, REX_W);
let rex = add_reg_extension(dst, rex);
let dst_mod = (dst as u8 % 8) << 3;
let base_mod = base as u8 % 8;
buf.reserve(9);
buf.extend(&[rex, 0x0F, 0xB6, 0x80 + dst_mod + base_mod]);
// Using RSP or R12 requires a secondary index byte.
if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 {
buf.push(0x24);
}
buf.extend(&offset.to_le_bytes());
}
/// `MOVSD xmm1,xmm2` -> Move scalar double-precision floating-point value from xmm2 to xmm1 register.
#[inline(always)]
fn movsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
@ -1429,7 +1602,7 @@ fn movsd_freg64_rip_offset32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, offset:
buf.extend(&offset.to_le_bytes());
}
/// `MOVSD r/m64,xmm1` -> Move xmm1 to r/m64. where m64 references the base pionter.
/// `MOVSD r/m64,xmm1` -> Move xmm1 to r/m64. where m64 references the base pointer.
#[inline(always)]
fn movsd_base64_offset32_freg64(
buf: &mut Vec<'_, u8>,
@ -1452,7 +1625,7 @@ fn movsd_base64_offset32_freg64(
buf.extend(&offset.to_le_bytes());
}
/// `MOVSD xmm1,r/m64` -> Move r/m64 to xmm1. where m64 references the base pionter.
/// `MOVSD xmm1,r/m64` -> Move r/m64 to xmm1. where m64 references the base pointer.
#[inline(always)]
fn movsd_freg64_base64_offset32(
buf: &mut Vec<'_, u8>,
@ -1585,6 +1758,12 @@ fn setl_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
set_reg64_help(0x9c, buf, reg);
}
/// `SETLE r/m64` -> Set byte if less or equal (ZF=1 or SF≠ OF).
#[inline(always)]
fn setle_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
set_reg64_help(0x9e, buf, reg);
}
/// `SETGE r/m64` -> Set byte if greater or equal (SF=OF).
#[inline(always)]
fn setge_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
@ -2081,6 +2260,35 @@ mod tests {
}
}
#[test]
fn test_movzx_reg64_base8_offset32() {
let arena = bumpalo::Bump::new();
let mut buf = bumpalo::vec![in &arena];
for ((dst, src, offset), expected) in &[
(
(X86_64GeneralReg::RAX, X86_64GeneralReg::RBP, TEST_I32),
vec![0x48, 0x0F, 0xB6, 0x85],
),
(
(X86_64GeneralReg::R15, X86_64GeneralReg::RBP, TEST_I32),
vec![0x4C, 0x0F, 0xB6, 0xBD],
),
(
(X86_64GeneralReg::RAX, X86_64GeneralReg::RSP, TEST_I32),
vec![0x48, 0x0F, 0xB6, 0x84, 0x24],
),
(
(X86_64GeneralReg::R15, X86_64GeneralReg::RSP, TEST_I32),
vec![0x4C, 0x0F, 0xB6, 0xBC, 0x24],
),
] {
buf.clear();
movzx_reg64_base8_offset32(&mut buf, *dst, *src, *offset);
assert_eq!(expected, &buf[..expected.len()]);
assert_eq!(TEST_I32.to_le_bytes(), &buf[expected.len()..]);
}
}
#[test]
fn test_mov_reg64_stack32() {
let arena = bumpalo::Bump::new();

View file

@ -14,7 +14,7 @@ use roc_mono::ir::{
BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, Param, Proc, ProcLayout,
SelfRecursive, Stmt,
};
use roc_mono::layout::{Builtin, Layout, LayoutId, LayoutIds};
use roc_mono::layout::{Builtin, Layout, LayoutId, LayoutIds, TagIdIntType, UnionLayout};
mod generic64;
mod object_builder;
@ -233,7 +233,7 @@ trait Backend<'a> {
fn build_jump(
&mut self,
id: &JoinPointId,
args: &'a [Symbol],
args: &[Symbol],
arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>,
);
@ -277,13 +277,7 @@ trait Backend<'a> {
self.load_literal_symbols(arguments);
self.build_fn_call(sym, fn_name, arguments, arg_layouts, ret_layout)
} else {
self.build_inline_builtin(
sym,
*func_sym,
arguments,
arg_layouts,
ret_layout,
)
self.build_builtin(sym, *func_sym, arguments, arg_layouts, ret_layout)
}
}
@ -321,6 +315,29 @@ trait Backend<'a> {
} => {
self.load_struct_at_index(sym, structure, *index, field_layouts);
}
Expr::UnionAtIndex {
structure,
tag_id,
union_layout,
index,
} => {
self.load_union_at_index(sym, structure, *tag_id, *index, union_layout);
}
Expr::GetTagId {
structure,
union_layout,
} => {
self.get_tag_id(sym, structure, union_layout);
}
Expr::Tag {
tag_layout,
tag_id,
arguments,
..
} => {
self.load_literal_symbols(arguments);
self.tag(sym, arguments, tag_layout, *tag_id);
}
x => todo!("the expression, {:?}", x),
}
}
@ -501,6 +518,23 @@ trait Backend<'a> {
);
self.build_num_to_float(sym, &args[0], &arg_layouts[0], ret_layout)
}
LowLevel::NumLte => {
debug_assert_eq!(
2,
args.len(),
"NumLte: expected to have exactly two argument"
);
debug_assert_eq!(
arg_layouts[0], arg_layouts[1],
"NumLte: expected all arguments of to have the same layout"
);
debug_assert_eq!(
Layout::Builtin(Builtin::Bool),
*ret_layout,
"NumLte: expected to have return layout of type Bool"
);
self.build_num_lte(sym, &args[0], &args[1], &arg_layouts[0])
}
LowLevel::NumGte => {
debug_assert_eq!(
2,
@ -525,6 +559,30 @@ trait Backend<'a> {
arg_layouts,
ret_layout,
),
LowLevel::ListLen => {
debug_assert_eq!(
1,
args.len(),
"ListLen: expected to have exactly one argument"
);
self.build_list_len(sym, &args[0])
}
LowLevel::ListGetUnsafe => {
debug_assert_eq!(
2,
args.len(),
"ListGetUnsafe: expected to have exactly two arguments"
);
self.build_list_get_unsafe(sym, &args[0], &args[1], ret_layout)
}
LowLevel::ListReplaceUnsafe => {
debug_assert_eq!(
3,
args.len(),
"ListReplaceUnsafe: expected to have exactly three arguments"
);
self.build_list_replace_unsafe(sym, args, arg_layouts, ret_layout)
}
LowLevel::StrConcat => self.build_fn_call(
sym,
bitcode::STR_CONCAT.to_string(),
@ -558,8 +616,9 @@ trait Backend<'a> {
}
}
// inlines simple builtin functions that do not map directly to a low level
fn build_inline_builtin(
/// Builds a builtin functions that do not map directly to a low level
/// If the builtin is simple enough, it will be inlined.
fn build_builtin(
&mut self,
sym: &Symbol,
func_sym: Symbol,
@ -585,6 +644,14 @@ trait Backend<'a> {
self.build_eq(sym, &args[0], &Symbol::DEV_TMP, &arg_layouts[0]);
self.free_symbol(&Symbol::DEV_TMP)
}
Symbol::LIST_GET | Symbol::LIST_SET | Symbol::LIST_REPLACE => {
// TODO: This is probably simple enough to be worth inlining.
let layout_id = LayoutIds::default().get(func_sym, ret_layout);
let fn_name = self.symbol_to_string(func_sym, layout_id);
// Now that the arguments are needed, load them if they are literals.
self.load_literal_symbols(args);
self.build_fn_call(sym, fn_name, args, arg_layouts, ret_layout)
}
_ => todo!("the function, {:?}", func_sym),
}
}
@ -595,7 +662,7 @@ trait Backend<'a> {
&mut self,
dst: &Symbol,
fn_name: String,
args: &'a [Symbol],
args: &[Symbol],
arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>,
);
@ -633,6 +700,15 @@ trait Backend<'a> {
ret_layout: &Layout<'a>,
);
/// build_num_lte stores the result of `src1 <= src2` into dst.
fn build_num_lte(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
arg_layout: &Layout<'a>,
);
/// build_num_gte stores the result of `src1 >= src2` into dst.
fn build_num_gte(
&mut self,
@ -642,6 +718,27 @@ trait Backend<'a> {
arg_layout: &Layout<'a>,
);
/// build_list_len returns the length of a list.
fn build_list_len(&mut self, dst: &Symbol, list: &Symbol);
/// build_list_get_unsafe loads the element from the list at the index.
fn build_list_get_unsafe(
&mut self,
dst: &Symbol,
list: &Symbol,
index: &Symbol,
ret_layout: &Layout<'a>,
);
/// build_list_replace_unsafe returns the old element and new list with the list having the new element inserted.
fn build_list_replace_unsafe(
&mut self,
dst: &Symbol,
args: &'a [Symbol],
arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>,
);
/// build_refcount_getptr loads the pointer to the reference count of src into dst.
fn build_ptr_cast(&mut self, dst: &Symbol, src: &Symbol);
@ -677,6 +774,28 @@ trait Backend<'a> {
field_layouts: &'a [Layout<'a>],
);
/// load_union_at_index loads into `sym` the value at `index` for `tag_id`.
fn load_union_at_index(
&mut self,
sym: &Symbol,
structure: &Symbol,
tag_id: TagIdIntType,
index: u64,
union_layout: &UnionLayout<'a>,
);
/// get_tag_id loads the tag id from a the union.
fn get_tag_id(&mut self, sym: &Symbol, structure: &Symbol, union_layout: &UnionLayout<'a>);
/// tag sets the tag for a union.
fn tag(
&mut self,
sym: &Symbol,
args: &'a [Symbol],
tag_layout: &UnionLayout<'a>,
tag_id: TagIdIntType,
);
/// return_symbol moves a symbol to the correct return location for the backend and adds a jump to the end of the function.
fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>);
@ -831,15 +950,16 @@ trait Backend<'a> {
parameters,
body: continuation,
remainder,
id,
id: JoinPointId(sym),
..
} => {
join_map.insert(*id, parameters);
self.set_last_seen(*sym, stmt);
join_map.insert(JoinPointId(*sym), parameters);
for param in *parameters {
self.set_last_seen(param.symbol, stmt);
}
self.scan_ast(continuation);
self.scan_ast(remainder);
self.scan_ast(continuation);
}
Stmt::Jump(JoinPointId(sym), symbols) => {
if let Some(parameters) = join_map.get(&JoinPointId(*sym)) {
@ -848,7 +968,6 @@ trait Backend<'a> {
self.set_last_seen(param.symbol, stmt);
}
}
self.set_last_seen(*sym, stmt);
for sym in *symbols {
self.set_last_seen(*sym, stmt);
}

View file

@ -267,6 +267,27 @@ fn build_object<'a, B: Backend<'a>>(
helper_names_symbols_procs.push((fn_name, section_id, proc_id, proc));
continue;
}
} else {
// The symbol isn't defined yet and will just be used by other rc procs.
let section_id = output.add_section(
output.segment_name(StandardSegment::Text).to_vec(),
format!(".text.{:x}", sym.as_u64()).as_bytes().to_vec(),
SectionKind::Text,
);
let rc_symbol = Symbol {
name: fn_name.as_bytes().to_vec(),
value: 0,
size: 0,
kind: SymbolKind::Text,
scope: SymbolScope::Linkage,
weak: false,
section: SymbolSection::Section(section_id),
flags: SymbolFlags::None,
};
let proc_id = output.add_symbol(rc_symbol);
helper_names_symbols_procs.push((fn_name, section_id, proc_id, proc));
continue;
}
internal_error!("failed to create rc fn for symbol {:?}", sym);
}

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