Merge remote-tracking branch 'origin/trunk' into expect-dont-panic

This commit is contained in:
Richard Feldman 2022-01-30 20:22:26 -05:00
commit a55ff62e6c
No known key found for this signature in database
GPG key ID: 7E4127D1E4241798
501 changed files with 26570 additions and 12276 deletions

View file

@ -1,7 +1,4 @@
on:
pull_request:
paths-ignore:
- '**.md'
on: [pull_request]
name: Benchmarks
@ -20,13 +17,13 @@ jobs:
- uses: actions/checkout@v2
with:
ref: "trunk"
clean: "true"
clean: "true"
- name: Earthly version
run: earthly --version
- name: on trunk; prepare a self-contained benchmark folder
run: ./ci/safe-earthly.sh --build-arg BENCH_SUFFIX=trunk +prep-bench-folder
run: ./ci/safe-earthly.sh --build-arg BENCH_SUFFIX=trunk +prep-bench-folder
- uses: actions/checkout@v2
with:

View file

@ -1,7 +1,4 @@
on:
pull_request:
paths-ignore:
- '**.md'
on: [pull_request]
name: CI

2
.gitignore vendored
View file

@ -7,7 +7,7 @@ zig-cache
*.o
*.tmp
# llvm human-readable output
# llvm human-readable output
*.ll
*.bc

View file

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

View file

@ -15,7 +15,7 @@ To run the test suite (via `cargo test`), you additionally need to install:
* [`valgrind`](https://www.valgrind.org/) (needs special treatment to [install on macOS](https://stackoverflow.com/a/61359781)
Alternatively, you can use `cargo test --no-fail-fast` or `cargo test -p specific_tests` to skip over the valgrind failures & tests.
For debugging LLVM IR, we use [DebugIR](https://github.com/vaivaswatha/debugir). This dependency is only required to build with the `--debug` flag, and for normal developtment you should be fine without it.
For debugging LLVM IR, we use [DebugIR](https://github.com/vaivaswatha/debugir). This dependency is only required to build with the `--debug` flag, and for normal developtment you should be fine without it.
### libcxb libraries

View file

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

257
Cargo.lock generated
View file

@ -459,7 +459,7 @@ name = "cli_utils"
version = "0.1.0"
dependencies = [
"bumpalo",
"criterion",
"criterion 0.3.5 (git+https://github.com/Anton-4/criterion.rs)",
"rlimit",
"roc_cli",
"roc_collections",
@ -481,6 +481,17 @@ dependencies = [
"winapi",
]
[[package]]
name = "clipboard-win"
version = "4.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3db8340083d28acb43451166543b98c838299b7e0863621be53a338adceea0ed"
dependencies = [
"error-code",
"str-buf",
"winapi",
]
[[package]]
name = "cocoa"
version = "0.24.0"
@ -585,7 +596,7 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4423d79fed83ebd9ab81ec21fa97144300a961782158287dc9bf7eddac37ff0b"
dependencies = [
"clipboard-win",
"clipboard-win 3.1.1",
"objc",
"objc-foundation",
"objc_id",
@ -800,18 +811,44 @@ dependencies = [
[[package]]
name = "criterion"
version = "0.3.5"
source = "git+https://github.com/Anton-4/criterion.rs#3e46ad2b234e36928fb5234d36cf53b5837cbb87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1604dafd25fba2fe2d5895a9da139f8dc9b319a5fe5354ca137cbbce4e178d10"
dependencies = [
"atty",
"cast",
"clap 2.33.3",
"criterion-plot",
"criterion-plot 0.4.4",
"csv",
"itertools 0.10.1",
"lazy_static",
"num-traits",
"oorandom",
"plotters",
"plotters 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon",
"regex",
"serde",
"serde_cbor",
"serde_derive",
"serde_json",
"tinytemplate",
"walkdir",
]
[[package]]
name = "criterion"
version = "0.3.5"
source = "git+https://github.com/Anton-4/criterion.rs#3e46ad2b234e36928fb5234d36cf53b5837cbb87"
dependencies = [
"atty",
"cast",
"clap 2.33.3",
"criterion-plot 0.4.3",
"csv",
"itertools 0.10.1",
"lazy_static",
"num-traits",
"oorandom",
"plotters 0.3.1 (git+https://github.com/Anton-4/plotters)",
"rayon",
"regex",
"serde",
@ -831,6 +868,16 @@ dependencies = [
"itertools 0.9.0",
]
[[package]]
name = "criterion-plot"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d00996de9f2f7559f7f4dc286073197f83e92256a59ed395f9aac01fe717da57"
dependencies = [
"cast",
"itertools 0.10.1",
]
[[package]]
name = "crossbeam"
version = "0.8.1"
@ -1068,9 +1115,9 @@ dependencies = [
[[package]]
name = "dirs-next"
version = "1.0.2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf36e65a80337bea855cd4ef9b8401ffce06a7baedf2e85ec467b1ac3f6e82b6"
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
dependencies = [
"cfg-if 1.0.0",
"dirs-sys-next",
@ -1158,6 +1205,12 @@ version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "endian-type"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]]
name = "enumset"
version = "1.0.8"
@ -1211,6 +1264,16 @@ dependencies = [
"serde",
]
[[package]]
name = "error-code"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5115567ac25674e0043e472be13d14e537f37ea8aa4bdc4aef0c89add1db1ff"
dependencies = [
"libc",
"str-buf",
]
[[package]]
name = "fake-simd"
version = "0.1.2"
@ -1223,6 +1286,17 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
[[package]]
name = "fd-lock"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a16910e685088843d53132b04e0f10a571fdb193224fc589685b3ba1ce4cb03d"
dependencies = [
"cfg-if 1.0.0",
"libc",
"windows-sys",
]
[[package]]
name = "find-crate"
version = "0.6.3"
@ -2211,16 +2285,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c44922cb3dbb1c70b5e5f443d63b64363a898564d739ba5198e3a9138442868d"
[[package]]
name = "nix"
version = "0.17.0"
name = "nibble_vec"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363"
checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
dependencies = [
"bitflags",
"cc",
"cfg-if 0.1.10",
"libc",
"void",
"smallvec",
]
[[package]]
@ -2260,6 +2330,19 @@ dependencies = [
"memoffset",
]
[[package]]
name = "nix"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6"
dependencies = [
"bitflags",
"cc",
"cfg-if 1.0.0",
"libc",
"memoffset",
]
[[package]]
name = "nom"
version = "5.1.2"
@ -2731,6 +2814,19 @@ version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f"
[[package]]
name = "plotters"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a"
dependencies = [
"num-traits",
"plotters-backend",
"plotters-svg",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "plotters"
version = "0.3.1"
@ -2923,6 +3019,16 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb"
[[package]]
name = "radix_trie"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
dependencies = [
"endian-type",
"nibble_vec",
]
[[package]]
name = "rand"
version = "0.8.4"
@ -3142,12 +3248,13 @@ dependencies = [
"roc_builtins",
"roc_can",
"roc_collections",
"roc_error_macros",
"roc_load",
"roc_module",
"roc_parse",
"roc_problem",
"roc_region",
"roc_reporting",
"roc_target",
"roc_types",
"roc_unify",
"snafu",
@ -3177,6 +3284,7 @@ dependencies = [
"roc_reporting",
"roc_solve",
"roc_std",
"roc_target",
"roc_types",
"roc_unify",
"serde_json",
@ -3191,6 +3299,7 @@ dependencies = [
"roc_collections",
"roc_module",
"roc_region",
"roc_target",
"roc_types",
]
@ -3219,7 +3328,7 @@ dependencies = [
"clap 3.0.0-beta.5",
"cli_utils",
"const_format",
"criterion",
"criterion 0.3.5 (git+https://github.com/Anton-4/criterion.rs)",
"indoc",
"inkwell 0.1.0",
"libloading 0.7.1",
@ -3232,6 +3341,7 @@ dependencies = [
"roc_constrain",
"roc_docs",
"roc_editor",
"roc_error_macros",
"roc_fmt",
"roc_gen_llvm",
"roc_linker",
@ -3243,6 +3353,8 @@ dependencies = [
"roc_region",
"roc_reporting",
"roc_solve",
"roc_target",
"roc_test_utils",
"roc_types",
"roc_unify",
"rustyline",
@ -3308,6 +3420,7 @@ dependencies = [
"roc_module",
"roc_parse",
"roc_region",
"roc_target",
"roc_types",
"snafu",
"tempfile",
@ -3363,6 +3476,10 @@ dependencies = [
"winit",
]
[[package]]
name = "roc_error_macros"
version = "0.1.0"
[[package]]
name = "roc_fmt"
version = "0.1.0"
@ -3375,6 +3492,7 @@ dependencies = [
"roc_parse",
"roc_region",
"roc_test_utils",
"walkdir",
]
[[package]]
@ -3387,14 +3505,15 @@ dependencies = [
"roc_builtins",
"roc_can",
"roc_collections",
"roc_error_macros",
"roc_module",
"roc_mono",
"roc_parse",
"roc_problem",
"roc_region",
"roc_reporting",
"roc_solve",
"roc_std",
"roc_target",
"roc_types",
"roc_unify",
"target-lexicon",
@ -3409,10 +3528,11 @@ dependencies = [
"morphic_lib",
"roc_builtins",
"roc_collections",
"roc_error_macros",
"roc_module",
"roc_mono",
"roc_reporting",
"roc_std",
"roc_target",
"target-lexicon",
]
@ -3423,10 +3543,11 @@ dependencies = [
"bumpalo",
"roc_builtins",
"roc_collections",
"roc_error_macros",
"roc_module",
"roc_mono",
"roc_reporting",
"roc_std",
"roc_target",
]
[[package]]
@ -3474,6 +3595,7 @@ dependencies = [
"roc_region",
"roc_reporting",
"roc_solve",
"roc_target",
"roc_types",
"roc_unify",
"tempfile",
@ -3508,6 +3630,7 @@ dependencies = [
"roc_region",
"roc_solve",
"roc_std",
"roc_target",
"roc_types",
"roc_unify",
"static_assertions",
@ -3520,6 +3643,7 @@ name = "roc_parse"
version = "0.1.0"
dependencies = [
"bumpalo",
"criterion 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"encode_unicode",
"indoc",
"pretty_assertions",
@ -3544,6 +3668,9 @@ dependencies = [
[[package]]
name = "roc_region"
version = "0.1.0"
dependencies = [
"static_assertions",
]
[[package]]
name = "roc_reporting"
@ -3563,6 +3690,8 @@ dependencies = [
"roc_problem",
"roc_region",
"roc_solve",
"roc_target",
"roc_test_utils",
"roc_types",
"ven_pretty",
]
@ -3584,6 +3713,7 @@ dependencies = [
"roc_problem",
"roc_region",
"roc_solve",
"roc_target",
"roc_types",
"roc_unify",
"tempfile",
@ -3592,6 +3722,19 @@ dependencies = [
[[package]]
name = "roc_std"
version = "0.1.0"
dependencies = [
"indoc",
"pretty_assertions",
"quickcheck",
"quickcheck_macros",
]
[[package]]
name = "roc_target"
version = "0.1.0"
dependencies = [
"target-lexicon",
]
[[package]]
name = "roc_test_utils"
@ -3680,16 +3823,21 @@ checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088"
[[package]]
name = "rustyline"
version = "6.2.0"
source = "git+https://github.com/rtfeldman/rustyline?tag=prompt-fix#a6b8a20d2bf5c3793d7367848be2f4afec2f0d99"
version = "9.1.1"
source = "git+https://github.com/rtfeldman/rustyline?tag=v9.1.1#7053ae0fe0ee710d38ed5845dd979113382994dc"
dependencies = [
"cfg-if 0.1.10",
"bitflags",
"cfg-if 1.0.0",
"clipboard-win 4.2.2",
"dirs-next",
"fd-lock",
"libc",
"log",
"memchr",
"nix 0.17.0",
"nix 0.23.1",
"radix_trie",
"scopeguard",
"smallvec",
"unicode-segmentation",
"unicode-width",
"utf8parse",
@ -3698,8 +3846,8 @@ dependencies = [
[[package]]
name = "rustyline-derive"
version = "0.3.1"
source = "git+https://github.com/rtfeldman/rustyline?tag=prompt-fix#a6b8a20d2bf5c3793d7367848be2f4afec2f0d99"
version = "0.6.0"
source = "git+https://github.com/rtfeldman/rustyline?tag=v9.1.1#7053ae0fe0ee710d38ed5845dd979113382994dc"
dependencies = [
"quote",
"syn",
@ -4034,6 +4182,12 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef5430c8e36b713e13b48a9f709cc21e046723fe44ce34587b73a830203b533e"
[[package]]
name = "str-buf"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d44a3643b4ff9caf57abcee9c2c621d6c03d9135e0d8b589bd9afb5992cb176a"
[[package]]
name = "strip-ansi-escapes"
version = "0.1.1"
@ -4128,6 +4282,7 @@ dependencies = [
"roc_reporting",
"roc_solve",
"roc_std",
"roc_target",
"roc_types",
"roc_unify",
"target-lexicon",
@ -4149,6 +4304,7 @@ dependencies = [
"roc_load",
"roc_module",
"roc_mono",
"roc_target",
"test_mono_macros",
]
@ -4435,12 +4591,6 @@ version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]]
name = "vte"
version = "0.10.1"
@ -5063,6 +5213,49 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82ca39602d5cbfa692c4b67e3bcbb2751477355141c1ed434c94da4186836ff6"
dependencies = [
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_msvc"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52695a41e536859d5308cc613b4a022261a274390b25bd29dfff4bf08505f3c2"
[[package]]
name = "windows_i686_gnu"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f54725ac23affef038fecb177de6c9bf065787c2f432f79e3c373da92f3e1d8a"
[[package]]
name = "windows_i686_msvc"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d5158a43cc43623c0729d1ad6647e62fa384a3d135fd15108d37c683461f64"
[[package]]
name = "windows_x86_64_gnu"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc31f409f565611535130cfe7ee8e6655d3fa99c1c61013981e491921b5ce954"
[[package]]
name = "windows_x86_64_msvc"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f2b8c7cbd3bfdddd9ab98769f9746a7fad1bca236554cd032b78d768bc0e89f"
[[package]]
name = "winit"
version = "0.25.0"

View file

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

View file

@ -1,41 +1,44 @@
# The Roc Code of Conduct
# Code of Conduct for the Roc GitHub Repository and Zulip Chat
A version of this document [can be found online](https://www.roc-lang.org/conduct).
It is based on the Rust Code of Conduct, which [can also be found online](https://www.rust-lang.org/conduct).
## Our Pledge
## Conduct
In the interest of fostering an open and welcoming environment, we as participants in the Roc GitHub repository and Zulip Chat pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
**Contact**: [roc-mods@roc-lang.org](mailto:roc-mods@roc-lang.org)
## Our Standards
* We are committed to providing a friendly, safe and welcoming environment for all, regardless of level of experience, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, or other similar characteristic.
* On IRC, please avoid using overtly sexual nicknames or other nicknames that might detract from a friendly, safe and welcoming environment for all.
* Please be kind and courteous. There's no need to be mean or rude.
: Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and numerous costs. There is seldom a right answer.
* Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and see how it works.
* We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behavior. We interpret the term "harassment" as including the definition in the <a href="http://citizencodeofconduct.org/">Citizen Code of Conduct</a>; if you have any lack of clarity about what might be included in that concept, please read their definition. In particular, we don't tolerate behavior that excludes people in socially marginalized groups.
* Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or made uncomfortable by a community member, please contact one of the channel ops or any of the [Roc moderation team][mod_team] immediately. Whether you're a regular contributor or a newcomer, we care about making this community a safe place for you and we've got your back.
* Likewise any spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome.
Examples of behavior that contributes to creating a positive environment include:
## Moderation
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Kindly giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
These are the policies for upholding our community's standards of conduct. If you feel that a thread needs moderation, please contact the [Roc moderation team][mod_team].
* The use of sexualized language or imagery, and sexual attention or advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address, without their explicit permission
* Telling others to be less sensitive, or that they should not feel hurt or offended by something
1. Remarks that violate the Roc standards of conduct, including hateful, hurtful, oppressive, or exclusionary remarks, are not allowed. (Cursing is allowed, but never targeting another user, and never in a hateful manner.)
2. Remarks that moderators find inappropriate, whether listed in the code of conduct or not, are also not allowed.
3. Moderators will first respond to such remarks with a warning.
4. If the warning is unheeded, the user will be "kicked," i.e., kicked out of the communication channel to cool off.
5. If the user comes back and continues to make trouble, they will be banned, i.e., indefinitely excluded.
6. Moderators may choose at their discretion to un-ban the user if it was a first offense and they offer the offended party a genuine apology.
7. If a moderator bans someone and you think it was unjustified, please take it up with that moderator, or with a different moderator, **in private**. Complaints about bans in-channel are not allowed.
8. Moderators are held to a higher standard than other community members. If a moderator creates an inappropriate situation, they should expect less leeway than others.
## Enforcement Responsibilities
In the Roc community we strive to go the extra step to look out for each other. Don't just aim to be technically unimpeachable, try to be your best self. In particular, avoid flirting with offensive or sensitive issues, particularly if they're off-topic; this all too often leads to unnecessary fights, hurt feelings, and damaged trust; worse, it can drive people away from the community entirely.
Moderators are responsible for clarifying and enforcing the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior, including by project leaders or otherwise prominent community members.
And if someone takes issue with something you said or did, resist the urge to be defensive. Just stop doing what it was they complained about and apologize. Even if you feel you were misinterpreted or unfairly accused, chances are good there was something you could've communicated better — remember that it's your responsibility to make your fellow Roc programmers comfortable. Everyone wants to get along and we are all here first and foremost because we want to talk about cool technology. You will find that people will be eager to assume good intent and forgive as long as you earn their trust.
Moderators have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. If a moderator is the subject of a reported code of conduct breach, or has a major conflict of interest, they should recuse themselves from participation in the resolution of that report. An exception to this is if more than 50% of the moderators would recuse themselves for the same reported breach; in such a case, none of them need to.
The enforcement policies listed above apply to all official Roc venues; including official Zulip chat (https://roc.zulipchat.com); and GitHub repositories under the roc-lang organization. If you wish to use this code of conduct (or the Rust code of conduct, on which it is based) for your own project, consider explicitly mentioning your moderation policy or making a copy with your own moderation policy so as to avoid confusion.
## Scope
*Adapted from the [Node.js Policy on Trolling](http://blog.izs.me/post/30036893703/policy-on-trolling) as well as the [Contributor Covenant v1.3.0](https://www.contributor-covenant.org/version/1/3/0/).*
This Code of Conduct applies within the Roc GitHub repository as well as the Roc Zulip Chat. It also applies when an individual is officially representing the project or its community in public spaces. Examples of representing the project or community include using an official roc-lang.org e-mail address, posting via an official social media account, or acting as the project's appointed representative at an online or offline event. Representation of the project may be further defined and clarified by moderators.
[mod_team]: https://www.roc-lang.org/moderation
## Reporting
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [conduct@roc-lang.org](mailto:conduct@roc-lang.org). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The moderation team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Moderators who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of project leadership.
## Attribution
This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html

View file

@ -47,7 +47,7 @@ install-zig-llvm-valgrind-clippy-rustfmt:
copy-dirs:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
COPY --dir cli cli_utils compiler docs editor ast code_markup utils test_utils reporting roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./
COPY --dir cli cli_utils compiler docs editor ast code_markup error_macros utils test_utils reporting roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./
test-zig:
FROM +install-zig-llvm-valgrind-clippy-rustfmt

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
use roc_module::{ident::Ident, module_err::ModuleError};
use roc_parse::parser::SyntaxError;
use roc_region::all::{Located, Region};
use roc_region::all::{Loc, Region};
use snafu::{Backtrace, Snafu};
use crate::lang::core::ast::ASTNodeId;
@ -56,8 +56,8 @@ impl From<ModuleError> for ASTError {
}
}
impl From<(Region, Located<Ident>)> for ASTError {
fn from(ident_exists_err: (Region, Located<Ident>)) -> Self {
impl From<(Region, Loc<Ident>)> for ASTError {
fn from(ident_exists_err: (Region, Loc<Ident>)) -> Self {
Self::IdentExistsError {
msg: format!("{:?}", ident_exists_err),
}

View file

@ -1,6 +1,6 @@
use roc_collections::all::MutMap;
use roc_problem::can::Problem;
use roc_region::all::{Located, Region};
use roc_region::all::{Loc, Region};
use roc_types::subs::Variable;
use crate::{
@ -38,7 +38,7 @@ enum FieldVar {
pub(crate) fn canonicalize_fields<'a>(
env: &mut Env<'a>,
scope: &mut Scope,
fields: &'a [Located<roc_parse::ast::AssignedField<'a, roc_parse::ast::Expr<'a>>>],
fields: &'a [Loc<roc_parse::ast::AssignedField<'a, roc_parse::ast::Expr<'a>>>],
) -> Result<(PoolVec<RecordField>, Output), CanonicalizeRecordProblem> {
let mut can_fields: MutMap<&'a str, FieldVar> = MutMap::default();
let mut output = Output::default();

View file

@ -11,7 +11,7 @@ use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_parse::ast;
use roc_parse::pattern::PatternType;
use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Located, Region};
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use crate::lang::core::def::def::canonicalize_defs;
@ -44,7 +44,7 @@ pub struct ModuleOutput {
#[allow(clippy::too_many_arguments)]
pub fn canonicalize_module_defs<'a>(
arena: &Bump,
loc_defs: &'a [Located<ast::Def<'a>>],
loc_defs: &'a [Loc<ast::Def<'a>>],
home: ModuleId,
module_ids: &ModuleIds,
exposed_ident_ids: IdentIds,
@ -80,7 +80,7 @@ pub fn canonicalize_module_defs<'a>(
bumpalo::collections::Vec::with_capacity_in(loc_defs.len() + num_deps, arena);
for loc_def in loc_defs.iter() {
desugared.push(&*arena.alloc(Located {
desugared.push(&*arena.alloc(Loc {
value: desugar_def(arena, &loc_def.value),
region: loc_def.region,
}));

View file

@ -30,6 +30,17 @@ use crate::{
mem_pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone},
};
/// 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)]
pub enum PresenceConstraint<'a> {
IncludesTag(TagName, BumpVec<'a, Type2>),
IsOpen,
Pattern(Region, PatternCategory, PExpected<Type2>),
}
#[derive(Debug)]
pub enum Constraint<'a> {
Eq(Type2, Expected<Type2>, Category, Region),
@ -40,6 +51,7 @@ pub enum Constraint<'a> {
Let(&'a LetConstraint<'a>),
// SaveTheEnvironment,
True, // Used for things that always unify, e.g. blanks and runtime errors
Present(Type2, PresenceConstraint<'a>),
}
#[derive(Debug)]
@ -782,7 +794,15 @@ pub fn constrain_expr<'a>(
constraints: BumpVec::with_capacity_in(1, arena),
};
constrain_pattern(arena, env, pattern, region, pattern_expected, &mut state);
constrain_pattern(
arena,
env,
pattern,
region,
pattern_expected,
&mut state,
false,
);
state.vars.push(*expr_var);
let def_expr = env.pool.get(*expr_id);
@ -1302,6 +1322,7 @@ fn constrain_when_branch<'a>(
region,
pattern_expected.shallow_clone(),
&mut state,
true,
);
}
@ -1346,6 +1367,23 @@ fn constrain_when_branch<'a>(
}
}
fn make_pattern_constraint(
region: Region,
category: PatternCategory,
actual: Type2,
expected: PExpected<Type2>,
presence_con: bool,
) -> Constraint<'static> {
if presence_con {
Constraint::Present(
actual,
PresenceConstraint::Pattern(region, category, expected),
)
} else {
Constraint::Pattern(region, category, actual, expected)
}
}
/// This accepts PatternState (rather than returning it) so that the caller can
/// initialize the Vecs in PatternState using with_capacity
/// based on its knowledge of their lengths.
@ -1356,15 +1394,35 @@ pub fn constrain_pattern<'a>(
region: Region,
expected: PExpected<Type2>,
state: &mut PatternState2<'a>,
destruct_position: bool,
) {
use Pattern2::*;
match pattern {
Underscore if destruct_position => {
// 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,
));
}
Underscore | UnsupportedPattern(_) | MalformedPattern(_, _) | Shadowed { .. } => {
// Neither the _ pattern nor erroneous ones add any constraints.
}
Identifier(symbol) => {
if destruct_position {
state.constraints.push(Constraint::Present(
expected.get_type_ref().shallow_clone(),
PresenceConstraint::IsOpen,
));
}
state.headers.insert(*symbol, expected.get_type());
}
@ -1446,7 +1504,7 @@ pub fn constrain_pattern<'a>(
let field_type = match destruct_type {
DestructType::Guard(guard_var, guard_id) => {
state.constraints.push(Constraint::Pattern(
state.constraints.push(make_pattern_constraint(
region,
PatternCategory::PatternGuard,
Type2::Variable(*guard_var),
@ -1456,6 +1514,7 @@ pub fn constrain_pattern<'a>(
// TODO: region should be from guard_id
region,
),
destruct_position,
));
state.vars.push(*guard_var);
@ -1463,12 +1522,20 @@ pub fn constrain_pattern<'a>(
let guard = env.pool.get(*guard_id);
// TODO: region should be from guard_id
constrain_pattern(arena, env, guard, region, expected, state);
constrain_pattern(
arena,
env,
guard,
region,
expected,
state,
destruct_position,
);
types::RecordField::Demanded(env.pool.add(pat_type))
}
DestructType::Optional(expr_var, expr_id) => {
state.constraints.push(Constraint::Pattern(
state.constraints.push(make_pattern_constraint(
region,
PatternCategory::PatternDefault,
Type2::Variable(*expr_var),
@ -1478,6 +1545,7 @@ pub fn constrain_pattern<'a>(
// TODO: region should be from expr_id
region,
),
destruct_position,
));
state.vars.push(*expr_var);
@ -1521,11 +1589,12 @@ pub fn constrain_pattern<'a>(
region,
);
let record_con = Constraint::Pattern(
let record_con = make_pattern_constraint(
region,
PatternCategory::Record,
Type2::Variable(*whole_var),
expected,
destruct_position,
);
state.constraints.push(whole_con);
@ -1540,7 +1609,16 @@ pub fn constrain_pattern<'a>(
let tag_name = TagName::Global(name.as_str(env.pool).into());
constrain_tag_pattern(
arena, env, region, expected, state, *whole_var, *ext_var, arguments, tag_name,
arena,
env,
region,
expected,
state,
*whole_var,
*ext_var,
arguments,
tag_name,
destruct_position,
);
}
PrivateTag {
@ -1552,7 +1630,16 @@ pub fn constrain_pattern<'a>(
let tag_name = TagName::Private(*name);
constrain_tag_pattern(
arena, env, region, expected, state, *whole_var, *ext_var, arguments, tag_name,
arena,
env,
region,
expected,
state,
*whole_var,
*ext_var,
arguments,
tag_name,
destruct_position,
);
}
}
@ -1569,6 +1656,7 @@ fn constrain_tag_pattern<'a>(
ext_var: Variable,
arguments: &PoolVec<(Variable, PatternId)>,
tag_name: TagName,
destruct_position: bool,
) {
let mut argument_types = Vec::with_capacity(arguments.len());
@ -1591,31 +1679,42 @@ fn constrain_tag_pattern<'a>(
);
// TODO region should come from pattern
constrain_pattern(arena, env, pattern, region, expected, state);
constrain_pattern(arena, env, pattern, region, expected, state, false);
}
let whole_con = Constraint::Eq(
Type2::Variable(whole_var),
Expected::NoExpectation(Type2::TagUnion(
PoolVec::new(
vec![(
tag_name.clone(),
PoolVec::new(argument_types.into_iter(), env.pool),
)]
.into_iter(),
env.pool,
let whole_con = if destruct_position {
Constraint::Present(
expected.get_type_ref().shallow_clone(),
PresenceConstraint::IncludesTag(
tag_name.clone(),
BumpVec::from_iter_in(argument_types.into_iter(), arena),
),
env.pool.add(Type2::Variable(ext_var)),
)),
Category::Storage(std::file!(), std::line!()),
region,
);
)
} else {
Constraint::Eq(
Type2::Variable(whole_var),
Expected::NoExpectation(Type2::TagUnion(
PoolVec::new(
vec![(
tag_name.clone(),
PoolVec::new(argument_types.into_iter(), env.pool),
)]
.into_iter(),
env.pool,
),
env.pool.add(Type2::Variable(ext_var)),
)),
Category::Storage(std::file!(), std::line!()),
region,
)
};
let tag_con = Constraint::Pattern(
let tag_con = make_pattern_constraint(
region,
PatternCategory::Ctor(tag_name),
Type2::Variable(whole_var),
expected,
destruct_position,
);
state.vars.push(whole_var);
@ -1660,6 +1759,7 @@ fn constrain_untyped_args<'a>(
Region::zero(),
pattern_expected,
&mut pattern_state,
false,
);
vars.push(*pattern_var);
@ -1885,7 +1985,7 @@ pub mod test_constrain {
ident::Lowercase,
symbol::{IdentIds, Interns, ModuleIds, Symbol},
};
use roc_parse::parser::SyntaxError;
use roc_parse::parser::{SourceError, SyntaxError};
use roc_region::all::Region;
use roc_types::{
pretty_print::{content_to_string, name_all_type_vars},
@ -2028,7 +2128,7 @@ pub mod test_constrain {
env: &mut Env<'a>,
scope: &mut Scope,
region: Region,
) -> Result<(Expr2, Output), SyntaxError<'a>> {
) -> Result<(Expr2, Output), SourceError<'a, SyntaxError<'a>>> {
match roc_parse::test_helpers::parse_loc_with(arena, input.trim()) {
Ok(loc_expr) => Ok(loc_expr_to_expr2(arena, loc_expr, env, scope, region)),
Err(fail) => Err(fail),
@ -2225,7 +2325,7 @@ pub mod test_constrain {
indoc!(
r#"
person = { name: "roc" }
person
"#
),
@ -2239,8 +2339,8 @@ pub mod test_constrain {
indoc!(
r#"
person = { name: "roc" }
{ person & name: "bird" }
{ person & name: "bird" }
"#
),
"{ name : Str }",
@ -2362,7 +2462,7 @@ pub mod test_constrain {
indoc!(
r#"
x = 1
\{} -> x
"#
),
@ -2612,4 +2712,168 @@ pub mod test_constrain {
"{ email : Str, name : Str }a -> { email : Str, name : Str }a",
)
}
#[test]
fn infer_union_input_position1() {
infer_eq(
indoc!(
r#"
\tag ->
when tag is
A -> X
B -> Y
"#
),
"[ A, B ] -> [ X, Y ]*",
)
}
#[test]
fn infer_union_input_position2() {
infer_eq(
indoc!(
r#"
\tag ->
when tag is
A -> X
B -> Y
_ -> Z
"#
),
"[ A, B ]* -> [ X, Y, Z ]*",
)
}
#[test]
fn infer_union_input_position3() {
infer_eq(
indoc!(
r#"
\tag ->
when tag is
A M -> X
A N -> Y
"#
),
"[ A [ M, N ] ] -> [ X, Y ]*",
)
}
#[test]
fn infer_union_input_position4() {
infer_eq(
indoc!(
r#"
\tag ->
when tag is
A M -> X
A N -> Y
A _ -> Z
"#
),
"[ A [ M, N ]* ] -> [ X, Y, Z ]*",
)
}
#[test]
#[ignore = "TODO: currently [ A [ M [ J ]*, N [ K ]* ] ] -> [ X ]*"]
fn infer_union_input_position5() {
infer_eq(
indoc!(
r#"
\tag ->
when tag is
A (M J) -> X
A (N K) -> X
"#
),
"[ A [ M [ J ], N [ K ] ] ] -> [ X ]*",
)
}
#[test]
fn infer_union_input_position6() {
infer_eq(
indoc!(
r#"
\tag ->
when tag is
A M -> X
B -> X
A N -> X
"#
),
"[ A [ M, N ], B ] -> [ X ]*",
)
}
#[test]
#[ignore = "TODO: currently [ A ]* -> [ A, X ]*"]
fn infer_union_input_position7() {
infer_eq(
indoc!(
r#"
\tag ->
when tag is
A -> X
t -> t
"#
),
// TODO: we could be a bit smarter by subtracting "A" as a possible
// tag in the union known by t, which would yield the principal type
// [ A, ]a -> [ X ]a
"[ A, X ]a -> [ A, X ]a",
)
}
#[test]
fn infer_union_input_position8() {
infer_eq(
indoc!(
r#"
\opt ->
when opt is
Some ({tag: A}) -> 1
Some ({tag: B}) -> 1
None -> 0
"#
),
"[ None, Some { tag : [ A, B ] }* ] -> Num *",
)
}
#[test]
#[ignore = "TODO: panicked at 'Invalid Cycle', ast/src/lang/core/def/def.rs:1208:21"]
fn infer_union_input_position9() {
infer_eq(
indoc!(
r#"
opt : [ Some Str, None ]
opt = Some ""
rcd = { opt }
when rcd is
{ opt: Some s } -> s
{ opt: None } -> "?"
"#
),
"Str",
)
}
#[test]
#[ignore = "TODO: currently <type mismatch> -> Num a"]
fn infer_union_input_position10() {
infer_eq(
indoc!(
r#"
\r ->
when r is
{ x: Blue, y ? 3 } -> y
{ x: Red, y ? 5 } -> y
"#
),
"{ x : [ Blue, Red ], y ? Num a }* -> Num a",
)
}
}

View file

@ -15,10 +15,10 @@
use roc_collections::all::{default_hasher, ImMap, MutMap, MutSet, SendMap};
use roc_module::ident::Lowercase;
use roc_module::symbol::Symbol;
use roc_parse::ast;
use roc_parse::ast::{self, AliasHeader};
use roc_parse::pattern::PatternType;
use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Located, Region};
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use std::collections::HashMap;
use std::fmt::Debug;
@ -96,29 +96,25 @@ impl ShallowClone for Def {
pub enum PendingDef<'a> {
/// A standalone annotation with no body
AnnotationOnly(
&'a Located<ast::Pattern<'a>>,
&'a Loc<ast::Pattern<'a>>,
PatternId,
&'a Located<ast::TypeAnnotation<'a>>,
&'a Loc<ast::TypeAnnotation<'a>>,
),
/// A body with no type annotation
Body(
&'a Located<ast::Pattern<'a>>,
PatternId,
&'a Located<ast::Expr<'a>>,
),
Body(&'a Loc<ast::Pattern<'a>>, PatternId, &'a Loc<ast::Expr<'a>>),
/// A body with a type annotation
TypedBody(
&'a Located<ast::Pattern<'a>>,
&'a Loc<ast::Pattern<'a>>,
PatternId,
&'a Located<ast::TypeAnnotation<'a>>,
&'a Located<ast::Expr<'a>>,
&'a Loc<ast::TypeAnnotation<'a>>,
&'a Loc<ast::Expr<'a>>,
),
/// A type alias, e.g. `Ints : List Int`
Alias {
name: Located<Symbol>,
vars: Vec<Located<Lowercase>>,
ann: &'a Located<ast::TypeAnnotation<'a>>,
name: Loc<Symbol>,
vars: Vec<Loc<Lowercase>>,
ann: &'a Loc<ast::TypeAnnotation<'a>>,
},
/// An invalid alias, that is ignored in the rest of the pipeline
@ -202,7 +198,10 @@ fn to_pending_def<'a>(
}
}
roc_parse::ast::Def::Alias { name, vars, ann } => {
roc_parse::ast::Def::Alias {
header: AliasHeader { name, vars },
ann,
} => {
let region = Region::span_across(&name.region, &ann.region);
match scope.introduce(
@ -212,7 +211,7 @@ fn to_pending_def<'a>(
region,
) {
Ok(symbol) => {
let mut can_rigids: Vec<Located<Lowercase>> = Vec::with_capacity(vars.len());
let mut can_rigids: Vec<Loc<Lowercase>> = Vec::with_capacity(vars.len());
for loc_var in vars.iter() {
match loc_var.value {
@ -220,7 +219,7 @@ fn to_pending_def<'a>(
if name.chars().next().unwrap().is_lowercase() =>
{
let lowercase = Lowercase::from(name);
can_rigids.push(Located {
can_rigids.push(Loc {
value: lowercase,
region: loc_var.region,
});
@ -240,7 +239,7 @@ fn to_pending_def<'a>(
Some((
Output::default(),
PendingDef::Alias {
name: Located {
name: Loc {
region: name.region,
value: symbol,
},
@ -273,9 +272,9 @@ fn to_pending_def<'a>(
fn pending_typed_body<'a>(
env: &mut Env<'a>,
loc_pattern: &'a Located<ast::Pattern<'a>>,
loc_ann: &'a Located<ast::TypeAnnotation<'a>>,
loc_expr: &'a Located<ast::Expr<'a>>,
loc_pattern: &'a Loc<ast::Pattern<'a>>,
loc_ann: &'a Loc<ast::TypeAnnotation<'a>>,
loc_expr: &'a Loc<ast::Expr<'a>>,
scope: &mut Scope,
pattern_type: PatternType,
) -> (Output, PendingDef<'a>) {
@ -297,9 +296,9 @@ fn pending_typed_body<'a>(
fn from_pending_alias<'a>(
env: &mut Env<'a>,
scope: &mut Scope,
name: Located<Symbol>,
vars: Vec<Located<Lowercase>>,
ann: &'a Located<ast::TypeAnnotation<'a>>,
name: Loc<Symbol>,
vars: Vec<Loc<Lowercase>>,
ann: &'a Loc<ast::TypeAnnotation<'a>>,
mut output: Output,
) -> Output {
let symbol = name.value;
@ -512,7 +511,7 @@ fn canonicalize_pending_def<'a>(
// remove its generated name from the closure map.
let references =
env.closures.remove(&closure_symbol).unwrap_or_else(|| {
panic!( r"Tried to remove symbol {:?} from procedures, but it was not found: {:?}", closure_symbol, env.closures)
panic!( r"Tried to remove symbol {:?} from procedures, but it was not found: {:?}", closure_symbol, env.closures)
});
// TODO should we re-insert this function into env.closures?
@ -681,7 +680,7 @@ fn canonicalize_pending_def<'a>(
// remove its generated name from the closure map.
let references =
env.closures.remove(&closure_symbol).unwrap_or_else(|| {
panic!( r"Tried to remove symbol {:?} from procedures, but it was not found: {:?}", closure_symbol, env.closures)
panic!( r"Tried to remove symbol {:?} from procedures, but it was not found: {:?}", closure_symbol, env.closures)
});
// TODO should we re-insert this function into env.closures?
@ -787,7 +786,7 @@ pub fn canonicalize_defs<'a>(
env: &mut Env<'a>,
mut output: Output,
original_scope: &Scope,
loc_defs: &'a [&'a Located<ast::Def<'a>>],
loc_defs: &'a [&'a Loc<ast::Def<'a>>],
pattern_type: PatternType,
) -> (CanDefs, Scope, Output, MutMap<Symbol, Region>) {
// Canonicalizing defs while detecting shadowing involves a multi-step process:
@ -1185,7 +1184,7 @@ pub fn sort_can_defs(
symbol, refs_by_symbol
),
Some((region, _)) => {
loc_symbols.push(Located::at(*region, symbol));
loc_symbols.push(Loc::at(*region, symbol));
}
}
}

View file

@ -6,7 +6,7 @@ use roc_collections::all::MutSet;
use roc_module::symbol::Symbol;
use roc_parse::{ast::Expr, pattern::PatternType};
use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Located, Region};
use roc_region::all::{Loc, Region};
use super::{expr2::Expr2, output::Output};
use crate::canonicalization::canonicalize::{
@ -29,7 +29,7 @@ use crate::{
pub fn loc_expr_to_expr2<'a>(
arena: &'a Bump,
loc_expr: Located<Expr<'a>>,
loc_expr: Loc<Expr<'a>>,
env: &mut Env<'a>,
scope: &mut Scope,
region: Region,

View file

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

View file

@ -5,7 +5,7 @@
use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region};
use roc_region::all::{Loc, Region};
use roc_types::types::{Problem, RecordField};
use roc_types::{subs::Variable, types::ErrorType};
@ -56,7 +56,7 @@ pub enum Problem2 {
CircularType(Symbol, NodeId<ErrorType>), // 12B = 8B + 4B
CyclicAlias(Symbol, PoolVec<Symbol>), // 20B = 8B + 12B
UnrecognizedIdent(PoolStr), // 8B
Shadowed(Located<PoolStr>),
Shadowed(Loc<PoolStr>),
BadTypeArguments {
symbol: Symbol, // 8B
type_got: u8, // 1B
@ -329,6 +329,8 @@ 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::*;
match annotation {
@ -450,127 +452,123 @@ pub fn to_type2<'a>(
Type2::TagUnion(tag_types, ext_type)
}
As(loc_inner, _spaces, loc_as) => {
// e.g. `{ x : Int, y : Int } as Point }`
match loc_as.value {
Apply(module_name, ident, loc_vars) if module_name.is_empty() => {
let symbol = match scope.introduce(
ident.into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
region,
) {
Ok(symbol) => symbol,
As(
loc_inner,
_spaces,
AliasHeader {
name,
vars: loc_vars,
},
) => {
// e.g. `{ x : Int, y : Int } as Point`
let symbol = match scope.introduce(
name.value.into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
region,
) {
Ok(symbol) => symbol,
Err((_original_region, _shadow)) => {
// let problem = Problem2::Shadowed(original_region, shadow.clone());
Err((_original_region, _shadow)) => {
// let problem = Problem2::Shadowed(original_region, shadow.clone());
// env.problem(roc_problem::can::Problem::ShadowingInAnnotation {
// original_region,
// shadow,
// });
// env.problem(roc_problem::can::Problem::ShadowingInAnnotation {
// original_region,
// shadow,
// });
// return Type2::Erroneous(problem);
todo!();
}
};
let inner_type = to_type2(env, scope, references, &loc_inner.value, region);
let vars = PoolVec::with_capacity(loc_vars.len() as u32, env.pool);
let lowercase_vars = PoolVec::with_capacity(loc_vars.len() as u32, env.pool);
for ((loc_var, named_id), var_id) in loc_vars
.iter()
.zip(lowercase_vars.iter_node_ids())
.zip(vars.iter_node_ids())
{
match loc_var.value {
BoundVariable(ident) => {
let var_name = Lowercase::from(ident);
if let Some(var) = references.named.get(&var_name) {
let poolstr = PoolStr::new(var_name.as_str(), env.pool);
let type_id = env.pool.add(Type2::Variable(*var));
env.pool[var_id] = (poolstr.shallow_clone(), type_id);
env.pool[named_id] = (poolstr, *var);
env.set_region(named_id, loc_var.region);
} else {
let var = env.var_store.fresh();
references.named.insert(var_name.clone(), var);
let poolstr = PoolStr::new(var_name.as_str(), env.pool);
let type_id = env.pool.add(Type2::Variable(var));
env.pool[var_id] = (poolstr.shallow_clone(), type_id);
env.pool[named_id] = (poolstr, var);
env.set_region(named_id, loc_var.region);
}
}
_ => {
// If anything other than a lowercase identifier
// appears here, the whole annotation is invalid.
return Type2::Erroneous(Problem2::CanonicalizationProblem);
}
}
}
let alias_actual = inner_type;
// TODO instantiate recursive tag union
// let alias_actual = if let Type2::TagUnion(tags, ext) = inner_type {
// let rec_var = env.var_store.fresh();
//
// let mut new_tags = Vec::with_capacity(tags.len());
// for (tag_name, args) in tags {
// let mut new_args = Vec::with_capacity(args.len());
// for arg in args {
// let mut new_arg = arg.clone();
// new_arg.substitute_alias(symbol, &Type2::Variable(rec_var));
// new_args.push(new_arg);
// }
// new_tags.push((tag_name.clone(), new_args));
// }
// Type2::RecursiveTagUnion(rec_var, new_tags, ext)
// } else {
// inner_type
// };
let mut hidden_variables = MutSet::default();
hidden_variables.extend(alias_actual.variables(env.pool));
for (_, var) in lowercase_vars.iter(env.pool) {
hidden_variables.remove(var);
}
let alias_actual_id = env.pool.add(alias_actual);
scope.add_alias(env.pool, symbol, lowercase_vars, alias_actual_id);
let alias = scope.lookup_alias(symbol).unwrap();
// local_aliases.insert(symbol, alias.clone());
// TODO host-exposed
// if vars.is_empty() && env.home == symbol.module_id() {
// let actual_var = env.var_store.fresh();
// rigids.host_exposed.insert(symbol, actual_var);
// Type::HostExposedAlias {
// name: symbol,
// arguments: vars,
// actual: Box::new(alias.typ.clone()),
// actual_var,
// }
// } else {
// Type::Alias(symbol, vars, Box::new(alias.typ.clone()))
// }
Type2::AsAlias(symbol, vars, alias.actual)
// return Type2::Erroneous(problem);
todo!();
}
_ => {
// This is a syntactically invalid type alias.
Type2::Erroneous(Problem2::CanonicalizationProblem)
};
let inner_type = to_type2(env, scope, references, &loc_inner.value, region);
let vars = PoolVec::with_capacity(loc_vars.len() as u32, env.pool);
let lowercase_vars = PoolVec::with_capacity(loc_vars.len() as u32, env.pool);
for ((loc_var, named_id), var_id) in loc_vars
.iter()
.zip(lowercase_vars.iter_node_ids())
.zip(vars.iter_node_ids())
{
let var = match loc_var.value {
Pattern::Identifier(name) if name.chars().next().unwrap().is_lowercase() => {
name
}
_ => unreachable!("I thought this was validated during parsing"),
};
let var_name = Lowercase::from(var);
if let Some(var) = references.named.get(&var_name) {
let poolstr = PoolStr::new(var_name.as_str(), env.pool);
let type_id = env.pool.add(Type2::Variable(*var));
env.pool[var_id] = (poolstr.shallow_clone(), type_id);
env.pool[named_id] = (poolstr, *var);
env.set_region(named_id, loc_var.region);
} else {
let var = env.var_store.fresh();
references.named.insert(var_name.clone(), var);
let poolstr = PoolStr::new(var_name.as_str(), env.pool);
let type_id = env.pool.add(Type2::Variable(var));
env.pool[var_id] = (poolstr.shallow_clone(), type_id);
env.pool[named_id] = (poolstr, var);
env.set_region(named_id, loc_var.region);
}
}
let alias_actual = inner_type;
// TODO instantiate recursive tag union
// let alias_actual = if let Type2::TagUnion(tags, ext) = inner_type {
// let rec_var = env.var_store.fresh();
//
// let mut new_tags = Vec::with_capacity(tags.len());
// for (tag_name, args) in tags {
// let mut new_args = Vec::with_capacity(args.len());
// for arg in args {
// let mut new_arg = arg.clone();
// new_arg.substitute_alias(symbol, &Type2::Variable(rec_var));
// new_args.push(new_arg);
// }
// new_tags.push((tag_name.clone(), new_args));
// }
// Type2::RecursiveTagUnion(rec_var, new_tags, ext)
// } else {
// inner_type
// };
let mut hidden_variables = MutSet::default();
hidden_variables.extend(alias_actual.variables(env.pool));
for (_, var) in lowercase_vars.iter(env.pool) {
hidden_variables.remove(var);
}
let alias_actual_id = env.pool.add(alias_actual);
scope.add_alias(env.pool, symbol, lowercase_vars, alias_actual_id);
let alias = scope.lookup_alias(symbol).unwrap();
// local_aliases.insert(symbol, alias.clone());
// TODO host-exposed
// if vars.is_empty() && env.home == symbol.module_id() {
// let actual_var = env.var_store.fresh();
// rigids.host_exposed.insert(symbol, actual_var);
// Type::HostExposedAlias {
// name: symbol,
// arguments: vars,
// actual: Box::new(alias.typ.clone()),
// actual_var,
// }
// } else {
// Type::Alias(symbol, vars, Box::new(alias.typ.clone()))
// }
Type2::AsAlias(symbol, vars, alias.actual)
}
SpaceBefore(nested, _) | SpaceAfter(nested, _) => {
to_type2(env, scope, references, nested, region)
@ -584,7 +582,7 @@ fn can_assigned_fields<'a>(
env: &mut Env,
scope: &mut Scope,
rigids: &mut References,
fields: &&[Located<roc_parse::ast::AssignedField<'a, roc_parse::ast::TypeAnnotation<'a>>>],
fields: &&[Loc<roc_parse::ast::AssignedField<'a, roc_parse::ast::TypeAnnotation<'a>>>],
region: Region,
) -> MutMap<Lowercase, RecordField<Type2>> {
use roc_parse::ast::AssignedField::*;
@ -672,7 +670,7 @@ fn can_tags<'a>(
env: &mut Env,
scope: &mut Scope,
rigids: &mut References,
tags: &'a [Located<roc_parse::ast::Tag<'a>>],
tags: &'a [Loc<roc_parse::ast::Tag<'a>>],
region: Region,
) -> Vec<(TagName, PoolVec<Type2>)> {
use roc_parse::ast::Tag;
@ -758,7 +756,7 @@ fn to_type_apply<'a>(
rigids: &mut References,
module_name: &str,
ident: &str,
type_arguments: &[Located<roc_parse::ast::TypeAnnotation<'a>>],
type_arguments: &[Loc<roc_parse::ast::TypeAnnotation<'a>>],
region: Region,
) -> TypeApply {
let symbol = if module_name.is_empty() {

View file

@ -4,11 +4,12 @@ use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::{Ident, Lowercase, ModuleName};
use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Located, Region};
use roc_region::all::{Loc, Region};
use roc_types::subs::VarStore;
use super::core::def::def::References;
/// TODO document
#[derive(Debug)]
pub struct Env<'a> {
pub home: ModuleId,
@ -123,7 +124,7 @@ impl<'a> Env<'a> {
Ok(symbol)
}
None => Err(RuntimeError::LookupNotInScope(
Located {
Loc {
value: ident,
region,
},

View file

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

View file

@ -15,7 +15,7 @@ use roc_module::symbol::{
get_module_ident_ids, get_module_ident_ids_mut, IdentIds, Interns, ModuleId, Symbol,
};
use roc_problem::can::RuntimeError;
use roc_region::all::{Located, Region};
use roc_region::all::{Loc, Region};
use roc_types::{
builtin_aliases,
solved_types::{BuiltinAlias, FreeVars, SolvedType},
@ -205,7 +205,7 @@ impl Scope {
match self.idents.get(ident) {
Some((symbol, _)) => Ok(*symbol),
None => Err(RuntimeError::LookupNotInScope(
Located {
Loc {
region,
value: ident.clone().into(),
},
@ -228,10 +228,10 @@ impl Scope {
exposed_ident_ids: &IdentIds,
all_ident_ids: &mut IdentIds,
region: Region,
) -> Result<Symbol, (Region, Located<Ident>)> {
) -> Result<Symbol, (Region, Loc<Ident>)> {
match self.idents.get(&ident) {
Some((_, original_region)) => {
let shadow = Located {
let shadow = Loc {
value: ident,
region,
};

View file

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

View file

@ -33,7 +33,7 @@ pub fn parse_from_string<'a>(
let mut scope = Scope::new(env.home, env.pool, env.var_store);
scope.fill_scope(env, &mut interns.all_ident_ids)?;
let region = Region::new(0, 0, 0, 0);
let region = Region::zero();
let mut def_ids = Vec::<DefId>::new();

View file

@ -116,13 +116,13 @@ mod test_file {
indoc!(
r#"
interface Simple
exposes [
exposes [
v, x
]
imports []
v : Str
v = "Value!"
x : Int

View file

@ -5,7 +5,7 @@ use roc_can::expected::{Expected, PExpected};
use roc_collections::all::{BumpMap, BumpMapDefault, MutMap};
use roc_module::ident::TagName;
use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region};
use roc_region::all::{Loc, Region};
use roc_types::solved_types::Solved;
use roc_types::subs::{
AliasVariables, Content, Descriptor, FlatType, Mark, OptVariable, Rank, RecordFields, Subs,
@ -15,9 +15,10 @@ use roc_types::types::{
gather_fields_unsorted_iter, Alias, Category, ErrorType, PatternCategory, RecordField,
};
use roc_unify::unify::unify;
use roc_unify::unify::Mode;
use roc_unify::unify::Unified::*;
use crate::constrain::Constraint;
use crate::constrain::{Constraint, PresenceConstraint};
use crate::lang::core::types::Type2;
use crate::mem_pool::pool::Pool;
use crate::mem_pool::pool_vec::PoolVec;
@ -224,7 +225,7 @@ fn solve<'a>(
expectation.get_type_ref(),
);
match unify(subs, actual, expected) {
match unify(subs, actual, expected, Mode::Eq) {
Success(vars) => {
introduce(subs, rank, pools, &vars);
@ -317,7 +318,7 @@ fn solve<'a>(
expectation.get_type_ref(),
);
match unify(subs, actual, expected) {
match unify(subs, actual, expected, Mode::Eq) {
Success(vars) => {
introduce(subs, rank, pools, &vars);
@ -374,7 +375,8 @@ fn solve<'a>(
state
}
Pattern(region, category, typ, expectation) => {
Pattern(region, category, typ, expectation)
| Present(typ, PresenceConstraint::Pattern(region, category, expectation)) => {
let actual = type_to_var(arena, mempool, subs, rank, pools, cached_aliases, typ);
let expected = type_to_var(
arena,
@ -386,7 +388,8 @@ fn solve<'a>(
expectation.get_type_ref(),
);
match unify(subs, actual, expected) {
// TODO(ayazhafiz): presence constraints for Expr2/Type2
match unify(subs, actual, expected, Mode::Eq) {
Success(vars) => {
introduce(subs, rank, pools, &vars);
@ -459,7 +462,7 @@ fn solve<'a>(
// TODO: region should come from typ
local_def_vars.insert(
*symbol,
Located {
Loc {
value: var,
region: Region::zero(),
},
@ -542,7 +545,7 @@ fn solve<'a>(
// TODO: region should come from type
local_def_vars.insert(
*symbol,
Located {
Loc {
value: var,
region: Region::zero(),
},
@ -657,7 +660,73 @@ fn solve<'a>(
new_state
}
}
} // _ => todo!("implement {:?}", constraint),
}
Present(typ, PresenceConstraint::IsOpen) => {
let actual = type_to_var(arena, mempool, subs, rank, pools, cached_aliases, typ);
let mut new_desc = subs.get(actual);
match new_desc.content {
Content::Structure(FlatType::TagUnion(tags, _)) => {
let new_ext = subs.fresh_unnamed_flex_var();
let new_union = Content::Structure(FlatType::TagUnion(tags, new_ext));
new_desc.content = new_union;
subs.set(actual, new_desc);
state
}
_ => {
// Today, an "open" constraint doesn't affect any types
// other than tag unions. Recursive tag unions are constructed
// at a later time (during occurs checks after tag unions are
// resolved), so that's not handled here either.
// NB: Handle record types here if we add presence constraints
// to their type inference as well.
state
}
}
}
Present(typ, PresenceConstraint::IncludesTag(tag_name, tys)) => {
let actual = type_to_var(arena, mempool, subs, rank, pools, cached_aliases, typ);
let tag_ty = Type2::TagUnion(
PoolVec::new(
std::iter::once((
tag_name.clone(),
PoolVec::new(tys.into_iter().map(ShallowClone::shallow_clone), mempool),
)),
mempool,
),
mempool.add(Type2::EmptyTagUnion),
);
let includes = type_to_var(arena, mempool, subs, rank, pools, cached_aliases, &tag_ty);
match unify(subs, actual, includes, Mode::Present) {
Success(vars) => {
introduce(subs, rank, pools, &vars);
state
}
Failure(vars, actual_type, expected_type) => {
introduce(subs, rank, pools, &vars);
// TODO: do we need a better error type here?
let problem = TypeError::BadExpr(
Region::zero(),
Category::When,
actual_type,
Expected::NoExpectation(expected_type),
);
problems.push(problem);
state
}
BadType(vars, problem) => {
introduce(subs, rank, pools, &vars);
problems.push(TypeError::BadType(problem));
state
}
}
}
}
}
@ -765,7 +834,8 @@ fn type_to_variable<'a>(
let temp_ext_var = type_to_variable(arena, mempool, subs, rank, pools, cached, ext);
let (it, new_ext_var) =
gather_fields_unsorted_iter(subs, RecordFields::empty(), temp_ext_var);
gather_fields_unsorted_iter(subs, RecordFields::empty(), temp_ext_var)
.expect("Something ended up weird in this record type");
let it = it
.into_iter()
@ -1013,7 +1083,7 @@ fn check_for_infinite_type(
subs: &mut Subs,
problems: &mut Vec<TypeError>,
symbol: Symbol,
loc_var: Located<Variable>,
loc_var: Loc<Variable>,
) {
let var = loc_var.value;
@ -1066,7 +1136,7 @@ fn circular_error(
subs: &mut Subs,
problems: &mut Vec<TypeError>,
symbol: Symbol,
loc_var: &Located<Variable>,
loc_var: &Loc<Variable>,
) {
let var = loc_var.value;
let (error_type, _) = subs.var_to_error_type(var);

View file

@ -65,7 +65,7 @@ fn finish(all_regressed_benches: HashSet<String>, nr_repeat_benchmarks: usize) {
r#"
FAILED: The following benchmarks have shown a regression {:?} times: {:?}
"#,
nr_repeat_benchmarks, all_regressed_benches
);

View file

@ -14,7 +14,7 @@ path = "src/main.rs"
test = false
bench = false
[features]
[features]
default = ["target-aarch64", "target-x86_64", "target-wasm32", "llvm", "editor"]
wasm32-cli-run = ["target-wasm32", "run-wasm32"]
@ -61,13 +61,15 @@ roc_load = { path = "../compiler/load" }
roc_gen_llvm = { path = "../compiler/gen_llvm", optional = true }
roc_build = { path = "../compiler/build", default-features = false }
roc_fmt = { path = "../compiler/fmt" }
roc_target = { path = "../compiler/roc_target" }
roc_reporting = { path = "../reporting" }
roc_error_macros = { path = "../error_macros" }
roc_editor = { path = "../editor", optional = true }
roc_linker = { path = "../linker" }
clap = { version = "= 3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] }
const_format = "0.2.22"
rustyline = { git = "https://github.com/rtfeldman/rustyline", tag = "prompt-fix" }
rustyline-derive = { git = "https://github.com/rtfeldman/rustyline", tag = "prompt-fix" }
rustyline = { git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1" }
rustyline-derive = { git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1" }
bumpalo = { version = "3.8.0", features = ["collections"] }
libloading = "0.7.1"
mimalloc = { version = "0.1.26", default-features = false }
@ -83,6 +85,7 @@ wasmer-wasi = { version = "2.0.0", optional = true }
wasmer = { version = "2.0.0", default-features = false, features = ["default-cranelift", "default-universal"] }
wasmer-wasi = "2.0.0"
pretty_assertions = "1.0.0"
roc_test_utils = { path = "../test_utils" }
indoc = "1.0.3"
serial_test = "0.5.1"
tempfile = "3.2.0"

View file

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

View file

@ -2,25 +2,27 @@ use std::path::PathBuf;
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_error_macros::{internal_error, user_error};
use roc_fmt::def::fmt_def;
use roc_fmt::module::fmt_module;
use roc_fmt::Buf;
use roc_module::called_via::{BinOp, UnaryOp};
use roc_parse::ast::{
AssignedField, Collection, Expr, Pattern, StrLiteral, StrSegment, Tag, TypeAnnotation,
WhenBranch,
AliasHeader, AssignedField, Collection, Expr, Pattern, Spaced, StrLiteral, StrSegment, Tag,
TypeAnnotation, WhenBranch,
};
use roc_parse::header::{
AppHeader, Effects, ExposesEntry, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
PackageName, PackageOrPath, PlatformHeader, PlatformRequires, PlatformRigid, To, TypedIdent,
AppHeader, Effects, ExposedName, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
PackageName, PlatformHeader, PlatformRequires, To, TypedIdent,
};
use roc_parse::{
ast::{Def, Module},
ident::UppercaseIdent,
module::{self, module_defs},
parser::{Parser, State, SyntaxError},
parser::{Parser, SyntaxError},
state::State,
};
use roc_region::all::Located;
use roc_reporting::{internal_error, user_error};
use roc_region::all::{Loc, Region};
pub fn format(files: std::vec::Vec<PathBuf>) {
for file in files {
@ -105,12 +107,12 @@ pub fn format(files: std::vec::Vec<PathBuf>) {
#[derive(Debug, PartialEq)]
struct Ast<'a> {
module: Module<'a>,
defs: Vec<'a, Located<Def<'a>>>,
defs: Vec<'a, Loc<Def<'a>>>,
}
fn parse_all<'a>(arena: &'a Bump, src: &'a str) -> Result<Ast<'a>, SyntaxError<'a>> {
let (module, state) =
module::parse_header(arena, State::new(src.as_bytes())).map_err(SyntaxError::Header)?;
let (module, state) = module::parse_header(arena, State::new(src.as_bytes()))
.map_err(|e| SyntaxError::Header(e.problem))?;
let (_, defs, _) = module_defs().parse(arena, state).map_err(|(_, e, _)| e)?;
@ -175,6 +177,7 @@ impl<'a> RemoveSpaces<'a> for Module<'a> {
packages: header.packages.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
provides: header.provides.remove_spaces(arena),
provides_types: header.provides_types.map(|ts| ts.remove_spaces(arena)),
to: header.to.remove_spaces(arena),
before_header: &[],
after_app_keyword: &[],
@ -228,16 +231,22 @@ impl<'a> RemoveSpaces<'a> for &'a str {
}
}
impl<'a, T: RemoveSpaces<'a> + Copy> RemoveSpaces<'a> for ExposesEntry<'a, T> {
impl<'a, T: RemoveSpaces<'a> + Copy> RemoveSpaces<'a> for Spaced<'a, T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
ExposesEntry::Exposed(a) => ExposesEntry::Exposed(a.remove_spaces(arena)),
ExposesEntry::SpaceBefore(a, _) => a.remove_spaces(arena),
ExposesEntry::SpaceAfter(a, _) => a.remove_spaces(arena),
Spaced::Item(a) => Spaced::Item(a.remove_spaces(arena)),
Spaced::SpaceBefore(a, _) => a.remove_spaces(arena),
Spaced::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for ExposedName<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for ModuleName<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
@ -261,18 +270,10 @@ impl<'a> RemoveSpaces<'a> for To<'a> {
impl<'a> RemoveSpaces<'a> for TypedIdent<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
TypedIdent::Entry {
ident,
spaces_before_colon: _,
ann,
} => TypedIdent::Entry {
ident: ident.remove_spaces(arena),
spaces_before_colon: &[],
ann: ann.remove_spaces(arena),
},
TypedIdent::SpaceBefore(a, _) => a.remove_spaces(arena),
TypedIdent::SpaceAfter(a, _) => a.remove_spaces(arena),
TypedIdent {
ident: self.ident.remove_spaces(arena),
spaces_before_colon: &[],
ann: self.ann.remove_spaces(arena),
}
}
}
@ -286,39 +287,18 @@ impl<'a> RemoveSpaces<'a> for PlatformRequires<'a> {
}
}
impl<'a> RemoveSpaces<'a> for PlatformRigid<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
PlatformRigid::Entry { rigid, alias } => PlatformRigid::Entry { rigid, alias },
PlatformRigid::SpaceBefore(a, _) => a.remove_spaces(arena),
PlatformRigid::SpaceAfter(a, _) => a.remove_spaces(arena),
}
impl<'a> RemoveSpaces<'a> for UppercaseIdent<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for PackageEntry<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
PackageEntry::Entry {
shorthand,
spaces_after_shorthand: _,
package_or_path,
} => PackageEntry::Entry {
shorthand,
spaces_after_shorthand: &[],
package_or_path: package_or_path.remove_spaces(arena),
},
PackageEntry::SpaceBefore(a, _) => a.remove_spaces(arena),
PackageEntry::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for PackageOrPath<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
PackageOrPath::Package(a, b) => PackageOrPath::Package(a, b),
PackageOrPath::Path(p) => PackageOrPath::Path(p.remove_spaces(arena)),
PackageEntry {
shorthand: self.shorthand,
spaces_after_shorthand: &[],
package_name: self.package_name.remove_spaces(arena),
}
}
}
@ -328,8 +308,6 @@ impl<'a> RemoveSpaces<'a> for ImportsEntry<'a> {
match *self {
ImportsEntry::Module(a, b) => ImportsEntry::Module(a, b.remove_spaces(arena)),
ImportsEntry::Package(a, b, c) => ImportsEntry::Package(a, b, c.remove_spaces(arena)),
ImportsEntry::SpaceBefore(a, _) => a.remove_spaces(arena),
ImportsEntry::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}
@ -340,10 +318,10 @@ impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for Option<T> {
}
}
impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for Located<T> {
impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for Loc<T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
let res = self.value.remove_spaces(arena);
Located::new(0, 0, 0, 0, res)
Loc::at(Region::zero(), res)
}
}
@ -398,9 +376,14 @@ impl<'a> RemoveSpaces<'a> for Def<'a> {
Def::Annotation(a, b) => {
Def::Annotation(a.remove_spaces(arena), b.remove_spaces(arena))
}
Def::Alias { name, vars, ann } => Def::Alias {
name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena),
Def::Alias {
header: AliasHeader { name, vars },
ann,
} => Def::Alias {
header: AliasHeader {
name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena),
},
ann: ann.remove_spaces(arena),
},
Def::Body(a, b) => Def::Body(
@ -600,11 +583,9 @@ impl<'a> RemoveSpaces<'a> for TypeAnnotation<'a> {
),
TypeAnnotation::Apply(a, b, c) => TypeAnnotation::Apply(a, b, c.remove_spaces(arena)),
TypeAnnotation::BoundVariable(a) => TypeAnnotation::BoundVariable(a),
TypeAnnotation::As(a, _, c) => TypeAnnotation::As(
arena.alloc(a.remove_spaces(arena)),
&[],
arena.alloc(c.remove_spaces(arena)),
),
TypeAnnotation::As(a, _, c) => {
TypeAnnotation::As(arena.alloc(a.remove_spaces(arena)), &[], c)
}
TypeAnnotation::Record { fields, ext } => TypeAnnotation::Record {
fields: fields.remove_spaces(arena),
ext: ext.remove_spaces(arena),

View file

@ -1,7 +1,7 @@
use const_format::concatcp;
#[cfg(feature = "llvm")]
use gen::{gen_and_eval, ReplOutput};
use roc_parse::parser::{EExpr, SyntaxError};
use roc_parse::parser::{EExpr, ELambda, SyntaxError};
use rustyline::highlight::{Highlighter, PromptInfo};
use rustyline::validate::{self, ValidationContext, ValidationResult, Validator};
use rustyline_derive::{Completer, Helper, Hinter};
@ -27,6 +27,7 @@ pub const INSTRUCTIONS: &str = "Enter an expression, or :help, or :exit/:q.\n";
pub const PROMPT: &str = concatcp!("\n", BLUE, "»", END_COL, " ");
pub const CONT_PROMPT: &str = concatcp!(BLUE, "", END_COL, " ");
mod app_memory;
#[cfg(feature = "llvm")]
mod eval;
#[cfg(feature = "llvm")]
@ -92,12 +93,13 @@ impl Validator for InputValidator {
Ok(ValidationResult::Incomplete)
} else {
let arena = bumpalo::Bump::new();
let state = roc_parse::parser::State::new(ctx.input().trim().as_bytes());
let state = roc_parse::state::State::new(ctx.input().trim().as_bytes());
match roc_parse::expr::parse_loc_expr(0, &arena, state) {
// Special case some syntax errors to allow for multi-line inputs
Err((_, EExpr::DefMissingFinalExpr(_, _), _))
| Err((_, EExpr::DefMissingFinalExpr2(_, _, _), _)) => {
Err((_, EExpr::DefMissingFinalExpr(_), _))
| Err((_, EExpr::DefMissingFinalExpr2(_, _), _))
| Err((_, EExpr::Lambda(ELambda::Body(_, _), _), _)) => {
Ok(ValidationResult::Incomplete)
}
_ => Ok(ValidationResult::Valid(None)),

159
cli/src/repl/app_memory.rs Normal file
View file

@ -0,0 +1,159 @@
use std::mem::size_of;
pub trait AppMemory {
fn deref_bool(&self, addr: usize) -> bool;
fn deref_u8(&self, addr: usize) -> u8;
fn deref_u16(&self, addr: usize) -> u16;
fn deref_u32(&self, addr: usize) -> u32;
fn deref_u64(&self, addr: usize) -> u64;
fn deref_u128(&self, addr: usize) -> u128;
fn deref_usize(&self, addr: usize) -> usize;
fn deref_i8(&self, addr: usize) -> i8;
fn deref_i16(&self, addr: usize) -> i16;
fn deref_i32(&self, addr: usize) -> i32;
fn deref_i64(&self, addr: usize) -> i64;
fn deref_i128(&self, addr: usize) -> i128;
fn deref_isize(&self, addr: usize) -> isize;
fn deref_f32(&self, addr: usize) -> f32;
fn deref_f64(&self, addr: usize) -> f64;
fn deref_str(&self, addr: usize) -> &str;
}
/// A block of app memory in the same address space as the compiler
pub struct AppMemoryInternal;
macro_rules! internal_number_type {
($name: ident, $t: ty) => {
fn $name(&self, addr: usize) -> $t {
let ptr = addr as *const _;
unsafe { *ptr }
}
};
}
impl AppMemory for AppMemoryInternal {
internal_number_type!(deref_bool, bool);
internal_number_type!(deref_u8, u8);
internal_number_type!(deref_u16, u16);
internal_number_type!(deref_u32, u32);
internal_number_type!(deref_u64, u64);
internal_number_type!(deref_u128, u128);
internal_number_type!(deref_usize, usize);
internal_number_type!(deref_i8, i8);
internal_number_type!(deref_i16, i16);
internal_number_type!(deref_i32, i32);
internal_number_type!(deref_i64, i64);
internal_number_type!(deref_i128, i128);
internal_number_type!(deref_isize, isize);
internal_number_type!(deref_f32, f32);
internal_number_type!(deref_f64, f64);
fn deref_str(&self, addr: usize) -> &str {
unsafe { *(addr as *const &'static str) }
}
}
/// A block of app memory copied from an exteral address space outside the compiler
/// (e.g. compiler and app are in separate Wasm modules)
pub struct AppMemoryExternal<'a> {
bytes: &'a [u8],
}
macro_rules! external_number_type {
($name: ident, $t: ty) => {
fn $name(&self, address: usize) -> $t {
const N: usize = size_of::<$t>();
let mut array = [0; N];
array.copy_from_slice(&self.bytes[address..][..N]);
<$t>::from_le_bytes(array)
}
};
}
impl<'a> AppMemory for AppMemoryExternal<'a> {
fn deref_bool(&self, address: usize) -> bool {
self.bytes[address] != 0
}
external_number_type!(deref_u8, u8);
external_number_type!(deref_u16, u16);
external_number_type!(deref_u32, u32);
external_number_type!(deref_u64, u64);
external_number_type!(deref_u128, u128);
external_number_type!(deref_usize, usize);
external_number_type!(deref_i8, i8);
external_number_type!(deref_i16, i16);
external_number_type!(deref_i32, i32);
external_number_type!(deref_i64, i64);
external_number_type!(deref_i128, i128);
external_number_type!(deref_isize, isize);
external_number_type!(deref_f32, f32);
external_number_type!(deref_f64, f64);
fn deref_str(&self, addr: usize) -> &str {
let elems_addr = self.deref_usize(addr);
let len = self.deref_usize(addr + size_of::<usize>());
let bytes = &self.bytes[elems_addr..][..len];
std::str::from_utf8(bytes).unwrap()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn internal_u8() {
let value: u8 = 123;
let ptr = &value as *const u8;
let addr = ptr as usize;
let memory = AppMemoryInternal;
let recovered: u8 = memory.deref_u8(addr);
assert_eq!(value, recovered);
}
#[test]
fn external_u8() {
let value: u8 = 123;
let memory = AppMemoryExternal {
bytes: &[0, 0, value, 0, 0],
};
let addr = 2;
let recovered: u8 = memory.deref_u8(addr);
assert_eq!(value, recovered);
}
#[test]
fn internal_i64() {
let value: i64 = -123 << 33;
let ptr = &value as *const i64;
let addr = ptr as usize;
let memory = AppMemoryInternal;
let recovered: i64 = memory.deref_i64(addr);
assert_eq!(value, recovered);
}
#[test]
fn external_i64() {
let value: i64 = -1 << 33;
let memory = AppMemoryExternal {
bytes: &[
0, 0, //
0, 0, 0, 0, 0xfe, 0xff, 0xff, 0xff, //
0, 0,
],
};
let addr = 2;
let recovered: i64 = memory.deref_i64(addr);
assert_eq!(value, recovered);
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,4 @@
use crate::repl::app_memory::AppMemoryInternal;
use crate::repl::eval;
use bumpalo::Bump;
use inkwell::context::Context;
@ -12,6 +13,8 @@ use roc_gen_llvm::llvm::externs::add_default_roc_externs;
use roc_load::file::LoadingProblem;
use roc_mono::ir::OptLevel;
use roc_parse::parser::SyntaxError;
use roc_region::all::LineInfo;
use roc_target::TargetInfo;
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
use std::path::{Path, PathBuf};
use std::str::from_utf8_unchecked;
@ -42,7 +45,7 @@ pub fn gen_and_eval<'a>(
let module_src = promote_expr_to_module(src_str);
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let target_info = TargetInfo::from(&target);
let exposed_types = MutMap::default();
let loaded = roc_load::file::load_and_monomorphize_from_str(
@ -52,7 +55,7 @@ pub fn gen_and_eval<'a>(
&stdlib,
src_dir,
exposed_types,
ptr_bytes,
target_info,
builtin_defs_map,
);
@ -91,6 +94,7 @@ pub fn gen_and_eval<'a>(
continue;
}
let line_info = LineInfo::new(&module_src);
let src_lines: Vec<&str> = src.split('\n').collect();
let palette = DEFAULT_PALETTE;
@ -98,7 +102,7 @@ pub fn gen_and_eval<'a>(
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
for problem in can_problems.into_iter() {
let report = can_problem(&alloc, module_path.clone(), problem);
let report = can_problem(&alloc, &line_info, module_path.clone(), problem);
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
@ -107,7 +111,7 @@ pub fn gen_and_eval<'a>(
}
for problem in type_problems {
if let Some(report) = type_problem(&alloc, module_path.clone(), problem) {
if let Some(report) = type_problem(&alloc, &line_info, module_path.clone(), problem) {
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
@ -117,7 +121,7 @@ pub fn gen_and_eval<'a>(
}
for problem in mono_problems {
let report = mono_problem(&alloc, module_path.clone(), problem);
let report = mono_problem(&alloc, &line_info, module_path.clone(), problem);
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
@ -131,7 +135,6 @@ pub fn gen_and_eval<'a>(
} else {
let context = Context::create();
let builder = context.create_builder();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let module = arena.alloc(roc_gen_llvm::llvm::build::module_from_builtins(
&target, &context, "",
));
@ -144,8 +147,8 @@ pub fn gen_and_eval<'a>(
}
}
debug_assert_eq!(exposed_to_host.len(), 1);
let (main_fn_symbol, main_fn_var) = exposed_to_host.iter().next().unwrap();
debug_assert_eq!(exposed_to_host.values.len(), 1);
let (main_fn_symbol, main_fn_var) = exposed_to_host.values.iter().next().unwrap();
let main_fn_symbol = *main_fn_symbol;
let main_fn_var = *main_fn_var;
@ -179,7 +182,7 @@ pub fn gen_and_eval<'a>(
context: &context,
interns,
module,
ptr_bytes,
target_info,
is_gen_test: true, // so roc_panic is generated
// important! we don't want any procedures to get the C calling convention
exposed_to_host: MutSet::default(),
@ -235,7 +238,8 @@ pub fn gen_and_eval<'a>(
&env.interns,
home,
&subs,
ptr_bytes,
target_info,
&AppMemoryInternal,
)
};
let mut expr = roc_fmt::Buf::new_in(&arena);

View file

@ -6,12 +6,16 @@ extern crate roc_collections;
extern crate roc_load;
extern crate roc_module;
#[macro_use]
extern crate indoc;
#[cfg(test)]
mod cli_run {
use cli_utils::helpers::{
example_file, examples_dir, extract_valgrind_errors, fixture_file, run_cmd, run_roc,
run_with_valgrind, ValgrindError, ValgrindErrorXWhat,
example_file, examples_dir, extract_valgrind_errors, fixture_file, known_bad_file, run_cmd,
run_roc, run_with_valgrind, ValgrindError, ValgrindErrorXWhat,
};
use roc_test_utils::assert_multiline_str_eq;
use serial_test::serial;
use std::path::{Path, PathBuf};
@ -44,6 +48,27 @@ mod cli_run {
use_valgrind: bool,
}
fn strip_colors(str: &str) -> String {
use roc_reporting::report::*;
str.replace(RED_CODE, "")
.replace(WHITE_CODE, "")
.replace(BLUE_CODE, "")
.replace(YELLOW_CODE, "")
.replace(GREEN_CODE, "")
.replace(CYAN_CODE, "")
.replace(MAGENTA_CODE, "")
.replace(RESET_CODE, "")
.replace(BOLD_CODE, "")
.replace(UNDERLINE_CODE, "")
}
fn check_compile_error(file: &Path, flags: &[&str], expected: &str) {
let compile_out = run_roc(&[&["check", file.to_str().unwrap()], &flags[..]].concat());
let err = compile_out.stdout.trim();
let err = strip_colors(&err);
assert_multiline_str_eq!(err, expected.into());
}
fn check_output_with_stdin(
file: &Path,
stdin: &[&str],
@ -62,7 +87,7 @@ mod cli_run {
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], &all_flags[..]].concat());
if !compile_out.stderr.is_empty() {
panic!("{}", compile_out.stderr);
panic!("roc build had stderr: {}", compile_out.stderr);
}
assert!(compile_out.status.success(), "bad status {:?}", compile_out);
@ -351,11 +376,19 @@ mod cli_run {
// use_valgrind: true,
// },
cli:"cli" => Example {
filename: "Echo.roc",
executable_filename: "echo",
filename: "form.roc",
executable_filename: "form",
stdin: &["Giovanni\n", "Giorgio\n"],
input_file: None,
expected_ending: "Hi, Giovanni Giorgio!\n",
expected_ending: "Hi, Giovanni Giorgio! 👋\n",
use_valgrind: true,
},
tui:"tui" => Example {
filename: "Main.roc",
executable_filename: "tui",
stdin: &["foo\n"], // NOTE: adding more lines leads to memory leaks
input_file: None,
expected_ending: "Hello Worldfoo!\n",
use_valgrind: true,
},
// custom_malloc:"custom-malloc" => Example {
@ -388,9 +421,11 @@ mod cli_run {
macro_rules! benchmarks {
($($test_name:ident => $benchmark:expr,)+) => {
$(
#[test]
#[cfg_attr(not(debug_assertions), serial(benchmark))]
#[cfg(all(not(feature = "wasm32-cli-run"), not(feature = "i386-cli-run")))]
fn $test_name() {
let benchmark = $benchmark;
let file_name = examples_dir("benchmarks").join(benchmark.filename);
@ -601,6 +636,14 @@ mod cli_run {
expected_ending: "",
use_valgrind: true,
},
issue2279 => Example {
filename: "Issue2279.roc",
executable_filename: "issue2279",
stdin: &[],
input_file: None,
expected_ending: "Hello, world!\n",
use_valgrind: true,
},
quicksort_app => Example {
filename: "QuicksortApp.roc",
executable_filename: "quicksortapp",
@ -732,6 +775,32 @@ mod cli_run {
true,
);
}
#[test]
fn known_type_error() {
check_compile_error(
&known_bad_file("TypeError.roc"),
&[],
indoc!(
r#"
UNRECOGNIZED NAME
I cannot find a `d` value
10 _ <- await (line d)
^
Did you mean one of these?
U8
Ok
I8
F64
"#
),
);
}
}
#[allow(dead_code)]

View file

@ -1,7 +1,7 @@
app "multi-dep-str"
packages { base: "platform" }
packages { pf: "platform" }
imports [ Dep1 ]
provides [ main ] to base
provides [ main ] to pf
main : Str
main = Dep1.str1

View file

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

View file

@ -1,7 +1,7 @@
app "multi-dep-thunk"
packages { base: "platform" }
packages { pf: "platform" }
imports [ Dep1 ]
provides [ main ] to base
provides [ main ] to pf
main : Str
main = Dep1.value1 {}

View file

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

View file

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

View file

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

View file

@ -1,20 +1,18 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
#[cfg(test)]
mod repl_eval {
use cli_utils::helpers;
use roc_test_utils::assert_multiline_str_eq;
const ERROR_MESSAGE_START: char = '─';
fn expect_success(input: &str, expected: &str) {
let out = helpers::repl_eval(input);
assert_eq!(&out.stderr, "");
assert_eq!(&out.stdout, expected);
assert_multiline_str_eq!("", out.stderr.as_str());
assert_multiline_str_eq!(expected, out.stdout.as_str());
assert!(out.status.success());
}
@ -25,12 +23,12 @@ mod repl_eval {
// so skip till the header of the first error
match out.stdout.find(ERROR_MESSAGE_START) {
Some(index) => {
assert_eq!(&out.stderr, "");
assert_eq!(&out.stdout[index..], expected);
assert_multiline_str_eq!("", out.stderr.as_str());
assert_multiline_str_eq!(expected, &out.stdout[index..]);
assert!(out.status.success());
}
None => {
assert_eq!(&out.stderr, "");
assert_multiline_str_eq!("", out.stderr.as_str());
assert!(out.status.success());
panic!(
"I expected a failure, but there is no error message in stdout:\n\n{}",
@ -177,6 +175,51 @@ mod repl_eval {
expect_success("Foo Bar", "Foo Bar : [ Foo [ Bar ]* ]*");
}
#[test]
fn newtype_of_big_data() {
expect_success(
indoc!(
r#"
Either a b : [ Left a, Right b ]
lefty : Either Str Str
lefty = Left "loosey"
A lefty
"#
),
r#"A (Left "loosey") : [ A (Either Str Str) ]*"#,
)
}
#[test]
fn newtype_nested() {
expect_success(
indoc!(
r#"
Either a b : [ Left a, Right b ]
lefty : Either Str Str
lefty = Left "loosey"
A (B (C lefty))
"#
),
r#"A (B (C (Left "loosey"))) : [ A [ B [ C (Either Str Str) ]* ]* ]*"#,
)
}
#[test]
fn newtype_of_big_of_newtype() {
expect_success(
indoc!(
r#"
Big a : [ Big a [ Wrapper [ Newtype a ] ] ]
big : Big Str
big = Big "s" (Wrapper (Newtype "t"))
A big
"#
),
r#"A (Big "s" (Wrapper (Newtype "t"))) : [ A (Big Str) ]*"#,
)
}
#[test]
fn tag_with_arguments() {
expect_success("True 1", "True 1 : [ True (Num *) ]*");
@ -299,24 +342,30 @@ mod repl_eval {
#[test]
fn num_add_wrap() {
expect_success("Num.addWrap Num.maxInt 1", "-9223372036854775808 : Int *");
expect_success(
"Num.addWrap Num.maxI64 1",
"-9223372036854775808 : Int Signed64",
);
}
#[test]
fn num_sub_wrap() {
expect_success("Num.subWrap Num.minInt 1", "9223372036854775807 : Int *");
expect_success(
"Num.subWrap Num.minI64 1",
"9223372036854775807 : Int Signed64",
);
}
#[test]
fn num_mul_wrap() {
expect_success("Num.mulWrap Num.maxInt 2", "-2 : Int *");
expect_success("Num.mulWrap Num.maxI64 2", "-2 : Int Signed64");
}
#[test]
fn num_add_checked() {
expect_success("Num.addChecked 1 1", "Ok 2 : Result (Num *) [ Overflow ]*");
expect_success(
"Num.addChecked Num.maxInt 1",
"Num.addChecked Num.maxI64 1",
"Err Overflow : Result I64 [ Overflow ]*",
);
}
@ -325,7 +374,7 @@ mod repl_eval {
fn num_sub_checked() {
expect_success("Num.subChecked 1 1", "Ok 0 : Result (Num *) [ Overflow ]*");
expect_success(
"Num.subChecked Num.minInt 1",
"Num.subChecked Num.minI64 1",
"Err Overflow : Result I64 [ Overflow ]*",
);
}
@ -337,7 +386,7 @@ mod repl_eval {
"Ok 40 : Result (Num *) [ Overflow ]*",
);
expect_success(
"Num.mulChecked Num.maxInt 2",
"Num.mulChecked Num.maxI64 2",
"Err Overflow : Result I64 [ Overflow ]*",
);
}
@ -567,13 +616,347 @@ mod repl_eval {
);
}
// #[test]
// fn parse_problem() {
// // can't find something that won't parse currently
// }
//
// #[test]
// fn mono_problem() {
// // can't produce a mono error (non-exhaustive pattern) yet
// }
#[test]
fn issue_2149() {
expect_success(r#"Str.toI8 "127""#, "Ok 127 : Result I8 [ InvalidNumStr ]*");
expect_success(
r#"Str.toI8 "128""#,
"Err InvalidNumStr : Result I8 [ InvalidNumStr ]*",
);
expect_success(
r#"Str.toI16 "32767""#,
"Ok 32767 : Result I16 [ InvalidNumStr ]*",
);
expect_success(
r#"Str.toI16 "32768""#,
"Err InvalidNumStr : Result I16 [ InvalidNumStr ]*",
);
}
#[test]
fn multiline_input() {
expect_success(
indoc!(
r#"
a : Str
a = "123"
a
"#
),
r#""123" : Str"#,
)
}
#[test]
fn recursive_tag_union_flat_variant() {
expect_success(
indoc!(
r#"
Expr : [ Sym Str, Add Expr Expr ]
s : Expr
s = Sym "levitating"
s
"#
),
r#"Sym "levitating" : Expr"#,
)
}
#[test]
fn large_recursive_tag_union_flat_variant() {
expect_success(
// > 7 variants so that to force tag storage alongside the data
indoc!(
r#"
Item : [ A Str, B Str, C Str, D Str, E Str, F Str, G Str, H Str, I Str, J Str, K Item ]
s : Item
s = H "woo"
s
"#
),
r#"H "woo" : Item"#,
)
}
#[test]
fn recursive_tag_union_recursive_variant() {
expect_success(
indoc!(
r#"
Expr : [ Sym Str, Add Expr Expr ]
s : Expr
s = Add (Add (Sym "one") (Sym "two")) (Sym "four")
s
"#
),
r#"Add (Add (Sym "one") (Sym "two")) (Sym "four") : Expr"#,
)
}
#[test]
fn large_recursive_tag_union_recursive_variant() {
expect_success(
// > 7 variants so that to force tag storage alongside the data
indoc!(
r#"
Item : [ A Str, B Str, C Str, D Str, E Str, F Str, G Str, H Str, I Str, J Str, K Item, L Item ]
s : Item
s = K (L (E "woo"))
s
"#
),
r#"K (L (E "woo")) : Item"#,
)
}
#[test]
fn recursive_tag_union_into_flat_tag_union() {
expect_success(
indoc!(
r#"
Item : [ One [ A Str, B Str ], Deep Item ]
i : Item
i = Deep (One (A "woo"))
i
"#
),
r#"Deep (One (A "woo")) : Item"#,
)
}
#[test]
fn non_nullable_unwrapped_tag_union() {
expect_success(
indoc!(
r#"
RoseTree a : [ Tree a (List (RoseTree a)) ]
e1 : RoseTree Str
e1 = Tree "e1" []
e2 : RoseTree Str
e2 = Tree "e2" []
combo : RoseTree Str
combo = Tree "combo" [e1, e2]
combo
"#
),
r#"Tree "combo" [ Tree "e1" [], Tree "e2" [] ] : RoseTree Str"#,
)
}
#[test]
fn nullable_unwrapped_tag_union() {
expect_success(
indoc!(
r#"
LinkedList a : [ Nil, Cons a (LinkedList a) ]
c1 : LinkedList Str
c1 = Cons "Red" Nil
c2 : LinkedList Str
c2 = Cons "Yellow" c1
c3 : LinkedList Str
c3 = Cons "Green" c2
c3
"#
),
r#"Cons "Green" (Cons "Yellow" (Cons "Red" Nil)) : LinkedList Str"#,
)
}
#[test]
fn nullable_wrapped_tag_union() {
expect_success(
indoc!(
r#"
Container a : [ Empty, Whole a, Halved (Container a) (Container a) ]
meats : Container Str
meats = Halved (Whole "Brisket") (Whole "Ribs")
sides : Container Str
sides = Halved (Whole "Coleslaw") Empty
bbqPlate : Container Str
bbqPlate = Halved meats sides
bbqPlate
"#
),
r#"Halved (Halved (Whole "Brisket") (Whole "Ribs")) (Halved (Whole "Coleslaw") Empty) : Container Str"#,
)
}
#[test]
fn large_nullable_wrapped_tag_union() {
// > 7 non-empty variants so that to force tag storage alongside the data
expect_success(
indoc!(
r#"
Cont a : [ Empty, S1 a, S2 a, S3 a, S4 a, S5 a, S6 a, S7 a, Tup (Cont a) (Cont a) ]
fst : Cont Str
fst = Tup (S1 "S1") (S2 "S2")
snd : Cont Str
snd = Tup (S5 "S5") Empty
tup : Cont Str
tup = Tup fst snd
tup
"#
),
r#"Tup (Tup (S1 "S1") (S2 "S2")) (Tup (S5 "S5") Empty) : Cont Str"#,
)
}
#[test]
fn issue_2300() {
expect_success(
r#"\Email str -> str == """#,
r#"<function> : [ Email Str ] -> Bool"#,
)
}
#[test]
fn function_in_list() {
expect_success(
r#"[\x -> x + 1, \s -> s * 2]"#,
r#"[ <function>, <function> ] : List (Num a -> Num a)"#,
)
}
#[test]
fn function_in_record() {
expect_success(
r#"{ n: 1, adder: \x -> x + 1 }"#,
r#"{ adder: <function>, n: <function> } : { adder : Num a -> Num a, n : Num * }"#,
)
}
#[test]
fn function_in_unwrapped_record() {
expect_success(
r#"{ adder: \x -> x + 1 }"#,
r#"{ adder: <function> } : { adder : Num a -> Num a }"#,
)
}
#[test]
fn function_in_tag() {
expect_success(
r#"Adder (\x -> x + 1)"#,
r#"Adder <function> : [ Adder (Num a -> Num a) ]*"#,
)
}
#[test]
fn newtype_of_record_of_tag_of_record_of_tag() {
expect_success(
r#"A {b: C {d: 1}}"#,
r#"A { b: C { d: 1 } } : [ A { b : [ C { d : Num * } ]* } ]*"#,
)
}
#[test]
fn print_u8s() {
expect_success(
indoc!(
r#"
x : U8
x = 129
x
"#
),
"129 : U8",
)
}
#[test]
fn parse_problem() {
expect_failure(
"add m n = m + n",
indoc!(
r#"
ARGUMENTS BEFORE EQUALS
I am partway through parsing a definition, but I got stuck here:
1 app "app" provides [ replOutput ] to "./platform"
2
3 replOutput =
4 add m n = m + n
^^^
Looks like you are trying to define a function. In roc, functions are
always written as a lambda, like increment = \n -> n + 1.
"#
),
);
}
#[test]
fn mono_problem() {
expect_failure(
r#"
t : [A, B, C]
t = A
when t is
A -> "a"
"#,
indoc!(
r#"
UNSAFE PATTERN
This when does not cover all the possibilities:
7> when t is
8> A -> "a"
Other possibilities include:
B
C
I would have to crash if I saw one of those! Add branches for them!
Enter an expression, or :help, or :exit/:q."#
),
);
}
#[test]
fn issue_2343_complete_mono_with_shadowed_vars() {
expect_failure(
indoc!(
r#"
b = False
f = \b ->
when b is
True -> 5
False -> 15
f b
"#
),
indoc!(
r#"
DUPLICATE NAME
The b name is first defined here:
4 b = False
^
But then it's defined a second time here:
5 f = \b ->
^
Since these variables have the same name, it's easy to use the wrong
one on accident. Give one of them a new name.
"#
),
);
}
}

164
cli_utils/Cargo.lock generated
View file

@ -386,6 +386,17 @@ dependencies = [
"winapi",
]
[[package]]
name = "clipboard-win"
version = "4.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3db8340083d28acb43451166543b98c838299b7e0863621be53a338adceea0ed"
dependencies = [
"error-code",
"str-buf",
"winapi",
]
[[package]]
name = "cocoa"
version = "0.24.0"
@ -480,7 +491,7 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4423d79fed83ebd9ab81ec21fa97144300a961782158287dc9bf7eddac37ff0b"
dependencies = [
"clipboard-win",
"clipboard-win 3.1.1",
"objc",
"objc-foundation",
"objc_id",
@ -808,9 +819,9 @@ dependencies = [
[[package]]
name = "dirs-next"
version = "1.0.2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf36e65a80337bea855cd4ef9b8401ffce06a7baedf2e85ec467b1ac3f6e82b6"
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
dependencies = [
"cfg-if 1.0.0",
"dirs-sys-next",
@ -898,6 +909,12 @@ version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "endian-type"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]]
name = "env_logger"
version = "0.9.0"
@ -911,12 +928,33 @@ dependencies = [
"termcolor",
]
[[package]]
name = "error-code"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5115567ac25674e0043e472be13d14e537f37ea8aa4bdc4aef0c89add1db1ff"
dependencies = [
"libc",
"str-buf",
]
[[package]]
name = "fake-simd"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
[[package]]
name = "fd-lock"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a16910e685088843d53132b04e0f10a571fdb193224fc589685b3ba1ce4cb03d"
dependencies = [
"cfg-if 1.0.0",
"libc",
"windows-sys",
]
[[package]]
name = "find-crate"
version = "0.6.3"
@ -1310,13 +1348,13 @@ dependencies = [
name = "inkwell"
version = "0.1.0"
dependencies = [
"inkwell 0.1.0 (git+https://github.com/rtfeldman/inkwell?tag=llvm12-0.release8)",
"inkwell 0.1.0 (git+https://github.com/rtfeldman/inkwell?tag=llvm13-0.release1)",
]
[[package]]
name = "inkwell"
version = "0.1.0"
source = "git+https://github.com/rtfeldman/inkwell?tag=llvm12-0.release8#14b78d96d2dbc95694e181be66e4cd53df3fc02f"
source = "git+https://github.com/rtfeldman/inkwell?tag=llvm13-0.release1#e15d665227b2acad4ca949820d80048e09f3f4e5"
dependencies = [
"either",
"inkwell_internals",
@ -1324,13 +1362,12 @@ dependencies = [
"llvm-sys",
"once_cell",
"parking_lot",
"regex",
]
[[package]]
name = "inkwell_internals"
version = "0.3.0"
source = "git+https://github.com/rtfeldman/inkwell?tag=llvm12-0.release8#14b78d96d2dbc95694e181be66e4cd53df3fc02f"
version = "0.5.0"
source = "git+https://github.com/rtfeldman/inkwell?tag=llvm13-0.release1#e15d665227b2acad4ca949820d80048e09f3f4e5"
dependencies = [
"proc-macro2",
"quote",
@ -1692,16 +1729,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c44922cb3dbb1c70b5e5f443d63b64363a898564d739ba5198e3a9138442868d"
[[package]]
name = "nix"
version = "0.17.0"
name = "nibble_vec"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363"
checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
dependencies = [
"bitflags",
"cc",
"cfg-if 0.1.10",
"libc",
"void",
"smallvec",
]
[[package]]
@ -1741,6 +1774,19 @@ dependencies = [
"memoffset",
]
[[package]]
name = "nix"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6"
dependencies = [
"bitflags",
"cc",
"cfg-if 1.0.0",
"libc",
"memoffset",
]
[[package]]
name = "nom"
version = "7.1.0"
@ -2247,6 +2293,16 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb"
[[package]]
name = "radix_trie"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
dependencies = [
"endian-type",
"nibble_vec",
]
[[package]]
name = "rand"
version = "0.8.4"
@ -2666,6 +2722,7 @@ dependencies = [
"roc_collections",
"roc_module",
"roc_mono",
"roc_reporting",
"roc_std",
"target-lexicon",
]
@ -2789,6 +2846,9 @@ dependencies = [
[[package]]
name = "roc_region"
version = "0.1.0"
dependencies = [
"static_assertions",
]
[[package]]
name = "roc_reporting"
@ -2887,16 +2947,21 @@ dependencies = [
[[package]]
name = "rustyline"
version = "6.2.0"
source = "git+https://github.com/rtfeldman/rustyline?tag=prompt-fix#a6b8a20d2bf5c3793d7367848be2f4afec2f0d99"
version = "9.1.1"
source = "git+https://github.com/rtfeldman/rustyline?tag=v9.1.1#7053ae0fe0ee710d38ed5845dd979113382994dc"
dependencies = [
"cfg-if 0.1.10",
"bitflags",
"cfg-if 1.0.0",
"clipboard-win 4.2.2",
"dirs-next",
"fd-lock",
"libc",
"log",
"memchr",
"nix 0.17.0",
"nix 0.23.1",
"radix_trie",
"scopeguard",
"smallvec",
"unicode-segmentation",
"unicode-width",
"utf8parse",
@ -2905,8 +2970,8 @@ dependencies = [
[[package]]
name = "rustyline-derive"
version = "0.3.1"
source = "git+https://github.com/rtfeldman/rustyline?tag=prompt-fix#a6b8a20d2bf5c3793d7367848be2f4afec2f0d99"
version = "0.6.0"
source = "git+https://github.com/rtfeldman/rustyline?tag=v9.1.1#7053ae0fe0ee710d38ed5845dd979113382994dc"
dependencies = [
"quote",
"syn",
@ -3175,6 +3240,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "str-buf"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d44a3643b4ff9caf57abcee9c2c621d6c03d9135e0d8b589bd9afb5992cb176a"
[[package]]
name = "strip-ansi-escapes"
version = "0.1.1"
@ -3418,12 +3489,6 @@ version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]]
name = "vte"
version = "0.10.1"
@ -3816,6 +3881,49 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82ca39602d5cbfa692c4b67e3bcbb2751477355141c1ed434c94da4186836ff6"
dependencies = [
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_msvc"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52695a41e536859d5308cc613b4a022261a274390b25bd29dfff4bf08505f3c2"
[[package]]
name = "windows_i686_gnu"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f54725ac23affef038fecb177de6c9bf065787c2f432f79e3c373da92f3e1d8a"
[[package]]
name = "windows_i686_msvc"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d5158a43cc43623c0729d1ad6647e62fa384a3d135fd15108d37c683461f64"
[[package]]
name = "windows_x86_64_gnu"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc31f409f565611535130cfe7ee8e6655d3fa99c1c61013981e491921b5ce954"
[[package]]
name = "windows_x86_64_msvc"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f2b8c7cbd3bfdddd9ab98769f9746a7fad1bca236554cd032b78d768bc0e89f"
[[package]]
name = "winit"
version = "0.25.0"

View file

@ -32,7 +32,8 @@ pub fn path_to_roc_binary() -> PathBuf {
.or_else(|| {
env::current_exe().ok().map(|mut path| {
path.pop();
if path.ends_with("deps") { path.pop();
if path.ends_with("deps") {
path.pop();
}
path
})
@ -293,6 +294,19 @@ pub fn fixture_file(dir_name: &str, file_name: &str) -> PathBuf {
path
}
#[allow(dead_code)]
pub fn known_bad_file(file_name: &str) -> PathBuf {
let mut path = root_dir();
// Descend into cli/tests/known_bad/{file_name}
path.push("cli");
path.push("tests");
path.push("known_bad");
path.push(file_name);
path
}
#[allow(dead_code)]
pub fn repl_eval(input: &str) -> Out {
let mut cmd = Command::new(path_to_roc_binary());

View file

@ -147,7 +147,7 @@ The compiler contains a lot of code! If you're new to the project it can be hard
After you get into the details, you'll discover that some parts of the compiler have more than one entry point. And things can be interwoven together in subtle and complex ways, for reasons to do with performance, edge case handling, etc. But if this is "day one" for you, and you're just trying to get familiar with things, this should be "good enough".
The compiler is invoked from the CLI via `build_file` in cli/src/build.rs
The compiler is invoked from the CLI via `build_file` in cli/src/build.rs
| Phase | Entry point / main functions |
| ------------------------------------- | ------------------------------------------------ |
@ -164,3 +164,24 @@ The compiler is invoked from the CLI via `build_file` in cli/src/build.rs
| Code gen (unoptimized but fast, Wasm) | gen_wasm/src/lib.rs: build_module |
For a more detailed understanding of the compilation phases, see the `Phase`, `BuildTask`, and `Msg` enums in `load/src/file.rs`.
## Debugging intermediate representations
### The mono IR
If you observe a miscomplication, you may first want to check the generated mono
IR for your code - maybe there was a problem during specialization or layout
generation. One way to do this is to add a test to `test_mono/src/tests.rs`
and run the tests with `cargo test -p test_mono`; this will write the mono
IR to a file.
You may also want to set some or all of the following environment variables:
- `PRINT_IR_AFTER_SPECIALIZATION=1` prints the mono IR after function
specialization to stdout
- `PRINT_IR_AFTER_RESET_REUSE=1` prints the mono IR after insertion of
reset/reuse isntructions to stdout
- `PRINT_IR_AFTER_REFCOUNT=1` prints the mono IR after insertion of reference
counting instructions to stdout
- `PRETTY_PRINT_IR_SYMBOLS=1` instructs the pretty printer to dump all the
information it knows about the mono IR whenever it is printed

View file

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

View file

@ -11,6 +11,13 @@ use std::path::{Path, PathBuf};
use std::process::{Child, Command, Output};
use target_lexicon::{Architecture, OperatingSystem, Triple};
fn zig_executable() -> String {
match std::env::var("ROC_ZIG") {
Ok(path) => path,
Err(_) => "zig".into(),
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum LinkType {
// These numbers correspond to the --lib flag; if it's present
@ -88,7 +95,7 @@ pub fn build_zig_host_native(
shared_lib_path: Option<&Path>,
_target_valgrind: bool,
) -> Output {
let mut command = Command::new("zig");
let mut command = Command::new(&zig_executable());
command
.env_clear()
.env("PATH", env_path)
@ -151,7 +158,10 @@ pub fn build_zig_host_native(
use serde_json::Value;
// Run `zig env` to find the location of zig's std/ directory
let zig_env_output = Command::new("zig").args(&["env"]).output().unwrap();
let zig_env_output = Command::new(&zig_executable())
.args(&["env"])
.output()
.unwrap();
let zig_env_json = if zig_env_output.status.success() {
std::str::from_utf8(&zig_env_output.stdout).unwrap_or_else(|utf8_err| {
@ -188,7 +198,7 @@ pub fn build_zig_host_native(
zig_compiler_rt_path.push("special");
zig_compiler_rt_path.push("compiler_rt.zig");
let mut command = Command::new("zig");
let mut command = Command::new(&zig_executable());
command
.env_clear()
.env("PATH", &env_path)
@ -245,7 +255,7 @@ pub fn build_zig_host_wasm32(
// we'd like to compile with `-target wasm32-wasi` but that is blocked on
//
// https://github.com/ziglang/zig/issues/9414
let mut command = Command::new("zig");
let mut command = Command::new(&zig_executable());
command
.env_clear()
.env("PATH", env_path)
@ -442,7 +452,7 @@ pub fn rebuild_host(
_ => panic!("Unsupported architecture {:?}", target.architecture),
};
validate_output("host.zig", "zig", output)
validate_output("host.zig", &zig_executable(), output)
} else if cargo_host_src.exists() {
// Compile and link Cargo.toml, if it exists
let cargo_dir = host_input_path.parent().unwrap();
@ -646,7 +656,7 @@ fn link_linux(
if let Architecture::X86_32(_) = target.architecture {
return Ok((
Command::new("zig")
Command::new(&zig_executable())
.args(&["build-exe"])
.args(input_paths)
.args(&[
@ -743,49 +753,52 @@ fn link_linux(
// NOTE: order of arguments to `ld` matters here!
// The `-l` flags should go after the `.o` arguments
Ok((
Command::new("ld")
// Don't allow LD_ env vars to affect this
.env_clear()
.env("PATH", &env_path)
// Keep NIX_ env vars
.envs(
env::vars()
.filter(|&(ref k, _)| k.starts_with("NIX_"))
.collect::<HashMap<String, String>>(),
)
.args(&[
"--gc-sections",
"--eh-frame-hdr",
"-A",
arch_str(target),
"-pie",
libcrt_path.join("crti.o").to_str().unwrap(),
libcrt_path.join("crtn.o").to_str().unwrap(),
])
.args(&base_args)
.args(&["-dynamic-linker", ld_linux])
.args(input_paths)
// ld.lld requires this argument, and does not accept --arch
// .args(&["-L/usr/lib/x86_64-linux-gnu"])
.args(&[
// Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496365925
// for discussion and further references
"-lc",
"-lm",
"-lpthread",
"-ldl",
"-lrt",
"-lutil",
"-lc_nonshared",
libgcc_path.to_str().unwrap(),
// Output
"-o",
output_path.as_path().to_str().unwrap(), // app (or app.so or app.dylib etc.)
])
.spawn()?,
output_path,
))
let mut command = Command::new("ld");
command
// Don't allow LD_ env vars to affect this
.env_clear()
.env("PATH", &env_path)
// Keep NIX_ env vars
.envs(
env::vars()
.filter(|&(ref k, _)| k.starts_with("NIX_"))
.collect::<HashMap<String, String>>(),
)
.args(&[
"--gc-sections",
"--eh-frame-hdr",
"-A",
arch_str(target),
"-pie",
libcrt_path.join("crti.o").to_str().unwrap(),
libcrt_path.join("crtn.o").to_str().unwrap(),
])
.args(&base_args)
.args(&["-dynamic-linker", ld_linux])
.args(input_paths)
// ld.lld requires this argument, and does not accept --arch
// .args(&["-L/usr/lib/x86_64-linux-gnu"])
.args(&[
// Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496365925
// for discussion and further references
"-lc",
"-lm",
"-lpthread",
"-ldl",
"-lrt",
"-lutil",
"-lc_nonshared",
libgcc_path.to_str().unwrap(),
// Output
"-o",
output_path.as_path().to_str().unwrap(), // app (or app.so or app.dylib etc.)
]);
let output = command.spawn()?;
Ok((output, output_path))
}
fn link_macos(
@ -843,11 +856,23 @@ fn link_macos(
"-lSystem",
"-lresolv",
"-lpthread",
// This `-F PATH` flag is needed for `-framework` flags to work
"-F",
"/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/",
// These frameworks are needed for GUI examples to work
"-framework",
"Cocoa",
"-framework",
"CoreVideo",
"-framework",
"Metal",
"-framework",
"QuartzCore",
// "-lrt", // TODO shouldn't we need this?
// "-lc_nonshared", // TODO shouldn't we need this?
// "-lgcc", // TODO will eventually need compiler_rt from gcc or something - see https://github.com/rtfeldman/roc/pull/554#discussion_r496370840
// "-framework", // Uncomment this line & the following ro run the `rand` crate in examples/cli
// "Security",
"-framework",
"Security",
// Output
"-o",
output_path.to_str().unwrap(), // app
@ -895,7 +920,7 @@ fn link_wasm32(
let zig_str_path = find_zig_str_path();
let wasi_libc_path = find_wasi_libc_path();
let child = Command::new("zig")
let child = Command::new(&zig_executable())
// .env_clear()
// .env("PATH", &env_path)
.args(&["build-exe"])

View file

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

View file

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

View file

@ -4,7 +4,7 @@ Builtins are the functions and modules that are implicitly imported into every m
### module/src/symbol.rs
Towards the bottom of `symbol.rs` there is a `define_builtins!` macro being used that takes many modules and function names. The first level (`List`, `Int` ..) is the module name, and the second level is the function or value name (`reverse`, `mod` ..). If you wanted to add a `Int` function called `addTwo` go to `2 Int: "Int" => {` and inside that case add to the bottom `38 INT_ADD_TWO: "addTwo"` (assuming there are 37 existing ones).
Towards the bottom of `symbol.rs` there is a `define_builtins!` macro being used that takes many modules and function names. The first level (`List`, `Int` ..) is the module name, and the second level is the function or value name (`reverse`, `mod` ..). If you wanted to add a `Int` function called `addTwo` go to `2 Int: "Int" => {` and inside that case add to the bottom `38 INT_ADD_TWO: "addTwo"` (assuming there are 37 existing ones).
Some of these have `#` inside their name (`first#list`, `#lt` ..). This is a trick we are doing to hide implementation details from Roc programmers. To a Roc programmer, a name with `#` in it is invalid, because `#` means everything after it is parsed to a comment. We are constructing these functions manually, so we are circumventing the parsing step and dont have such restrictions. We get to make functions and values with `#` which as a consequence are not accessible to Roc programmers. Roc programmers simply cannot reference them.
@ -80,7 +80,7 @@ fn atan() {
```
But replace `Num.atan` and the type signature with the new builtin.
### gen/test/*.rs
### test_gen/test/*.rs
In this directory, there are a couple files like `gen_num.rs`, `gen_str.rs`, etc. For the `Str` module builtins, put the test in `gen_str.rs`, etc. Find the one for the new builtin, and add a test like:
```
#[test]

View file

@ -27,7 +27,7 @@ There will be two directories like `roc_builtins-[some random characters]`, look
`out` directory as a child.
> The bitcode is a bunch of bytes that aren't particularly human-readable.
> If you want to take a look at the human-readable LLVM IR, look at
> If you want to take a look at the human-readable LLVM IR, look at
> `target/debug/build/roc_builtins-[some random characters]/out/builtins.ll`
## Calling bitcode functions

View file

@ -216,7 +216,7 @@ pub const RocDict = extern struct {
}
// otherwise, check if the refcount is one
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(8, self.dict_bytes));
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(@alignOf(usize), self.dict_bytes));
return (ptr - 1)[0] == utils.REFCOUNT_ONE;
}

View file

@ -36,7 +36,7 @@ pub const RocList = extern struct {
}
// otherwise, check if the refcount is one
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(8, self.bytes));
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(@alignOf(usize), self.bytes));
return (ptr - 1)[0] == utils.REFCOUNT_ONE;
}
@ -1282,7 +1282,7 @@ pub fn listSet(
// `if inBounds then LowLevelListGet 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(8, bytes));
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(@alignOf(usize), bytes));
if ((ptr - 1)[0] == utils.REFCOUNT_ONE) {
return listSetInPlaceHelp(bytes, index, element, element_width, dec);

View file

@ -16,7 +16,7 @@ const InPlace = enum(u8) {
};
const SMALL_STR_MAX_LENGTH = small_string_size - 1;
const small_string_size = 2 * @sizeOf(usize);
const small_string_size = @sizeOf(RocStr);
const blank_small_string: [@sizeOf(RocStr)]u8 = init_blank_small_string(small_string_size);
fn init_blank_small_string(comptime n: usize) [n]u8 {
@ -37,8 +37,9 @@ pub const RocStr = extern struct {
pub const alignment = @alignOf(usize);
pub inline fn empty() RocStr {
const small_str_flag: isize = std.math.minInt(isize);
return RocStr{
.str_len = 0,
.str_len = @bitCast(usize, small_str_flag),
.str_bytes = null,
};
}
@ -80,7 +81,7 @@ pub const RocStr = extern struct {
}
pub fn deinit(self: RocStr) void {
if (!self.isSmallStr() and !self.isEmpty()) {
if (!self.isSmallStr()) {
utils.decref(self.str_bytes, self.str_len, RocStr.alignment);
}
}
@ -105,11 +106,8 @@ pub const RocStr = extern struct {
}
pub fn eq(self: RocStr, other: RocStr) bool {
const self_bytes_ptr: ?[*]const u8 = self.str_bytes;
const other_bytes_ptr: ?[*]const u8 = other.str_bytes;
// If they are byte-for-byte equal, they're definitely equal!
if (self_bytes_ptr == other_bytes_ptr and self.str_len == other.str_len) {
if (self.str_bytes == other.str_bytes and self.str_len == other.str_len) {
return true;
}
@ -121,28 +119,34 @@ pub const RocStr = extern struct {
return false;
}
const self_u8_ptr: [*]const u8 = @ptrCast([*]const u8, &self);
const other_u8_ptr: [*]const u8 = @ptrCast([*]const u8, &other);
const self_bytes: [*]const u8 = if (self.isSmallStr() or self.isEmpty()) self_u8_ptr else self_bytes_ptr orelse unreachable;
const other_bytes: [*]const u8 = if (other.isSmallStr() or other.isEmpty()) other_u8_ptr else other_bytes_ptr orelse unreachable;
// Now we have to look at the string contents
const self_bytes = self.asU8ptr();
const other_bytes = other.asU8ptr();
var index: usize = 0;
// TODO rewrite this into a for loop
const length = self.len();
while (index < length) {
if (self_bytes[index] != other_bytes[index]) {
// It's faster to compare pointer-sized words rather than bytes, as far as possible
// The bytes are always pointer-size aligned due to the refcount
const self_words = @ptrCast([*]const usize, @alignCast(@alignOf(usize), self_bytes));
const other_words = @ptrCast([*]const usize, @alignCast(@alignOf(usize), other_bytes));
var w: usize = 0;
while (w < self_len / @sizeOf(usize)) : (w += 1) {
if (self_words[w] != other_words[w]) {
return false;
}
}
index = index + 1;
// Compare the leftover bytes
var b = w * @sizeOf(usize);
while (b < self_len) : (b += 1) {
if (self_bytes[b] != other_bytes[b]) {
return false;
}
}
return true;
}
pub fn clone(in_place: InPlace, str: RocStr) RocStr {
if (str.isSmallStr() or str.isEmpty()) {
if (str.isSmallStr()) {
// just return the bytes
return str;
} else {
@ -214,7 +218,8 @@ pub const RocStr = extern struct {
}
pub fn isEmpty(self: RocStr) bool {
return self.len() == 0;
comptime const empty_len = RocStr.empty().str_len;
return self.str_len == empty_len;
}
// If a string happens to be null-terminated already, then we can pass its
@ -246,7 +251,7 @@ pub const RocStr = extern struct {
} else {
// This is a big string, and it's not empty, so we can safely
// dereference the pointer.
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(8, self.str_bytes));
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(@alignOf(usize), self.str_bytes));
const capacity_or_refcount: isize = (ptr - 1)[0];
// If capacity_or_refcount is positive, then it's a capacity value.
@ -277,7 +282,7 @@ pub const RocStr = extern struct {
// to first change its flag to mark it as a small string!
return longest_small_str;
} else {
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(8, self.str_bytes));
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(@alignOf(usize), self.str_bytes));
const capacity_or_refcount: isize = (ptr - 1)[0];
if (capacity_or_refcount > 0) {
@ -294,11 +299,6 @@ pub const RocStr = extern struct {
}
pub fn isUnique(self: RocStr) bool {
// the empty string is unique (in the sense that copying it will not leak memory)
if (self.isEmpty()) {
return true;
}
// small strings can be copied
if (self.isSmallStr()) {
return true;
@ -309,7 +309,7 @@ pub const RocStr = extern struct {
}
fn isRefcountOne(self: RocStr) bool {
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(8, self.str_bytes));
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(@alignOf(usize), self.str_bytes));
return (ptr - 1)[0] == utils.REFCOUNT_ONE;
}
@ -321,8 +321,8 @@ pub const RocStr = extern struct {
// Since this conditional would be prone to branch misprediction,
// make sure it will compile to a cmov.
// return if (self.isSmallStr() or self.isEmpty()) (&@bitCast([@sizeOf(RocStr)]u8, self)) else (@ptrCast([*]u8, self.str_bytes));
if (self.isSmallStr() or self.isEmpty()) {
// return if (self.isSmallStr()) (&@bitCast([@sizeOf(RocStr)]u8, self)) else (@ptrCast([*]u8, self.str_bytes));
if (self.isSmallStr()) {
const as_int = @ptrToInt(&self);
const as_ptr = @intToPtr([*]u8, as_int);
return as_ptr;
@ -342,7 +342,7 @@ pub const RocStr = extern struct {
@memcpy(dest, src, self.len());
}
test "RocStr.eq: equal" {
test "RocStr.eq: small, equal" {
const str1_len = 3;
var str1: [str1_len]u8 = "abc".*;
const str1_ptr: [*]u8 = &str1;
@ -359,7 +359,7 @@ pub const RocStr = extern struct {
roc_str2.deinit();
}
test "RocStr.eq: not equal different length" {
test "RocStr.eq: small, not equal, different length" {
const str1_len = 4;
var str1: [str1_len]u8 = "abcd".*;
const str1_ptr: [*]u8 = &str1;
@ -378,7 +378,7 @@ pub const RocStr = extern struct {
try expect(!roc_str1.eq(roc_str2));
}
test "RocStr.eq: not equal same length" {
test "RocStr.eq: small, not equal, same length" {
const str1_len = 3;
var str1: [str1_len]u8 = "acb".*;
const str1_ptr: [*]u8 = &str1;
@ -396,6 +396,67 @@ pub const RocStr = extern struct {
try expect(!roc_str1.eq(roc_str2));
}
test "RocStr.eq: large, equal" {
const content = "012345678901234567890123456789";
const roc_str1 = RocStr.init(content, content.len);
const roc_str2 = RocStr.init(content, content.len);
defer {
roc_str1.deinit();
roc_str2.deinit();
}
try expect(roc_str1.eq(roc_str2));
}
test "RocStr.eq: large, different lengths, unequal" {
const content1 = "012345678901234567890123456789";
const roc_str1 = RocStr.init(content1, content1.len);
const content2 = "012345678901234567890";
const roc_str2 = RocStr.init(content2, content2.len);
defer {
roc_str1.deinit();
roc_str2.deinit();
}
try expect(!roc_str1.eq(roc_str2));
}
test "RocStr.eq: large, different content, unequal" {
const content1 = "012345678901234567890123456789!!";
const roc_str1 = RocStr.init(content1, content1.len);
const content2 = "012345678901234567890123456789--";
const roc_str2 = RocStr.init(content2, content2.len);
defer {
roc_str1.deinit();
roc_str2.deinit();
}
try expect(!roc_str1.eq(roc_str2));
}
test "RocStr.eq: large, garbage after end, equal" {
const content = "012345678901234567890123456789";
const roc_str1 = RocStr.init(content, content.len);
const roc_str2 = RocStr.init(content, content.len);
try expect(roc_str1.str_bytes != roc_str2.str_bytes);
// Insert garbage after the end of each string
roc_str1.str_bytes.?[30] = '!';
roc_str1.str_bytes.?[31] = '!';
roc_str2.str_bytes.?[30] = '-';
roc_str2.str_bytes.?[31] = '-';
defer {
roc_str1.deinit();
roc_str2.deinit();
}
try expect(roc_str1.eq(roc_str2));
}
};
pub fn init(bytes_ptr: [*]const u8, length: usize) callconv(.C) RocStr {
@ -466,7 +527,7 @@ fn strSplitInPlace(array: [*]RocStr, string: RocStr, delimiter: RocStr) void {
const delimiter_bytes_ptrs = delimiter.asU8ptr();
const delimiter_len = delimiter.len();
if (str_len > delimiter_len) {
if (str_len > delimiter_len and delimiter_len > 0) {
const end_index: usize = str_len - delimiter_len + 1;
while (str_index <= end_index) {
var delimiter_index: usize = 0;
@ -500,6 +561,40 @@ fn strSplitInPlace(array: [*]RocStr, string: RocStr, delimiter: RocStr) void {
array[ret_array_index] = RocStr.init(str_bytes + slice_start_index, str_len - slice_start_index);
}
test "strSplitInPlace: empty delimiter" {
// Str.split "abc" "" == [ "abc" ]
const str_arr = "abc";
const str = RocStr.init(str_arr, str_arr.len);
const delimiter_arr = "";
const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len);
var array: [1]RocStr = undefined;
const array_ptr: [*]RocStr = &array;
strSplitInPlace(array_ptr, str, delimiter);
var expected = [1]RocStr{
str,
};
defer {
for (array) |roc_str| {
roc_str.deinit();
}
for (expected) |roc_str| {
roc_str.deinit();
}
str.deinit();
delimiter.deinit();
}
try expectEqual(array.len, expected.len);
try expect(array[0].eq(expected[0]));
}
test "strSplitInPlace: no delimiter" {
// Str.split "abc" "!" == [ "abc" ]
const str_arr = "abc";
@ -673,7 +768,7 @@ pub fn countSegments(string: RocStr, delimiter: RocStr) callconv(.C) usize {
var count: usize = 1;
if (str_len > delimiter_len) {
if (str_len > delimiter_len and delimiter_len > 0) {
var str_index: usize = 0;
const end_cond: usize = str_len - delimiter_len + 1;

View file

@ -364,7 +364,7 @@ pub const RocResult = extern struct {
// - the tag is the first field
// - the tag is usize bytes wide
// - Ok has tag_id 1, because Err < Ok
const usizes: [*]usize = @ptrCast([*]usize, @alignCast(8, self.bytes));
const usizes: [*]usize = @ptrCast([*]usize, @alignCast(@alignOf(usize), self.bytes));
return usizes[0] == 1;
}

View file

@ -7,6 +7,13 @@ use std::path::Path;
use std::process::Command;
use std::str;
fn zig_executable() -> String {
match std::env::var("ROC_ZIG") {
Ok(path) => path,
Err(_) => "zig".into(),
}
}
fn main() {
println!("cargo:rerun-if-changed=build.rs");
@ -89,7 +96,7 @@ fn generate_object_file(
run_command(
&bitcode_path,
"zig",
&zig_executable(),
&["build", zig_object, "-Drelease=true"],
);
@ -113,7 +120,7 @@ fn generate_bc_file(
run_command(
&bitcode_path,
"zig",
&zig_executable(),
&["build", zig_object, "-Drelease=true"],
);
@ -177,26 +184,3 @@ fn get_zig_files(dir: &Path, cb: &dyn Fn(&Path)) -> io::Result<()> {
}
Ok(())
}
// fn get_zig_files(dir: &Path) -> io::Result<Vec<&Path>> {
// let mut vec = Vec::new();
// if dir.is_dir() {
// for entry in fs::read_dir(dir)? {
// let entry = entry?;
// let path_buf = entry.path();
// if path_buf.is_dir() {
// match get_zig_files(&path_buf) {
// Ok(sub_files) => vec = [vec, sub_files].concat(),
// Err(_) => (),
// };
// } else {
// let path = path_buf.as_path();
// let path_ext = path.extension().unwrap();
// if path_ext == "zig" {
// vec.push(path.clone());
// }
// }
// }
// }
// Ok(vec)
// }

View file

@ -1,5 +1,5 @@
interface Bool
exposes [ not, and, or, xor, isEq, isNotEq ]
exposes [ and, isEq, isNotEq, not, or, xor ]
imports []
## Returns `False` when given `True`, and vice versa.
@ -44,9 +44,9 @@ and : Bool, Bool -> Bool
## `a || b` is shorthand for `Bool.or a b`.
##
## >>> True || True
#
##
## >>> True || False
#
##
## >>> False || True
##
## >>> False || False

View file

@ -2,19 +2,19 @@ interface Dict
exposes
[
Dict,
contains,
difference,
empty,
single,
get,
walk,
keys,
insert,
intersection,
len,
remove,
contains,
keys,
values,
single,
union,
intersection,
difference
values,
walk
]
imports []

View file

@ -2,41 +2,43 @@ interface List
exposes
[
List,
isEmpty,
get,
set,
append,
len,
walkBackwards,
concat,
first,
single,
repeat,
reverse,
prepend,
join,
keepIf,
contains,
sum,
walk,
last,
keepOks,
drop,
dropAt,
dropLast,
first,
get,
isEmpty,
join,
keepErrs,
keepIf,
keepOks,
last,
len,
map,
map2,
map3,
map4,
mapWithIndex,
mapOrDrop,
mapJoin,
mapOrDrop,
mapWithIndex,
prepend,
product,
walkUntil,
range,
repeat,
reverse,
set,
single,
sortWith,
drop,
dropAt,
dropLast,
swap
split,
sublist,
sum,
swap,
walk,
walkBackwards,
walkUntil
]
imports []
@ -205,7 +207,7 @@ empty : List *
## Returns a list with the given length, where every element is the given value.
##
##
repeat : elem, Nat -> List elem
repeat : Nat, elem -> List elem
## Returns a list of all the integers between one and another,
## including both of the given numbers.
@ -277,7 +279,7 @@ map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e
## This works like [List.map], except it also passes the index
## of the element to the conversion function.
mapWithIndex : List before, (before, Nat -> after) -> List after
mapWithIndex : List before, (Nat, before -> after) -> List after
## This works like [List.map], except at any time you can return `Err` to
## cancel the entire operation immediately, and return that #Err.
@ -446,9 +448,6 @@ drop : List elem, Nat -> List elem
## To replace the element at a given index, instead of dropping it, see [List.set].
dropAt : List elem, Nat -> List elem
## Drops the last element in a List.
dropLast : List elem -> List elem
## Adds a new element to the end of the list.
##
## >>> List.append [ "a", "b" ] "c"
@ -685,8 +684,6 @@ startsWith : List elem, List elem -> Bool
endsWith : List elem, List elem -> Bool
all : List elem, (elem -> Bool) -> Bool
## Run the given predicate on each element of the list, returning `True` if
## any of the elements satisfy it.
any : List elem, (elem -> Bool) -> Bool
@ -698,3 +695,11 @@ all : List elem, (elem -> Bool) -> Bool
## Returns the first element of the list satisfying a predicate function.
## If no satisfying element is found, an `Err NotFound` is returned.
find : List elem, (elem -> Bool) -> Result elem [ NotFound ]*
## Apply a function that returns a Result on a list, only successful
## Results are kept and returned unwrapped.
keepOks : List before, (before -> Result after *) -> List after
## Apply a function that returns a Result on a list, only unsuccessful
## Results are kept and returned unwrapped.
keepErrs : List before, (before -> Result * after) -> List after

View file

@ -2,91 +2,106 @@ interface Num
exposes
[
Num,
Int,
Float,
Natural,
Nat,
Decimal,
Dec,
Integer,
FloatingPoint,
I128,
U128,
I64,
U64,
I32,
U32,
I16,
U16,
I8,
U8,
F64,
F32,
maxInt,
minInt,
maxFloat,
minFloat,
abs,
neg,
add,
sub,
mul,
isLt,
isLte,
isGt,
isGte,
toFloat,
sin,
cos,
tan,
isZero,
isEven,
isOdd,
isPositive,
isNegative,
rem,
div,
divFloor,
modInt,
modFloat,
sqrt,
log,
round,
compare,
pow,
ceiling,
powInt,
floor,
addWrap,
addChecked,
atan,
acos,
toStr,
Signed128,
Signed64,
Signed32,
Signed16,
Signed8,
Unsigned128,
Unsigned64,
Unsigned32,
Unsigned16,
Unsigned8,
Binary64,
Binary32,
Dec,
Decimal,
Float,
FloatingPoint,
F32,
F64,
I8,
I16,
I32,
I64,
I128,
Int,
Integer,
Nat,
Natural,
Signed8,
Signed16,
Signed32,
Signed64,
Signed128,
U8,
U16,
U32,
U64,
U128,
Unsigned8,
Unsigned16,
Unsigned32,
Unsigned64,
Unsigned128,
abs,
acos,
add,
addChecked,
addWrap,
atan,
bitwiseAnd,
bitwiseXor,
bitwiseOr,
bitwiseXor,
ceiling,
compare,
cos,
div,
divFloor,
floor,
intCast,
isEven,
isGt,
isGte,
isLt,
isLte,
isMultipleOf,
isNegative,
isOdd,
isPositive,
isZero,
log,
maxFloat,
maxI8,
maxU8,
maxI16,
maxU16,
maxI32,
maxU32,
maxI64,
maxU64,
maxI128,
minFloat,
minI8,
minU8,
minI16,
minU16,
minI32,
minU32,
minI64,
minU64,
minI128,
modInt,
modFloat,
mul,
mulChecked,
mulWrap,
neg,
pow,
powInt,
rem,
round,
shiftLeftBy,
shiftRightBy,
shiftRightZfBy,
subWrap,
sin,
sub,
subChecked,
mulWrap,
mulChecked,
intCast,
maxI128,
isMultipleOf
subWrap,
sqrt,
tan,
toFloat,
toStr
]
imports []
@ -102,7 +117,7 @@ interface Num
##
## The number 1.5 technically has the type `Num (Fraction *)`, so when you pass
## two of them to [Num.add], the answer you get is `3.0 : Num (Fraction *)`.
#
##
## Similarly, the number 0x1 (that is, the integer 1 in hexadecimal notation)
## technically has the type `Num (Integer *)`, so when you pass two of them to
## [Num.add], the answer you get is `2 : Num (Integer *)`.
@ -359,47 +374,47 @@ Nat : Int [ @Natural ]
##
## | Range | Type | Size |
## |--------------------------------------------------------|-------|----------|
## | ` -128` | #I8 | 1 Byte |
## | ` -128` | [I8] | 1 Byte |
## | ` 127` | | |
## |--------------------------------------------------------|-------|----------|
## | ` 0` | #U8 | 1 Byte |
## | ` 0` | [U8] | 1 Byte |
## | ` 255` | | |
## |--------------------------------------------------------|-------|----------|
## | ` -32_768` | #I16 | 2 Bytes |
## | ` -32_768` | [I16] | 2 Bytes |
## | ` 32_767` | | |
## |--------------------------------------------------------|-------|----------|
## | ` 0` | #U16 | 2 Bytes |
## | ` 0` | [U16] | 2 Bytes |
## | ` 65_535` | | |
## |--------------------------------------------------------|-------|----------|
## | ` -2_147_483_648` | #I32 | 4 Bytes |
## | ` -2_147_483_648` | [I32] | 4 Bytes |
## | ` 2_147_483_647` | | |
## |--------------------------------------------------------|-------|----------|
## | ` 0` | #U32 | 4 Bytes |
## | ` 0` | [U32] | 4 Bytes |
## | ` (over 4 billion) 4_294_967_295` | | |
## |--------------------------------------------------------|-------|----------|
## | ` -9_223_372_036_854_775_808` | #I64 | 8 Bytes |
## | ` -9_223_372_036_854_775_808` | [I64] | 8 Bytes |
## | ` 9_223_372_036_854_775_807` | | |
## |--------------------------------------------------------|-------|----------|
## | ` 0` | #U64 | 8 Bytes |
## | ` 0` | [U64] | 8 Bytes |
## | ` (over 18 quintillion) 18_446_744_073_709_551_615` | | |
## |--------------------------------------------------------|-------|----------|
## | `-170_141_183_460_469_231_731_687_303_715_884_105_728` | #I128 | 16 Bytes |
## | `-170_141_183_460_469_231_731_687_303_715_884_105_728` | [I128]| 16 Bytes |
## | ` 170_141_183_460_469_231_731_687_303_715_884_105_727` | | |
## |--------------------------------------------------------|-------|----------|
## | ` (over 340 undecillion) 0` | #U128 | 16 Bytes |
## | ` (over 340 undecillion) 0` | [U128]| 16 Bytes |
## | ` 340_282_366_920_938_463_463_374_607_431_768_211_455` | | |
##
## Roc also has one variable-size integer type: #Nat. The size of #Nat is equal
## Roc also has one variable-size integer type: [Nat]. The size of [Nat] is equal
## to the size of a memory address, which varies by system. For example, when
## compiling for a 64-bit system, #Nat is the same as #U64. When compiling for a
## 32-bit system, it's the same as #U32.
## compiling for a 64-bit system, [Nat] is the same as [U64]. When compiling for a
## 32-bit system, it's the same as [U32].
##
## A common use for #Nat is to store the length ("len" for short) of a
## collection like #List, #Set, or #Map. 64-bit systems can represent longer
## A common use for [Nat] is to store the length ("len" for short) of a
## collection like a [List]. 64-bit systems can represent longer
## lists in memory than 32-bit systems can, which is why the length of a list
## is represented as a #Nat in Roc.
## is represented as a [Nat] in Roc.
##
## If any operation would result in an #Int that is either too big
## If any operation would result in an [Int] that is either too big
## or too small to fit in that range (e.g. calling `Int.maxI32 + 1`),
## then the operation will *overflow*. When an overflow occurs, the program will crash.
##
@ -501,7 +516,16 @@ add : Num a, Num a -> Num a
##
## This is the same as [Num.add] except if the operation overflows, instead of
## panicking or returning ∞ or -∞, it will return `Err Overflow`.
addCheckOverflow : Num a, Num a -> Result (Num a) [ Overflow ]*
addChecked : Num a, Num a -> Result (Num a) [ Overflow ]*
## Add two numbers, clamping on the maximum representable number rather than
## overflowing.
##
## This is the same as [Num.add] except for the saturating behavior if the
## addition is to overflow.
## For example, if `x : U8` is 200 and `y : U8` is 100, `addSaturated x y` will
## yield 255, the maximum value of a `U8`.
addSaturated : Num a, Num a -> Num a
## Subtract two numbers of the same type.
##
@ -528,7 +552,16 @@ sub : Num a, Num a -> Num a
##
## This is the same as [Num.sub] except if the operation overflows, instead of
## panicking or returning ∞ or -∞, it will return `Err Overflow`.
subCheckOverflow : Num a, Num a -> Result (Num a) [ Overflow ]*
subChecked : Num a, Num a -> Result (Num a) [ Overflow ]*
## Subtract two numbers, clamping on the minimum representable number rather
## than overflowing.
##
## This is the same as [Num.sub] except for the saturating behavior if the
## subtraction is to overflow.
## For example, if `x : U8` is 10 and `y : U8` is 20, `subSaturated x y` will
## yield 0, the minimum value of a `U8`.
subSaturated : Num a, Num a -> Num a
## Multiply two numbers of the same type.
##
@ -621,13 +654,13 @@ toStr : Num * -> Str
## Examples:
##
## In some countries (e.g. USA and UK), a comma is used to separate thousands:
## >>> Num.format 1_000_000 { base: Decimal, wholeSep: { mark: ",", places: 3 } }
## >>> Num.format 1_000_000 { pf: Decimal, wholeSep: { mark: ",", places: 3 } }
##
## Sometimes when rendering bits, it's nice to group them into groups of 4:
## >>> Num.format 1_000_000 { base: Binary, wholeSep: { mark: " ", places: 4 } }
## >>> Num.format 1_000_000 { pf: Binary, wholeSep: { mark: " ", places: 4 } }
##
## It's also common to render hexadecimal in groups of 2:
## >>> Num.format 1_000_000 { base: Hexadecimal, wholeSep: { mark: " ", places: 2 } }
## >>> Num.format 1_000_000 { pf: Hexadecimal, wholeSep: { mark: " ", places: 2 } }
format :
Num *,
{
@ -749,6 +782,15 @@ not : Int a -> Int a
## Limits
## The lowest number that can be stored in a #Nat without underflowing its
## available memory and crashing.
##
## For reference, this is the number zero, because #Nat is
## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations),
## and zero is the lowest unsigned number.
## Unsigned numbers cannot be negative.
minNat : Nat
## The highest number that can be stored in a #Nat without overflowing its
## available memory and crashing.
##
@ -757,81 +799,190 @@ not : Int a -> Int a
## 32-bit system, this will be equal to #Num.maxU32.
maxNat : Nat
## The number zero.
## The lowest number that can be stored in an #I8 without underflowing its
## available memory and crashing.
##
## #Num.minNat is the lowest number that can be stored in a #Nat, which is zero
## because #Nat is [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations),
## and zero is the lowest unsigned number. Unsigned numbers cannot be negative.
minNat : Nat
## For reference, this number is `-128`.
##
## Note that the positive version of this number is larger than #Int.maxI8,
## which means if you call #Num.abs on #Int.minI8, it will overflow and crash!
minI8 : I8
## The highest number that can be stored in an #I8 without overflowing its
## available memory and crashing.
##
## For reference, this number is `127`.
##
## Note that this is smaller than the positive version of #Int.minI8,
## which means if you call #Num.abs on #Int.minI8, it will overflow and crash!
maxI8 : I8
## The lowest number that can be stored in a #U8 without underflowing its
## available memory and crashing.
##
## For reference, this number is zero, because #U8 is
## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations),
## and zero is the lowest unsigned number.
## Unsigned numbers cannot be negative.
minU8 : U8
## The highest number that can be stored in a #U8 without overflowing its
## available memory and crashing.
##
## For reference, this number is `255`.
maxU8 : U8
## The lowest number that can be stored in an #I16 without underflowing its
## available memory and crashing.
##
## For reference, this number is `-32_768`.
##
## Note that the positive version of this number is larger than #Int.maxI16,
## which means if you call #Num.abs on #Int.minI16, it will overflow and crash!
minI16 : I16
## The highest number that can be stored in an #I16 without overflowing its
## available memory and crashing.
##
## For reference, this number is `32_767`.
##
## Note that this is smaller than the positive version of #Int.minI16,
## which means if you call #Num.abs on #Int.minI16, it will overflow and crash!
maxI16 : I16
## The lowest number that can be stored in a #U16 without underflowing its
## available memory and crashing.
##
## For reference, this number is zero, because #U16 is
## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations),
## and zero is the lowest unsigned number.
## Unsigned numbers cannot be negative.
minU16 : U16
## The highest number that can be stored in a #U16 without overflowing its
## available memory and crashing.
##
## For reference, this number is `65_535`.
maxU16 : U16
## The lowest number that can be stored in an #I32 without underflowing its
## available memory and crashing.
##
## For reference, this number is `-2_147_483_648`.
##
## Note that the positive version of this number is larger than #Int.maxI32,
## which means if you call #Num.abs on #Int.minI32, it will overflow and crash!
minI32 : I32
## The highest number that can be stored in an #I32 without overflowing its
## available memory and crashing.
##
## Note that this is smaller than the positive version of #Int.minI32
## For reference, this number is `2_147_483_647`,
## which is over 2 million.
##
## Note that this is smaller than the positive version of #Int.minI32,
## which means if you call #Num.abs on #Int.minI32, it will overflow and crash!
maxI32 : I32
## The min number that can be stored in an #I32 without overflowing its
## The lowest number that can be stored in a #U32 without underflowing its
## available memory and crashing.
##
## Note that the positive version of this number is this is larger than
## #Int.maxI32, which means if you call #Num.abs on #Int.minI32, it will overflow and crash!
minI32 : I32
## The highest number that can be stored in a #U64 without overflowing its
## available memory and crashing.
##
## For reference, that number is `18_446_744_073_709_551_615`, which is over 18 quintillion.
maxU64 : U64
## The number zero.
##
## #Num.minU64 is the lowest number that can be stored in a #U64, which is zero
## because #U64 is [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations),
## and zero is the lowest unsigned number. Unsigned numbers cannot be negative.
minU64 : U64
## For reference, this number is zero, because #U32 is
## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations),
## and zero is the lowest unsigned number.
## Unsigned numbers cannot be negative.
minU32 : U32
## The highest number that can be stored in a #U32 without overflowing its
## available memory and crashing.
##
## For reference, that number is `4_294_967_295`, which is over 4 million.
## For reference, this number is `4_294_967_295`,
## which is over 4 million.
maxU32 : U32
## The number zero.
## The min number that can be stored in an #I64 without underflowing its
## available memory and crashing.
##
## #Num.minU32 is the lowest number that can be stored in a #U32, which is zero
## because #U32 is [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations),
## For reference, this number is `-`.
##
## Note that the positive version of this number is larger than #Int.maxI64,
## which means if you call #Num.abs on #Int.minI64, it will overflow and crash!
minI64 : I64
## The highest number that can be stored in an #I64 without overflowing its
## available memory and crashing.
##
## For reference, this number is ``,
## which is over 2 million.
##
## Note that this is smaller than the positive version of #Int.minI64,
## which means if you call #Num.abs on #Int.minI64, it will overflow and crash!
maxI64 : I64
## The lowest number that can be stored in a #U64 without underflowing its
## available memory and crashing.
##
## For reference, this number is zero because #U64 is
## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations),
## and zero is the lowest unsigned number. Unsigned numbers cannot be negative.
minU32 : U32
minU64 : U64
## The highest supported #F64 value you can have, which is approximately 1.8 × 10^308.
## The highest number that can be stored in a #U64 without overflowing its
## available memory and crashing.
##
## If you go higher than this, your running Roc code will crash - so be careful not to!
maxF64 : F64
## For reference, this number is `18_446_744_073_709_551_615`,
## which is over 18 quintillion.
maxU64 : U64
## The lowest supported #F64 value you can have, which is approximately -1.8 × 10^308.
## The lowest number that can be stored in an #I128 without underflowing its
## available memory and crashing.
##
## If you go lower than this, your running Roc code will crash - so be careful not to!
minF64 : F64
## For reference, this number is `-170_141_183_460_469_231_731_687_303_715_884_105_728`.
##
## Note that the positive version of this number is larger than #Int.maxI128,
## which means if you call #Num.abs on #Int.minI128, it will overflow and crash!
minI128 : I128
## The highest supported #F32 value you can have, which is approximately 1.8 × 10^308.
## The highest number that can be stored in an #I128 without overflowing its
## available memory and crashing.
##
## If you go higher than this, your running Roc code will crash - so be careful not to!
maxF32 : F32
## For reference, this number is `170_141_183_460_469_231_731_687_303_715_884_105_727`,
## which is over 2 million.
##
## Note that this is smaller than the positive version of #Int.minI128,
## which means if you call #Num.abs on #Int.minI128, it will overflow and crash!
maxI128 : I128
## The lowest supported #F32 value you can have, which is approximately -1.8 × 10^308.
##
## If you go lower than this, your running Roc code will crash - so be careful not to!
minF32 : F32
## The highest supported #F64 value you can have, which is approximately 1.8 × 10^308.
## The highest supported #F32 value you can have, which is approximately 1.8 × 10^308.
##
## If you go higher than this, your running Roc code will crash - so be careful not to!
maxDec : Dec
maxF32 : F32
## The lowest supported #F64 value you can have, which is approximately -1.8 × 10^308.
##
## If you go lower than this, your running Roc code will crash - so be careful not to!
minF64 : F64
## The highest supported #F64 value you can have, which is approximately 1.8 × 10^308.
##
## If you go higher than this, your running Roc code will crash - so be careful not to!
maxF64 : F64
## The lowest supported #Dec value you can have,
## which is precisely -170_141_183_460_469_231_731.687303715884105728.
##
## If you go lower than this, your running Roc code will crash - so be careful not to!
minDec : Dec
## The highest supported #Dec value you can have,
## which is precisely 170_141_183_460_469_231_731.687303715884105727.
##
## If you go higher than this, your running Roc code will crash - so be careful not to!
maxDec : Dec
## Constants

View file

@ -2,10 +2,12 @@ interface Result
exposes
[
Result,
after,
isOk,
isErr,
map,
mapErr,
withDefault,
after
withDefault
]
imports []
@ -13,6 +15,16 @@ interface Result
## okay, or else there was an error of some sort.
Result ok err : [ @Result ok err ]
## Return True if the result indicates a success, else return False
##
## >>> Result.isOk (Ok 5)
isOk : Result * * -> bool
## Return True if the result indicates a failure, else return False
##
## >>> Result.isErr (Err "uh oh")
isErr : Result * * -> bool
## If the result is `Ok`, return the value it holds. Otherwise, return
## the given default value.
##

View file

@ -2,18 +2,18 @@ interface Set
exposes
[
Set,
empty,
single,
len,
insert,
remove,
union,
contains,
difference,
intersection,
toList,
empty,
fromList,
walk,
contains
insert,
intersection,
len,
remove,
single,
toList,
union,
walk
]
imports []
@ -29,6 +29,8 @@ isEmpty : Set * -> Bool
len : Set * -> Nat
## Modify
# TODO: removed `'` from signature because parser does not support it yet
# Original signature: `add : Set 'elem, 'elem -> Set 'elem`
## Make sure never to add a *NaN* to a [Set]! Because *NaN* is defined to be
@ -41,6 +43,8 @@ add : Set elem, elem -> Set elem
# Original signature: `drop : Set 'elem, 'elem -> Set 'elem`
drop : Set elem, elem -> Set elem
## Transform
## Convert each element in the set to something new, by calling a conversion
## function on each of them. Then return a new set of the converted values.
##

View file

@ -2,19 +2,19 @@ interface Str
exposes
[
Str,
isEmpty,
append,
concat,
joinWith,
split,
countGraphemes,
startsWith,
endsWith,
fromUtf8,
Utf8Problem,
Utf8ByteProblem,
isEmpty,
joinWith,
split,
startsWith,
startsWithCodePt,
toUtf8,
startsWithCodePt
Utf8Problem,
Utf8ByteProblem
]
imports []

View file

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

View file

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

View file

@ -2,9 +2,9 @@ use crate::env::Env;
use crate::scope::Scope;
use roc_collections::all::{ImMap, MutMap, MutSet, SendMap};
use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::Symbol;
use roc_parse::ast::{AssignedField, Tag, TypeAnnotation};
use roc_region::all::{Located, Region};
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_parse::ast::{AliasHeader, AssignedField, Pattern, Tag, TypeAnnotation};
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::{Alias, LambdaSet, Problem, RecordField, Type};
@ -102,6 +102,134 @@ pub fn canonicalize_annotation(
}
}
fn make_apply_symbol(
env: &mut Env,
region: Region,
scope: &mut Scope,
module_name: &str,
ident: &str,
) -> Result<Symbol, Type> {
if module_name.is_empty() {
// Since module_name was empty, this is an unqualified type.
// Look it up in scope!
let ident: Ident = (*ident).into();
match scope.lookup(&ident, region) {
Ok(symbol) => Ok(symbol),
Err(problem) => {
env.problem(roc_problem::can::Problem::RuntimeError(problem));
Err(Type::Erroneous(Problem::UnrecognizedIdent(ident)))
}
}
} else {
match env.qualified_lookup(module_name, ident, region) {
Ok(symbol) => Ok(symbol),
Err(problem) => {
// Either the module wasn't imported, or
// 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())))
}
}
}
}
pub fn find_alias_symbols(
module_id: ModuleId,
ident_ids: &mut IdentIds,
initial_annotation: &roc_parse::ast::TypeAnnotation,
) -> Vec<Symbol> {
use roc_parse::ast::TypeAnnotation::*;
let mut result = Vec::new();
let mut stack = vec![initial_annotation];
while let Some(annotation) = stack.pop() {
match annotation {
Apply(_module_name, ident, arguments) => {
let ident: Ident = (*ident).into();
let ident_id = ident_ids.get_or_insert(&ident);
let symbol = Symbol::new(module_id, ident_id);
result.push(symbol);
for t in arguments.iter() {
stack.push(&t.value);
}
}
Function(arguments, result) => {
for t in arguments.iter() {
stack.push(&t.value);
}
stack.push(&result.value);
}
BoundVariable(_) => {}
As(actual, _, _) => {
stack.push(&actual.value);
}
Record { fields, ext } => {
let mut inner_stack = Vec::with_capacity(fields.items.len());
for field in fields.items.iter() {
inner_stack.push(&field.value)
}
while let Some(assigned_field) = inner_stack.pop() {
match assigned_field {
AssignedField::RequiredValue(_, _, t)
| AssignedField::OptionalValue(_, _, t) => {
stack.push(&t.value);
}
AssignedField::LabelOnly(_) => {}
AssignedField::SpaceBefore(inner, _)
| AssignedField::SpaceAfter(inner, _) => inner_stack.push(inner),
AssignedField::Malformed(_) => {}
}
}
for t in ext.iter() {
stack.push(&t.value);
}
}
TagUnion { ext, tags } => {
let mut inner_stack = Vec::with_capacity(tags.items.len());
for tag in tags.items.iter() {
inner_stack.push(&tag.value)
}
while let Some(tag) = inner_stack.pop() {
match tag {
Tag::Global { args, .. } | Tag::Private { args, .. } => {
for t in args.iter() {
stack.push(&t.value);
}
}
Tag::SpaceBefore(inner, _) | Tag::SpaceAfter(inner, _) => {
inner_stack.push(inner)
}
Tag::Malformed(_) => {}
}
}
for t in ext.iter() {
stack.push(&t.value);
}
}
SpaceBefore(inner, _) | SpaceAfter(inner, _) => {
stack.push(inner);
}
Inferred | Wildcard | Malformed(_) => {}
}
}
result
}
#[allow(clippy::too_many_arguments)]
fn can_annotation_help(
env: &mut Env,
@ -150,30 +278,9 @@ fn can_annotation_help(
Type::Function(args, Box::new(closure), Box::new(ret))
}
Apply(module_name, ident, type_arguments) => {
let symbol = if module_name.is_empty() {
// Since module_name was empty, this is an unqualified type.
// Look it up in scope!
let ident: Ident = (*ident).into();
match scope.lookup(&ident, region) {
Ok(symbol) => symbol,
Err(problem) => {
env.problem(roc_problem::can::Problem::RuntimeError(problem));
return Type::Erroneous(Problem::UnrecognizedIdent(ident));
}
}
} else {
match env.qualified_lookup(module_name, ident, region) {
Ok(symbol) => symbol,
Err(problem) => {
// Either the module wasn't imported, or
// it was imported but it doesn't expose this ident.
env.problem(roc_problem::can::Problem::RuntimeError(problem));
return Type::Erroneous(Problem::UnrecognizedIdent((*ident).into()));
}
}
let symbol = match make_apply_symbol(env, region, scope, module_name, ident) {
Err(problem) => return problem,
Ok(symbol) => symbol,
};
let mut args = Vec::new();
@ -267,124 +374,122 @@ fn can_annotation_help(
}
}
}
As(loc_inner, _spaces, loc_as) => match loc_as.value {
TypeAnnotation::Apply(module_name, ident, loc_vars) if module_name.is_empty() => {
let symbol = match scope.introduce(
ident.into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
region,
) {
Ok(symbol) => symbol,
As(
loc_inner,
_spaces,
AliasHeader {
name,
vars: loc_vars,
},
) => {
let symbol = match scope.introduce(
name.value.into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
region,
) {
Ok(symbol) => symbol,
Err((original_region, shadow)) => {
let problem = Problem::Shadowed(original_region, shadow.clone());
Err((original_region, shadow, _new_symbol)) => {
let problem = Problem::Shadowed(original_region, shadow.clone());
env.problem(roc_problem::can::Problem::ShadowingInAnnotation {
original_region,
shadow,
});
env.problem(roc_problem::can::Problem::ShadowingInAnnotation {
original_region,
shadow,
});
return Type::Erroneous(problem);
}
};
let inner_type = can_annotation_help(
env,
&loc_inner.value,
region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
);
let mut vars = Vec::with_capacity(loc_vars.len());
let mut lowercase_vars = Vec::with_capacity(loc_vars.len());
references.insert(symbol);
for loc_var in loc_vars {
match loc_var.value {
BoundVariable(ident) => {
let var_name = Lowercase::from(ident);
if let Some(var) = introduced_variables.var_by_name(&var_name) {
vars.push((var_name.clone(), Type::Variable(*var)));
lowercase_vars.push(Located::at(loc_var.region, (var_name, *var)));
} else {
let var = var_store.fresh();
introduced_variables.insert_named(var_name.clone(), var);
vars.push((var_name.clone(), Type::Variable(var)));
lowercase_vars.push(Located::at(loc_var.region, (var_name, var)));
}
}
_ => {
// If anything other than a lowercase identifier
// appears here, the whole annotation is invalid.
return Type::Erroneous(Problem::CanonicalizationProblem);
}
}
return Type::Erroneous(problem);
}
};
let alias_actual = if let Type::TagUnion(tags, ext) = inner_type {
let rec_var = var_store.fresh();
let inner_type = can_annotation_help(
env,
&loc_inner.value,
region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
);
let mut vars = Vec::with_capacity(loc_vars.len());
let mut lowercase_vars = Vec::with_capacity(loc_vars.len());
let mut new_tags = Vec::with_capacity(tags.len());
for (tag_name, args) in tags {
let mut new_args = Vec::with_capacity(args.len());
for arg in args {
let mut new_arg = arg.clone();
new_arg.substitute_alias(symbol, &Type::Variable(rec_var));
new_args.push(new_arg);
}
new_tags.push((tag_name.clone(), new_args));
references.insert(symbol);
for loc_var in *loc_vars {
let var = match loc_var.value {
Pattern::Identifier(name) if name.chars().next().unwrap().is_lowercase() => {
name
}
Type::RecursiveTagUnion(rec_var, new_tags, ext)
} else {
inner_type
_ => unreachable!("I thought this was validated during parsing"),
};
let var_name = Lowercase::from(var);
let mut hidden_variables = MutSet::default();
hidden_variables.extend(alias_actual.variables());
for loc_var in lowercase_vars.iter() {
hidden_variables.remove(&loc_var.value.1);
}
scope.add_alias(symbol, region, lowercase_vars, alias_actual);
let alias = scope.lookup_alias(symbol).unwrap();
local_aliases.insert(symbol, alias.clone());
// Type::Alias(symbol, vars, Box::new(alias.typ.clone()))
if vars.is_empty() && env.home == symbol.module_id() {
let actual_var = var_store.fresh();
introduced_variables.insert_host_exposed_alias(symbol, actual_var);
Type::HostExposedAlias {
name: symbol,
type_arguments: vars,
lambda_set_variables: alias.lambda_set_variables.clone(),
actual: Box::new(alias.typ.clone()),
actual_var,
}
if let Some(var) = introduced_variables.var_by_name(&var_name) {
vars.push((var_name.clone(), Type::Variable(*var)));
lowercase_vars.push(Loc::at(loc_var.region, (var_name, *var)));
} else {
Type::Alias {
symbol,
type_arguments: vars,
lambda_set_variables: alias.lambda_set_variables.clone(),
actual: Box::new(alias.typ.clone()),
}
let var = var_store.fresh();
introduced_variables.insert_named(var_name.clone(), var);
vars.push((var_name.clone(), Type::Variable(var)));
lowercase_vars.push(Loc::at(loc_var.region, (var_name, var)));
}
}
_ => {
// This is a syntactically invalid type alias.
Type::Erroneous(Problem::CanonicalizationProblem)
let alias_actual = if let Type::TagUnion(tags, ext) = inner_type {
let rec_var = var_store.fresh();
let mut new_tags = Vec::with_capacity(tags.len());
for (tag_name, args) in tags {
let mut new_args = Vec::with_capacity(args.len());
for arg in args {
let mut new_arg = arg.clone();
new_arg.substitute_alias(symbol, &Type::Variable(rec_var));
new_args.push(new_arg);
}
new_tags.push((tag_name.clone(), new_args));
}
Type::RecursiveTagUnion(rec_var, new_tags, ext)
} else {
inner_type
};
let mut hidden_variables = MutSet::default();
hidden_variables.extend(alias_actual.variables());
for loc_var in lowercase_vars.iter() {
hidden_variables.remove(&loc_var.value.1);
}
},
scope.add_alias(symbol, region, lowercase_vars, alias_actual);
let alias = scope.lookup_alias(symbol).unwrap();
local_aliases.insert(symbol, alias.clone());
// Type::Alias(symbol, vars, Box::new(alias.typ.clone()))
if vars.is_empty() && env.home == symbol.module_id() {
let actual_var = var_store.fresh();
introduced_variables.insert_host_exposed_alias(symbol, actual_var);
Type::HostExposedAlias {
name: symbol,
type_arguments: vars,
lambda_set_variables: alias.lambda_set_variables.clone(),
actual: Box::new(alias.typ.clone()),
actual_var,
}
} else {
Type::Alias {
symbol,
type_arguments: vars,
lambda_set_variables: alias.lambda_set_variables.clone(),
actual: Box::new(alias.typ.clone()),
}
}
}
Record { fields, ext } => {
let ext_type = match ext {
@ -530,7 +635,7 @@ where
#[allow(clippy::too_many_arguments)]
fn can_assigned_fields<'a>(
env: &mut Env,
fields: &&[Located<AssignedField<'a, TypeAnnotation<'a>>>],
fields: &&[Loc<AssignedField<'a, TypeAnnotation<'a>>>],
region: Region,
scope: &mut Scope,
var_store: &mut VarStore,
@ -640,7 +745,7 @@ fn can_assigned_fields<'a>(
#[allow(clippy::too_many_arguments)]
fn can_tags<'a>(
env: &mut Env,
tags: &'a [Located<Tag<'a>>],
tags: &'a [Loc<Tag<'a>>],
region: Region,
scope: &mut Scope,
var_store: &mut VarStore,

View file

@ -1,13 +1,13 @@
use crate::def::Def;
use crate::expr::{ClosureData, Expr::*};
use crate::expr::{Expr, Field, Recursive, WhenBranch};
use crate::expr::{self, ClosureData, Expr::*};
use crate::expr::{Expr, Field, Recursive};
use crate::pattern::Pattern;
use roc_collections::all::SendMap;
use roc_module::called_via::CalledVia;
use roc_module::ident::{Lowercase, TagName};
use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region};
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
macro_rules! macro_magic {
@ -44,6 +44,23 @@ macro_rules! macro_magic {
/// delegates to the compiler-internal List.getUnsafe function to do the actual
/// lookup (if the bounds check passed). That internal function is hardcoded in code gen,
/// which works fine because it doesn't involve any open tag unions.
/// Does a builtin depend on any other builtins?
///
/// NOTE: you are supposed to give all symbols that are relied on,
/// even those that are relied on transitively!
pub fn builtin_dependencies(symbol: Symbol) -> &'static [Symbol] {
match symbol {
Symbol::LIST_SORT_ASC => &[Symbol::LIST_SORT_WITH, Symbol::NUM_COMPARE],
Symbol::LIST_SORT_DESC => &[Symbol::LIST_SORT_WITH],
Symbol::LIST_PRODUCT => &[Symbol::LIST_WALK, Symbol::NUM_MUL],
Symbol::LIST_SUM => &[Symbol::LIST_WALK, Symbol::NUM_ADD],
Symbol::LIST_JOIN_MAP => &[Symbol::LIST_WALK, Symbol::LIST_CONCAT],
_ => &[],
}
}
/// Implementation for a builtin
pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def> {
debug_assert!(symbol.is_builtin());
@ -113,6 +130,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_DROP => list_drop,
LIST_DROP_AT => list_drop_at,
LIST_DROP_FIRST => list_drop_first,
LIST_DROP_IF => list_drop_if,
LIST_DROP_LAST => list_drop_last,
LIST_SWAP => list_swap,
LIST_MAP_WITH_INDEX => list_map_with_index,
@ -124,6 +142,8 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_WALK_BACKWARDS => list_walk_backwards,
LIST_WALK_UNTIL => list_walk_until,
LIST_SORT_WITH => list_sort_with,
LIST_SORT_ASC => list_sort_asc,
LIST_SORT_DESC => list_sort_desc,
LIST_ANY => list_any,
LIST_ALL => list_all,
LIST_FIND => list_find,
@ -155,9 +175,11 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
NUM_ADD => num_add,
NUM_ADD_CHECKED => num_add_checked,
NUM_ADD_WRAP => num_add_wrap,
NUM_ADD_SATURATED => num_add_saturated,
NUM_SUB => num_sub,
NUM_SUB_WRAP => num_sub_wrap,
NUM_SUB_CHECKED => num_sub_checked,
NUM_SUB_SATURATED => num_sub_saturated,
NUM_MUL => num_mul,
NUM_MUL_WRAP => num_mul_wrap,
NUM_MUL_CHECKED => num_mul_checked,
@ -194,8 +216,6 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
NUM_ASIN => num_asin,
NUM_BYTES_TO_U16 => num_bytes_to_u16,
NUM_BYTES_TO_U32 => num_bytes_to_u32,
NUM_MAX_INT => num_max_int,
NUM_MIN_INT => num_min_int,
NUM_BITWISE_AND => num_bitwise_and,
NUM_BITWISE_XOR => num_bitwise_xor,
NUM_BITWISE_OR => num_bitwise_or,
@ -203,6 +223,23 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
NUM_SHIFT_RIGHT => num_shift_right_by,
NUM_SHIFT_RIGHT_ZERO_FILL => num_shift_right_zf_by,
NUM_INT_CAST=> num_int_cast,
NUM_MIN_I8=> num_min_i8,
NUM_MAX_I8=> num_max_i8,
NUM_MIN_U8=> num_min_u8,
NUM_MAX_U8=> num_max_u8,
NUM_MIN_I16=> num_min_i16,
NUM_MAX_I16=> num_max_i16,
NUM_MIN_U16=> num_min_u16,
NUM_MAX_U16=> num_max_u16,
NUM_MIN_I32=> num_min_i32,
NUM_MAX_I32=> num_max_i32,
NUM_MIN_U32=> num_min_u32,
NUM_MAX_U32=> num_max_u32,
NUM_MIN_I64=> num_min_i64,
NUM_MAX_I64=> num_max_i64,
NUM_MIN_U64=> num_min_u64,
NUM_MAX_U64=> num_max_u64,
NUM_MIN_I128=> num_min_i128,
NUM_MAX_I128=> num_max_i128,
NUM_TO_STR => num_to_str,
RESULT_MAP => result_map,
@ -352,36 +389,6 @@ fn lowlevel_5(symbol: Symbol, op: LowLevel, var_store: &mut VarStore) -> Def {
)
}
/// Num.maxInt : Int
fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
let int_var = var_store.fresh();
let int_precision_var = var_store.fresh();
let body = int(int_var, int_precision_var, i64::MAX.into());
Def {
annotation: None,
expr_var: int_var,
loc_expr: Located::at_zero(body),
loc_pattern: Located::at_zero(Pattern::Identifier(symbol)),
pattern_vars: SendMap::default(),
}
}
/// Num.minInt : Int
fn num_min_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
let int_var = var_store.fresh();
let int_precision_var = var_store.fresh();
let body = int(int_var, int_precision_var, i64::MIN.into());
Def {
annotation: None,
expr_var: int_var,
loc_expr: Located::at_zero(body),
loc_pattern: Located::at_zero(Pattern::Identifier(symbol)),
pattern_vars: SendMap::default(),
}
}
// Num.toStr : Num a -> Str
fn num_to_str(symbol: Symbol, var_store: &mut VarStore) -> Def {
let num_var = var_store.fresh();
@ -640,6 +647,11 @@ fn num_add_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_overflow_checked(symbol, var_store, LowLevel::NumAddChecked)
}
/// Num.addSaturated : Int a, Int a -> Int a
fn num_add_saturated(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumAddSaturated)
}
/// Num.sub : Num a, Num a -> Num a
fn num_sub(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumSub)
@ -655,6 +667,11 @@ fn num_sub_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_overflow_checked(symbol, var_store, LowLevel::NumSubChecked)
}
/// Num.subSaturated : Int a, Int a -> Int a
fn num_sub_saturated(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumSubSaturated)
}
/// Num.mul : Num a, Num a -> Num a
fn num_mul(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumMul)
@ -847,7 +864,10 @@ fn num_is_odd(symbol: Symbol, var_store: &mut VarStore) -> Def {
let body = RunLowLevel {
op: LowLevel::Eq,
args: vec![
(arg_var, int(var_store.fresh(), var_store.fresh(), 1)),
(
arg_var,
int::<i128>(var_store.fresh(), var_store.fresh(), 1),
),
(
arg_var,
RunLowLevel {
@ -1224,31 +1244,94 @@ fn num_int_cast(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
/// Num.minI8: I8
fn num_min_i8(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i8>(symbol, var_store, i8::MIN)
}
/// Num.maxI8: I8
fn num_max_i8(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i8>(symbol, var_store, i8::MAX)
}
/// Num.minU8: U8
fn num_min_u8(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u8>(symbol, var_store, u8::MIN)
}
/// Num.maxU8: U8
fn num_max_u8(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u8>(symbol, var_store, u8::MAX)
}
/// Num.minI16: I16
fn num_min_i16(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i16>(symbol, var_store, i16::MIN)
}
/// Num.maxI16: I16
fn num_max_i16(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i16>(symbol, var_store, i16::MAX)
}
/// Num.minU16: U16
fn num_min_u16(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u16>(symbol, var_store, u16::MIN)
}
/// Num.maxU16: U16
fn num_max_u16(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u16>(symbol, var_store, u16::MAX)
}
/// Num.minI32: I32
fn num_min_i32(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i32>(symbol, var_store, i32::MIN)
}
/// Num.maxI32: I32
fn num_max_i32(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i32>(symbol, var_store, i32::MAX)
}
/// Num.minU32: U32
fn num_min_u32(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u32>(symbol, var_store, u32::MIN)
}
/// Num.maxU32: U32
fn num_max_u32(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u32>(symbol, var_store, u32::MAX)
}
/// Num.minI64: I64
fn num_min_i64(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i64>(symbol, var_store, i64::MIN)
}
/// Num.maxI64: I64
fn num_max_i64(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i64>(symbol, var_store, i64::MAX)
}
/// Num.minU64: U64
fn num_min_u64(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u64>(symbol, var_store, u64::MIN)
}
/// Num.maxU64: U64
fn num_max_u64(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u64>(symbol, var_store, u64::MAX)
}
/// Num.minI128: I128
fn num_min_i128(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i128>(symbol, var_store, i128::MIN)
}
/// Num.maxI128: I128
fn num_max_i128(symbol: Symbol, var_store: &mut VarStore) -> Def {
let int_var = var_store.fresh();
let int_precision_var = var_store.fresh();
let body = int(int_var, int_precision_var, i128::MAX);
let std = roc_builtins::std::types();
let solved = std.get(&symbol).unwrap();
let mut free_vars = roc_types::solved_types::FreeVars::default();
let signature = roc_types::solved_types::to_type(&solved.0, &mut free_vars, var_store);
let annotation = crate::def::Annotation {
signature,
introduced_variables: Default::default(),
region: Region::zero(),
aliases: Default::default(),
};
Def {
annotation: Some(annotation),
expr_var: int_var,
loc_expr: Located::at_zero(body),
loc_pattern: Located::at_zero(Pattern::Identifier(symbol)),
pattern_vars: SendMap::default(),
}
int_min_or_max::<i128>(symbol, var_store, i128::MAX)
}
/// List.isEmpty : List * -> Bool
@ -1373,7 +1456,10 @@ fn str_to_num(symbol: Symbol, var_store: &mut VarStore) -> Def {
loc_expr: Box::new(no_region(Var(Symbol::ARG_2))),
},
),
(errorcode_var, int(errorcode_var, Variable::UNSIGNED8, 0)),
(
errorcode_var,
int::<i128>(errorcode_var, Variable::UNSIGNED8, 0),
),
],
ret_var: bool_var,
}),
@ -2115,7 +2201,7 @@ fn list_swap(symbol: Symbol, var_store: &mut VarStore) -> Def {
fn list_take_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
let len_var = var_store.fresh();
let zero = int(len_var, Variable::NATURAL, 0);
let zero = int::<i128>(len_var, Variable::NATURAL, 0);
let body = RunLowLevel {
op: LowLevel::ListSublist,
@ -2141,7 +2227,7 @@ fn list_take_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
let len_var = var_store.fresh();
let zero = int(len_var, Variable::NATURAL, 0);
let zero = int::<i128>(len_var, Variable::NATURAL, 0);
let bool_var = var_store.fresh();
let get_list_len = RunLowLevel {
@ -2251,7 +2337,7 @@ fn list_intersperse(symbol: Symbol, var_store: &mut VarStore) -> Def {
let clos_elem_sym = Symbol::ARG_4;
let int_var = var_store.fresh();
let zero = int(int_var, Variable::NATURAL, 0);
let zero = int::<i128>(int_var, Variable::NATURAL, 0);
// \acc, elem -> acc |> List.append sep |> List.append elem
let clos = Closure(ClosureData {
@ -2331,7 +2417,7 @@ fn list_split(symbol: Symbol, var_store: &mut VarStore) -> Def {
let clos_ret_var = var_store.fresh();
let ret_var = var_store.fresh();
let zero = int(index_var, Variable::NATURAL, 0);
let zero = int::<i128>(index_var, Variable::NATURAL, 0);
let clos = Closure(ClosureData {
function_type: clos_fun_var,
@ -2482,6 +2568,7 @@ fn list_drop_at(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// List.dropFirst : List elem -> Result { first: elem, others : List elem } [ ListWasEmpty ]*
fn list_drop_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
let index_var = var_store.fresh();
@ -2492,7 +2579,7 @@ fn list_drop_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::ListDropAt,
args: vec![
(list_var, Var(Symbol::ARG_1)),
(index_var, int(num_var, num_precision_var, 0)),
(index_var, int::<i128>(num_var, num_precision_var, 0)),
],
ret_var: list_var,
};
@ -2506,6 +2593,61 @@ fn list_drop_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// List.dropIf : List elem, (elem -> Bool) -> List elem
fn list_drop_if(symbol: Symbol, var_store: &mut VarStore) -> Def {
let sym_list = Symbol::ARG_1;
let sym_predicate = Symbol::ARG_2;
let t_list = var_store.fresh();
let t_predicate = var_store.fresh();
let t_keep_predicate = var_store.fresh();
let t_elem = var_store.fresh();
// Defer to keepIf for implementation
// List.dropIf l p = List.keepIf l (\e -> Bool.not (p e))
let keep_predicate = Closure(ClosureData {
function_type: t_keep_predicate,
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
return_type: Variable::BOOL,
name: Symbol::LIST_DROP_IF_PREDICATE,
recursive: Recursive::NotRecursive,
captured_symbols: vec![(sym_predicate, t_predicate)],
arguments: vec![(t_elem, no_region(Pattern::Identifier(Symbol::ARG_3)))],
loc_body: {
let should_drop = Call(
Box::new((
t_predicate,
no_region(Var(sym_predicate)),
var_store.fresh(),
Variable::BOOL,
)),
vec![(t_elem, no_region(Var(Symbol::ARG_3)))],
CalledVia::Space,
);
Box::new(no_region(RunLowLevel {
op: LowLevel::Not,
args: vec![(Variable::BOOL, should_drop)],
ret_var: Variable::BOOL,
}))
},
});
let body = RunLowLevel {
op: LowLevel::ListKeepIf,
args: vec![(t_list, Var(sym_list)), (t_keep_predicate, keep_predicate)],
ret_var: t_list,
};
defn(
symbol,
vec![(t_list, sym_list), (t_predicate, sym_predicate)],
var_store,
body,
t_list,
)
}
/// List.dropLast: List elem -> List elem
fn list_drop_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
@ -2534,7 +2676,7 @@ fn list_drop_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
ret_var: len_var,
},
),
(arg_var, int(num_var, num_precision_var, 1)),
(arg_var, int::<i128>(num_var, num_precision_var, 1)),
],
ret_var: len_var,
},
@ -2732,7 +2874,7 @@ fn list_min(symbol: Symbol, var_store: &mut VarStore) -> Def {
RunLowLevel {
op: LowLevel::NotEq,
args: vec![
(len_var, int(num_var, num_precision_var, 0)),
(len_var, int::<i128>(num_var, num_precision_var, 0)),
(
len_var,
RunLowLevel {
@ -2763,7 +2905,7 @@ fn list_min(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::ListGetUnsafe,
args: vec![
(list_var, Var(Symbol::ARG_1)),
(arg_var, int(num_var, num_precision_var, 0)),
(arg_var, int::<i128>(num_var, num_precision_var, 0)),
],
ret_var: list_elem_var,
},
@ -2862,7 +3004,7 @@ fn list_max(symbol: Symbol, var_store: &mut VarStore) -> Def {
RunLowLevel {
op: LowLevel::NotEq,
args: vec![
(len_var, int(num_var, num_precision_var, 0)),
(len_var, int::<i128>(num_var, num_precision_var, 0)),
(
len_var,
RunLowLevel {
@ -2893,7 +3035,7 @@ fn list_max(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::ListGetUnsafe,
args: vec![
(list_var, Var(Symbol::ARG_1)),
(arg_var, int(num_var, num_precision_var, 0)),
(arg_var, int::<i128>(num_var, num_precision_var, 0)),
],
ret_var: list_elem_var,
},
@ -2976,15 +3118,22 @@ fn list_sum(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
let closure_var = var_store.fresh();
let body = RunLowLevel {
op: LowLevel::ListWalk,
args: vec![
(list_var, Var(Symbol::ARG_1)),
(num_var, num(var_store.fresh(), 0)),
(closure_var, list_sum_add(num_var, var_store)),
],
let function = (
var_store.fresh(),
Loc::at_zero(Expr::Var(Symbol::LIST_WALK)),
var_store.fresh(),
ret_var,
};
);
let body = Expr::Call(
Box::new(function),
vec![
(list_var, Loc::at_zero(Var(Symbol::ARG_1))),
(num_var, Loc::at_zero(num(var_store.fresh(), 0))),
(closure_var, Loc::at_zero(Var(Symbol::NUM_ADD))),
],
CalledVia::Space,
);
defn(
symbol,
@ -2995,60 +3144,34 @@ fn list_sum(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
fn list_sum_add(num_var: Variable, var_store: &mut VarStore) -> Expr {
let body = RunLowLevel {
op: LowLevel::NumAdd,
args: vec![(num_var, Var(Symbol::ARG_3)), (num_var, Var(Symbol::ARG_4))],
ret_var: num_var,
};
defn_help(
Symbol::LIST_SUM_ADD,
vec![(num_var, Symbol::ARG_3), (num_var, Symbol::ARG_4)],
var_store,
body,
num_var,
)
}
/// List.product : List (Num a) -> Num a
fn list_product(symbol: Symbol, var_store: &mut VarStore) -> Def {
let num_var = var_store.fresh();
let ret_var = num_var;
let list_var = var_store.fresh();
let closure_var = var_store.fresh();
let body = RunLowLevel {
op: LowLevel::ListWalk,
args: vec![
(list_var, Var(Symbol::ARG_1)),
(num_var, num(var_store.fresh(), 1)),
(closure_var, list_product_mul(num_var, var_store)),
let function = (
var_store.fresh(),
Loc::at_zero(Expr::Var(Symbol::LIST_WALK)),
var_store.fresh(),
num_var,
);
let body = Expr::Call(
Box::new(function),
vec![
(list_var, Loc::at_zero(Var(Symbol::ARG_1))),
(num_var, Loc::at_zero(num(var_store.fresh(), 1))),
(closure_var, Loc::at_zero(Var(Symbol::NUM_MUL))),
],
ret_var,
};
CalledVia::Space,
);
defn(
symbol,
vec![(list_var, Symbol::ARG_1)],
var_store,
body,
ret_var,
)
}
fn list_product_mul(num_var: Variable, var_store: &mut VarStore) -> Expr {
let body = RunLowLevel {
op: LowLevel::NumMul,
args: vec![(num_var, Var(Symbol::ARG_3)), (num_var, Var(Symbol::ARG_4))],
ret_var: num_var,
};
defn_help(
Symbol::LIST_PRODUCT_MUL,
vec![(num_var, Symbol::ARG_3), (num_var, Symbol::ARG_4)],
var_store,
body,
num_var,
)
}
@ -3091,7 +3214,7 @@ fn list_keep_errs(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::ListKeepErrs, var_store)
}
/// List.keepErrs: List before, (before -> Result * after) -> List after
/// List.range: Int a, Int a -> List (Int a)
fn list_range(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::ListRange, var_store)
}
@ -3126,6 +3249,91 @@ fn list_sort_with(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::ListSortWith, var_store)
}
/// List.sortAsc : List (Num a) -> List (Num a)
fn list_sort_asc(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
let closure_var = var_store.fresh();
let ret_var = list_var;
let function = (
var_store.fresh(),
Loc::at_zero(Expr::Var(Symbol::LIST_SORT_WITH)),
var_store.fresh(),
ret_var,
);
let body = Expr::Call(
Box::new(function),
vec![
(list_var, Loc::at_zero(Var(Symbol::ARG_1))),
(closure_var, Loc::at_zero(Var(Symbol::NUM_COMPARE))),
],
CalledVia::Space,
);
defn(
symbol,
vec![(list_var, Symbol::ARG_1)],
var_store,
body,
ret_var,
)
}
/// List.sortDesc : List (Num a) -> List (Num a)
fn list_sort_desc(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
let num_var = var_store.fresh();
let closure_var = var_store.fresh();
let compare_ret_var = var_store.fresh();
let ret_var = list_var;
let closure = Closure(ClosureData {
function_type: closure_var,
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
return_type: compare_ret_var,
name: Symbol::LIST_SORT_DESC_COMPARE,
recursive: Recursive::NotRecursive,
captured_symbols: vec![],
arguments: vec![
(num_var, no_region(Pattern::Identifier(Symbol::ARG_2))),
(num_var, no_region(Pattern::Identifier(Symbol::ARG_3))),
],
loc_body: {
Box::new(no_region(RunLowLevel {
op: LowLevel::NumCompare,
args: vec![(num_var, Var(Symbol::ARG_3)), (num_var, Var(Symbol::ARG_2))],
ret_var: compare_ret_var,
}))
},
});
let function = (
var_store.fresh(),
Loc::at_zero(Expr::Var(Symbol::LIST_SORT_WITH)),
var_store.fresh(),
ret_var,
);
let body = Expr::Call(
Box::new(function),
vec![
(list_var, Loc::at_zero(Var(Symbol::ARG_1))),
(closure_var, Loc::at_zero(closure)),
],
CalledVia::Space,
);
defn(
symbol,
vec![(list_var, Symbol::ARG_1)],
var_store,
body,
ret_var,
)
}
/// List.any: List elem, (elem -> Bool) -> Bool
fn list_any(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::ListAny, var_store)
@ -3249,8 +3457,8 @@ fn dict_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
Def {
annotation: None,
expr_var: dict_var,
loc_expr: Located::at_zero(body),
loc_pattern: Located::at_zero(Pattern::Identifier(symbol)),
loc_expr: Loc::at_zero(body),
loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)),
pattern_vars: SendMap::default(),
}
}
@ -3331,8 +3539,8 @@ fn dict_get(symbol: Symbol, var_store: &mut VarStore) -> Def {
let def = Def {
annotation: None,
expr_var: temp_record_var,
loc_expr: Located::at_zero(def_body),
loc_pattern: Located::at_zero(Pattern::Identifier(temp_record)),
loc_expr: Loc::at_zero(def_body),
loc_pattern: Loc::at_zero(Pattern::Identifier(temp_record)),
pattern_vars: Default::default(),
};
@ -3424,8 +3632,8 @@ fn set_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
Def {
annotation: None,
expr_var: set_var,
loc_expr: Located::at_zero(body),
loc_pattern: Located::at_zero(Pattern::Identifier(symbol)),
loc_expr: Loc::at_zero(body),
loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)),
pattern_vars: SendMap::default(),
}
}
@ -3775,7 +3983,7 @@ fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
(num_var, Var(Symbol::ARG_2)),
(
num_var,
int(unbound_zero_var, unbound_zero_precision_var, 0),
int::<i128>(unbound_zero_var, unbound_zero_precision_var, 0),
),
],
ret_var: bool_var,
@ -3841,7 +4049,7 @@ fn num_div_ceil(symbol: Symbol, var_store: &mut VarStore) -> Def {
(num_var, Var(Symbol::ARG_2)),
(
num_var,
int(unbound_zero_var, unbound_zero_precision_var, 0),
int::<i128>(unbound_zero_var, unbound_zero_precision_var, 0),
),
],
ret_var: bool_var,
@ -3911,7 +4119,7 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
RunLowLevel {
op: LowLevel::NotEq,
args: vec![
(len_var, int(zero_var, zero_precision_var, 0)),
(len_var, int::<i128>(zero_var, zero_precision_var, 0)),
(
len_var,
RunLowLevel {
@ -3935,7 +4143,7 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::ListGetUnsafe,
args: vec![
(list_var, Var(Symbol::ARG_1)),
(len_var, int(zero_var, zero_precision_var, 0)),
(len_var, int::<i128>(zero_var, zero_precision_var, 0)),
],
ret_var: list_elem_var,
},
@ -3992,7 +4200,7 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
RunLowLevel {
op: LowLevel::NotEq,
args: vec![
(len_var, int(num_var, num_precision_var, 0)),
(len_var, int::<i128>(num_var, num_precision_var, 0)),
(
len_var,
RunLowLevel {
@ -4031,7 +4239,7 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
ret_var: len_var,
},
),
(arg_var, int(num_var, num_precision_var, 1)),
(arg_var, int::<i128>(num_var, num_precision_var, 1)),
],
ret_var: len_var,
},
@ -4107,7 +4315,7 @@ fn result_map(symbol: Symbol, var_store: &mut VarStore) -> Def {
)],
};
let branch = WhenBranch {
let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(ok),
guard: None,
@ -4137,7 +4345,7 @@ fn result_map(symbol: Symbol, var_store: &mut VarStore) -> Def {
)],
};
let branch = WhenBranch {
let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(err),
guard: None,
@ -4204,7 +4412,7 @@ fn result_map_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
)],
};
let branch = WhenBranch {
let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(ok),
guard: None,
@ -4234,7 +4442,7 @@ fn result_map_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
)],
};
let branch = WhenBranch {
let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(err),
guard: None,
@ -4277,7 +4485,7 @@ fn result_with_default(symbol: Symbol, var_store: &mut VarStore) -> Def {
arguments: vec![(ret_var, no_region(Pattern::Identifier(Symbol::ARG_3)))],
};
let branch = WhenBranch {
let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(Var(Symbol::ARG_3)),
guard: None,
@ -4297,7 +4505,7 @@ fn result_with_default(symbol: Symbol, var_store: &mut VarStore) -> Def {
arguments: vec![(var_store.fresh(), no_region(Pattern::Underscore))],
};
let branch = WhenBranch {
let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(Var(Symbol::ARG_2)),
guard: None,
@ -4347,7 +4555,7 @@ fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
arguments: vec![],
};
let branch = WhenBranch {
let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(false_expr),
guard: None,
@ -4374,7 +4582,7 @@ fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
arguments: vec![],
};
let branch = WhenBranch {
let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(true_expr),
guard: None,
@ -4424,7 +4632,7 @@ fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def {
arguments: vec![],
};
let branch = WhenBranch {
let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(true_expr),
guard: None,
@ -4451,7 +4659,7 @@ fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def {
arguments: vec![],
};
let branch = WhenBranch {
let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(false_expr),
guard: None,
@ -4513,7 +4721,7 @@ fn result_after(symbol: Symbol, var_store: &mut VarStore) -> Def {
)],
};
let branch = WhenBranch {
let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(ok),
guard: None,
@ -4543,7 +4751,7 @@ fn result_after(symbol: Symbol, var_store: &mut VarStore) -> Def {
)],
};
let branch = WhenBranch {
let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(err),
guard: None,
@ -4570,8 +4778,8 @@ fn result_after(symbol: Symbol, var_store: &mut VarStore) -> Def {
}
#[inline(always)]
fn no_region<T>(value: T) -> Located<T> {
Located {
fn no_region<T>(value: T) -> Loc<T> {
Loc {
region: Region::zero(),
value,
}
@ -4586,7 +4794,7 @@ fn tag(name: &'static str, args: Vec<Expr>, var_store: &mut VarStore) -> Expr {
arguments: args
.into_iter()
.map(|expr| (var_store.fresh(), no_region(expr)))
.collect::<Vec<(Variable, Located<Expr>)>>(),
.collect::<Vec<(Variable, Loc<Expr>)>>(),
}
}
@ -4613,11 +4821,11 @@ fn defn(
let expr = defn_help(fn_name, args, var_store, body, ret_var);
Def {
loc_pattern: Located {
loc_pattern: Loc {
region: Region::zero(),
value: Pattern::Identifier(fn_name),
},
loc_expr: Located {
loc_expr: Loc {
region: Region::zero(),
value: expr,
},
@ -4747,8 +4955,42 @@ fn defn_help(
}
#[inline(always)]
fn int(num_var: Variable, precision_var: Variable, i: i128) -> Expr {
Int(num_var, precision_var, i.to_string().into_boxed_str(), i)
fn int_min_or_max<I128>(symbol: Symbol, var_store: &mut VarStore, i: I128) -> Def
where
I128: Into<i128>,
{
let int_var = var_store.fresh();
let int_precision_var = var_store.fresh();
let body = int::<I128>(int_var, int_precision_var, i);
let std = roc_builtins::std::types();
let solved = std.get(&symbol).unwrap();
let mut free_vars = roc_types::solved_types::FreeVars::default();
let signature = roc_types::solved_types::to_type(&solved.0, &mut free_vars, var_store);
let annotation = crate::def::Annotation {
signature,
introduced_variables: Default::default(),
region: Region::zero(),
aliases: Default::default(),
};
Def {
annotation: Some(annotation),
expr_var: int_var,
loc_expr: Loc::at_zero(body),
loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)),
pattern_vars: SendMap::default(),
}
}
#[inline(always)]
fn int<I128>(num_var: Variable, precision_var: Variable, i: I128) -> Expr
where
I128: Into<i128>,
{
let ii = i.into();
Int(num_var, precision_var, ii.to_string().into_boxed_str(), ii)
}
#[inline(always)]

View file

@ -1,10 +1,21 @@
use crate::expected::{Expected, PExpected};
use roc_collections::all::{MutSet, SendMap};
use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region};
use roc_module::{ident::TagName, symbol::Symbol};
use roc_region::all::{Loc, Region};
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, Clone, PartialEq)]
pub enum Constraint {
Eq(Type, Expected<Type>, Category, Region),
@ -15,13 +26,14 @@ pub enum Constraint {
SaveTheEnvironment,
Let(Box<LetConstraint>),
And(Vec<Constraint>),
Present(Type, PresenceConstraint),
}
#[derive(Debug, Clone, PartialEq)]
pub struct LetConstraint {
pub rigid_vars: Vec<Variable>,
pub flex_vars: Vec<Variable>,
pub def_types: SendMap<Symbol, Located<Type>>,
pub def_types: SendMap<Symbol, Loc<Type>>,
pub defs_constraint: Constraint,
pub ret_constraint: Constraint,
}
@ -74,6 +86,7 @@ impl Constraint {
|| boxed.defs_constraint.contains_save_the_environment()
}
Constraint::And(cs) => cs.iter().any(|c| c.contains_save_the_environment()),
Constraint::Present(_, _) => false,
}
}
}
@ -143,5 +156,20 @@ fn validate_help(constraint: &Constraint, declared: &Declared, accum: &mut Varia
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);
}
}
}
}
}

View file

@ -14,19 +14,20 @@ 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::pattern::PatternType;
use roc_problem::can::{CycleEntry, Problem, RuntimeError};
use roc_region::all::{Located, Region};
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::{Alias, Type};
use std::collections::HashMap;
use std::fmt::Debug;
use ven_graph::{strongly_connected_components, topological_sort_into_groups};
use ven_graph::{strongly_connected_components, topological_sort, topological_sort_into_groups};
#[derive(Clone, Debug, PartialEq)]
pub struct Def {
pub loc_pattern: Located<Pattern>,
pub loc_expr: Located<Expr>,
pub loc_pattern: Loc<Pattern>,
pub loc_expr: Loc<Expr>,
pub expr_var: Variable,
pub pattern_vars: SendMap<Symbol, Variable>,
pub annotation: Option<Annotation>,
@ -53,29 +54,29 @@ pub struct CanDefs {
enum PendingDef<'a> {
/// A standalone annotation with no body
AnnotationOnly(
&'a Located<ast::Pattern<'a>>,
Located<Pattern>,
&'a Located<ast::TypeAnnotation<'a>>,
&'a Loc<ast::Pattern<'a>>,
Loc<Pattern>,
&'a Loc<ast::TypeAnnotation<'a>>,
),
/// A body with no type annotation
Body(
&'a Located<ast::Pattern<'a>>,
Located<Pattern>,
&'a Located<ast::Expr<'a>>,
&'a Loc<ast::Pattern<'a>>,
Loc<Pattern>,
&'a Loc<ast::Expr<'a>>,
),
/// A body with a type annotation
TypedBody(
&'a Located<ast::Pattern<'a>>,
Located<Pattern>,
&'a Located<ast::TypeAnnotation<'a>>,
&'a Located<ast::Expr<'a>>,
&'a Loc<ast::Pattern<'a>>,
Loc<Pattern>,
&'a Loc<ast::TypeAnnotation<'a>>,
&'a Loc<ast::Expr<'a>>,
),
/// A type alias, e.g. `Ints : List Int`
Alias {
name: Located<Symbol>,
vars: Vec<Located<Lowercase>>,
ann: &'a Located<ast::TypeAnnotation<'a>>,
name: Loc<Symbol>,
vars: Vec<Loc<Lowercase>>,
ann: &'a Loc<ast::TypeAnnotation<'a>>,
},
/// An invalid alias, that is ignored in the rest of the pipeline
@ -106,13 +107,76 @@ 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();
// 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() {
v.1.retain(|x| defined_symbols.iter().any(|s| s == x));
}
let all_successors_with_self = |symbol: &Symbol| alias_symbols[symbol].iter().copied();
strongly_connected_components(&defined_symbols, all_successors_with_self)
};
// then sort the strongly connected components
let groups: Vec<_> = (0..sccs.len()).collect();
let mut group_symbols: Vec<Vec<Symbol>> = vec![Vec::new(); groups.len()];
let mut symbol_to_group_index = MutMap::default();
let mut group_to_groups = vec![Vec::new(); groups.len()];
for (index, group) in sccs.iter().enumerate() {
for s in group {
symbol_to_group_index.insert(*s, index);
}
}
for (index, group) in sccs.iter().enumerate() {
for s in group {
let reachable = &alias_symbols[s];
for r in reachable {
let new_index = symbol_to_group_index[r];
if new_index != index {
group_to_groups[index].push(new_index);
}
}
}
}
for v in group_symbols.iter_mut() {
v.sort();
v.dedup();
}
let all_successors_with_self = |group: &usize| group_to_groups[*group].iter().copied();
// split into self-recursive and mutually recursive
match topological_sort(&groups, all_successors_with_self) {
Ok(result) => result
.iter()
.rev()
.map(|group_index| sccs[*group_index].iter())
.flatten()
.copied()
.collect(),
Err(_loop_detected) => unreachable!("the groups cannot recurse"),
}
}
#[inline(always)]
pub fn canonicalize_defs<'a>(
env: &mut Env<'a>,
mut output: Output,
var_store: &mut VarStore,
original_scope: &Scope,
loc_defs: &'a [&'a Located<ast::Def<'a>>],
loc_defs: &'a [&'a Loc<ast::Def<'a>>],
pattern_type: PatternType,
) -> (CanDefs, Scope, Output, MutMap<Symbol, Region>) {
// Canonicalizing defs while detecting shadowing involves a multi-step process:
@ -179,69 +243,87 @@ pub fn canonicalize_defs<'a>(
let mut aliases = SendMap::default();
let mut value_defs = Vec::new();
let mut alias_defs = MutMap::default();
let mut alias_symbols = MutMap::default();
for pending_def in pending.into_iter() {
match pending_def {
PendingDef::Alias { name, vars, ann } => {
let symbol = name.value;
let mut can_ann =
canonicalize_annotation(env, &mut scope, &ann.value, ann.region, var_store);
let symbols =
crate::annotation::find_alias_symbols(env.home, &mut env.ident_ids, &ann.value);
// Record all the annotation's references in output.references.lookups
for symbol in can_ann.references {
output.references.lookups.insert(symbol);
output.references.referenced_aliases.insert(symbol);
}
let mut can_vars: Vec<Located<(Lowercase, Variable)>> =
Vec::with_capacity(vars.len());
let mut is_phantom = false;
for loc_lowercase in vars {
if let Some(var) = can_ann
.introduced_variables
.var_by_name(&loc_lowercase.value)
{
// This is a valid lowercase rigid var for the alias.
can_vars.push(Located {
value: (loc_lowercase.value.clone(), *var),
region: loc_lowercase.region,
});
} else {
is_phantom = true;
env.problems.push(Problem::PhantomTypeArgument {
alias: symbol,
variable_region: loc_lowercase.region,
variable_name: loc_lowercase.value.clone(),
});
}
}
if is_phantom {
// Bail out
continue;
}
if can_ann.typ.contains_symbol(symbol) {
make_tag_union_recursive(
env,
symbol,
name.region,
vec![],
&mut can_ann.typ,
var_store,
&mut false,
);
}
scope.add_alias(symbol, ann.region, can_vars.clone(), can_ann.typ.clone());
let alias = scope.lookup_alias(symbol).expect("alias is added to scope");
aliases.insert(symbol, alias.clone());
alias_symbols.insert(name.value, symbols);
alias_defs.insert(name.value, (name, vars, ann));
}
other => value_defs.push(other),
}
}
let sorted = sort_aliases_before_introduction(alias_symbols);
for alias_name in sorted {
let (name, vars, ann) = alias_defs.remove(&alias_name).unwrap();
let symbol = name.value;
let mut can_ann =
canonicalize_annotation(env, &mut scope, &ann.value, ann.region, var_store);
// Record all the annotation's references in output.references.lookups
for symbol in can_ann.references {
output.references.lookups.insert(symbol);
output.references.referenced_aliases.insert(symbol);
}
let mut can_vars: Vec<Loc<(Lowercase, Variable)>> = Vec::with_capacity(vars.len());
let mut is_phantom = false;
for loc_lowercase in vars {
if let Some(var) = can_ann
.introduced_variables
.var_by_name(&loc_lowercase.value)
{
// This is a valid lowercase rigid var for the alias.
can_vars.push(Loc {
value: (loc_lowercase.value.clone(), *var),
region: loc_lowercase.region,
});
} else {
is_phantom = true;
env.problems.push(Problem::PhantomTypeArgument {
alias: symbol,
variable_region: loc_lowercase.region,
variable_name: loc_lowercase.value.clone(),
});
}
}
if is_phantom {
// Bail out
continue;
}
if can_ann.typ.contains_symbol(symbol) {
make_tag_union_recursive(
env,
symbol,
name.region,
vec![],
&mut can_ann.typ,
var_store,
// Don't report any errors yet. We'll take care of self and mutual
// recursion errors after the sorted introductions are complete.
&mut false,
);
}
scope.add_alias(symbol, name.region, can_vars.clone(), can_ann.typ.clone());
let alias = scope.lookup_alias(symbol).expect("alias is added to scope");
aliases.insert(symbol, alias.clone());
}
// Now that we know the alias dependency graph, we can try to insert recursion variables
// where aliases are recursive tag unions, or detect illegal recursions.
correct_mutual_recursive_type_alias(env, &mut aliases, var_store);
// Now that we have the scope completely assembled, and shadowing resolved,
@ -727,7 +809,7 @@ fn pattern_to_vars_by_symbol(
) {
use Pattern::*;
match pattern {
Identifier(symbol) => {
Identifier(symbol) | Shadowed(_, _, symbol) => {
vars_by_symbol.insert(*symbol, expr_var);
}
@ -750,8 +832,6 @@ fn pattern_to_vars_by_symbol(
| Underscore
| MalformedPattern(_, _)
| UnsupportedPattern(_) => {}
Shadowed(_, _) => {}
}
}
@ -802,7 +882,7 @@ fn canonicalize_pending_def<'a>(
Pattern::Identifier(symbol) => RuntimeError::NoImplementationNamed {
def_symbol: *symbol,
},
Pattern::Shadowed(region, loc_ident) => RuntimeError::Shadowing {
Pattern::Shadowed(region, loc_ident, _new_symbol) => RuntimeError::Shadowing {
original_region: *region,
shadow: loc_ident.clone(),
},
@ -813,7 +893,7 @@ fn canonicalize_pending_def<'a>(
let value = Expr::RuntimeError(problem);
let is_closure = arity > 0;
let loc_can_expr = if !is_closure {
Located {
Loc {
value,
region: loc_ann.region,
}
@ -825,7 +905,7 @@ fn canonicalize_pending_def<'a>(
let mut underscores = Vec::with_capacity(arity);
for _ in 0..arity {
let underscore: Located<Pattern> = Located {
let underscore: Loc<Pattern> = Loc {
value: Pattern::Underscore,
region: Region::zero(),
};
@ -833,12 +913,12 @@ fn canonicalize_pending_def<'a>(
underscores.push((var_store.fresh(), underscore));
}
let body_expr = Located {
let body_expr = Loc {
value,
region: loc_ann.region,
};
Located {
Loc {
value: Closure(ClosureData {
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
@ -867,7 +947,7 @@ fn canonicalize_pending_def<'a>(
expr_var,
// TODO try to remove this .clone()!
loc_pattern: loc_can_pattern.clone(),
loc_expr: Located {
loc_expr: Loc {
region: loc_can_expr.region,
// TODO try to remove this .clone()!
value: loc_can_expr.value.clone(),
@ -884,66 +964,7 @@ fn canonicalize_pending_def<'a>(
}
}
Alias {
name, ann, vars, ..
} => {
let symbol = name.value;
let can_ann = canonicalize_annotation(env, scope, &ann.value, ann.region, var_store);
// Record all the annotation's references in output.references.lookups
for symbol in can_ann.references {
output.references.lookups.insert(symbol);
output.references.referenced_aliases.insert(symbol);
}
let mut can_vars: Vec<Located<(Lowercase, Variable)>> = Vec::with_capacity(vars.len());
for loc_lowercase in vars {
if let Some(var) = can_ann
.introduced_variables
.var_by_name(&loc_lowercase.value)
{
// This is a valid lowercase rigid var for the alias.
can_vars.push(Located {
value: (loc_lowercase.value.clone(), *var),
region: loc_lowercase.region,
});
} else {
env.problems.push(Problem::PhantomTypeArgument {
alias: symbol,
variable_region: loc_lowercase.region,
variable_name: loc_lowercase.value.clone(),
});
}
}
scope.add_alias(symbol, name.region, can_vars.clone(), can_ann.typ.clone());
if can_ann.typ.contains_symbol(symbol) {
// the alias is recursive. If it's a tag union, we attempt to fix this
if let Type::TagUnion(tags, ext) = can_ann.typ {
// re-canonicalize the alias with the alias already in scope
let rec_var = var_store.fresh();
let mut rec_type_union = Type::RecursiveTagUnion(rec_var, tags, ext);
rec_type_union.substitute_alias(symbol, &Type::Variable(rec_var));
scope.add_alias(symbol, name.region, can_vars, rec_type_union);
} else {
env.problems
.push(Problem::CyclicAlias(symbol, name.region, vec![]));
return output;
}
}
let alias = scope.lookup_alias(symbol).expect("alias was not added");
aliases.insert(symbol, alias.clone());
output
.introduced_variables
.union(&can_ann.introduced_variables);
}
Alias { .. } => unreachable!("Aliases are handled in a separate pass"),
InvalidAlias => {
// invalid aliases (shadowed, incorrect patterns) get ignored
}
@ -1088,7 +1109,7 @@ fn canonicalize_pending_def<'a>(
expr_var,
// TODO try to remove this .clone()!
loc_pattern: loc_can_pattern.clone(),
loc_expr: Located {
loc_expr: Loc {
region: loc_can_expr.region,
// TODO try to remove this .clone()!
value: loc_can_expr.value.clone(),
@ -1226,7 +1247,7 @@ fn canonicalize_pending_def<'a>(
expr_var,
// TODO try to remove this .clone()!
loc_pattern: loc_can_pattern.clone(),
loc_expr: Located {
loc_expr: Loc {
// TODO try to remove this .clone()!
region: loc_can_expr.region,
value: loc_can_expr.value.clone(),
@ -1249,8 +1270,8 @@ pub fn can_defs_with_return<'a>(
env: &mut Env<'a>,
var_store: &mut VarStore,
scope: Scope,
loc_defs: &'a [&'a Located<ast::Def<'a>>],
loc_ret: &'a Located<ast::Expr<'a>>,
loc_defs: &'a [&'a Loc<ast::Def<'a>>],
loc_ret: &'a Loc<ast::Expr<'a>>,
) -> (Expr, Output) {
let (unsorted, mut scope, defs_output, symbols_introduced) = canonicalize_defs(
env,
@ -1283,10 +1304,10 @@ pub fn can_defs_with_return<'a>(
match can_defs {
Ok(decls) => {
let mut loc_expr: Located<Expr> = ret_expr;
let mut loc_expr: Loc<Expr> = ret_expr;
for declaration in decls.into_iter().rev() {
loc_expr = Located {
loc_expr = Loc {
region: Region::zero(),
value: decl_to_let(var_store, declaration, loc_expr),
};
@ -1298,7 +1319,7 @@ pub fn can_defs_with_return<'a>(
}
}
fn decl_to_let(var_store: &mut VarStore, decl: Declaration, loc_ret: Located<Expr>) -> Expr {
fn decl_to_let(var_store: &mut VarStore, decl: Declaration, loc_ret: Loc<Expr>) -> Expr {
match decl {
Declaration::Declare(def) => {
Expr::LetNonRec(Box::new(def), Box::new(loc_ret), var_store.fresh())
@ -1428,7 +1449,9 @@ fn to_pending_def<'a>(
}
Alias {
name, vars, ann, ..
header: AliasHeader { name, vars },
ann,
..
} => {
let region = Region::span_across(&name.region, &ann.region);
@ -1439,7 +1462,7 @@ fn to_pending_def<'a>(
region,
) {
Ok(symbol) => {
let mut can_rigids: Vec<Located<Lowercase>> = Vec::with_capacity(vars.len());
let mut can_rigids: Vec<Loc<Lowercase>> = Vec::with_capacity(vars.len());
for loc_var in vars.iter() {
match loc_var.value {
@ -1447,7 +1470,7 @@ fn to_pending_def<'a>(
if name.chars().next().unwrap().is_lowercase() =>
{
let lowercase = Lowercase::from(name);
can_rigids.push(Located {
can_rigids.push(Loc {
value: lowercase,
region: loc_var.region,
});
@ -1467,7 +1490,7 @@ fn to_pending_def<'a>(
Some((
Output::default(),
PendingDef::Alias {
name: Located {
name: Loc {
region: name.region,
value: symbol,
},
@ -1477,7 +1500,7 @@ fn to_pending_def<'a>(
))
}
Err((original_region, loc_shadowed_symbol)) => {
Err((original_region, loc_shadowed_symbol, _new_symbol)) => {
env.problem(Problem::ShadowingInAnnotation {
original_region,
shadow: loc_shadowed_symbol,
@ -1500,9 +1523,9 @@ fn to_pending_def<'a>(
fn pending_typed_body<'a>(
env: &mut Env<'a>,
loc_pattern: &'a Located<ast::Pattern<'a>>,
loc_ann: &'a Located<ast::TypeAnnotation<'a>>,
loc_expr: &'a Located<ast::Expr<'a>>,
loc_pattern: &'a Loc<ast::Pattern<'a>>,
loc_ann: &'a Loc<ast::TypeAnnotation<'a>>,
loc_expr: &'a Loc<ast::Expr<'a>>,
var_store: &mut VarStore,
scope: &mut Scope,
pattern_type: PatternType,

View file

@ -3,7 +3,7 @@ use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::{Ident, Lowercase, ModuleName};
use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Located, Region};
use roc_region::all::{Loc, Region};
/// The canonicalization environment for a particular module.
pub struct Env<'a> {
@ -88,7 +88,7 @@ impl<'a> Env<'a> {
Ok(symbol)
}
None => Err(RuntimeError::LookupNotInScope(
Located {
Loc {
value: ident,
region,
},

View file

@ -1,11 +1,11 @@
use crate::pattern::Pattern;
use roc_region::all::{Located, Region};
use roc_region::all::{Loc, Region};
use roc_types::types::{AnnotationSource, PReason, Reason};
#[derive(Debug, Clone, PartialEq)]
pub enum Expected<T> {
NoExpectation(T),
FromAnnotation(Located<Pattern>, usize, AnnotationSource, T),
FromAnnotation(Loc<Pattern>, usize, AnnotationSource, T),
ForReason(Reason, T, Region),
}

View file

@ -17,7 +17,7 @@ use roc_module::symbol::Symbol;
use roc_parse::ast::{self, EscapedChar, StrLiteral};
use roc_parse::pattern::PatternType::*;
use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError};
use roc_region::all::{Located, Region};
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::Alias;
use std::fmt::Debug;
@ -60,7 +60,7 @@ pub enum Expr {
Str(Box<str>),
List {
elem_var: Variable,
loc_elems: Vec<Located<Expr>>,
loc_elems: Vec<Loc<Expr>>,
},
// Lookups
@ -70,25 +70,25 @@ pub enum Expr {
cond_var: Variable,
expr_var: Variable,
region: Region,
loc_cond: Box<Located<Expr>>,
loc_cond: Box<Loc<Expr>>,
branches: Vec<WhenBranch>,
},
If {
cond_var: Variable,
branch_var: Variable,
branches: Vec<(Located<Expr>, Located<Expr>)>,
final_else: Box<Located<Expr>>,
branches: Vec<(Loc<Expr>, Loc<Expr>)>,
final_else: Box<Loc<Expr>>,
},
// Let
LetRec(Vec<Def>, Box<Located<Expr>>, Variable),
LetNonRec(Box<Def>, Box<Located<Expr>>, Variable),
LetRec(Vec<Def>, Box<Loc<Expr>>, Variable),
LetNonRec(Box<Def>, Box<Loc<Expr>>, Variable),
/// This is *only* for calling functions, not for tag application.
/// The Tag variant contains any applied values inside it.
Call(
Box<(Variable, Located<Expr>, Variable, Variable)>,
Vec<(Variable, Located<Expr>)>,
Box<(Variable, Loc<Expr>, Variable, Variable)>,
Vec<(Variable, Loc<Expr>)>,
CalledVia,
),
RunLowLevel {
@ -118,7 +118,7 @@ pub enum Expr {
record_var: Variable,
ext_var: Variable,
field_var: Variable,
loc_expr: Box<Located<Expr>>,
loc_expr: Box<Loc<Expr>>,
field: Lowercase,
},
/// field accessor as a function, e.g. (.foo) expr
@ -146,7 +146,7 @@ pub enum Expr {
variant_var: Variable,
ext_var: Variable,
name: TagName,
arguments: Vec<(Variable, Located<Expr>)>,
arguments: Vec<(Variable, Loc<Expr>)>,
},
ZeroArgumentTag {
@ -154,11 +154,11 @@ pub enum Expr {
variant_var: Variable,
ext_var: Variable,
name: TagName,
arguments: Vec<(Variable, Located<Expr>)>,
arguments: Vec<(Variable, Loc<Expr>)>,
},
// Test
Expect(Box<Located<Expr>>, Box<Located<Expr>>),
Expect(Box<Loc<Expr>>, Box<Loc<Expr>>),
// Compiles, but will crash if reached
RuntimeError(RuntimeError),
@ -172,8 +172,8 @@ pub struct ClosureData {
pub name: Symbol,
pub captured_symbols: Vec<(Symbol, Variable)>,
pub recursive: Recursive,
pub arguments: Vec<(Variable, Located<Pattern>)>,
pub loc_body: Box<Located<Expr>>,
pub arguments: Vec<(Variable, Loc<Pattern>)>,
pub loc_body: Box<Loc<Expr>>,
}
#[derive(Clone, Debug, PartialEq)]
@ -181,7 +181,7 @@ pub struct Field {
pub var: Variable,
// The region of the full `foo: f bar`, rather than just `f bar`
pub region: Region,
pub loc_expr: Box<Located<Expr>>,
pub loc_expr: Box<Loc<Expr>>,
}
#[derive(Clone, Debug, PartialEq)]
@ -193,9 +193,9 @@ pub enum Recursive {
#[derive(Clone, Debug, PartialEq)]
pub struct WhenBranch {
pub patterns: Vec<Located<Pattern>>,
pub value: Located<Expr>,
pub guard: Option<Located<Expr>>,
pub patterns: Vec<Loc<Pattern>>,
pub value: Loc<Expr>,
pub guard: Option<Loc<Expr>>,
}
pub fn canonicalize_expr<'a>(
@ -204,7 +204,7 @@ pub fn canonicalize_expr<'a>(
scope: &mut Scope,
region: Region,
expr: &'a ast::Expr<'a>,
) -> (Located<Expr>, Output) {
) -> (Loc<Expr>, Output) {
use Expr::*;
let (expr, output) = match expr {
@ -439,7 +439,7 @@ pub fn canonicalize_expr<'a>(
// we parse underscores, but they are not valid expression syntax
let problem = roc_problem::can::RuntimeError::MalformedIdentifier(
(*name).into(),
roc_parse::ident::BadIdent::Underscore(region.start_line, region.start_col),
roc_parse::ident::BadIdent::Underscore(region.start()),
region,
);
@ -757,20 +757,16 @@ pub fn canonicalize_expr<'a>(
use roc_problem::can::RuntimeError::*;
let region1 = Region::new(
binop1_position.row,
binop1_position.row,
binop1_position.col,
binop1_position.col + binop1.width(),
*binop1_position,
binop1_position.bump_column(binop1.width() as u32),
);
let loc_binop1 = Located::at(region1, *binop1);
let loc_binop1 = Loc::at(region1, *binop1);
let region2 = Region::new(
binop2_position.row,
binop2_position.row,
binop2_position.col,
binop2_position.col + binop2.width(),
*binop2_position,
binop2_position.bump_column(binop2.width() as u32),
);
let loc_binop2 = Located::at(region2, *binop2);
let loc_binop2 = Loc::at(region2, *binop2);
let problem =
PrecedenceProblem::BothNonAssociative(*whole_region, loc_binop1, loc_binop2);
@ -859,7 +855,7 @@ pub fn canonicalize_expr<'a>(
// a rounding error anyway (especially given that they'll be surfaced as warnings), LLVM will
// DCE them in optimized builds, and it's not worth the bookkeeping for dev builds.
(
Located {
Loc {
region,
value: expr,
},
@ -1076,7 +1072,7 @@ fn canonicalize_fields<'a>(
var_store: &mut VarStore,
scope: &mut Scope,
region: Region,
fields: &'a [Located<ast::AssignedField<'a, ast::Expr<'a>>>],
fields: &'a [Loc<ast::AssignedField<'a, ast::Expr<'a>>>],
) -> Result<(SendMap<Lowercase, Field>, Output), CanonicalizeRecordProblem> {
let mut can_fields = SendMap::default();
let mut output = Output::default();
@ -1136,7 +1132,7 @@ fn canonicalize_field<'a>(
scope: &mut Scope,
field: &'a ast::AssignedField<'a, ast::Expr<'a>>,
region: Region,
) -> Result<(Lowercase, Located<Expr>, Output, Variable), CanonicalizeFieldProblem> {
) -> Result<(Lowercase, Loc<Expr>, Output, Variable), CanonicalizeFieldProblem> {
use roc_parse::ast::AssignedField::*;
match field {
@ -1251,7 +1247,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
for loc_elem in loc_elems {
let value = inline_calls(var_store, scope, loc_elem.value);
new_elems.push(Located {
new_elems.push(Loc {
value,
region: loc_elem.region,
});
@ -1270,7 +1266,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
loc_cond,
branches,
} => {
let loc_cond = Box::new(Located {
let loc_cond = Box::new(Loc {
region: loc_cond.region,
value: inline_calls(var_store, scope, loc_cond.value),
});
@ -1278,12 +1274,12 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
let mut new_branches = Vec::with_capacity(branches.len());
for branch in branches {
let value = Located {
let value = Loc {
value: inline_calls(var_store, scope, branch.value.value),
region: branch.value.region,
};
let guard = match branch.guard {
Some(loc_expr) => Some(Located {
Some(loc_expr) => Some(Loc {
region: loc_expr.region,
value: inline_calls(var_store, scope, loc_expr.value),
}),
@ -1315,12 +1311,12 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
let mut new_branches = Vec::with_capacity(branches.len());
for (loc_cond, loc_expr) in branches {
let loc_cond = Located {
let loc_cond = Loc {
value: inline_calls(var_store, scope, loc_cond.value),
region: loc_cond.region,
};
let loc_expr = Located {
let loc_expr = Loc {
value: inline_calls(var_store, scope, loc_expr.value),
region: loc_expr.region,
};
@ -1328,7 +1324,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
new_branches.push((loc_cond, loc_expr));
}
let final_else = Box::new(Located {
let final_else = Box::new(Loc {
region: final_else.region,
value: inline_calls(var_store, scope, final_else.value),
});
@ -1342,12 +1338,12 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
}
Expect(loc_condition, loc_expr) => {
let loc_condition = Located {
let loc_condition = Loc {
region: loc_condition.region,
value: inline_calls(var_store, scope, loc_condition.value),
};
let loc_expr = Located {
let loc_expr = Loc {
region: loc_expr.region,
value: inline_calls(var_store, scope, loc_expr.value),
};
@ -1361,7 +1357,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
for def in defs {
new_defs.push(Def {
loc_pattern: def.loc_pattern,
loc_expr: Located {
loc_expr: Loc {
region: def.loc_expr.region,
value: inline_calls(var_store, scope, def.loc_expr.value),
},
@ -1371,7 +1367,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
});
}
let loc_expr = Located {
let loc_expr = Loc {
region: loc_expr.region,
value: inline_calls(var_store, scope, loc_expr.value),
};
@ -1382,7 +1378,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
LetNonRec(def, loc_expr, var) => {
let def = Def {
loc_pattern: def.loc_pattern,
loc_expr: Located {
loc_expr: Loc {
region: def.loc_expr.region,
value: inline_calls(var_store, scope, def.loc_expr.value),
},
@ -1391,7 +1387,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
annotation: def.annotation,
};
let loc_expr = Located {
let loc_expr = Loc {
region: loc_expr.region,
value: inline_calls(var_store, scope, loc_expr.value),
};
@ -1411,7 +1407,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
loc_body,
}) => {
let loc_expr = *loc_body;
let loc_expr = Located {
let loc_expr = Loc {
value: inline_calls(var_store, scope, loc_expr.value),
region: loc_expr.region,
};
@ -1486,7 +1482,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
Var(symbol) if symbol.is_builtin() => match builtin_defs_map(symbol, var_store) {
Some(Def {
loc_expr:
Located {
Loc {
value:
Closure(ClosureData {
recursive,
@ -1526,7 +1522,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
annotation: None,
};
loc_answer = Located {
loc_answer = Loc {
region: Region::zero(),
value: LetNonRec(
Box::new(def),
@ -1585,7 +1581,7 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
}
enum StrSegment {
Interpolation(Located<Expr>),
Interpolation(Loc<Expr>),
Plaintext(Box<str>),
}
@ -1684,22 +1680,22 @@ fn desugar_str_segments(var_store: &mut VarStore, segments: Vec<StrSegment>) ->
let mut iter = segments.into_iter().rev();
let mut loc_expr = match iter.next() {
Some(Plaintext(string)) => Located::new(0, 0, 0, 0, Expr::Str(string)),
Some(Plaintext(string)) => Loc::at(Region::zero(), Expr::Str(string)),
Some(Interpolation(loc_expr)) => loc_expr,
None => {
// No segments? Empty string!
Located::new(0, 0, 0, 0, Expr::Str("".into()))
Loc::at(Region::zero(), Expr::Str("".into()))
}
};
for seg in iter {
let loc_new_expr = match seg {
Plaintext(string) => Located::new(0, 0, 0, 0, Expr::Str(string)),
Plaintext(string) => Loc::at(Region::zero(), Expr::Str(string)),
Interpolation(loc_interpolated_expr) => loc_interpolated_expr,
};
let fn_expr = Located::new(0, 0, 0, 0, Expr::Var(Symbol::STR_CONCAT));
let fn_expr = Loc::at(Region::zero(), Expr::Var(Symbol::STR_CONCAT));
let expr = Expr::Call(
Box::new((
var_store.fresh(),
@ -1714,7 +1710,7 @@ fn desugar_str_segments(var_store: &mut VarStore, segments: Vec<StrSegment>) ->
CalledVia::StringInterpolation,
);
loc_expr = Located::new(0, 0, 0, 0, expr);
loc_expr = Loc::at(Region::zero(), expr);
}
loc_expr.value

View file

@ -12,7 +12,7 @@ use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_parse::ast;
use roc_parse::pattern::PatternType;
use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Located, Region};
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::Alias;
@ -43,7 +43,7 @@ pub struct ModuleOutput {
#[allow(clippy::too_many_arguments)]
pub fn canonicalize_module_defs<'a, F>(
arena: &Bump,
loc_defs: &'a [Located<ast::Def<'a>>],
loc_defs: &'a [Loc<ast::Def<'a>>],
home: ModuleId,
module_ids: &ModuleIds,
exposed_ident_ids: IdentIds,
@ -76,7 +76,7 @@ where
bumpalo::collections::Vec::with_capacity_in(loc_defs.len() + num_deps, arena);
for loc_def in loc_defs.iter() {
desugared.push(&*arena.alloc(Located {
desugared.push(&*arena.alloc(Loc {
value: desugar_def(arena, &loc_def.value),
region: loc_def.region,
}));
@ -124,7 +124,11 @@ where
// This is a type alias
// the symbol should already be added to the scope when this module is canonicalized
debug_assert!(scope.contains_alias(symbol));
debug_assert!(
scope.contains_alias(symbol),
"apparently, {:?} is not actually a type alias",
symbol
);
// but now we know this symbol by a different identifier, so we still need to add it to
// the scope
@ -181,6 +185,17 @@ where
references.insert(*symbol);
}
// add any builtins used by other builtins
let transitive_builtins: Vec<Symbol> = references
.iter()
.filter(|s| s.is_builtin())
.map(|s| crate::builtins::builtin_dependencies(*s))
.flatten()
.copied()
.collect();
references.extend(transitive_builtins);
// NOTE previously we inserted builtin defs into the list of defs here
// this is now done later, in file.rs.
@ -263,8 +278,8 @@ where
let runtime_error = RuntimeError::ExposedButNotDefined(symbol);
let def = Def {
loc_pattern: Located::new(0, 0, 0, 0, Pattern::Identifier(symbol)),
loc_expr: Located::new(0, 0, 0, 0, Expr::RuntimeError(runtime_error)),
loc_pattern: Loc::at(Region::zero(), Pattern::Identifier(symbol)),
loc_expr: Loc::at(Region::zero(), Expr::RuntimeError(runtime_error)),
expr_var: var_store.fresh(),
pattern_vars,
annotation: None,
@ -387,7 +402,7 @@ fn fix_values_captured_in_closure_pattern(
| FloatLiteral(_, _, _)
| StrLiteral(_)
| Underscore
| Shadowed(_, _)
| Shadowed(..)
| MalformedPattern(_, _)
| UnsupportedPattern(_) => (),
}

View file

@ -7,7 +7,7 @@ use roc_module::called_via::{BinOp, CalledVia};
use roc_module::ident::ModuleName;
use roc_parse::ast::Expr::{self, *};
use roc_parse::ast::{AssignedField, Def, WhenBranch};
use roc_region::all::{Located, Region};
use roc_region::all::{Loc, Region};
// BinOp precedence logic adapted from Gluon by Markus Westerlind
// https://github.com/gluon-lang/gluon - license information can be found in
@ -17,10 +17,10 @@ use roc_region::all::{Located, Region};
fn new_op_call_expr<'a>(
arena: &'a Bump,
left: &'a Located<Expr<'a>>,
loc_op: Located<BinOp>,
right: &'a Located<Expr<'a>>,
) -> Located<Expr<'a>> {
left: &'a Loc<Expr<'a>>,
loc_op: Loc<BinOp>,
right: &'a Loc<Expr<'a>>,
) -> Loc<Expr<'a>> {
let region = Region::span_across(&left.region, &right.region);
let value = match loc_op.value {
@ -51,7 +51,7 @@ fn new_op_call_expr<'a>(
let args = arena.alloc([left, right]);
let loc_expr = arena.alloc(Located {
let loc_expr = arena.alloc(Loc {
value: Expr::Var { module_name, ident },
region: loc_op.region,
});
@ -60,19 +60,19 @@ fn new_op_call_expr<'a>(
}
};
Located { region, value }
Loc { region, value }
}
fn desugar_def_helps<'a>(
arena: &'a Bump,
region: Region,
defs: &'a [&'a Located<Def<'a>>],
loc_ret: &'a Located<Expr<'a>>,
) -> &'a Located<Expr<'a>> {
defs: &'a [&'a Loc<Def<'a>>],
loc_ret: &'a Loc<Expr<'a>>,
) -> &'a Loc<Expr<'a>> {
let mut desugared_defs = Vec::with_capacity_in(defs.len(), arena);
for loc_def in defs.iter() {
let loc_def = Located {
let loc_def = Loc {
value: desugar_def(arena, &loc_def.value),
region: loc_def.region,
};
@ -82,7 +82,7 @@ fn desugar_def_helps<'a>(
let desugared_defs = desugared_defs.into_bump_slice();
arena.alloc(Located {
arena.alloc(Loc {
value: Defs(desugared_defs, desugar_expr(arena, loc_ret)),
region,
})
@ -119,7 +119,7 @@ pub fn desugar_def<'a>(arena: &'a Bump, def: &'a Def<'a>) -> Def<'a> {
/// Reorder the expression tree based on operator precedence and associativity rules,
/// then replace the BinOp nodes with Apply nodes. Also drop SpaceBefore and SpaceAfter nodes.
pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a Located<Expr<'a>> {
pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc<Expr<'a>> {
match &loc_expr.value {
Float(_)
| Num(_)
@ -136,13 +136,13 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
Access(sub_expr, paths) => {
let region = loc_expr.region;
let loc_sub_expr = Located {
let loc_sub_expr = Loc {
region,
value: **sub_expr,
};
let value = Access(&desugar_expr(arena, arena.alloc(loc_sub_expr)).value, paths);
arena.alloc(Located { region, value })
arena.alloc(Loc { region, value })
}
List(items) => {
let mut new_items = Vec::with_capacity_in(items.len(), arena);
@ -153,16 +153,16 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
let new_items = new_items.into_bump_slice();
let value: Expr<'a> = List(items.replace_items(new_items));
arena.alloc(Located {
arena.alloc(Loc {
region: loc_expr.region,
value,
})
}
Record(fields) => arena.alloc(Located {
Record(fields) => arena.alloc(Loc {
region: loc_expr.region,
value: Record(fields.map_items(arena, |field| {
let value = desugar_field(arena, &field.value);
Located {
Loc {
value,
region: field.region,
}
@ -173,13 +173,13 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
// NOTE the `update` field is always a `Var { .. }` and does not need to be desugared
let new_fields = fields.map_items(arena, |field| {
let value = desugar_field(arena, &field.value);
Located {
Loc {
value,
region: field.region,
}
});
arena.alloc(Located {
arena.alloc(Loc {
region: loc_expr.region,
value: RecordUpdate {
update: *update,
@ -187,7 +187,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
},
})
}
Closure(loc_patterns, loc_ret) => arena.alloc(Located {
Closure(loc_patterns, loc_ret) => arena.alloc(Loc {
region: loc_expr.region,
value: Closure(loc_patterns, desugar_expr(arena, loc_ret)),
}),
@ -201,17 +201,17 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
let desugared_ret = desugar_expr(arena, loc_ret);
let closure = Expr::Closure(loc_patterns, desugared_ret);
let loc_closure = Located::at(loc_expr.region, closure);
let loc_closure = Loc::at(loc_expr.region, closure);
match &desugared_body.value {
Expr::Apply(function, arguments, called_via) => {
let mut new_arguments: Vec<'a, &'a Located<Expr<'a>>> =
let mut new_arguments: Vec<'a, &'a Loc<Expr<'a>>> =
Vec::with_capacity_in(arguments.len() + 1, arena);
new_arguments.extend(arguments.iter());
new_arguments.push(arena.alloc(loc_closure));
let call = Expr::Apply(function, new_arguments.into_bump_slice(), *called_via);
let loc_call = Located::at(loc_expr.region, call);
let loc_call = Loc::at(loc_expr.region, call);
arena.alloc(loc_call)
}
@ -222,7 +222,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
arena.alloc([&*arena.alloc(loc_closure)]),
CalledVia::Space,
);
let loc_call = Located::at(loc_expr.region, call);
let loc_call = Loc::at(loc_expr.region, call);
arena.alloc(loc_call)
}
@ -239,7 +239,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
let desugared_args = desugared_args.into_bump_slice();
arena.alloc(Located {
arena.alloc(Loc {
value: Apply(desugar_expr(arena, loc_fn), desugared_args, *called_via),
region: loc_expr.region,
})
@ -271,7 +271,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
let desugared_branches = desugared_branches.into_bump_slice();
arena.alloc(Located {
arena.alloc(Loc {
value: When(loc_desugared_cond, desugared_branches),
region: loc_expr.region,
})
@ -294,10 +294,10 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
ident: "not",
},
};
let loc_fn_var = arena.alloc(Located { region, value });
let loc_fn_var = arena.alloc(Loc { region, value });
let desugared_args = arena.alloc([desugar_expr(arena, loc_arg)]);
arena.alloc(Located {
arena.alloc(Loc {
value: Apply(loc_fn_var, desugared_args, CalledVia::UnaryOp(op)),
region: loc_expr.region,
})
@ -307,7 +307,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
// are no longer needed and should be dropped.
desugar_expr(
arena,
arena.alloc(Located {
arena.alloc(Loc {
value: **expr,
region: loc_expr.region,
}),
@ -326,7 +326,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
));
}
arena.alloc(Located {
arena.alloc(Loc {
value: If(desugared_if_thens.into_bump_slice(), desugared_final_else),
region: loc_expr.region,
})
@ -334,7 +334,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
Expect(condition, continuation) => {
let desugared_condition = &*arena.alloc(desugar_expr(arena, condition));
let desugared_continuation = &*arena.alloc(desugar_expr(arena, continuation));
arena.alloc(Located {
arena.alloc(Loc {
value: Expect(desugared_condition, desugared_continuation),
region: loc_expr.region,
})
@ -350,7 +350,7 @@ fn desugar_field<'a>(
match field {
RequiredValue(loc_str, spaces, loc_expr) => RequiredValue(
Located {
Loc {
value: loc_str.value,
region: loc_str.region,
},
@ -358,7 +358,7 @@ fn desugar_field<'a>(
desugar_expr(arena, loc_expr),
),
OptionalValue(loc_str, spaces, loc_expr) => OptionalValue(
Located {
Loc {
value: loc_str.value,
region: loc_str.region,
},
@ -367,7 +367,7 @@ fn desugar_field<'a>(
),
LabelOnly(loc_str) => {
// Desugar { x } into { x: x }
let loc_expr = Located {
let loc_expr = Loc {
value: Var {
module_name: "",
ident: loc_str.value,
@ -376,7 +376,7 @@ fn desugar_field<'a>(
};
RequiredValue(
Located {
Loc {
value: loc_str.value,
region: loc_str.region,
},
@ -423,11 +423,11 @@ fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) {
fn desugar_bin_ops<'a>(
arena: &'a Bump,
whole_region: Region,
lefts: &'a [(Located<Expr<'_>>, Located<BinOp>)],
right: &'a Located<Expr<'_>>,
) -> &'a Located<Expr<'a>> {
let mut arg_stack: Vec<&'a Located<Expr>> = Vec::with_capacity_in(lefts.len() + 1, arena);
let mut op_stack: Vec<Located<BinOp>> = Vec::with_capacity_in(lefts.len(), arena);
lefts: &'a [(Loc<Expr<'_>>, Loc<BinOp>)],
right: &'a Loc<Expr<'_>>,
) -> &'a Loc<Expr<'a>> {
let mut arg_stack: Vec<&'a Loc<Expr>> = Vec::with_capacity_in(lefts.len() + 1, arena);
let mut op_stack: Vec<Loc<BinOp>> = Vec::with_capacity_in(lefts.len(), arena);
for (loc_expr, loc_op) in lefts {
arg_stack.push(desugar_expr(arena, loc_expr));
@ -447,18 +447,18 @@ fn desugar_bin_ops<'a>(
}
enum Step<'a> {
Error(&'a Located<Expr<'a>>),
Push(Located<BinOp>),
Error(&'a Loc<Expr<'a>>),
Push(Loc<BinOp>),
Skip,
}
fn run_binop_step<'a>(
arena: &'a Bump,
whole_region: Region,
arg_stack: &mut Vec<&'a Located<Expr<'a>>>,
op_stack: &mut Vec<Located<BinOp>>,
next_op: Located<BinOp>,
) -> Result<(), &'a Located<Expr<'a>>> {
arg_stack: &mut Vec<&'a Loc<Expr<'a>>>,
op_stack: &mut Vec<Loc<BinOp>>,
next_op: Loc<BinOp>,
) -> Result<(), &'a Loc<Expr<'a>>> {
use Step::*;
match binop_step(arena, whole_region, arg_stack, op_stack, next_op) {
@ -471,9 +471,9 @@ fn run_binop_step<'a>(
fn binop_step<'a>(
arena: &'a Bump,
whole_region: Region,
arg_stack: &mut Vec<&'a Located<Expr<'a>>>,
op_stack: &mut Vec<Located<BinOp>>,
next_op: Located<BinOp>,
arg_stack: &mut Vec<&'a Loc<Expr<'a>>>,
op_stack: &mut Vec<Loc<BinOp>>,
next_op: Loc<BinOp>,
) -> Step<'a> {
use roc_module::called_via::Associativity::*;
use std::cmp::Ordering;
@ -542,7 +542,7 @@ fn binop_step<'a>(
};
let value = Expr::PrecedenceConflict(arena.alloc(data));
Step::Error(arena.alloc(Located { region, value }))
Step::Error(arena.alloc(Loc { region, value }))
}
_ => {

View file

@ -7,7 +7,7 @@ use roc_module::symbol::Symbol;
use roc_parse::ast::{self, StrLiteral, StrSegment};
use roc_parse::pattern::PatternType;
use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError};
use roc_region::all::{Located, Region};
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
/// A pattern, including possible problems (e.g. shadowing) so that
/// codegen can generate a runtime error if this pattern is reached.
@ -18,12 +18,12 @@ pub enum Pattern {
whole_var: Variable,
ext_var: Variable,
tag_name: TagName,
arguments: Vec<(Variable, Located<Pattern>)>,
arguments: Vec<(Variable, Loc<Pattern>)>,
},
RecordDestructure {
whole_var: Variable,
ext_var: Variable,
destructs: Vec<Located<RecordDestruct>>,
destructs: Vec<Loc<RecordDestruct>>,
},
IntLiteral(Variable, Box<str>, i64),
NumLiteral(Variable, Box<str>, i64),
@ -32,7 +32,7 @@ pub enum Pattern {
Underscore,
// Runtime Exceptions
Shadowed(Region, Located<Ident>),
Shadowed(Region, Loc<Ident>, Symbol),
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
UnsupportedPattern(Region),
// parse error patterns
@ -50,8 +50,8 @@ pub struct RecordDestruct {
#[derive(Clone, Debug, PartialEq)]
pub enum DestructType {
Required,
Optional(Variable, Located<Expr>),
Guard(Variable, Located<Pattern>),
Optional(Variable, Loc<Expr>),
Guard(Variable, Loc<Pattern>),
}
pub fn symbols_from_pattern(pattern: &Pattern) -> Vec<Symbol> {
@ -65,7 +65,7 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
use Pattern::*;
match pattern {
Identifier(symbol) => {
Identifier(symbol) | Shadowed(_, _, symbol) => {
symbols.push(*symbol);
}
@ -92,8 +92,6 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
| Underscore
| MalformedPattern(_, _)
| UnsupportedPattern(_) => {}
Shadowed(_, _) => {}
}
}
@ -104,7 +102,7 @@ pub fn canonicalize_pattern<'a>(
pattern_type: PatternType,
pattern: &ast::Pattern<'a>,
region: Region,
) -> (Output, Located<Pattern>) {
) -> (Output, Loc<Pattern>) {
use roc_parse::ast::Pattern::*;
use PatternType::*;
@ -121,13 +119,14 @@ pub fn canonicalize_pattern<'a>(
Pattern::Identifier(symbol)
}
Err((original_region, shadow)) => {
Err((original_region, shadow, new_symbol)) => {
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region,
shadow: shadow.clone(),
}));
output.references.bound_symbols.insert(new_symbol);
Pattern::Shadowed(original_region, shadow)
Pattern::Shadowed(original_region, shadow, new_symbol)
}
},
GlobalTag(name) => {
@ -258,7 +257,7 @@ pub fn canonicalize_pattern<'a>(
Ok(symbol) => {
output.references.bound_symbols.insert(symbol);
destructs.push(Located {
destructs.push(Loc {
region: loc_pattern.region,
value: RecordDestruct {
var: var_store.fresh(),
@ -268,7 +267,7 @@ pub fn canonicalize_pattern<'a>(
},
});
}
Err((original_region, shadow)) => {
Err((original_region, shadow, new_symbol)) => {
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region,
shadow: shadow.clone(),
@ -278,7 +277,8 @@ pub fn canonicalize_pattern<'a>(
// are, we're definitely shadowed and will
// get a runtime exception as soon as we
// encounter the first bad pattern.
opt_erroneous = Some(Pattern::Shadowed(original_region, shadow));
opt_erroneous =
Some(Pattern::Shadowed(original_region, shadow, new_symbol));
}
};
}
@ -297,7 +297,7 @@ pub fn canonicalize_pattern<'a>(
output.union(new_output);
destructs.push(Located {
destructs.push(Loc {
region: loc_pattern.region,
value: RecordDestruct {
var: var_store.fresh(),
@ -329,7 +329,7 @@ pub fn canonicalize_pattern<'a>(
output.union(expr_output);
destructs.push(Located {
destructs.push(Loc {
region: loc_pattern.region,
value: RecordDestruct {
var: var_store.fresh(),
@ -339,7 +339,7 @@ pub fn canonicalize_pattern<'a>(
},
});
}
Err((original_region, shadow)) => {
Err((original_region, shadow, new_symbol)) => {
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region,
shadow: shadow.clone(),
@ -349,7 +349,8 @@ pub fn canonicalize_pattern<'a>(
// are, we're definitely shadowed and will
// get a runtime exception as soon as we
// encounter the first bad pattern.
opt_erroneous = Some(Pattern::Shadowed(original_region, shadow));
opt_erroneous =
Some(Pattern::Shadowed(original_region, shadow, new_symbol));
}
};
}
@ -391,7 +392,7 @@ pub fn canonicalize_pattern<'a>(
(
output,
Located {
Loc {
region,
value: can_pattern,
},
@ -432,7 +433,7 @@ fn malformed_pattern(env: &mut Env, problem: MalformedPatternProblem, region: Re
pub fn bindings_from_patterns<'a, I>(loc_patterns: I) -> Vec<(Symbol, Region)>
where
I: Iterator<Item = &'a Located<Pattern>>,
I: Iterator<Item = &'a Loc<Pattern>>,
{
let mut answer = Vec::new();
@ -452,7 +453,7 @@ fn add_bindings_from_patterns(
use Pattern::*;
match pattern {
Identifier(symbol) => {
Identifier(symbol) | Shadowed(_, _, symbol) => {
answer.push((*symbol, *region));
}
AppliedTag {
@ -464,7 +465,7 @@ fn add_bindings_from_patterns(
}
}
RecordDestructure { destructs, .. } => {
for Located {
for Loc {
region,
value: RecordDestruct { symbol, .. },
} in destructs
@ -477,7 +478,6 @@ fn add_bindings_from_patterns(
| FloatLiteral(_, _, _)
| StrLiteral(_)
| Underscore
| Shadowed(_, _)
| MalformedPattern(_, _)
| UnsupportedPattern(_) => (),
}

View file

@ -2,7 +2,7 @@ use crate::expr::Expr;
use crate::pattern::Pattern;
use roc_collections::all::ImSet;
use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region};
use roc_region::all::{Loc, Region};
use roc_types::subs::Variable;
#[derive(Clone, Debug, PartialEq)]
@ -10,8 +10,8 @@ pub struct Procedure {
pub name: Option<Box<str>>,
pub is_self_tail_recursive: bool,
pub definition: Region,
pub args: Vec<Located<Pattern>>,
pub body: Located<Expr>,
pub args: Vec<Loc<Pattern>>,
pub body: Loc<Expr>,
pub references: References,
pub var: Variable,
pub ret_var: Variable,
@ -20,8 +20,8 @@ pub struct Procedure {
impl Procedure {
pub fn new(
definition: Region,
args: Vec<Located<Pattern>>,
body: Located<Expr>,
args: Vec<Loc<Pattern>>,
body: Loc<Expr>,
references: References,
var: Variable,
ret_var: Variable,

View file

@ -2,7 +2,7 @@ use roc_collections::all::{MutSet, SendMap};
use roc_module::ident::{Ident, Lowercase};
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_problem::can::RuntimeError;
use roc_region::all::{Located, Region};
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::{Alias, Type};
@ -41,7 +41,7 @@ impl Scope {
let mut type_variables: Vec<_> = free_vars.unnamed_vars.into_iter().collect();
type_variables.sort();
for (loc_name, (_, var)) in vars.iter().zip(type_variables) {
variables.push(Located::at(loc_name.region, (loc_name.value.clone(), var)));
variables.push(Loc::at(loc_name.region, (loc_name.value.clone(), var)));
}
let alias = Alias {
@ -87,7 +87,7 @@ impl Scope {
match self.idents.get(ident) {
Some((symbol, _)) => Ok(*symbol),
None => Err(RuntimeError::LookupNotInScope(
Located {
Loc {
region,
value: ident.clone(),
},
@ -110,15 +110,21 @@ impl Scope {
exposed_ident_ids: &IdentIds,
all_ident_ids: &mut IdentIds,
region: Region,
) -> Result<Symbol, (Region, Located<Ident>)> {
) -> Result<Symbol, (Region, Loc<Ident>, Symbol)> {
match self.idents.get(&ident) {
Some((_, original_region)) => {
let shadow = Located {
value: ident,
Some(&(_, original_region)) => {
let shadow = Loc {
value: ident.clone(),
region,
};
Err((*original_region, shadow))
let ident_id = all_ident_ids.add(ident.clone());
let symbol = Symbol::new(self.home, ident_id);
self.symbols.insert(symbol, region);
self.idents.insert(ident, (symbol, region));
Err((original_region, shadow, symbol))
}
None => {
// If this IdentId was already added previously
@ -172,7 +178,7 @@ impl Scope {
&mut self,
name: Symbol,
region: Region,
vars: Vec<Located<(Lowercase, Variable)>>,
vars: Vec<Loc<(Lowercase, Variable)>>,
typ: Type,
) {
let roc_types::types::VariableDetail {

View file

@ -118,7 +118,7 @@ pub fn canonical_string_literal<'a>(_arena: &Bump, _raw: &'a str, _region: Regio
// }
// }
// fn loc_char<'a, V>(value: V, state: &State<'a>, buf_len: usize) -> Loc<V> {
// fn loc_char<'a, V>(value: V, state: &State<'a>, buf_len: usize) -> Located<V> {
// let start_line = state.line;
// let start_col = state.column + buf_len as u16;
// let end_line = start_line;
@ -135,7 +135,7 @@ pub fn canonical_string_literal<'a>(_arena: &Bump, _raw: &'a str, _region: Regio
// Loc { region, value }
// }
// fn loc_escaped_char<'a, V>(value: V, state: &State<'a>, buf_len: usize) -> Loc<V> {
// fn loc_escaped_char<'a, V>(value: V, state: &State<'a>, buf_len: usize) -> Located<V> {
// let start_line = state.line;
// let start_col = state.column + buf_len as u16;
// let end_line = start_line;
@ -157,7 +157,7 @@ pub fn canonical_string_literal<'a>(_arena: &Bump, _raw: &'a str, _region: Regio
// state: &State<'a>,
// buf_len: usize,
// hex_str_len: usize,
// ) -> Loc<V> {
// ) -> Located<V> {
// let start_line = state.line;
// // +1 due to the `"` which precedes buf.
// let start_col = state.column + buf_len as u16 + 1;

View file

@ -9,7 +9,7 @@ use roc_can::scope::Scope;
use roc_collections::all::MutMap;
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds};
use roc_problem::can::Problem;
use roc_region::all::{Located, Region};
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use std::hash::Hash;
@ -23,7 +23,7 @@ pub fn can_expr(expr_str: &str) -> CanExprOut {
}
pub struct CanExprOut {
pub loc_expr: Located<Expr>,
pub loc_expr: Loc<Expr>,
pub output: Output,
pub problems: Vec<Problem>,
pub home: ModuleId,

View file

@ -17,7 +17,7 @@ mod test_can {
use roc_can::expr::Expr::{self, *};
use roc_can::expr::{ClosureData, Recursive};
use roc_problem::can::{CycleEntry, FloatErrorKind, IntErrorKind, Problem, RuntimeError};
use roc_region::all::Region;
use roc_region::all::{Position, Region};
use std::{f64, i64};
fn assert_can(input: &str, expected: Expr) {
@ -368,9 +368,11 @@ mod test_can {
let arena = Bump::new();
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
assert_eq!(problems.len(), 1);
assert_eq!(problems.len(), 2);
assert!(problems.iter().all(|problem| match problem {
Problem::RuntimeError(RuntimeError::Shadowing { .. }) => true,
// Due to one of the shadows
Problem::UnusedDef(..) => true,
_ => false,
}));
}
@ -389,9 +391,11 @@ mod test_can {
let arena = Bump::new();
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
assert_eq!(problems.len(), 1);
assert_eq!(problems.len(), 2);
assert!(problems.iter().all(|problem| match problem {
Problem::RuntimeError(RuntimeError::Shadowing { .. }) => true,
// Due to one of the shadows
Problem::UnusedDef(..) => true,
_ => false,
}));
}
@ -410,10 +414,12 @@ mod test_can {
let arena = Bump::new();
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
assert_eq!(problems.len(), 1);
assert_eq!(problems.len(), 2);
println!("{:#?}", problems);
assert!(problems.iter().all(|problem| match problem {
Problem::RuntimeError(RuntimeError::Shadowing { .. }) => true,
// Due to one of the shadows
Problem::UnusedDef(..) => true,
_ => false,
}));
}
@ -524,7 +530,7 @@ mod test_can {
fn annotation_followed_with_unrelated_affectation() {
let src = indoc!(
r#"
F : Str
F : Str
x = 1
@ -545,10 +551,10 @@ mod test_can {
fn two_annotations_followed_with_unrelated_affectation() {
let src = indoc!(
r#"
G : Str
G : Str
F : {}
F : {}
x = 1
x
@ -629,7 +635,7 @@ mod test_can {
fn incorrect_optional_value() {
let src = indoc!(
r#"
{ x ? 42 }
{ x ? 42 }
"#
);
let arena = Bump::new();
@ -943,8 +949,8 @@ mod test_can {
let problem = Problem::RuntimeError(RuntimeError::CircularDef(vec![CycleEntry {
symbol: interns.symbol(home, "x".into()),
symbol_region: Region::new(0, 0, 0, 1),
expr_region: Region::new(0, 0, 4, 5),
symbol_region: Region::new(Position::new(0), Position::new(1)),
expr_region: Region::new(Position::new(4), Position::new(5)),
}]));
assert_eq!(is_circular_def, true);
@ -974,18 +980,18 @@ mod test_can {
let problem = Problem::RuntimeError(RuntimeError::CircularDef(vec![
CycleEntry {
symbol: interns.symbol(home, "x".into()),
symbol_region: Region::new(0, 0, 0, 1),
expr_region: Region::new(0, 0, 4, 5),
symbol_region: Region::new(Position::new(0), Position::new(1)),
expr_region: Region::new(Position::new(4), Position::new(5)),
},
CycleEntry {
symbol: interns.symbol(home, "y".into()),
symbol_region: Region::new(1, 1, 0, 1),
expr_region: Region::new(1, 1, 4, 5),
symbol_region: Region::new(Position::new(6), Position::new(7)),
expr_region: Region::new(Position::new(10), Position::new(11)),
},
CycleEntry {
symbol: interns.symbol(home, "z".into()),
symbol_region: Region::new(2, 2, 0, 1),
expr_region: Region::new(2, 2, 4, 5),
symbol_region: Region::new(Position::new(12), Position::new(13)),
expr_region: Region::new(Position::new(16), Position::new(17)),
},
]));
@ -1004,7 +1010,7 @@ mod test_can {
let src = indoc!(
r#"
x = Dict.empty
Dict.len x
"#
);

View file

@ -12,7 +12,7 @@ use roc_can::pattern::Pattern;
use roc_collections::all::{ImMap, Index, MutSet, SendMap};
use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::{Located, Region};
use roc_region::all::{Loc, Region};
use roc_types::subs::Variable;
use roc_types::types::Type::{self, *};
use roc_types::types::{AnnotationSource, Category, PReason, Reason, RecordField};
@ -22,7 +22,7 @@ use roc_types::types::{AnnotationSource, Category, PReason, Reason, RecordField}
pub struct Info {
pub vars: Vec<Variable>,
pub constraints: Vec<Constraint>,
pub def_types: SendMap<Symbol, Located<Type>>,
pub def_types: SendMap<Symbol, Loc<Type>>,
}
impl Info {
@ -56,7 +56,7 @@ pub struct Env {
fn constrain_untyped_args(
env: &Env,
arguments: &[(Variable, Located<Pattern>)],
arguments: &[(Variable, Loc<Pattern>)],
closure_type: Type,
return_type: Type,
) -> (Vec<Variable>, PatternState, Type) {
@ -77,6 +77,7 @@ fn constrain_untyped_args(
loc_pattern.region,
pattern_expected,
&mut pattern_state,
true,
);
vars.push(*pattern_var);
@ -1039,6 +1040,7 @@ fn constrain_when_branch(
loc_pattern.region,
pattern_expected.clone(),
&mut state,
true,
);
}
@ -1079,7 +1081,7 @@ fn constrain_when_branch(
}
}
fn constrain_field(env: &Env, field_var: Variable, loc_expr: &Located<Expr>) -> (Type, Constraint) {
fn constrain_field(env: &Env, field_var: Variable, loc_expr: &Loc<Expr>) -> (Type, Constraint) {
let field_type = Variable(field_var);
let field_expected = NoExpectation(field_type.clone());
let constraint = constrain_expr(env, loc_expr.region, &loc_expr.value, field_expected);
@ -1127,11 +1129,7 @@ pub fn constrain_decls(home: ModuleId, decls: &[Declaration]) -> Constraint {
constraint
}
fn constrain_def_pattern(
env: &Env,
loc_pattern: &Located<Pattern>,
expr_type: Type,
) -> PatternState {
fn constrain_def_pattern(env: &Env, loc_pattern: &Loc<Pattern>, expr_type: Type) -> PatternState {
let pattern_expected = PExpected::NoExpectation(expr_type);
let mut state = PatternState {
@ -1146,6 +1144,7 @@ fn constrain_def_pattern(
loc_pattern.region,
pattern_expected,
&mut state,
true,
);
state
@ -1266,6 +1265,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
loc_pattern.region,
pattern_expected,
&mut state,
false,
);
}
@ -1448,8 +1448,8 @@ fn instantiate_rigids(
introduced_vars: &IntroducedVariables,
new_rigids: &mut Vec<Variable>,
ftv: &mut ImMap<Lowercase, Variable>, // rigids defined before the current annotation
loc_pattern: &Located<Pattern>,
headers: &mut SendMap<Symbol, Located<Type>>,
loc_pattern: &Loc<Pattern>,
headers: &mut SendMap<Symbol, Loc<Type>>,
) -> Type {
let mut annotation = annotation.clone();
let mut rigid_substitution: ImMap<Variable, Type> = ImMap::default();
@ -1472,7 +1472,7 @@ fn instantiate_rigids(
if let Some(new_headers) = crate::pattern::headers_from_annotation(
&loc_pattern.value,
&Located::at(loc_pattern.region, annotation.clone()),
&Loc::at(loc_pattern.region, annotation.clone()),
) {
for (symbol, loc_type) in new_headers {
for var in loc_type.value.variables() {
@ -1633,6 +1633,7 @@ pub fn rec_defs_help(
loc_pattern.region,
pattern_expected,
&mut state,
false,
);
}
@ -1769,7 +1770,7 @@ fn constrain_field_update(
var: Variable,
region: Region,
field: Lowercase,
loc_expr: &Located<Expr>,
loc_expr: &Loc<Expr>,
) -> (Variable, Type, Constraint) {
let field_type = Type::Variable(var);
let reason = Reason::RecordUpdateValue(field);

View file

@ -4,7 +4,7 @@ use roc_can::constraint::{Constraint, LetConstraint};
use roc_can::def::Declaration;
use roc_collections::all::{MutMap, MutSet, SendMap};
use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::{Located, Region};
use roc_region::all::{Loc, Region};
use roc_types::solved_types::{FreeVars, SolvedType};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::{Alias, Problem};
@ -28,7 +28,7 @@ pub fn constrain_module(declarations: &[Declaration], home: ModuleId) -> Constra
#[derive(Debug, Clone)]
pub struct Import {
pub loc_symbol: Located<Symbol>,
pub loc_symbol: Loc<Symbol>,
pub solved_type: SolvedType,
}
@ -59,7 +59,7 @@ pub fn constrain_imported_values(
def_types.insert(
loc_symbol.value,
Located {
Loc {
region: loc_symbol.region,
value: typ,
},
@ -149,7 +149,7 @@ pub fn pre_constrain_imports(
// hardcoded builtin map.
match stdlib.types.get(&symbol) {
Some((solved_type, region)) => {
let loc_symbol = Located {
let loc_symbol = Loc {
value: symbol,
region: *region,
};
@ -175,7 +175,7 @@ pub fn pre_constrain_imports(
} else if module_id != home {
// We already have constraints for our own symbols.
let region = Region::zero(); // TODO this should be the region where this symbol was declared in its home module. Look that up!
let loc_symbol = Located {
let loc_symbol = Loc {
value: symbol,
region,
};

View file

@ -1,19 +1,19 @@
use crate::builtins;
use crate::expr::{constrain_expr, Env};
use roc_can::constraint::Constraint;
use roc_can::constraint::{Constraint, PresenceConstraint};
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_module::ident::Lowercase;
use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region};
use roc_region::all::{Loc, Region};
use roc_types::subs::Variable;
use roc_types::types::{Category, PReason, PatternCategory, Reason, RecordField, Type};
#[derive(Default)]
pub struct PatternState {
pub headers: SendMap<Symbol, Located<Type>>,
pub headers: SendMap<Symbol, Loc<Type>>,
pub vars: Vec<Variable>,
pub constraints: Vec<Constraint>,
}
@ -27,8 +27,8 @@ pub struct PatternState {
/// definition has an annotation, we instead now add `x => Int`.
pub fn headers_from_annotation(
pattern: &Pattern,
annotation: &Located<Type>,
) -> Option<SendMap<Symbol, Located<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`
// in such incorrect cases we don't put the full annotation in headers, just a variable, and let
@ -44,16 +44,15 @@ pub fn headers_from_annotation(
fn headers_from_annotation_help(
pattern: &Pattern,
annotation: &Located<Type>,
headers: &mut SendMap<Symbol, Located<Type>>,
annotation: &Loc<Type>,
headers: &mut SendMap<Symbol, Loc<Type>>,
) -> bool {
match pattern {
Identifier(symbol) => {
Identifier(symbol) | Shadowed(_, _, symbol) => {
headers.insert(*symbol, annotation.clone());
true
}
Underscore
| Shadowed(_, _)
| MalformedPattern(_, _)
| UnsupportedPattern(_)
| NumLiteral(_, _, _)
@ -76,7 +75,7 @@ fn headers_from_annotation_help(
if let Some(field_type) = fields.get(&destruct.label) {
headers.insert(
destruct.symbol,
Located::at(annotation.region, field_type.clone().into_inner()),
Loc::at(annotation.region, field_type.clone().into_inner()),
);
} else {
return false;
@ -105,7 +104,7 @@ fn headers_from_annotation_help(
.all(|(arg_pattern, arg_type)| {
headers_from_annotation_help(
&arg_pattern.1.value,
&Located::at(annotation.region, arg_type.clone()),
&Loc::at(annotation.region, arg_type.clone()),
headers,
)
})
@ -118,6 +117,23 @@ fn headers_from_annotation_help(
}
}
fn make_pattern_constraint(
region: Region,
category: PatternCategory,
actual: Type,
expected: PExpected<Type>,
presence_con: bool,
) -> Constraint {
if presence_con {
Constraint::Present(
actual,
PresenceConstraint::Pattern(region, category, expected),
)
} else {
Constraint::Pattern(region, category, actual, expected)
}
}
/// This accepts PatternState (rather than returning it) so that the caller can
/// initialize the Vecs in PatternState using with_capacity
/// based on its knowledge of their lengths.
@ -127,16 +143,35 @@ pub fn constrain_pattern(
region: Region,
expected: PExpected<Type>,
state: &mut PatternState,
destruct_position: bool,
) {
match pattern {
Underscore | UnsupportedPattern(_) | MalformedPattern(_, _) | Shadowed(_, _) => {
Underscore if destruct_position => {
// 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,
));
}
Underscore | UnsupportedPattern(_) | MalformedPattern(_, _) => {
// Neither the _ pattern nor erroneous ones add any constraints.
}
Identifier(symbol) => {
Identifier(symbol) | Shadowed(_, _, symbol) => {
if destruct_position {
state.constraints.push(Constraint::Present(
expected.get_type_ref().clone(),
PresenceConstraint::IsOpen,
));
}
state.headers.insert(
*symbol,
Located {
Loc {
region,
value: expected.get_type(),
},
@ -192,7 +227,7 @@ pub fn constrain_pattern(
let mut field_types: SendMap<Lowercase, RecordField<Type>> = SendMap::default();
for Located {
for Loc {
value:
RecordDestruct {
var,
@ -209,12 +244,12 @@ pub fn constrain_pattern(
if !state.headers.contains_key(symbol) {
state
.headers
.insert(*symbol, Located::at(region, pat_type.clone()));
.insert(*symbol, Loc::at(region, pat_type.clone()));
}
let field_type = match typ {
DestructType::Guard(guard_var, loc_guard) => {
state.constraints.push(Constraint::Pattern(
state.constraints.push(make_pattern_constraint(
region,
PatternCategory::PatternGuard,
Type::Variable(*guard_var),
@ -223,15 +258,23 @@ pub fn constrain_pattern(
pat_type.clone(),
loc_guard.region,
),
destruct_position,
));
state.vars.push(*guard_var);
constrain_pattern(env, &loc_guard.value, loc_guard.region, expected, state);
constrain_pattern(
env,
&loc_guard.value,
loc_guard.region,
expected,
state,
destruct_position,
);
RecordField::Demanded(pat_type)
}
DestructType::Optional(expr_var, loc_expr) => {
state.constraints.push(Constraint::Pattern(
state.constraints.push(make_pattern_constraint(
region,
PatternCategory::PatternDefault,
Type::Variable(*expr_var),
@ -240,6 +283,7 @@ pub fn constrain_pattern(
pat_type.clone(),
loc_expr.region,
),
destruct_position,
));
state.vars.push(*expr_var);
@ -276,11 +320,12 @@ pub fn constrain_pattern(
region,
);
let record_con = Constraint::Pattern(
let record_con = make_pattern_constraint(
region,
PatternCategory::Record,
Type::Variable(*whole_var),
expected,
destruct_position,
);
state.constraints.push(whole_con);
@ -307,24 +352,39 @@ pub fn constrain_pattern(
pattern_type,
region,
);
constrain_pattern(env, &loc_pattern.value, loc_pattern.region, expected, state);
constrain_pattern(
env,
&loc_pattern.value,
loc_pattern.region,
expected,
state,
destruct_position,
);
}
let whole_con = 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 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 tag_con = Constraint::Pattern(
let tag_con = make_pattern_constraint(
region,
PatternCategory::Ctor(tag_name.clone()),
Type::Variable(*whole_var),
expected,
destruct_position,
);
state.vars.push(*whole_var);

View file

@ -16,3 +16,4 @@ bumpalo = { version = "3.8.0", features = ["collections"] }
pretty_assertions = "1.0.0"
indoc = "1.0.3"
roc_test_utils = { path = "../../test_utils" }
walkdir = "2.3.2"

View file

@ -3,8 +3,9 @@ use crate::{
spaces::{fmt_comments_only, fmt_spaces, NewlineAt, INDENT},
Buf,
};
use roc_parse::ast::{AssignedField, Expr, Tag, TypeAnnotation};
use roc_region::all::Located;
use roc_parse::ast::{AliasHeader, AssignedField, Expr, Tag, TypeAnnotation};
use roc_parse::ident::UppercaseIdent;
use roc_region::all::Loc;
/// Does an AST node need parens around it?
///
@ -40,12 +41,12 @@ pub enum Newlines {
No,
}
pub trait Formattable<'a> {
pub trait Formattable {
fn is_multiline(&self) -> bool;
fn format_with_options(
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'a>,
buf: &mut Buf<'buf>,
_parens: Parens,
_newlines: Newlines,
indent: u16,
@ -53,23 +54,23 @@ pub trait Formattable<'a> {
self.format(buf, indent);
}
fn format(&self, buf: &mut Buf<'a>, indent: u16) {
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
self.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
}
}
/// A reference to a formattable value is also formattable
impl<'a, T> Formattable<'a> for &'a T
impl<'a, T> Formattable for &'a T
where
T: Formattable<'a>,
T: Formattable,
{
fn is_multiline(&self) -> bool {
(*self).is_multiline()
}
fn format_with_options(
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'a>,
buf: &mut Buf<'buf>,
parens: Parens,
newlines: Newlines,
indent: u16,
@ -77,23 +78,23 @@ where
(*self).format_with_options(buf, parens, newlines, indent)
}
fn format(&self, buf: &mut Buf<'a>, indent: u16) {
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
(*self).format(buf, indent)
}
}
/// A Located formattable value is also formattable
impl<'a, T> Formattable<'a> for Located<T>
impl<T> Formattable for Loc<T>
where
T: Formattable<'a>,
T: Formattable,
{
fn is_multiline(&self) -> bool {
self.value.is_multiline()
}
fn format_with_options(
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'a>,
buf: &mut Buf<'buf>,
parens: Parens,
newlines: Newlines,
indent: u16,
@ -102,12 +103,28 @@ where
.format_with_options(buf, parens, newlines, indent)
}
fn format(&self, buf: &mut Buf<'a>, indent: u16) {
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
self.value.format(buf, indent)
}
}
impl<'a> Formattable<'a> for TypeAnnotation<'a> {
impl<'a> Formattable for UppercaseIdent<'a> {
fn is_multiline(&self) -> bool {
false
}
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'buf>,
_parens: Parens,
_newlines: Newlines,
_indent: u16,
) {
buf.push_str((*self).into())
}
}
impl<'a> Formattable for TypeAnnotation<'a> {
fn is_multiline(&self) -> bool {
use roc_parse::ast::TypeAnnotation::*;
@ -126,7 +143,7 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
|| args.iter().any(|loc_arg| (&loc_arg.value).is_multiline())
}
Apply(_, _, args) => args.iter().any(|loc_arg| loc_arg.value.is_multiline()),
As(lhs, _, rhs) => lhs.value.is_multiline() || rhs.value.is_multiline(),
As(lhs, _, _) => lhs.value.is_multiline(),
Record { fields, ext } => {
match ext {
@ -148,9 +165,9 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
}
}
fn format_with_options(
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'a>,
buf: &mut Buf<'buf>,
parens: Parens,
newlines: Newlines,
indent: u16,
@ -176,11 +193,13 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
);
if it.peek().is_some() {
buf.push_str(", ");
buf.push_str(",");
buf.spaces(1);
}
}
buf.push_str(" -> ");
buf.push_str(" ->");
buf.spaces(1);
(&result.value).format_with_options(
buf,
@ -210,7 +229,7 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
buf.push_str(name);
for argument in *arguments {
buf.push(' ');
buf.spaces(1);
(&argument.value).format_with_options(
buf,
Parens::InApply,
@ -243,11 +262,19 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
}
}
As(lhs, _spaces, rhs) => {
// TODO use spaces?
lhs.value.format(buf, indent);
buf.push_str(" as ");
rhs.value.format(buf, indent);
As(lhs, _spaces, AliasHeader { name, vars }) => {
// TODO use _spaces?
lhs.value
.format_with_options(buf, Parens::InFunctionType, Newlines::No, indent);
buf.spaces(1);
buf.push_str("as");
buf.spaces(1);
buf.push_str(name.value);
for var in *vars {
buf.spaces(1);
var.value
.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
}
}
SpaceBefore(ann, spaces) => {
@ -275,41 +302,41 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
/// > term: { x: 100, y: True }
///
/// So we need two instances, each having the specific separator
impl<'a> Formattable<'a> for AssignedField<'a, TypeAnnotation<'a>> {
impl<'a> Formattable for AssignedField<'a, TypeAnnotation<'a>> {
fn is_multiline(&self) -> bool {
is_multiline_assigned_field_help(self)
}
fn format_with_options(
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'a>,
buf: &mut Buf<'buf>,
parens: Parens,
newlines: Newlines,
indent: u16,
) {
// we abuse the `Newlines` type to decide between multiline or single-line layout
format_assigned_field_help(self, buf, parens, indent, " ", newlines == Newlines::Yes);
format_assigned_field_help(self, buf, parens, indent, 1, newlines == Newlines::Yes);
}
}
impl<'a> Formattable<'a> for AssignedField<'a, Expr<'a>> {
impl<'a> Formattable for AssignedField<'a, Expr<'a>> {
fn is_multiline(&self) -> bool {
is_multiline_assigned_field_help(self)
}
fn format_with_options(
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'a>,
buf: &mut Buf<'buf>,
parens: Parens,
newlines: Newlines,
indent: u16,
) {
// we abuse the `Newlines` type to decide between multiline or single-line layout
format_assigned_field_help(self, buf, parens, indent, "", newlines == Newlines::Yes);
format_assigned_field_help(self, buf, parens, indent, 0, newlines == Newlines::Yes);
}
}
fn is_multiline_assigned_field_help<'a, T: Formattable<'a>>(afield: &AssignedField<'a, T>) -> bool {
fn is_multiline_assigned_field_help<T: Formattable>(afield: &AssignedField<'_, T>) -> bool {
use self::AssignedField::*;
match afield {
@ -322,15 +349,15 @@ fn is_multiline_assigned_field_help<'a, T: Formattable<'a>>(afield: &AssignedFie
}
}
fn format_assigned_field_help<'a, T>(
fn format_assigned_field_help<'a, 'buf, T>(
zelf: &AssignedField<'a, T>,
buf: &mut Buf<'a>,
buf: &mut Buf<'buf>,
parens: Parens,
indent: u16,
separator_prefix: &str,
separator_spaces: usize,
is_multiline: bool,
) where
T: Formattable<'a>,
T: Formattable,
{
use self::AssignedField::*;
@ -347,8 +374,9 @@ fn format_assigned_field_help<'a, T>(
fmt_spaces(buf, spaces.iter(), indent);
}
buf.push_str(separator_prefix);
buf.push_str(": ");
buf.spaces(separator_spaces);
buf.push_str(":");
buf.spaces(1);
ann.value.format(buf, indent);
}
OptionalValue(name, spaces, ann) => {
@ -363,7 +391,7 @@ fn format_assigned_field_help<'a, T>(
fmt_spaces(buf, spaces.iter(), indent);
}
buf.push_str(separator_prefix);
buf.spaces(separator_spaces);
buf.push('?');
ann.value.format(buf, indent);
}
@ -382,7 +410,7 @@ fn format_assigned_field_help<'a, T>(
buf,
parens,
indent,
separator_prefix,
separator_spaces,
is_multiline,
);
}
@ -392,7 +420,7 @@ fn format_assigned_field_help<'a, T>(
buf,
parens,
indent,
separator_prefix,
separator_spaces,
is_multiline,
);
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
@ -403,7 +431,7 @@ fn format_assigned_field_help<'a, T>(
}
}
impl<'a> Formattable<'a> for Tag<'a> {
impl<'a> Formattable for Tag<'a> {
fn is_multiline(&self) -> bool {
use self::Tag::*;
@ -416,9 +444,9 @@ impl<'a> Formattable<'a> for Tag<'a> {
}
}
fn format_with_options(
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'a>,
buf: &mut Buf<'buf>,
_parens: Parens,
_newlines: Newlines,
indent: u16,
@ -438,7 +466,7 @@ impl<'a> Formattable<'a> for Tag<'a> {
}
} else {
for arg in *args {
buf.push(' ');
buf.spaces(1);
arg.format_with_options(buf, Parens::InApply, Newlines::No, indent);
}
}
@ -456,7 +484,7 @@ impl<'a> Formattable<'a> for Tag<'a> {
}
} else {
for arg in *args {
buf.push(' ');
buf.spaces(1);
arg.format_with_options(buf, Parens::InApply, Newlines::No, indent);
}
}

View file

@ -6,15 +6,15 @@ use crate::{
Buf,
};
pub fn fmt_collection<'a, 'b, T: ExtractSpaces<'a> + Formattable<'b>>(
buf: &mut Buf<'b>,
pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
buf: &mut Buf<'buf>,
indent: u16,
start: char,
end: char,
items: Collection<'a, T>,
newline: Newlines,
) where
<T as ExtractSpaces<'a>>::Item: Formattable<'b>,
<T as ExtractSpaces<'a>>::Item: Formattable,
{
buf.indent(indent);
let is_multiline =
@ -52,14 +52,13 @@ pub fn fmt_collection<'a, 'b, T: ExtractSpaces<'a> + Formattable<'b>>(
item_indent,
);
buf.newline();
buf.indent(braces_indent);
} else {
// is_multiline == false
// there is no comment to add
buf.push(start);
let mut iter = items.iter().peekable();
while let Some(item) = iter.next() {
buf.push(' ');
buf.spaces(1);
item.format(buf, indent);
if iter.peek().is_some() {
buf.push(',');
@ -67,8 +66,9 @@ pub fn fmt_collection<'a, 'b, T: ExtractSpaces<'a> + Formattable<'b>>(
}
if !items.is_empty() {
buf.push(' ');
buf.spaces(1);
}
}
buf.indent(indent);
buf.push(end);
}

View file

@ -2,11 +2,11 @@ use crate::annotation::{Formattable, Newlines, Parens};
use crate::pattern::fmt_pattern;
use crate::spaces::{fmt_spaces, INDENT};
use crate::Buf;
use roc_parse::ast::{Def, Expr, Pattern};
use roc_region::all::Located;
use roc_parse::ast::{AliasHeader, Def, Expr, Pattern};
use roc_region::all::Loc;
/// A Located formattable value is also formattable
impl<'a> Formattable<'a> for Def<'a> {
impl<'a> Formattable for Def<'a> {
fn is_multiline(&self) -> bool {
use roc_parse::ast::Def::*;
@ -25,9 +25,9 @@ impl<'a> Formattable<'a> for Def<'a> {
}
}
fn format_with_options(
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'a>,
buf: &mut Buf<'buf>,
_parens: Parens,
_newlines: Newlines,
indent: u16,
@ -55,20 +55,20 @@ impl<'a> Formattable<'a> for Def<'a> {
);
}
}
Alias { name, vars, ann } => {
Alias {
header: AliasHeader { name, vars },
ann,
} => {
buf.indent(indent);
buf.push_str(name.value);
if vars.is_empty() {
buf.push(' ');
} else {
for var in *vars {
buf.push(' ');
fmt_pattern(buf, &var.value, indent, Parens::NotNeeded);
}
for var in *vars {
buf.spaces(1);
fmt_pattern(buf, &var.value, indent, Parens::NotNeeded);
}
buf.push_str(" : ");
buf.push_str(" :");
buf.spaces(1);
ann.format(buf, indent + INDENT)
}
@ -84,10 +84,12 @@ impl<'a> Formattable<'a> for Def<'a> {
body_expr,
} => {
ann_pattern.format(buf, indent);
buf.push_str(" : ");
buf.push_str(" :");
buf.spaces(1);
ann_type.format(buf, indent);
if let Some(comment_str) = comment {
buf.push_str(" # ");
buf.push_str(" #");
buf.spaces(1);
buf.push_str(comment_str.trim());
}
buf.newline();
@ -107,9 +109,9 @@ impl<'a> Formattable<'a> for Def<'a> {
}
}
fn fmt_expect<'a>(
buf: &mut Buf<'a>,
condition: &'a Located<Expr<'a>>,
fn fmt_expect<'a, 'buf>(
buf: &mut Buf<'buf>,
condition: &'a Loc<Expr<'a>>,
is_multiline: bool,
indent: u16,
) {
@ -123,11 +125,16 @@ fn fmt_expect<'a>(
condition.format(buf, return_indent);
}
pub fn fmt_def<'a>(buf: &mut Buf<'a>, def: &Def<'a>, indent: u16) {
pub fn fmt_def<'a, 'buf>(buf: &mut Buf<'buf>, def: &Def<'a>, indent: u16) {
def.format(buf, indent);
}
pub fn fmt_body<'a>(buf: &mut Buf<'a>, pattern: &'a Pattern<'a>, body: &'a Expr<'a>, indent: u16) {
pub fn fmt_body<'a, 'buf>(
buf: &mut Buf<'buf>,
pattern: &'a Pattern<'a>,
body: &'a Expr<'a>,
indent: u16,
) {
pattern.format_with_options(buf, Parens::InApply, Newlines::No, indent);
buf.push_str(" =");
if body.is_multiline() {
@ -140,12 +147,12 @@ pub fn fmt_body<'a>(buf: &mut Buf<'a>, pattern: &'a Pattern<'a>, body: &'a Expr<
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT);
}
_ => {
buf.push(' ');
buf.spaces(1);
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
}
}
} else {
buf.push(' ');
buf.spaces(1);
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
}
}

View file

@ -6,12 +6,12 @@ use crate::spaces::{fmt_comments_only, fmt_spaces, NewlineAt, INDENT};
use crate::Buf;
use roc_module::called_via::{self, BinOp};
use roc_parse::ast::{
AssignedField, Base, Collection, CommentOrNewline, Expr, Pattern, WhenBranch,
AssignedField, Base, Collection, CommentOrNewline, Expr, ExtractSpaces, Pattern, WhenBranch,
};
use roc_parse::ast::{StrLiteral, StrSegment};
use roc_region::all::Located;
use roc_region::all::Loc;
impl<'a> Formattable<'a> for Expr<'a> {
impl<'a> Formattable for Expr<'a> {
fn is_multiline(&self) -> bool {
use roc_parse::ast::Expr::*;
// TODO cache these answers using a Map<Pointer, bool>, so
@ -106,9 +106,9 @@ impl<'a> Formattable<'a> for Expr<'a> {
}
}
fn format_with_options(
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'a>,
buf: &mut Buf<'buf>,
parens: Parens,
newlines: Newlines,
indent: u16,
@ -187,7 +187,7 @@ impl<'a> Formattable<'a> for Expr<'a> {
}
} else {
for loc_arg in loc_args.iter() {
buf.push(' ');
buf.spaces(1);
loc_arg.format_with_options(buf, Parens::InApply, Newlines::Yes, indent);
}
}
@ -289,12 +289,12 @@ impl<'a> Formattable<'a> for Expr<'a> {
}
}
fn format_str_segment<'a>(seg: &StrSegment<'a>, buf: &mut Buf<'a>, indent: u16) {
fn format_str_segment<'a, 'buf>(seg: &StrSegment<'a>, buf: &mut Buf<'buf>, indent: u16) {
use StrSegment::*;
match seg {
Plaintext(string) => {
buf.push_str(string);
buf.push_str_allow_spaces(string);
}
Unicode(loc_str) => {
buf.push_str("\\u(");
@ -344,14 +344,14 @@ fn push_op(buf: &mut Buf, op: BinOp) {
}
}
pub fn fmt_str_literal<'a>(buf: &mut Buf<'a>, literal: StrLiteral<'a>, indent: u16) {
pub fn fmt_str_literal<'buf>(buf: &mut Buf<'buf>, literal: StrLiteral, indent: u16) {
use roc_parse::ast::StrLiteral::*;
buf.indent(indent);
buf.push('"');
match literal {
PlainLine(string) => {
buf.push_str(string);
buf.push_str_allow_spaces(string);
}
Line(segments) => {
for seg in segments.iter() {
@ -395,10 +395,10 @@ pub fn fmt_str_literal<'a>(buf: &mut Buf<'a>, literal: StrLiteral<'a>, indent: u
buf.push('"');
}
fn fmt_bin_ops<'a>(
buf: &mut Buf<'a>,
lefts: &'a [(Located<Expr<'a>>, Located<BinOp>)],
loc_right_side: &'a Located<Expr<'a>>,
fn fmt_bin_ops<'a, 'buf>(
buf: &mut Buf<'buf>,
lefts: &'a [(Loc<Expr<'a>>, Loc<BinOp>)],
loc_right_side: &'a Loc<Expr<'a>>,
part_of_multi_line_bin_ops: bool,
apply_needs_parens: Parens,
indent: u16,
@ -416,12 +416,12 @@ fn fmt_bin_ops<'a>(
buf.newline();
buf.indent(indent + INDENT);
} else {
buf.push(' ');
buf.spaces(1);
}
push_op(buf, bin_op);
buf.push(' ');
buf.spaces(1);
}
loc_right_side.format_with_options(buf, apply_needs_parens, Newlines::Yes, indent);
@ -454,9 +454,9 @@ fn empty_line_before_expr<'a>(expr: &'a Expr<'a>) -> bool {
}
}
fn fmt_when<'a>(
buf: &mut Buf<'a>,
loc_condition: &'a Located<Expr<'a>>,
fn fmt_when<'a, 'buf>(
buf: &mut Buf<'buf>,
loc_condition: &'a Loc<Expr<'a>>,
branches: &[&'a WhenBranch<'a>],
indent: u16,
) {
@ -502,9 +502,9 @@ fn fmt_when<'a>(
}
buf.indent(indent);
} else {
buf.push(' ');
buf.spaces(1);
loc_condition.format(buf, indent);
buf.push(' ');
buf.spaces(1);
}
buf.push_str("is");
buf.newline();
@ -514,9 +514,15 @@ fn fmt_when<'a>(
let patterns = &branch.patterns;
let expr = &branch.value;
let (first_pattern, rest) = patterns.split_first().unwrap();
let is_multiline = match rest.last() {
None => false,
Some(last_pattern) => first_pattern.region.start_line != last_pattern.region.end_line,
let is_multiline = if let Some((last_pattern, inner_patterns)) = rest.split_last() {
!first_pattern.value.extract_spaces().after.is_empty()
|| !last_pattern.value.extract_spaces().before.is_empty()
|| inner_patterns.iter().any(|p| {
let spaces = p.value.extract_spaces();
!spaces.before.is_empty() || !spaces.after.is_empty()
})
} else {
false
};
fmt_pattern(
@ -530,12 +536,14 @@ fn fmt_when<'a>(
buf.newline();
buf.indent(indent + INDENT);
}
buf.push_str(" | ");
buf.push_str(" |");
buf.spaces(1);
fmt_pattern(buf, &when_pattern.value, indent + INDENT, Parens::NotNeeded);
}
if let Some(guard_expr) = &branch.guard {
buf.push_str(" if ");
buf.push_str(" if");
buf.spaces(1);
guard_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT);
}
@ -569,10 +577,10 @@ fn fmt_when<'a>(
}
}
fn fmt_expect<'a>(
buf: &mut Buf<'a>,
condition: &'a Located<Expr<'a>>,
continuation: &'a Located<Expr<'a>>,
fn fmt_expect<'a, 'buf>(
buf: &mut Buf<'buf>,
condition: &'a Loc<Expr<'a>>,
continuation: &'a Loc<Expr<'a>>,
is_multiline: bool,
indent: u16,
) {
@ -588,10 +596,10 @@ fn fmt_expect<'a>(
continuation.format(buf, return_indent);
}
fn fmt_if<'a>(
buf: &mut Buf<'a>,
branches: &'a [(Located<Expr<'a>>, Located<Expr<'a>>)],
final_else: &'a Located<Expr<'a>>,
fn fmt_if<'a, 'buf>(
buf: &mut Buf<'buf>,
branches: &'a [(Loc<Expr<'a>>, Loc<Expr<'a>>)],
final_else: &'a Loc<Expr<'a>>,
is_multiline: bool,
indent: u16,
) {
@ -612,7 +620,8 @@ fn fmt_if<'a>(
buf.indent(indent);
if i > 0 {
buf.push_str("else ");
buf.push_str("else");
buf.spaces(1);
}
buf.push_str("if");
@ -656,9 +665,9 @@ fn fmt_if<'a>(
}
buf.indent(indent);
} else {
buf.push(' ');
buf.spaces(1);
loc_condition.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
buf.push(' ');
buf.spaces(1);
}
buf.push_str("then");
@ -693,7 +702,8 @@ fn fmt_if<'a>(
}
}
} else {
buf.push_str(" ");
buf.push_str("");
buf.spaces(1);
loc_then.format(buf, return_indent);
}
}
@ -703,16 +713,17 @@ fn fmt_if<'a>(
buf.push_str("else");
buf.newline();
} else {
buf.push_str(" else ");
buf.push_str(" else");
buf.spaces(1);
}
final_else.format(buf, return_indent);
}
fn fmt_closure<'a>(
buf: &mut Buf<'a>,
loc_patterns: &'a [Located<Pattern<'a>>],
loc_ret: &'a Located<Expr<'a>>,
fn fmt_closure<'a, 'buf>(
buf: &mut Buf<'buf>,
loc_patterns: &'a [Loc<Pattern<'a>>],
loc_ret: &'a Loc<Expr<'a>>,
indent: u16,
) {
use self::Expr::*;
@ -742,7 +753,8 @@ fn fmt_closure<'a>(
buf.push(',');
buf.newline();
} else {
buf.push_str(", ");
buf.push_str(",");
buf.spaces(1);
}
}
}
@ -751,7 +763,7 @@ fn fmt_closure<'a>(
buf.newline();
buf.indent(indent);
} else {
buf.push(' ');
buf.spaces(1);
}
buf.push_str("->");
@ -775,18 +787,18 @@ fn fmt_closure<'a>(
}
_ => {
// add a space after the `->`
buf.push(' ');
buf.spaces(1);
}
};
loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, body_indent);
}
fn fmt_backpassing<'a>(
buf: &mut Buf<'a>,
loc_patterns: &'a [Located<Pattern<'a>>],
loc_body: &'a Located<Expr<'a>>,
loc_ret: &'a Located<Expr<'a>>,
fn fmt_backpassing<'a, 'buf>(
buf: &mut Buf<'buf>,
loc_patterns: &'a [Loc<Pattern<'a>>],
loc_body: &'a Loc<Expr<'a>>,
loc_ret: &'a Loc<Expr<'a>>,
indent: u16,
) {
use self::Expr::*;
@ -821,7 +833,8 @@ fn fmt_backpassing<'a>(
buf.push(',');
buf.newline();
} else {
buf.push_str(", ");
buf.push_str(",");
buf.spaces(1);
}
}
}
@ -834,7 +847,7 @@ fn fmt_backpassing<'a>(
buf.newline();
buf.indent(indent);
} else {
buf.push(' ');
buf.spaces(1);
}
buf.push_str("<-");
@ -858,7 +871,7 @@ fn fmt_backpassing<'a>(
}
_ => {
// add a space after the `<-`
buf.push(' ');
buf.spaces(1);
}
};
@ -876,10 +889,10 @@ fn pattern_needs_parens_when_backpassing(pat: &Pattern) -> bool {
}
}
fn fmt_record<'a>(
buf: &mut Buf<'a>,
update: Option<&'a Located<Expr<'a>>>,
fields: Collection<'a, Located<AssignedField<'a, Expr<'a>>>>,
fn fmt_record<'a, 'buf>(
buf: &mut Buf<'buf>,
update: Option<&'a Loc<Expr<'a>>>,
fields: Collection<'a, Loc<AssignedField<'a, Expr<'a>>>>,
indent: u16,
) {
let loc_fields = fields.items;
@ -897,7 +910,7 @@ fn fmt_record<'a>(
// it this far. For example "{ 4 & hello = 9 }"
// doesnt make sense.
Some(record_var) => {
buf.push(' ');
buf.spaces(1);
record_var.format(buf, indent);
buf.push_str(" &");
}
@ -923,17 +936,18 @@ fn fmt_record<'a>(
buf.newline();
} else {
// is_multiline == false
buf.push(' ');
buf.spaces(1);
let field_indent = indent;
let mut iter = loc_fields.iter().peekable();
while let Some(field) = iter.next() {
field.format_with_options(buf, Parens::NotNeeded, Newlines::No, field_indent);
if iter.peek().is_some() {
buf.push_str(", ");
buf.push_str(",");
buf.spaces(1);
}
}
buf.push(' ');
buf.spaces(1);
// if we are here, that means that `final_comments` is empty, thus we don't have
// to add a comment. Anyway, it is not possible to have a single line record with
// a comment in it.
@ -945,13 +959,13 @@ fn fmt_record<'a>(
}
}
fn format_field_multiline<'a, T>(
buf: &mut Buf<'a>,
fn format_field_multiline<'a, 'buf, T>(
buf: &mut Buf<'buf>,
field: &AssignedField<'a, T>,
indent: u16,
separator_prefix: &str,
) where
T: Formattable<'a>,
T: Formattable,
{
use self::AssignedField::*;
match field {
@ -965,7 +979,8 @@ fn format_field_multiline<'a, T>(
}
buf.push_str(separator_prefix);
buf.push_str(": ");
buf.push_str(":");
buf.spaces(1);
ann.value.format(buf, indent);
buf.push(',');
}
@ -979,7 +994,8 @@ fn format_field_multiline<'a, T>(
}
buf.push_str(separator_prefix);
buf.push_str("? ");
buf.push_str("?");
buf.spaces(1);
ann.value.format(buf, indent);
buf.push(',');
}

View file

@ -13,6 +13,7 @@ use bumpalo::{collections::String, Bump};
pub struct Buf<'a> {
text: String<'a>,
spaces_to_flush: usize,
beginning_of_line: bool,
}
@ -20,6 +21,7 @@ impl<'a> Buf<'a> {
pub fn new_in(arena: &'a Bump) -> Buf<'a> {
Buf {
text: String::new_in(arena),
spaces_to_flush: 0,
beginning_of_line: true,
}
}
@ -43,18 +45,49 @@ impl<'a> Buf<'a> {
pub fn push(&mut self, ch: char) {
debug_assert!(!self.beginning_of_line);
debug_assert!(ch != '\n');
debug_assert!(ch != '\n' && ch != ' ');
self.flush_spaces();
self.text.push(ch);
}
pub fn push_str_allow_spaces(&mut self, s: &str) {
debug_assert!(!self.beginning_of_line);
debug_assert!(!s.contains('\n'));
self.flush_spaces();
self.text.push_str(s);
}
pub fn push_str(&mut self, s: &str) {
debug_assert!(!self.beginning_of_line);
debug_assert!(!s.contains('\n'));
debug_assert!(!s.contains('\n') && !s.ends_with(' '));
if !s.is_empty() {
self.flush_spaces();
}
self.text.push_str(s);
}
pub fn spaces(&mut self, count: usize) {
self.spaces_to_flush += count;
}
pub fn newline(&mut self) {
self.spaces_to_flush = 0;
self.text.push('\n');
self.beginning_of_line = true;
}
fn flush_spaces(&mut self) {
if self.spaces_to_flush > 0 {
for _ in 0..self.spaces_to_flush {
self.text.push(' ');
}
self.spaces_to_flush = 0;
}
}
}

View file

@ -3,14 +3,15 @@ use crate::collection::fmt_collection;
use crate::expr::fmt_str_literal;
use crate::spaces::{fmt_default_spaces, fmt_spaces, INDENT};
use crate::Buf;
use roc_parse::ast::{Collection, Module};
use roc_parse::ast::{Collection, Module, Spaced};
use roc_parse::header::{
AppHeader, Effects, ExposesEntry, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
PackageName, PackageOrPath, PlatformHeader, PlatformRequires, PlatformRigid, To, TypedIdent,
AppHeader, Effects, ExposedName, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
PackageName, PlatformHeader, PlatformRequires, To, TypedIdent,
};
use roc_region::all::Located;
use roc_parse::ident::UppercaseIdent;
use roc_region::all::Loc;
pub fn fmt_module<'a>(buf: &mut Buf<'a>, module: &'a Module<'a>) {
pub fn fmt_module<'a, 'buf>(buf: &mut Buf<'buf>, module: &'a Module<'a>) {
match module {
Module::Interface { header } => {
fmt_interface_header(buf, header);
@ -24,350 +25,303 @@ pub fn fmt_module<'a>(buf: &mut Buf<'a>, module: &'a Module<'a>) {
}
}
pub fn fmt_interface_header<'a>(buf: &mut Buf<'a>, header: &'a InterfaceHeader<'a>) {
pub fn fmt_interface_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a InterfaceHeader<'a>) {
let indent = INDENT;
buf.indent(0);
buf.push_str("interface");
// module name
fmt_default_spaces(buf, header.after_interface_keyword, " ", indent);
fmt_default_spaces(buf, header.after_interface_keyword, indent);
buf.push_str(header.name.value.as_str());
// exposes
fmt_default_spaces(buf, header.before_exposes, " ", indent);
fmt_default_spaces(buf, header.before_exposes, indent);
buf.indent(indent);
buf.push_str("exposes");
fmt_default_spaces(buf, header.after_exposes, " ", indent);
fmt_default_spaces(buf, header.after_exposes, indent);
fmt_exposes(buf, header.exposes, indent);
// imports
fmt_default_spaces(buf, header.before_imports, " ", indent);
fmt_default_spaces(buf, header.before_imports, indent);
buf.indent(indent);
buf.push_str("imports");
fmt_default_spaces(buf, header.after_imports, " ", indent);
fmt_default_spaces(buf, header.after_imports, indent);
fmt_imports(buf, header.imports, indent);
}
pub fn fmt_app_header<'a>(buf: &mut Buf<'a>, header: &'a AppHeader<'a>) {
pub fn fmt_app_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a AppHeader<'a>) {
let indent = INDENT;
buf.indent(0);
buf.push_str("app");
fmt_default_spaces(buf, header.after_app_keyword, " ", indent);
fmt_default_spaces(buf, header.after_app_keyword, indent);
fmt_str_literal(buf, header.name.value, indent);
// packages
fmt_default_spaces(buf, header.before_packages, " ", indent);
fmt_default_spaces(buf, header.before_packages, indent);
buf.indent(indent);
buf.push_str("packages");
fmt_default_spaces(buf, header.after_packages, " ", indent);
fmt_default_spaces(buf, header.after_packages, indent);
fmt_packages(buf, header.packages, indent);
// imports
fmt_default_spaces(buf, header.before_imports, " ", indent);
fmt_default_spaces(buf, header.before_imports, indent);
buf.indent(indent);
buf.push_str("imports");
fmt_default_spaces(buf, header.after_imports, " ", indent);
fmt_default_spaces(buf, header.after_imports, indent);
fmt_imports(buf, header.imports, indent);
// provides
fmt_default_spaces(buf, header.before_provides, " ", indent);
fmt_default_spaces(buf, header.before_provides, indent);
buf.indent(indent);
buf.push_str("provides");
fmt_default_spaces(buf, header.after_provides, " ", indent);
fmt_provides(buf, header.provides, indent);
fmt_default_spaces(buf, header.before_to, " ", indent);
fmt_default_spaces(buf, header.after_provides, indent);
fmt_provides(buf, header.provides, header.provides_types, indent);
fmt_default_spaces(buf, header.before_to, indent);
buf.indent(indent);
buf.push_str("to");
fmt_default_spaces(buf, header.after_to, " ", indent);
fmt_default_spaces(buf, header.after_to, indent);
fmt_to(buf, header.to.value, indent);
}
pub fn fmt_platform_header<'a>(buf: &mut Buf<'a>, header: &'a PlatformHeader<'a>) {
pub fn fmt_platform_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a PlatformHeader<'a>) {
let indent = INDENT;
buf.indent(0);
buf.push_str("platform");
fmt_default_spaces(buf, header.after_platform_keyword, " ", indent);
fmt_package_name(buf, header.name.value);
fmt_default_spaces(buf, header.after_platform_keyword, indent);
fmt_package_name(buf, header.name.value, indent);
// requires
fmt_default_spaces(buf, header.before_requires, " ", indent);
fmt_default_spaces(buf, header.before_requires, indent);
buf.indent(indent);
buf.push_str("requires");
fmt_default_spaces(buf, header.after_requires, " ", indent);
fmt_default_spaces(buf, header.after_requires, indent);
fmt_requires(buf, &header.requires, indent);
// exposes
fmt_default_spaces(buf, header.before_exposes, " ", indent);
fmt_default_spaces(buf, header.before_exposes, indent);
buf.indent(indent);
buf.push_str("exposes");
fmt_default_spaces(buf, header.after_exposes, " ", indent);
fmt_default_spaces(buf, header.after_exposes, indent);
fmt_exposes(buf, header.exposes, indent);
// packages
fmt_default_spaces(buf, header.before_packages, " ", indent);
fmt_default_spaces(buf, header.before_packages, indent);
buf.indent(indent);
buf.push_str("packages");
fmt_default_spaces(buf, header.after_packages, " ", indent);
fmt_default_spaces(buf, header.after_packages, indent);
fmt_packages(buf, header.packages, indent);
// imports
fmt_default_spaces(buf, header.before_imports, " ", indent);
fmt_default_spaces(buf, header.before_imports, indent);
buf.indent(indent);
buf.push_str("imports");
fmt_default_spaces(buf, header.after_imports, " ", indent);
fmt_default_spaces(buf, header.after_imports, indent);
fmt_imports(buf, header.imports, indent);
// provides
fmt_default_spaces(buf, header.before_provides, " ", indent);
fmt_default_spaces(buf, header.before_provides, indent);
buf.indent(indent);
buf.push_str("provides");
fmt_default_spaces(buf, header.after_provides, " ", indent);
fmt_provides(buf, header.provides, indent);
fmt_default_spaces(buf, header.after_provides, indent);
fmt_provides(buf, header.provides, None, indent);
fmt_effects(buf, &header.effects, indent);
}
fn fmt_requires<'a>(buf: &mut Buf<'a>, requires: &PlatformRequires<'a>, indent: u16) {
fn fmt_requires<'a, 'buf>(buf: &mut Buf<'buf>, requires: &PlatformRequires<'a>, indent: u16) {
fmt_collection(buf, indent, '{', '}', requires.rigids, Newlines::No);
buf.push_str(" { ");
fmt_typed_ident(buf, &requires.signature.value, indent);
buf.push_str(" {");
buf.spaces(1);
requires.signature.value.format(buf, indent);
buf.push_str(" }");
}
fn fmt_effects<'a>(buf: &mut Buf<'a>, effects: &Effects<'a>, indent: u16) {
fmt_default_spaces(buf, effects.spaces_before_effects_keyword, " ", indent);
fn fmt_effects<'a, 'buf>(buf: &mut Buf<'buf>, effects: &Effects<'a>, indent: u16) {
fmt_default_spaces(buf, effects.spaces_before_effects_keyword, indent);
buf.indent(indent);
buf.push_str("effects");
fmt_default_spaces(buf, effects.spaces_after_effects_keyword, " ", indent);
fmt_default_spaces(buf, effects.spaces_after_effects_keyword, indent);
buf.indent(indent);
buf.push_str(effects.effect_shortname);
buf.push('.');
buf.push_str(effects.effect_type_name);
fmt_default_spaces(buf, effects.spaces_after_type_name, " ", indent);
fmt_default_spaces(buf, effects.spaces_after_type_name, indent);
fmt_collection(buf, indent, '{', '}', effects.entries, Newlines::No)
}
fn fmt_typed_ident<'a>(buf: &mut Buf<'a>, entry: &TypedIdent<'a>, indent: u16) {
use TypedIdent::*;
match entry {
Entry {
ident,
spaces_before_colon,
ann,
} => {
buf.indent(indent);
buf.push_str(ident.value);
fmt_default_spaces(buf, spaces_before_colon, " ", indent);
buf.push_str(": ");
ann.value.format(buf, indent);
}
SpaceBefore(sub_entry, spaces) => {
fmt_spaces(buf, spaces.iter(), indent);
fmt_typed_ident(buf, sub_entry, indent);
}
SpaceAfter(sub_entry, spaces) => {
fmt_typed_ident(buf, sub_entry, indent);
fmt_spaces(buf, spaces.iter(), indent);
}
}
}
impl<'a> Formattable<'a> for TypedIdent<'a> {
impl<'a> Formattable for TypedIdent<'a> {
fn is_multiline(&self) -> bool {
false
}
fn format(&self, buf: &mut Buf<'a>, indent: u16) {
fmt_typed_ident(buf, self, indent);
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
buf.indent(indent);
buf.push_str(self.ident.value);
fmt_default_spaces(buf, self.spaces_before_colon, indent);
buf.push_str(":");
buf.spaces(1);
self.ann.value.format(buf, indent);
}
}
impl<'a> Formattable<'a> for PlatformRigid<'a> {
fn fmt_package_name<'buf>(buf: &mut Buf<'buf>, name: PackageName, _indent: u16) {
buf.push('"');
buf.push_str_allow_spaces(name.0);
buf.push('"');
}
impl<'a, T: Formattable> Formattable for Spaced<'a, T> {
fn is_multiline(&self) -> bool {
// TODO
false
}
fn format(&self, buf: &mut Buf<'a>, indent: u16) {
fmt_platform_rigid(buf, self, indent);
}
}
fn fmt_package_name<'a>(buf: &mut Buf<'a>, name: PackageName) {
buf.push_str(name.account);
buf.push('/');
buf.push_str(name.pkg);
}
fn fmt_platform_rigid<'a>(buf: &mut Buf<'a>, entry: &PlatformRigid<'a>, indent: u16) {
use roc_parse::header::PlatformRigid::*;
match entry {
Entry { rigid, alias } => {
buf.push_str(rigid);
buf.push_str("=>");
buf.push_str(alias);
}
SpaceBefore(sub_entry, spaces) => {
fmt_spaces(buf, spaces.iter(), indent);
fmt_platform_rigid(buf, sub_entry, indent);
}
SpaceAfter(sub_entry, spaces) => {
fmt_platform_rigid(buf, sub_entry, indent);
fmt_spaces(buf, spaces.iter(), indent);
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'buf>,
parens: crate::annotation::Parens,
newlines: Newlines,
indent: u16,
) {
match self {
Spaced::Item(item) => {
item.format_with_options(buf, parens, newlines, indent);
}
Spaced::SpaceBefore(item, spaces) => {
fmt_spaces(buf, spaces.iter(), indent);
item.format_with_options(buf, parens, newlines, indent);
}
Spaced::SpaceAfter(item, spaces) => {
item.format_with_options(buf, parens, newlines, indent);
fmt_spaces(buf, spaces.iter(), indent);
}
}
}
}
fn fmt_imports<'a>(
buf: &mut Buf<'a>,
loc_entries: Collection<'a, Located<ImportsEntry<'a>>>,
fn fmt_imports<'a, 'buf>(
buf: &mut Buf<'buf>,
loc_entries: Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>,
indent: u16,
) {
fmt_collection(buf, indent, '[', ']', loc_entries, Newlines::No)
}
fn fmt_provides<'a>(
buf: &mut Buf<'a>,
loc_entries: Collection<'a, Located<ExposesEntry<'a, &'a str>>>,
fn fmt_provides<'a, 'buf>(
buf: &mut Buf<'buf>,
loc_exposed_names: Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
loc_provided_types: Option<Collection<'a, Loc<Spaced<'a, UppercaseIdent<'a>>>>>,
indent: u16,
) {
fmt_collection(buf, indent, '[', ']', loc_entries, Newlines::No)
fmt_collection(buf, indent, '[', ']', loc_exposed_names, Newlines::No);
if let Some(loc_provided_types) = loc_provided_types {
fmt_default_spaces(buf, &[], indent);
fmt_collection(buf, indent, '{', '}', loc_provided_types, Newlines::No);
}
}
fn fmt_to<'a>(buf: &mut Buf<'a>, to: To<'a>, indent: u16) {
fn fmt_to<'buf>(buf: &mut Buf<'buf>, to: To, indent: u16) {
match to {
To::ExistingPackage(name) => {
buf.push_str(name);
}
To::NewPackage(package_or_path) => fmt_package_or_path(buf, &package_or_path, indent),
To::NewPackage(package_name) => fmt_package_name(buf, package_name, indent),
}
}
fn fmt_exposes<'a, N: FormatName + Copy + 'a>(
buf: &mut Buf<'a>,
loc_entries: Collection<'_, Located<ExposesEntry<'_, N>>>,
fn fmt_exposes<'buf, N: Formattable + Copy>(
buf: &mut Buf<'buf>,
loc_entries: Collection<'_, Loc<Spaced<'_, N>>>,
indent: u16,
) {
fmt_collection(buf, indent, '[', ']', loc_entries, Newlines::No)
}
impl<'a, 'b, N: FormatName> Formattable<'a> for ExposesEntry<'b, N> {
fn is_multiline(&self) -> bool {
false
}
fn format(&self, buf: &mut Buf<'a>, indent: u16) {
fmt_exposes_entry(buf, self, indent);
}
}
pub trait FormatName {
fn format<'a>(&self, buf: &mut Buf<'a>);
fn format<'buf>(&self, buf: &mut Buf<'buf>);
}
impl<'a> FormatName for &'a str {
fn format<'b>(&self, buf: &mut Buf<'b>) {
fn format<'buf>(&self, buf: &mut Buf<'buf>) {
buf.push_str(self)
}
}
impl<'a> FormatName for ModuleName<'a> {
fn format<'b>(&self, buf: &mut Buf<'b>) {
fn format<'buf>(&self, buf: &mut Buf<'buf>) {
buf.push_str(self.as_str());
}
}
fn fmt_exposes_entry<'a, 'b, N: FormatName>(
buf: &mut Buf<'a>,
entry: &ExposesEntry<'b, N>,
indent: u16,
) {
use roc_parse::header::ExposesEntry::*;
impl<'a> Formattable for ModuleName<'a> {
fn is_multiline(&self) -> bool {
false
}
match entry {
Exposed(ident) => ident.format(buf),
SpaceBefore(sub_entry, spaces) => {
fmt_spaces(buf, spaces.iter(), indent);
fmt_exposes_entry(buf, sub_entry, indent);
}
SpaceAfter(sub_entry, spaces) => {
fmt_exposes_entry(buf, sub_entry, indent);
fmt_spaces(buf, spaces.iter(), indent);
}
fn format<'buf>(&self, buf: &mut Buf<'buf>, _indent: u16) {
buf.push_str(self.as_str());
}
}
fn fmt_packages<'a>(
buf: &mut Buf<'a>,
loc_entries: Collection<'a, Located<PackageEntry<'a>>>,
impl<'a> Formattable for ExposedName<'a> {
fn is_multiline(&self) -> bool {
false
}
fn format<'buf>(&self, buf: &mut Buf<'buf>, _indent: u16) {
buf.push_str(self.as_str());
}
}
impl<'a> FormatName for ExposedName<'a> {
fn format<'buf>(&self, buf: &mut Buf<'buf>) {
buf.push_str(self.as_str());
}
}
fn fmt_packages<'a, 'buf>(
buf: &mut Buf<'buf>,
loc_entries: Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>,
indent: u16,
) {
fmt_collection(buf, indent, '{', '}', loc_entries, Newlines::No)
}
impl<'a> Formattable<'a> for PackageEntry<'a> {
impl<'a> Formattable for PackageEntry<'a> {
fn is_multiline(&self) -> bool {
false
}
fn format(&self, buf: &mut Buf<'a>, indent: u16) {
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
fmt_packages_entry(buf, self, indent);
}
}
impl<'a> Formattable<'a> for ImportsEntry<'a> {
impl<'a> Formattable for ImportsEntry<'a> {
fn is_multiline(&self) -> bool {
false
}
fn format(&self, buf: &mut Buf<'a>, indent: u16) {
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
fmt_imports_entry(buf, self, indent);
}
}
fn fmt_packages_entry<'a>(buf: &mut Buf<'a>, entry: &PackageEntry<'a>, indent: u16) {
use PackageEntry::*;
match entry {
Entry {
shorthand,
spaces_after_shorthand,
package_or_path,
} => {
buf.push_str(shorthand);
buf.push(':');
fmt_default_spaces(buf, spaces_after_shorthand, " ", indent);
fmt_package_or_path(buf, &package_or_path.value, indent);
}
SpaceBefore(sub_entry, spaces) => {
fmt_spaces(buf, spaces.iter(), indent);
fmt_packages_entry(buf, sub_entry, indent);
}
SpaceAfter(sub_entry, spaces) => {
fmt_packages_entry(buf, sub_entry, indent);
fmt_spaces(buf, spaces.iter(), indent);
}
}
fn fmt_packages_entry<'a, 'buf>(buf: &mut Buf<'buf>, entry: &PackageEntry<'a>, indent: u16) {
buf.push_str(entry.shorthand);
buf.push(':');
fmt_default_spaces(buf, entry.spaces_after_shorthand, indent);
fmt_package_name(buf, entry.package_name.value, indent);
}
fn fmt_package_or_path<'a>(buf: &mut Buf<'a>, package_or_path: &PackageOrPath<'a>, indent: u16) {
match package_or_path {
PackageOrPath::Package(_name, _version) => {
todo!("format package");
}
PackageOrPath::Path(str_literal) => fmt_str_literal(buf, *str_literal, indent),
}
}
fn fmt_imports_entry<'a>(buf: &mut Buf<'a>, entry: &ImportsEntry<'a>, indent: u16) {
fn fmt_imports_entry<'a, 'buf>(buf: &mut Buf<'buf>, entry: &ImportsEntry<'a>, indent: u16) {
use roc_parse::header::ImportsEntry::*;
match entry {
@ -392,14 +346,5 @@ fn fmt_imports_entry<'a>(buf: &mut Buf<'a>, entry: &ImportsEntry<'a>, indent: u1
fmt_collection(buf, indent, '{', '}', *entries, Newlines::No)
}
}
SpaceBefore(sub_entry, spaces) => {
fmt_spaces(buf, spaces.iter(), indent);
fmt_imports_entry(buf, sub_entry, indent);
}
SpaceAfter(sub_entry, spaces) => {
fmt_imports_entry(buf, sub_entry, indent);
fmt_spaces(buf, spaces.iter(), indent);
}
}
}

View file

@ -3,11 +3,16 @@ use crate::spaces::{fmt_comments_only, fmt_spaces, NewlineAt};
use crate::Buf;
use roc_parse::ast::{Base, Pattern};
pub fn fmt_pattern<'a>(buf: &mut Buf<'a>, pattern: &'a Pattern<'a>, indent: u16, parens: Parens) {
pub fn fmt_pattern<'a, 'buf>(
buf: &mut Buf<'buf>,
pattern: &'a Pattern<'a>,
indent: u16,
parens: Parens,
) {
pattern.format_with_options(buf, parens, Newlines::No, indent);
}
impl<'a> Formattable<'a> for Pattern<'a> {
impl<'a> Formattable for Pattern<'a> {
fn is_multiline(&self) -> bool {
// Theory: a pattern should only be multiline when it contains a comment
match self {
@ -37,9 +42,9 @@ impl<'a> Formattable<'a> for Pattern<'a> {
}
}
fn format_with_options(
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'a>,
buf: &mut Buf<'buf>,
parens: Parens,
newlines: Newlines,
indent: u16,
@ -68,7 +73,7 @@ impl<'a> Formattable<'a> for Pattern<'a> {
loc_pattern.format_with_options(buf, Parens::InApply, Newlines::No, indent);
for loc_arg in loc_arg_patterns.iter() {
buf.push(' ');
buf.spaces(1);
loc_arg.format_with_options(buf, Parens::InApply, Newlines::No, indent);
}
@ -78,7 +83,8 @@ impl<'a> Formattable<'a> for Pattern<'a> {
}
RecordDestructure(loc_patterns) => {
buf.indent(indent);
buf.push_str("{ ");
buf.push_str("{");
buf.spaces(1);
let mut it = loc_patterns.iter().peekable();
@ -86,7 +92,8 @@ impl<'a> Formattable<'a> for Pattern<'a> {
loc_pattern.format(buf, indent);
if it.peek().is_some() {
buf.push_str(", ");
buf.push_str(",");
buf.spaces(1);
}
}
@ -96,14 +103,16 @@ impl<'a> Formattable<'a> for Pattern<'a> {
RequiredField(name, loc_pattern) => {
buf.indent(indent);
buf.push_str(name);
buf.push_str(": ");
buf.push_str(":");
buf.spaces(1);
loc_pattern.format(buf, indent);
}
OptionalField(name, loc_pattern) => {
buf.indent(indent);
buf.push_str(name);
buf.push_str(" ? ");
buf.push_str(" ?");
buf.spaces(1);
loc_pattern.format(buf, indent);
}

View file

@ -1,4 +1,3 @@
use bumpalo::collections::String;
use roc_parse::ast::CommentOrNewline;
use crate::Buf;
@ -6,34 +5,21 @@ use crate::Buf;
/// The number of spaces to indent.
pub const INDENT: u16 = 4;
pub fn newline(buf: &mut String<'_>, indent: u16) {
buf.push('\n');
add_spaces(buf, indent);
}
pub fn add_spaces(buf: &mut String<'_>, spaces: u16) {
for _ in 0..spaces {
buf.push(' ');
}
}
pub fn fmt_default_spaces<'a>(
buf: &mut Buf<'a>,
pub fn fmt_default_spaces<'a, 'buf>(
buf: &mut Buf<'buf>,
spaces: &[CommentOrNewline<'a>],
default: &str,
indent: u16,
) {
if spaces.is_empty() {
buf.push_str(default);
buf.spaces(1);
} else {
fmt_spaces(buf, spaces.iter(), indent);
}
}
pub fn fmt_spaces<'a, 'b, I>(buf: &mut Buf<'a>, spaces: I, indent: u16)
pub fn fmt_spaces<'a, 'buf, I>(buf: &mut Buf<'buf>, spaces: I, indent: u16)
where
I: Iterator<Item = &'b CommentOrNewline<'b>>,
I: Iterator<Item = &'a CommentOrNewline<'a>>,
{
use self::CommentOrNewline::*;
@ -84,13 +70,13 @@ pub enum NewlineAt {
/// The `new_line_at` argument describes how new lines should be inserted
/// at the beginning or at the end of the block
/// in the case of there is some comment in the `spaces` argument.
pub fn fmt_comments_only<'a, 'b, I>(
buf: &mut Buf<'a>,
pub fn fmt_comments_only<'a, 'buf, I>(
buf: &mut Buf<'buf>,
spaces: I,
new_line_at: NewlineAt,
indent: u16,
) where
I: Iterator<Item = &'b CommentOrNewline<'b>>,
I: Iterator<Item = &'a CommentOrNewline<'a>>,
{
use self::CommentOrNewline::*;
use NewlineAt::*;
@ -123,18 +109,18 @@ pub fn fmt_comments_only<'a, 'b, I>(
}
}
fn fmt_comment<'a>(buf: &mut Buf<'a>, comment: &str) {
fn fmt_comment<'buf>(buf: &mut Buf<'buf>, comment: &str) {
buf.push('#');
if !comment.starts_with(' ') {
buf.push(' ');
buf.spaces(1);
}
buf.push_str(comment);
buf.push_str(comment.trim_end());
}
fn fmt_docs<'a>(buf: &mut Buf<'a>, docs: &str) {
fn fmt_docs<'buf>(buf: &mut Buf<'buf>, docs: &str) {
buf.push_str("##");
if !docs.starts_with(' ') {
buf.push(' ');
buf.spaces(1);
}
buf.push_str(docs);
}

View file

@ -11,11 +11,12 @@ mod test_fmt {
use roc_fmt::module::fmt_module;
use roc_fmt::Buf;
use roc_parse::module::{self, module_defs};
use roc_parse::parser::{Parser, State};
use roc_parse::parser::Parser;
use roc_parse::state::State;
use roc_test_utils::assert_multiline_str_eq;
// Not intended to be used directly in tests; please use expr_formats_to or expr_formats_same
fn expect_format_helper(input: &str, expected: &str) {
fn expect_format_expr_helper(input: &str, expected: &str) {
let arena = Bump::new();
match roc_parse::test_helpers::parse_expr_with(&arena, input.trim()) {
Ok(actual) => {
@ -34,21 +35,20 @@ mod test_fmt {
let expected = expected.trim_end();
// First check that input formats to the expected version
expect_format_helper(input, expected);
expect_format_expr_helper(input, expected);
// Parse the expected result format it, asserting that it doesn't change
// It's important that formatting be stable / idempotent
expect_format_helper(expected, expected);
expect_format_expr_helper(expected, expected);
}
fn expr_formats_same(input: &str) {
expr_formats_to(input, input);
}
fn module_formats_to(src: &str, expected: &str) {
// Not intended to be used directly in tests; please use module_formats_to or module_formats_same
fn expect_format_module_helper(src: &str, expected: &str) {
let arena = Bump::new();
let src = src.trim_end();
match module::parse_header(&arena, State::new(src.as_bytes())) {
Ok((actual, state)) => {
let mut buf = Buf::new_in(&arena);
@ -63,13 +63,21 @@ mod test_fmt {
}
Err(error) => panic!("Unexpected parse failure when parsing this for defs formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, error)
}
assert_multiline_str_eq!(expected, buf.as_str())
}
Err(error) => panic!("Unexpected parse failure when parsing this for module header formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, error)
};
}
fn module_formats_to(input: &str, expected: &str) {
// First check that input formats to the expected version
expect_format_module_helper(input, expected);
// Parse the expected result format it, asserting that it doesn't change
// It's important that formatting be stable / idempotent
expect_format_module_helper(expected, expected);
}
fn module_formats_same(input: &str) {
module_formats_to(input, input);
}
@ -402,19 +410,19 @@ mod test_fmt {
indoc!(
r#"
x = (5)
y = ((10))
42
"#
),
indoc!(
r#"
x = 5
y = 10
42
"#
),
@ -758,9 +766,9 @@ mod test_fmt {
# comment 2
x: 42
# comment 3
# comment 4
}"#
),
@ -811,7 +819,7 @@ mod test_fmt {
f: { y : Int *,
x : Int * ,
}
f"#
),
indoc!(
@ -902,7 +910,7 @@ mod test_fmt {
{
# comment
}
f"#
),
);
@ -927,7 +935,7 @@ mod test_fmt {
indoc!(
r#"
f :
{
{
x: Int * # comment 1
,
# comment 2
@ -943,7 +951,7 @@ mod test_fmt {
# comment 1
# comment 2
}
f"#
),
);
@ -999,7 +1007,7 @@ mod test_fmt {
indoc!(
r#"
identity = \a
->
->
a + b
identity 4010
@ -1379,7 +1387,7 @@ mod test_fmt {
expr_formats_to(
indoc!(
r#"
{
{
}"#
),
"{}",
@ -2563,6 +2571,17 @@ mod test_fmt {
);
}
#[test]
fn pipline_op_with_apply() {
expr_formats_same(indoc!(
r#"
output
|> List.set (offset + 0) b
|> List.set (offset + 1) a
"#
));
}
// MODULES
#[test]
@ -2625,15 +2644,15 @@ mod test_fmt {
fn single_line_app() {
module_formats_same(indoc!(
r#"
app "Foo" packages { base: "platform" } imports [] provides [ main ] to base"#
app "Foo" packages { pf: "platform" } imports [] provides [ main ] to pf"#
));
}
#[test]
fn single_line_platform() {
module_formats_same(
"platform folkertdev/foo \
requires { model=>Model, msg=>Msg } { main : Effect {} } \
"platform \"folkertdev/foo\" \
requires { Model, Msg } { main : Effect {} } \
exposes [] \
packages {} \
imports [ Task.{ Task } ] \
@ -2706,7 +2725,7 @@ mod test_fmt {
fn multiline_tag_union_annotation_beginning_on_same_line() {
expr_formats_same(indoc!(
r#"
Expr : [
Expr : [
Add Expr Expr,
Mul Expr Expr,
Val I64,
@ -2744,7 +2763,7 @@ mod test_fmt {
# comment 2
# comment 3
]
b
"#
),
@ -2919,6 +2938,38 @@ mod test_fmt {
));
}
#[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.
/// Try this:
/// `cargo run -- format $(find examples -name \*.roc)`
fn test_fmt_examples() {
let mut count = 0;
let mut root = std::env::current_dir()
.unwrap()
.parent()
.unwrap()
.parent()
.unwrap()
.to_owned();
root.push("examples");
for entry in walkdir::WalkDir::new(&root) {
let entry = entry.unwrap();
let path = entry.path();
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());
module_formats_same(&src);
}
}
assert!(
count > 0,
"Expecting to find at least 1 .roc file to format under {}",
root.display()
);
}
// this is a parse error atm
// #[test]
// fn multiline_apply() {

View file

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

View file

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

View file

@ -2,17 +2,18 @@ use crate::{Backend, Env, Relocation};
use bumpalo::collections::Vec;
use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_collections::all::{MutMap, MutSet};
use roc_error_macros::internal_error;
use roc_module::symbol::{Interns, Symbol};
use roc_mono::gen_refcount::RefcountProcGenerator;
use roc_mono::code_gen_help::CodeGenHelp;
use roc_mono::ir::{BranchInfo, JoinPointId, Literal, Param, ProcLayout, SelfRecursive, Stmt};
use roc_mono::layout::{Builtin, Layout};
use roc_reporting::internal_error;
use roc_target::TargetInfo;
use std::marker::PhantomData;
pub mod aarch64;
pub mod x86_64;
const PTR_SIZE: u32 = 8;
const TARGET_INFO: TargetInfo = TargetInfo::default_x86_64();
pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait> {
const BASE_PTR_REG: GeneralReg;
@ -256,8 +257,8 @@ pub struct Backend64Bit<
phantom_cc: PhantomData<CC>,
env: &'a Env<'a>,
interns: &'a mut Interns,
refcount_proc_gen: RefcountProcGenerator<'a>,
refcount_proc_symbols: Vec<'a, (Symbol, ProcLayout<'a>)>,
helper_proc_gen: CodeGenHelp<'a>,
helper_proc_symbols: Vec<'a, (Symbol, ProcLayout<'a>)>,
buf: Vec<'a, u8>,
relocs: Vec<'a, Relocation>,
proc_name: Option<String>,
@ -308,8 +309,8 @@ pub fn new_backend_64bit<
phantom_cc: PhantomData,
env,
interns,
refcount_proc_gen: RefcountProcGenerator::new(env.arena, IntWidth::I64, env.module_id),
refcount_proc_symbols: bumpalo::vec![in env.arena],
helper_proc_gen: CodeGenHelp::new(env.arena, TARGET_INFO, env.module_id),
helper_proc_symbols: bumpalo::vec![in env.arena],
proc_name: None,
is_self_recursive: None,
buf: bumpalo::vec![in env.arena],
@ -346,19 +347,17 @@ impl<
fn interns(&self) -> &Interns {
self.interns
}
fn env_interns_refcount_mut(
&mut self,
) -> (&Env<'a>, &mut Interns, &mut RefcountProcGenerator<'a>) {
(self.env, self.interns, &mut self.refcount_proc_gen)
fn env_interns_helpers_mut(&mut self) -> (&Env<'a>, &mut Interns, &mut CodeGenHelp<'a>) {
(self.env, self.interns, &mut self.helper_proc_gen)
}
fn refcount_proc_gen_mut(&mut self) -> &mut RefcountProcGenerator<'a> {
&mut self.refcount_proc_gen
fn helper_proc_gen_mut(&mut self) -> &mut CodeGenHelp<'a> {
&mut self.helper_proc_gen
}
fn refcount_proc_symbols_mut(&mut self) -> &mut Vec<'a, (Symbol, ProcLayout<'a>)> {
&mut self.refcount_proc_symbols
fn helper_proc_symbols_mut(&mut self) -> &mut Vec<'a, (Symbol, ProcLayout<'a>)> {
&mut self.helper_proc_symbols
}
fn refcount_proc_symbols(&self) -> &Vec<'a, (Symbol, ProcLayout<'a>)> {
&self.refcount_proc_symbols
fn helper_proc_symbols(&self) -> &Vec<'a, (Symbol, ProcLayout<'a>)> {
&self.helper_proc_symbols
}
fn reset(&mut self, name: String, is_self_recursive: SelfRecursive) {
@ -383,7 +382,7 @@ impl<
self.float_used_regs.clear();
self.float_free_regs
.extend_from_slice(CC::FLOAT_DEFAULT_FREE_REGS);
self.refcount_proc_symbols.clear();
self.helper_proc_symbols.clear();
}
fn literal_map(&mut self) -> &mut MutMap<Symbol, (*const Literal<'a>, *const Layout<'a>)> {
@ -966,15 +965,17 @@ impl<
}
}
fn build_refcount_getptr(&mut self, dst: &Symbol, src: &Symbol) {
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.claim_general_reg(dst);
let src_reg = self.load_to_general_reg(src);
// The refcount pointer is the value before the pointer.
ASM::sub_reg64_reg64_imm32(&mut self.buf, dst_reg, src_reg, PTR_SIZE as i32);
ASM::mov_reg64_reg64(&mut self.buf, dst_reg, src_reg);
}
fn create_struct(&mut self, sym: &Symbol, layout: &Layout<'a>, fields: &'a [Symbol]) {
let struct_size = layout.stack_size(PTR_SIZE);
let struct_size = layout.stack_size(TARGET_INFO);
if let Layout::Struct(field_layouts) = layout {
if struct_size > 0 {
@ -991,7 +992,7 @@ impl<
let mut current_offset = offset;
for (field, field_layout) in fields.iter().zip(field_layouts.iter()) {
self.copy_symbol_to_stack_offset(current_offset, field, field_layout);
let field_size = field_layout.stack_size(PTR_SIZE);
let field_size = field_layout.stack_size(TARGET_INFO);
current_offset += field_size as i32;
}
} else {
@ -1029,14 +1030,14 @@ impl<
if let Some(SymbolStorage::Base { offset, .. }) = self.symbol_storage_map.get(structure) {
let mut data_offset = *offset;
for i in 0..index {
let field_size = field_layouts[i as usize].stack_size(PTR_SIZE);
let field_size = field_layouts[i as usize].stack_size(TARGET_INFO);
data_offset += field_size as i32;
}
self.symbol_storage_map.insert(
*sym,
SymbolStorage::Base {
offset: data_offset,
size: field_layouts[index as usize].stack_size(PTR_SIZE),
size: field_layouts[index as usize].stack_size(TARGET_INFO),
owned: false,
},
);
@ -1569,10 +1570,10 @@ impl<
{
debug_assert_eq!(
*size,
layout.stack_size(PTR_SIZE),
layout.stack_size(TARGET_INFO),
"expected struct to have same size as data being stored in it"
);
for i in 0..layout.stack_size(PTR_SIZE) as i32 {
for i in 0..layout.stack_size(TARGET_INFO) as i32 {
ASM::mov_reg64_base32(&mut self.buf, tmp_reg, from_offset + i);
ASM::mov_base32_reg64(&mut self.buf, to_offset + i, tmp_reg);
}

View file

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

View file

@ -5,16 +5,16 @@
use bumpalo::{collections::Vec, Bump};
use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
use roc_collections::all::{MutMap, MutSet};
use roc_error_macros::internal_error;
use roc_module::ident::{ModuleName, TagName};
use roc_module::low_level::LowLevel;
use roc_module::low_level::{LowLevel, LowLevelWrapperType};
use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::gen_refcount::RefcountProcGenerator;
use roc_mono::code_gen_help::CodeGenHelp;
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_reporting::internal_error;
mod generic64;
mod object_builder;
@ -62,9 +62,7 @@ trait Backend<'a> {
// This method is suboptimal, but it seems to be the only way to make rust understand
// that all of these values can be mutable at the same time. By returning them together,
// rust understands that they are part of a single use of mutable self.
fn env_interns_refcount_mut(
&mut self,
) -> (&Env<'a>, &mut Interns, &mut RefcountProcGenerator<'a>);
fn env_interns_helpers_mut(&mut self) -> (&Env<'a>, &mut Interns, &mut CodeGenHelp<'a>);
fn symbol_to_string(&self, symbol: Symbol, layout_id: LayoutId) -> String {
layout_id.to_symbol_string(symbol, self.interns())
@ -76,11 +74,11 @@ trait Backend<'a> {
.starts_with(ModuleName::APP)
}
fn refcount_proc_gen_mut(&mut self) -> &mut RefcountProcGenerator<'a>;
fn helper_proc_gen_mut(&mut self) -> &mut CodeGenHelp<'a>;
fn refcount_proc_symbols_mut(&mut self) -> &mut Vec<'a, (Symbol, ProcLayout<'a>)>;
fn helper_proc_symbols_mut(&mut self) -> &mut Vec<'a, (Symbol, ProcLayout<'a>)>;
fn refcount_proc_symbols(&self) -> &Vec<'a, (Symbol, ProcLayout<'a>)>;
fn helper_proc_symbols(&self) -> &Vec<'a, (Symbol, ProcLayout<'a>)>;
/// reset resets any registers or other values that may be occupied at the end of a procedure.
/// It also passes basic procedure information to the builder for setup of the next function.
@ -116,17 +114,17 @@ trait Backend<'a> {
self.scan_ast(&proc.body);
self.create_free_map();
self.build_stmt(&proc.body, &proc.ret_layout);
let mut rc_proc_names = bumpalo::vec![in self.env().arena];
rc_proc_names.reserve(self.refcount_proc_symbols().len());
for (rc_proc_sym, rc_proc_layout) in self.refcount_proc_symbols() {
let mut helper_proc_names = bumpalo::vec![in self.env().arena];
helper_proc_names.reserve(self.helper_proc_symbols().len());
for (rc_proc_sym, rc_proc_layout) in self.helper_proc_symbols() {
let name = layout_ids
.get_toplevel(*rc_proc_sym, rc_proc_layout)
.to_symbol_string(*rc_proc_sym, self.interns());
rc_proc_names.push((*rc_proc_sym, name));
helper_proc_names.push((*rc_proc_sym, name));
}
let (bytes, relocs) = self.finalize();
(bytes, relocs, rc_proc_names)
(bytes, relocs, helper_proc_names)
}
/// build_stmt builds a statement and outputs at the end of the buffer.
@ -150,20 +148,19 @@ trait Backend<'a> {
// Expand the Refcounting statement into more detailed IR with a function call
// If this layout requires a new RC proc, we get enough info to create a linker symbol
// for it. Here we don't create linker symbols at this time, but in Wasm backend, we do.
let (rc_stmt, new_proc_info) = {
let (env, interns, rc_proc_gen) = self.env_interns_refcount_mut();
let (rc_stmt, new_specializations) = {
let (env, interns, rc_proc_gen) = self.env_interns_helpers_mut();
let module_id = env.module_id;
let ident_ids = interns.all_ident_ids.get_mut(&module_id).unwrap();
rc_proc_gen.expand_refcount_stmt(ident_ids, layout, modify, *following)
};
if let Some((rc_proc_symbol, rc_proc_layout)) = new_proc_info {
self.refcount_proc_symbols_mut()
.push((rc_proc_symbol, rc_proc_layout));
for spec in new_specializations.into_iter() {
self.helper_proc_symbols_mut().push(spec);
}
self.build_stmt(&rc_stmt, ret_layout)
self.build_stmt(rc_stmt, ret_layout)
}
Stmt::Switch {
cond_symbol,
@ -263,8 +260,9 @@ trait Backend<'a> {
ret_layout,
..
} => {
// If this function is just a lowlevel wrapper, then inline it
if let Some(lowlevel) = LowLevel::from_inlined_wrapper(*func_sym) {
if let LowLevelWrapperType::CanBeReplacedBy(lowlevel) =
LowLevelWrapperType::from_symbol(*func_sym)
{
self.build_run_low_level(
sym,
&lowlevel,
@ -534,13 +532,13 @@ trait Backend<'a> {
arg_layouts,
ret_layout,
),
LowLevel::RefCountGetPtr => {
LowLevel::PtrCast => {
debug_assert_eq!(
1,
args.len(),
"RefCountGetPtr: expected to have exactly two argument"
"RefCountGetPtr: expected to have exactly one argument"
);
self.build_refcount_getptr(sym, &args[0])
self.build_ptr_cast(sym, &args[0])
}
LowLevel::RefCountDec => self.build_fn_call(
sym,
@ -645,7 +643,7 @@ trait Backend<'a> {
);
/// build_refcount_getptr loads the pointer to the reference count of src into dst.
fn build_refcount_getptr(&mut self, dst: &Symbol, src: &Symbol);
fn build_ptr_cast(&mut self, dst: &Symbol, src: &Symbol);
/// literal_map gets the map from symbol to literal and layout, used for lazy loading and literal folding.
fn literal_map(&mut self) -> &mut MutMap<Symbol, (*const Literal<'a>, *const Layout<'a>)>;

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