Merge branch 'trunk' of github.com:rtfeldman/roc into editor-comments

This commit is contained in:
Anton-4 2022-01-01 11:10:54 +01:00
commit f941e30b86
271 changed files with 15287 additions and 8370 deletions

View file

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

View file

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

View file

@ -60,3 +60,4 @@ Yann Simon <yann.simon.fr@gmail.com>
Shahn Hogan <shahnhogan@hotmail.com> Shahn Hogan <shahnhogan@hotmail.com>
Tankor Smash <tankorsmash+github@gmail.com> Tankor Smash <tankorsmash+github@gmail.com>
Matthias Devlamynck <matthias.devlamynck@mailoo.org> Matthias Devlamynck <matthias.devlamynck@mailoo.org>
Jan Van Bruggen <JanCVanB@users.noreply.github.com>

View file

@ -22,6 +22,7 @@ Earthly may temporarily use a lot of disk space, up to 90 GB. This disk space is
- Before making your first pull request, definitely talk to an existing contributor on [Roc Zulip](https://roc.zulipchat.com) first about what you plan to do! This can not only avoid duplicated effort, it can also avoid making a whole PR only to discover it won't be accepted because the change doesn't fit with the goals of the language's design or implementation. - Before making your first pull request, definitely talk to an existing contributor on [Roc Zulip](https://roc.zulipchat.com) first about what you plan to do! This can not only avoid duplicated effort, it can also avoid making a whole PR only to discover it won't be accepted because the change doesn't fit with the goals of the language's design or implementation.
- It's a good idea to open a work-in-progress pull request as you begin working on something. This way, others can see that you're working on it, which avoids duplicate effort, and others can give feedback sooner rather than later if they notice a problem in the direction things are going. Be sure to include "WIP" in the title of the PR as long as it's not ready for review! - It's a good idea to open a work-in-progress pull request as you begin working on something. This way, others can see that you're working on it, which avoids duplicate effort, and others can give feedback sooner rather than later if they notice a problem in the direction things are going. Be sure to include "WIP" in the title of the PR as long as it's not ready for review!
- Make sure to create a branch on the roc repository for your changes. We do not allow CI to be run on forks for security. - Make sure to create a branch on the roc repository for your changes. We do not allow CI to be run on forks for security.
- You find good first issues [here](https://github.com/rtfeldman/roc/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22).
## Can we do better? ## Can we do better?

228
Cargo.lock generated
View file

@ -459,7 +459,7 @@ name = "cli_utils"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"criterion", "criterion 0.3.5 (git+https://github.com/Anton-4/criterion.rs)",
"rlimit", "rlimit",
"roc_cli", "roc_cli",
"roc_collections", "roc_collections",
@ -481,6 +481,17 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "clipboard-win"
version = "4.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3db8340083d28acb43451166543b98c838299b7e0863621be53a338adceea0ed"
dependencies = [
"error-code",
"str-buf",
"winapi",
]
[[package]] [[package]]
name = "cocoa" name = "cocoa"
version = "0.24.0" version = "0.24.0"
@ -585,7 +596,7 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4423d79fed83ebd9ab81ec21fa97144300a961782158287dc9bf7eddac37ff0b" checksum = "4423d79fed83ebd9ab81ec21fa97144300a961782158287dc9bf7eddac37ff0b"
dependencies = [ dependencies = [
"clipboard-win", "clipboard-win 3.1.1",
"objc", "objc",
"objc-foundation", "objc-foundation",
"objc_id", "objc_id",
@ -800,18 +811,44 @@ dependencies = [
[[package]] [[package]]
name = "criterion" name = "criterion"
version = "0.3.5" 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 = [ dependencies = [
"atty", "atty",
"cast", "cast",
"clap 2.33.3", "clap 2.33.3",
"criterion-plot", "criterion-plot 0.4.4",
"csv", "csv",
"itertools 0.10.1", "itertools 0.10.1",
"lazy_static", "lazy_static",
"num-traits", "num-traits",
"oorandom", "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", "rayon",
"regex", "regex",
"serde", "serde",
@ -831,6 +868,16 @@ dependencies = [
"itertools 0.9.0", "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]] [[package]]
name = "crossbeam" name = "crossbeam"
version = "0.8.1" version = "0.8.1"
@ -1068,9 +1115,9 @@ dependencies = [
[[package]] [[package]]
name = "dirs-next" name = "dirs-next"
version = "1.0.2" version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf36e65a80337bea855cd4ef9b8401ffce06a7baedf2e85ec467b1ac3f6e82b6" checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"dirs-sys-next", "dirs-sys-next",
@ -1158,6 +1205,12 @@ version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "endian-type"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]] [[package]]
name = "enumset" name = "enumset"
version = "1.0.8" version = "1.0.8"
@ -1211,6 +1264,16 @@ dependencies = [
"serde", "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]] [[package]]
name = "fake-simd" name = "fake-simd"
version = "0.1.2" version = "0.1.2"
@ -1223,6 +1286,17 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" 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]] [[package]]
name = "find-crate" name = "find-crate"
version = "0.6.3" version = "0.6.3"
@ -1670,13 +1744,13 @@ dependencies = [
name = "inkwell" name = "inkwell"
version = "0.1.0" version = "0.1.0"
dependencies = [ 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]] [[package]]
name = "inkwell" name = "inkwell"
version = "0.1.0" 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 = [ dependencies = [
"either", "either",
"inkwell_internals", "inkwell_internals",
@ -1684,13 +1758,12 @@ dependencies = [
"llvm-sys", "llvm-sys",
"once_cell", "once_cell",
"parking_lot", "parking_lot",
"regex",
] ]
[[package]] [[package]]
name = "inkwell_internals" name = "inkwell_internals"
version = "0.3.0" version = "0.5.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 = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2212,16 +2285,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c44922cb3dbb1c70b5e5f443d63b64363a898564d739ba5198e3a9138442868d" checksum = "c44922cb3dbb1c70b5e5f443d63b64363a898564d739ba5198e3a9138442868d"
[[package]] [[package]]
name = "nix" name = "nibble_vec"
version = "0.17.0" version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
dependencies = [ dependencies = [
"bitflags", "smallvec",
"cc",
"cfg-if 0.1.10",
"libc",
"void",
] ]
[[package]] [[package]]
@ -2261,6 +2330,19 @@ dependencies = [
"memoffset", "memoffset",
] ]
[[package]]
name = "nix"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6"
dependencies = [
"bitflags",
"cc",
"cfg-if 1.0.0",
"libc",
"memoffset",
]
[[package]] [[package]]
name = "nom" name = "nom"
version = "5.1.2" version = "5.1.2"
@ -2732,6 +2814,19 @@ version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f" 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]] [[package]]
name = "plotters" name = "plotters"
version = "0.3.1" version = "0.3.1"
@ -2924,6 +3019,16 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb"
[[package]]
name = "radix_trie"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
dependencies = [
"endian-type",
"nibble_vec",
]
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.8.4" version = "0.8.4"
@ -3220,7 +3325,7 @@ dependencies = [
"clap 3.0.0-beta.5", "clap 3.0.0-beta.5",
"cli_utils", "cli_utils",
"const_format", "const_format",
"criterion", "criterion 0.3.5 (git+https://github.com/Anton-4/criterion.rs)",
"indoc", "indoc",
"inkwell 0.1.0", "inkwell 0.1.0",
"libloading 0.7.1", "libloading 0.7.1",
@ -3377,6 +3482,7 @@ dependencies = [
"roc_parse", "roc_parse",
"roc_region", "roc_region",
"roc_test_utils", "roc_test_utils",
"walkdir",
] ]
[[package]] [[package]]
@ -3413,6 +3519,7 @@ dependencies = [
"roc_collections", "roc_collections",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
"roc_reporting",
"roc_std", "roc_std",
"target-lexicon", "target-lexicon",
] ]
@ -3521,6 +3628,7 @@ name = "roc_parse"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"criterion 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"encode_unicode", "encode_unicode",
"indoc", "indoc",
"pretty_assertions", "pretty_assertions",
@ -3593,6 +3701,12 @@ dependencies = [
[[package]] [[package]]
name = "roc_std" name = "roc_std"
version = "0.1.0" version = "0.1.0"
dependencies = [
"indoc",
"pretty_assertions",
"quickcheck",
"quickcheck_macros",
]
[[package]] [[package]]
name = "roc_test_utils" name = "roc_test_utils"
@ -3681,16 +3795,21 @@ checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088"
[[package]] [[package]]
name = "rustyline" name = "rustyline"
version = "6.2.0" version = "9.1.1"
source = "git+https://github.com/rtfeldman/rustyline?tag=prompt-fix#a6b8a20d2bf5c3793d7367848be2f4afec2f0d99" source = "git+https://github.com/rtfeldman/rustyline?tag=v9.1.1#7053ae0fe0ee710d38ed5845dd979113382994dc"
dependencies = [ dependencies = [
"cfg-if 0.1.10", "bitflags",
"cfg-if 1.0.0",
"clipboard-win 4.2.2",
"dirs-next", "dirs-next",
"fd-lock",
"libc", "libc",
"log", "log",
"memchr", "memchr",
"nix 0.17.0", "nix 0.23.1",
"radix_trie",
"scopeguard", "scopeguard",
"smallvec",
"unicode-segmentation", "unicode-segmentation",
"unicode-width", "unicode-width",
"utf8parse", "utf8parse",
@ -3699,8 +3818,8 @@ dependencies = [
[[package]] [[package]]
name = "rustyline-derive" name = "rustyline-derive"
version = "0.3.1" version = "0.6.0"
source = "git+https://github.com/rtfeldman/rustyline?tag=prompt-fix#a6b8a20d2bf5c3793d7367848be2f4afec2f0d99" source = "git+https://github.com/rtfeldman/rustyline?tag=v9.1.1#7053ae0fe0ee710d38ed5845dd979113382994dc"
dependencies = [ dependencies = [
"quote", "quote",
"syn", "syn",
@ -4035,6 +4154,12 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef5430c8e36b713e13b48a9f709cc21e046723fe44ce34587b73a830203b533e" checksum = "ef5430c8e36b713e13b48a9f709cc21e046723fe44ce34587b73a830203b533e"
[[package]]
name = "str-buf"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d44a3643b4ff9caf57abcee9c2c621d6c03d9135e0d8b589bd9afb5992cb176a"
[[package]] [[package]]
name = "strip-ansi-escapes" name = "strip-ansi-escapes"
version = "0.1.1" version = "0.1.1"
@ -4436,12 +4561,6 @@ version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]] [[package]]
name = "vte" name = "vte"
version = "0.10.1" version = "0.10.1"
@ -5064,6 +5183,49 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82ca39602d5cbfa692c4b67e3bcbb2751477355141c1ed434c94da4186836ff6"
dependencies = [
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_msvc"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52695a41e536859d5308cc613b4a022261a274390b25bd29dfff4bf08505f3c2"
[[package]]
name = "windows_i686_gnu"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f54725ac23affef038fecb177de6c9bf065787c2f432f79e3c373da92f3e1d8a"
[[package]]
name = "windows_i686_msvc"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d5158a43cc43623c0729d1ad6647e62fa384a3d135fd15108d37c683461f64"
[[package]]
name = "windows_x86_64_gnu"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc31f409f565611535130cfe7ee8e6655d3fa99c1c61013981e491921b5ce954"
[[package]]
name = "windows_x86_64_msvc"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f2b8c7cbd3bfdddd9ab98769f9746a7fad1bca236554cd032b78d768bc0e89f"
[[package]] [[package]]
name = "winit" name = "winit"
version = "0.25.0" version = "0.25.0"

View file

@ -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). ## Our Pledge
It is based on the Rust Code of Conduct, which [can also be found online](https://www.rust-lang.org/conduct).
## 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. Examples of behavior that contributes to creating a positive environment include:
* 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.
## 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.) ## Enforcement Responsibilities
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.
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

@ -30,6 +30,8 @@ For NQueens, input 10 in the terminal and press enter.
[examples/benchmarks](examples/benchmarks) contains larger examples. [examples/benchmarks](examples/benchmarks) contains larger examples.
**Tip:** when programming in roc, we recommend to execute `./roc check myproject/Foo.roc` before `./roc myproject/Foo.roc` or `./roc build myproject/Foo.roc`. `./roc check` can produce clear error messages in cases where building/running may panic.
## Applications and Platforms ## Applications and Platforms
Applications are often built on a *framework.* Typically, both application and framework are written in the same language. Applications are often built on a *framework.* Typically, both application and framework are written in the same language.

View file

@ -275,7 +275,7 @@ convention is to write `else if` on the same line.
## Records ## 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: it take one argument like so:
```coffee ```coffee
@ -1053,7 +1053,7 @@ Let's take a closer look at the part of `Hello.roc` above `main`:
```coffee ```coffee
app "hello" app "hello"
packages [ pf: "examples/cli/platform" ] packages { pf: "examples/cli/platform" }
imports [ pf.Stdout ] imports [ pf.Stdout ]
provides main to pf provides main to pf
``` ```
@ -1071,12 +1071,12 @@ without running it by running `roc build Hello.roc`.
The remaining lines all involve the *platform* this application is built on: The remaining lines all involve the *platform* this application is built on:
```coffee ```coffee
packages [ pf: "examples/cli/platform" ] packages { pf: "examples/cli/platform" }
imports [ pf.Stdout ] imports [ pf.Stdout ]
provides main to pf provides main to pf
``` ```
The `packages [ pf: "examples/cli/platform" ]` part says two things: The `packages { pf: "examples/cli/platform" }` part says two things:
- We're going to be using a *package* (that is, a collection of modules) called `"examples/cli/platform"` - We're going to be using a *package* (that is, a collection of modules) called `"examples/cli/platform"`
- We're going to name that package `pf` so we can refer to it more concisely in the future. - We're going to name that package `pf` so we can refer to it more concisely in the future.
@ -1099,7 +1099,7 @@ When we write `imports [ pf.Stdout ]`, it specifies that the `Stdout`
module comes from the `pf` package. module comes from the `pf` package.
Since `pf` was the name we chose for the `examples/cli/platform` package Since `pf` was the name we chose for the `examples/cli/platform` package
(when we wrote `packages [ pf: "examples/cli/platform" ]`), this `imports` line (when we wrote `packages { pf: "examples/cli/platform" }`), this `imports` line
tells the Roc compiler that when we call `Stdout.line`, it should look for that tells the Roc compiler that when we call `Stdout.line`, it should look for that
`line` function in the `Stdout` module of the `examples/cli/platform` package. `line` function in the `Stdout` module of the `examples/cli/platform` package.

View file

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

View file

@ -1,6 +1,6 @@
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_problem::can::Problem; use roc_problem::can::Problem;
use roc_region::all::{Located, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::Variable; use roc_types::subs::Variable;
use crate::{ use crate::{
@ -38,7 +38,7 @@ enum FieldVar {
pub(crate) fn canonicalize_fields<'a>( pub(crate) fn canonicalize_fields<'a>(
env: &mut Env<'a>, env: &mut Env<'a>,
scope: &mut Scope, 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> { ) -> Result<(PoolVec<RecordField>, Output), CanonicalizeRecordProblem> {
let mut can_fields: MutMap<&'a str, FieldVar> = MutMap::default(); let mut can_fields: MutMap<&'a str, FieldVar> = MutMap::default();
let mut output = Output::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::ast;
use roc_parse::pattern::PatternType; use roc_parse::pattern::PatternType;
use roc_problem::can::{Problem, RuntimeError}; use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Located, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
use crate::lang::core::def::def::canonicalize_defs; use crate::lang::core::def::def::canonicalize_defs;
@ -44,7 +44,7 @@ pub struct ModuleOutput {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn canonicalize_module_defs<'a>( pub fn canonicalize_module_defs<'a>(
arena: &Bump, arena: &Bump,
loc_defs: &'a [Located<ast::Def<'a>>], loc_defs: &'a [Loc<ast::Def<'a>>],
home: ModuleId, home: ModuleId,
module_ids: &ModuleIds, module_ids: &ModuleIds,
exposed_ident_ids: IdentIds, 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); bumpalo::collections::Vec::with_capacity_in(loc_defs.len() + num_deps, arena);
for loc_def in loc_defs.iter() { for loc_def in loc_defs.iter() {
desugared.push(&*arena.alloc(Located { desugared.push(&*arena.alloc(Loc {
value: desugar_def(arena, &loc_def.value), value: desugar_def(arena, &loc_def.value),
region: loc_def.region, 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}, 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)] #[derive(Debug)]
pub enum Constraint<'a> { pub enum Constraint<'a> {
Eq(Type2, Expected<Type2>, Category, Region), Eq(Type2, Expected<Type2>, Category, Region),
@ -40,6 +51,7 @@ pub enum Constraint<'a> {
Let(&'a LetConstraint<'a>), Let(&'a LetConstraint<'a>),
// SaveTheEnvironment, // SaveTheEnvironment,
True, // Used for things that always unify, e.g. blanks and runtime errors True, // Used for things that always unify, e.g. blanks and runtime errors
Present(Type2, PresenceConstraint<'a>),
} }
#[derive(Debug)] #[derive(Debug)]
@ -782,7 +794,15 @@ pub fn constrain_expr<'a>(
constraints: BumpVec::with_capacity_in(1, arena), 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); state.vars.push(*expr_var);
let def_expr = env.pool.get(*expr_id); let def_expr = env.pool.get(*expr_id);
@ -1302,6 +1322,7 @@ fn constrain_when_branch<'a>(
region, region,
pattern_expected.shallow_clone(), pattern_expected.shallow_clone(),
&mut state, &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 /// This accepts PatternState (rather than returning it) so that the caller can
/// initialize the Vecs in PatternState using with_capacity /// initialize the Vecs in PatternState using with_capacity
/// based on its knowledge of their lengths. /// based on its knowledge of their lengths.
@ -1356,15 +1394,35 @@ pub fn constrain_pattern<'a>(
region: Region, region: Region,
expected: PExpected<Type2>, expected: PExpected<Type2>,
state: &mut PatternState2<'a>, state: &mut PatternState2<'a>,
destruct_position: bool,
) { ) {
use Pattern2::*; use Pattern2::*;
match pattern { 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 { .. } => { Underscore | UnsupportedPattern(_) | MalformedPattern(_, _) | Shadowed { .. } => {
// Neither the _ pattern nor erroneous ones add any constraints. // Neither the _ pattern nor erroneous ones add any constraints.
} }
Identifier(symbol) => { 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()); state.headers.insert(*symbol, expected.get_type());
} }
@ -1446,7 +1504,7 @@ pub fn constrain_pattern<'a>(
let field_type = match destruct_type { let field_type = match destruct_type {
DestructType::Guard(guard_var, guard_id) => { DestructType::Guard(guard_var, guard_id) => {
state.constraints.push(Constraint::Pattern( state.constraints.push(make_pattern_constraint(
region, region,
PatternCategory::PatternGuard, PatternCategory::PatternGuard,
Type2::Variable(*guard_var), Type2::Variable(*guard_var),
@ -1456,6 +1514,7 @@ pub fn constrain_pattern<'a>(
// TODO: region should be from guard_id // TODO: region should be from guard_id
region, region,
), ),
destruct_position,
)); ));
state.vars.push(*guard_var); state.vars.push(*guard_var);
@ -1463,12 +1522,20 @@ pub fn constrain_pattern<'a>(
let guard = env.pool.get(*guard_id); let guard = env.pool.get(*guard_id);
// TODO: region should be from 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)) types::RecordField::Demanded(env.pool.add(pat_type))
} }
DestructType::Optional(expr_var, expr_id) => { DestructType::Optional(expr_var, expr_id) => {
state.constraints.push(Constraint::Pattern( state.constraints.push(make_pattern_constraint(
region, region,
PatternCategory::PatternDefault, PatternCategory::PatternDefault,
Type2::Variable(*expr_var), Type2::Variable(*expr_var),
@ -1478,6 +1545,7 @@ pub fn constrain_pattern<'a>(
// TODO: region should be from expr_id // TODO: region should be from expr_id
region, region,
), ),
destruct_position,
)); ));
state.vars.push(*expr_var); state.vars.push(*expr_var);
@ -1521,11 +1589,12 @@ pub fn constrain_pattern<'a>(
region, region,
); );
let record_con = Constraint::Pattern( let record_con = make_pattern_constraint(
region, region,
PatternCategory::Record, PatternCategory::Record,
Type2::Variable(*whole_var), Type2::Variable(*whole_var),
expected, expected,
destruct_position,
); );
state.constraints.push(whole_con); 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()); let tag_name = TagName::Global(name.as_str(env.pool).into());
constrain_tag_pattern( 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 { PrivateTag {
@ -1552,7 +1630,16 @@ pub fn constrain_pattern<'a>(
let tag_name = TagName::Private(*name); let tag_name = TagName::Private(*name);
constrain_tag_pattern( 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, ext_var: Variable,
arguments: &PoolVec<(Variable, PatternId)>, arguments: &PoolVec<(Variable, PatternId)>,
tag_name: TagName, tag_name: TagName,
destruct_position: bool,
) { ) {
let mut argument_types = Vec::with_capacity(arguments.len()); let mut argument_types = Vec::with_capacity(arguments.len());
@ -1591,10 +1679,19 @@ fn constrain_tag_pattern<'a>(
); );
// TODO region should come from pattern // 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( 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),
),
)
} else {
Constraint::Eq(
Type2::Variable(whole_var), Type2::Variable(whole_var),
Expected::NoExpectation(Type2::TagUnion( Expected::NoExpectation(Type2::TagUnion(
PoolVec::new( PoolVec::new(
@ -1609,13 +1706,15 @@ fn constrain_tag_pattern<'a>(
)), )),
Category::Storage(std::file!(), std::line!()), Category::Storage(std::file!(), std::line!()),
region, region,
); )
};
let tag_con = Constraint::Pattern( let tag_con = make_pattern_constraint(
region, region,
PatternCategory::Ctor(tag_name), PatternCategory::Ctor(tag_name),
Type2::Variable(whole_var), Type2::Variable(whole_var),
expected, expected,
destruct_position,
); );
state.vars.push(whole_var); state.vars.push(whole_var);
@ -1660,6 +1759,7 @@ fn constrain_untyped_args<'a>(
Region::zero(), Region::zero(),
pattern_expected, pattern_expected,
&mut pattern_state, &mut pattern_state,
false,
); );
vars.push(*pattern_var); vars.push(*pattern_var);
@ -2612,4 +2712,168 @@ pub mod test_constrain {
"{ email : Str, name : Str }a -> { email : Str, name : Str }a", "{ 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_collections::all::{default_hasher, ImMap, MutMap, MutSet, SendMap};
use roc_module::ident::Lowercase; use roc_module::ident::Lowercase;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_parse::ast; use roc_parse::ast::{self, AliasHeader};
use roc_parse::pattern::PatternType; use roc_parse::pattern::PatternType;
use roc_problem::can::{Problem, RuntimeError}; use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Located, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::Debug; use std::fmt::Debug;
@ -96,29 +96,25 @@ impl ShallowClone for Def {
pub enum PendingDef<'a> { pub enum PendingDef<'a> {
/// A standalone annotation with no body /// A standalone annotation with no body
AnnotationOnly( AnnotationOnly(
&'a Located<ast::Pattern<'a>>, &'a Loc<ast::Pattern<'a>>,
PatternId, PatternId,
&'a Located<ast::TypeAnnotation<'a>>, &'a Loc<ast::TypeAnnotation<'a>>,
), ),
/// A body with no type annotation /// A body with no type annotation
Body( Body(&'a Loc<ast::Pattern<'a>>, PatternId, &'a Loc<ast::Expr<'a>>),
&'a Located<ast::Pattern<'a>>,
PatternId,
&'a Located<ast::Expr<'a>>,
),
/// A body with a type annotation /// A body with a type annotation
TypedBody( TypedBody(
&'a Located<ast::Pattern<'a>>, &'a Loc<ast::Pattern<'a>>,
PatternId, PatternId,
&'a Located<ast::TypeAnnotation<'a>>, &'a Loc<ast::TypeAnnotation<'a>>,
&'a Located<ast::Expr<'a>>, &'a Loc<ast::Expr<'a>>,
), ),
/// A type alias, e.g. `Ints : List Int` /// A type alias, e.g. `Ints : List Int`
Alias { Alias {
name: Located<Symbol>, name: Loc<Symbol>,
vars: Vec<Located<Lowercase>>, vars: Vec<Loc<Lowercase>>,
ann: &'a Located<ast::TypeAnnotation<'a>>, ann: &'a Loc<ast::TypeAnnotation<'a>>,
}, },
/// An invalid alias, that is ignored in the rest of the pipeline /// 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); let region = Region::span_across(&name.region, &ann.region);
match scope.introduce( match scope.introduce(
@ -212,7 +211,7 @@ fn to_pending_def<'a>(
region, region,
) { ) {
Ok(symbol) => { 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() { for loc_var in vars.iter() {
match loc_var.value { match loc_var.value {
@ -220,7 +219,7 @@ fn to_pending_def<'a>(
if name.chars().next().unwrap().is_lowercase() => if name.chars().next().unwrap().is_lowercase() =>
{ {
let lowercase = Lowercase::from(name); let lowercase = Lowercase::from(name);
can_rigids.push(Located { can_rigids.push(Loc {
value: lowercase, value: lowercase,
region: loc_var.region, region: loc_var.region,
}); });
@ -240,7 +239,7 @@ fn to_pending_def<'a>(
Some(( Some((
Output::default(), Output::default(),
PendingDef::Alias { PendingDef::Alias {
name: Located { name: Loc {
region: name.region, region: name.region,
value: symbol, value: symbol,
}, },
@ -273,9 +272,9 @@ fn to_pending_def<'a>(
fn pending_typed_body<'a>( fn pending_typed_body<'a>(
env: &mut Env<'a>, env: &mut Env<'a>,
loc_pattern: &'a Located<ast::Pattern<'a>>, loc_pattern: &'a Loc<ast::Pattern<'a>>,
loc_ann: &'a Located<ast::TypeAnnotation<'a>>, loc_ann: &'a Loc<ast::TypeAnnotation<'a>>,
loc_expr: &'a Located<ast::Expr<'a>>, loc_expr: &'a Loc<ast::Expr<'a>>,
scope: &mut Scope, scope: &mut Scope,
pattern_type: PatternType, pattern_type: PatternType,
) -> (Output, PendingDef<'a>) { ) -> (Output, PendingDef<'a>) {
@ -297,9 +296,9 @@ fn pending_typed_body<'a>(
fn from_pending_alias<'a>( fn from_pending_alias<'a>(
env: &mut Env<'a>, env: &mut Env<'a>,
scope: &mut Scope, scope: &mut Scope,
name: Located<Symbol>, name: Loc<Symbol>,
vars: Vec<Located<Lowercase>>, vars: Vec<Loc<Lowercase>>,
ann: &'a Located<ast::TypeAnnotation<'a>>, ann: &'a Loc<ast::TypeAnnotation<'a>>,
mut output: Output, mut output: Output,
) -> Output { ) -> Output {
let symbol = name.value; let symbol = name.value;
@ -787,7 +786,7 @@ pub fn canonicalize_defs<'a>(
env: &mut Env<'a>, env: &mut Env<'a>,
mut output: Output, mut output: Output,
original_scope: &Scope, original_scope: &Scope,
loc_defs: &'a [&'a Located<ast::Def<'a>>], loc_defs: &'a [&'a Loc<ast::Def<'a>>],
pattern_type: PatternType, pattern_type: PatternType,
) -> (CanDefs, Scope, Output, MutMap<Symbol, Region>) { ) -> (CanDefs, Scope, Output, MutMap<Symbol, Region>) {
// Canonicalizing defs while detecting shadowing involves a multi-step process: // Canonicalizing defs while detecting shadowing involves a multi-step process:
@ -1185,7 +1184,7 @@ pub fn sort_can_defs(
symbol, refs_by_symbol symbol, refs_by_symbol
), ),
Some((region, _)) => { 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_module::symbol::Symbol;
use roc_parse::{ast::Expr, pattern::PatternType}; use roc_parse::{ast::Expr, pattern::PatternType};
use roc_problem::can::{Problem, RuntimeError}; 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 super::{expr2::Expr2, output::Output};
use crate::canonicalization::canonicalize::{ use crate::canonicalization::canonicalize::{
@ -29,7 +29,7 @@ use crate::{
pub fn loc_expr_to_expr2<'a>( pub fn loc_expr_to_expr2<'a>(
arena: &'a Bump, arena: &'a Bump,
loc_expr: Located<Expr<'a>>, loc_expr: Loc<Expr<'a>>,
env: &mut Env<'a>, env: &mut Env<'a>,
scope: &mut Scope, scope: &mut Scope,
region: Region, region: Region,

View file

@ -5,7 +5,7 @@
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::Symbol; 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::types::{Problem, RecordField};
use roc_types::{subs::Variable, types::ErrorType}; use roc_types::{subs::Variable, types::ErrorType};
@ -56,7 +56,7 @@ pub enum Problem2 {
CircularType(Symbol, NodeId<ErrorType>), // 12B = 8B + 4B CircularType(Symbol, NodeId<ErrorType>), // 12B = 8B + 4B
CyclicAlias(Symbol, PoolVec<Symbol>), // 20B = 8B + 12B CyclicAlias(Symbol, PoolVec<Symbol>), // 20B = 8B + 12B
UnrecognizedIdent(PoolStr), // 8B UnrecognizedIdent(PoolStr), // 8B
Shadowed(Located<PoolStr>), Shadowed(Loc<PoolStr>),
BadTypeArguments { BadTypeArguments {
symbol: Symbol, // 8B symbol: Symbol, // 8B
type_got: u8, // 1B type_got: u8, // 1B
@ -329,6 +329,8 @@ pub fn to_type2<'a>(
annotation: &roc_parse::ast::TypeAnnotation<'a>, annotation: &roc_parse::ast::TypeAnnotation<'a>,
region: Region, region: Region,
) -> Type2 { ) -> Type2 {
use roc_parse::ast::AliasHeader;
use roc_parse::ast::Pattern;
use roc_parse::ast::TypeAnnotation::*; use roc_parse::ast::TypeAnnotation::*;
match annotation { match annotation {
@ -450,12 +452,17 @@ pub fn to_type2<'a>(
Type2::TagUnion(tag_types, ext_type) Type2::TagUnion(tag_types, ext_type)
} }
As(loc_inner, _spaces, loc_as) => { As(
// e.g. `{ x : Int, y : Int } as Point }` loc_inner,
match loc_as.value { _spaces,
Apply(module_name, ident, loc_vars) if module_name.is_empty() => { AliasHeader {
name,
vars: loc_vars,
},
) => {
// e.g. `{ x : Int, y : Int } as Point`
let symbol = match scope.introduce( let symbol = match scope.introduce(
ident.into(), name.value.into(),
&env.exposed_ident_ids, &env.exposed_ident_ids,
&mut env.ident_ids, &mut env.ident_ids,
region, region,
@ -485,9 +492,13 @@ pub fn to_type2<'a>(
.zip(lowercase_vars.iter_node_ids()) .zip(lowercase_vars.iter_node_ids())
.zip(vars.iter_node_ids()) .zip(vars.iter_node_ids())
{ {
match loc_var.value { let var = match loc_var.value {
BoundVariable(ident) => { Pattern::Identifier(name) if name.chars().next().unwrap().is_lowercase() => {
let var_name = Lowercase::from(ident); name
}
_ => unreachable!("I thought this was validated during parsing"),
};
let var_name = Lowercase::from(var);
if let Some(var) = references.named.get(&var_name) { if let Some(var) = references.named.get(&var_name) {
let poolstr = PoolStr::new(var_name.as_str(), env.pool); let poolstr = PoolStr::new(var_name.as_str(), env.pool);
@ -510,13 +521,6 @@ pub fn to_type2<'a>(
env.set_region(named_id, loc_var.region); 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; let alias_actual = inner_type;
// TODO instantiate recursive tag union // TODO instantiate recursive tag union
@ -566,12 +570,6 @@ pub fn to_type2<'a>(
// } // }
Type2::AsAlias(symbol, vars, alias.actual) Type2::AsAlias(symbol, vars, alias.actual)
} }
_ => {
// This is a syntactically invalid type alias.
Type2::Erroneous(Problem2::CanonicalizationProblem)
}
}
}
SpaceBefore(nested, _) | SpaceAfter(nested, _) => { SpaceBefore(nested, _) | SpaceAfter(nested, _) => {
to_type2(env, scope, references, nested, region) to_type2(env, scope, references, nested, region)
} }
@ -584,7 +582,7 @@ fn can_assigned_fields<'a>(
env: &mut Env, env: &mut Env,
scope: &mut Scope, scope: &mut Scope,
rigids: &mut References, 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, region: Region,
) -> MutMap<Lowercase, RecordField<Type2>> { ) -> MutMap<Lowercase, RecordField<Type2>> {
use roc_parse::ast::AssignedField::*; use roc_parse::ast::AssignedField::*;
@ -672,7 +670,7 @@ fn can_tags<'a>(
env: &mut Env, env: &mut Env,
scope: &mut Scope, scope: &mut Scope,
rigids: &mut References, rigids: &mut References,
tags: &'a [Located<roc_parse::ast::Tag<'a>>], tags: &'a [Loc<roc_parse::ast::Tag<'a>>],
region: Region, region: Region,
) -> Vec<(TagName, PoolVec<Type2>)> { ) -> Vec<(TagName, PoolVec<Type2>)> {
use roc_parse::ast::Tag; use roc_parse::ast::Tag;
@ -758,7 +756,7 @@ fn to_type_apply<'a>(
rigids: &mut References, rigids: &mut References,
module_name: &str, module_name: &str,
ident: &str, ident: &str,
type_arguments: &[Located<roc_parse::ast::TypeAnnotation<'a>>], type_arguments: &[Loc<roc_parse::ast::TypeAnnotation<'a>>],
region: Region, region: Region,
) -> TypeApply { ) -> TypeApply {
let symbol = if module_name.is_empty() { 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::ident::{Ident, Lowercase, ModuleName};
use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_problem::can::{Problem, RuntimeError}; use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Located, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::VarStore; use roc_types::subs::VarStore;
use super::core::def::def::References; use super::core::def::def::References;
/// TODO document
#[derive(Debug)] #[derive(Debug)]
pub struct Env<'a> { pub struct Env<'a> {
pub home: ModuleId, pub home: ModuleId,
@ -123,7 +124,7 @@ impl<'a> Env<'a> {
Ok(symbol) Ok(symbol)
} }
None => Err(RuntimeError::LookupNotInScope( None => Err(RuntimeError::LookupNotInScope(
Located { Loc {
value: ident, value: ident,
region, region,
}, },

View file

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

View file

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

View file

@ -5,7 +5,7 @@ use roc_can::expected::{Expected, PExpected};
use roc_collections::all::{BumpMap, BumpMapDefault, MutMap}; use roc_collections::all::{BumpMap, BumpMapDefault, MutMap};
use roc_module::ident::TagName; use roc_module::ident::TagName;
use roc_module::symbol::Symbol; 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::solved_types::Solved;
use roc_types::subs::{ use roc_types::subs::{
AliasVariables, Content, Descriptor, FlatType, Mark, OptVariable, Rank, RecordFields, 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, gather_fields_unsorted_iter, Alias, Category, ErrorType, PatternCategory, RecordField,
}; };
use roc_unify::unify::unify; use roc_unify::unify::unify;
use roc_unify::unify::Mode;
use roc_unify::unify::Unified::*; use roc_unify::unify::Unified::*;
use crate::constrain::Constraint; use crate::constrain::{Constraint, PresenceConstraint};
use crate::lang::core::types::Type2; use crate::lang::core::types::Type2;
use crate::mem_pool::pool::Pool; use crate::mem_pool::pool::Pool;
use crate::mem_pool::pool_vec::PoolVec; use crate::mem_pool::pool_vec::PoolVec;
@ -224,7 +225,7 @@ fn solve<'a>(
expectation.get_type_ref(), expectation.get_type_ref(),
); );
match unify(subs, actual, expected) { match unify(subs, actual, expected, Mode::Eq) {
Success(vars) => { Success(vars) => {
introduce(subs, rank, pools, &vars); introduce(subs, rank, pools, &vars);
@ -317,7 +318,7 @@ fn solve<'a>(
expectation.get_type_ref(), expectation.get_type_ref(),
); );
match unify(subs, actual, expected) { match unify(subs, actual, expected, Mode::Eq) {
Success(vars) => { Success(vars) => {
introduce(subs, rank, pools, &vars); introduce(subs, rank, pools, &vars);
@ -374,7 +375,8 @@ fn solve<'a>(
state 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 actual = type_to_var(arena, mempool, subs, rank, pools, cached_aliases, typ);
let expected = type_to_var( let expected = type_to_var(
arena, arena,
@ -386,7 +388,8 @@ fn solve<'a>(
expectation.get_type_ref(), 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) => { Success(vars) => {
introduce(subs, rank, pools, &vars); introduce(subs, rank, pools, &vars);
@ -459,7 +462,7 @@ fn solve<'a>(
// TODO: region should come from typ // TODO: region should come from typ
local_def_vars.insert( local_def_vars.insert(
*symbol, *symbol,
Located { Loc {
value: var, value: var,
region: Region::zero(), region: Region::zero(),
}, },
@ -542,7 +545,7 @@ fn solve<'a>(
// TODO: region should come from type // TODO: region should come from type
local_def_vars.insert( local_def_vars.insert(
*symbol, *symbol,
Located { Loc {
value: var, value: var,
region: Region::zero(), region: Region::zero(),
}, },
@ -657,7 +660,73 @@ fn solve<'a>(
new_state 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 temp_ext_var = type_to_variable(arena, mempool, subs, rank, pools, cached, ext);
let (it, new_ext_var) = 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 let it = it
.into_iter() .into_iter()
@ -1013,7 +1083,7 @@ fn check_for_infinite_type(
subs: &mut Subs, subs: &mut Subs,
problems: &mut Vec<TypeError>, problems: &mut Vec<TypeError>,
symbol: Symbol, symbol: Symbol,
loc_var: Located<Variable>, loc_var: Loc<Variable>,
) { ) {
let var = loc_var.value; let var = loc_var.value;
@ -1066,7 +1136,7 @@ fn circular_error(
subs: &mut Subs, subs: &mut Subs,
problems: &mut Vec<TypeError>, problems: &mut Vec<TypeError>,
symbol: Symbol, symbol: Symbol,
loc_var: &Located<Variable>, loc_var: &Loc<Variable>,
) { ) {
let var = loc_var.value; let var = loc_var.value;
let (error_type, _) = subs.var_to_error_type(var); let (error_type, _) = subs.var_to_error_type(var);

View file

@ -66,8 +66,8 @@ roc_editor = { path = "../editor", optional = true }
roc_linker = { path = "../linker" } roc_linker = { path = "../linker" }
clap = { version = "= 3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] } clap = { version = "= 3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] }
const_format = "0.2.22" const_format = "0.2.22"
rustyline = { git = "https://github.com/rtfeldman/rustyline", tag = "prompt-fix" } rustyline = { git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1" }
rustyline-derive = { git = "https://github.com/rtfeldman/rustyline", tag = "prompt-fix" } rustyline-derive = { git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1" }
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo = { version = "3.8.0", features = ["collections"] }
libloading = "0.7.1" libloading = "0.7.1"
mimalloc = { version = "0.1.26", default-features = false } mimalloc = { version = "0.1.26", default-features = false }

View file

@ -7,19 +7,20 @@ use roc_fmt::module::fmt_module;
use roc_fmt::Buf; use roc_fmt::Buf;
use roc_module::called_via::{BinOp, UnaryOp}; use roc_module::called_via::{BinOp, UnaryOp};
use roc_parse::ast::{ use roc_parse::ast::{
AssignedField, Collection, Expr, Pattern, StrLiteral, StrSegment, Tag, TypeAnnotation, AliasHeader, AssignedField, Collection, Expr, Pattern, Spaced, StrLiteral, StrSegment, Tag,
WhenBranch, TypeAnnotation, WhenBranch,
}; };
use roc_parse::header::{ use roc_parse::header::{
AppHeader, Effects, ExposesEntry, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry, AppHeader, Effects, ExposedName, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
PackageName, PackageOrPath, PlatformHeader, PlatformRequires, PlatformRigid, To, TypedIdent, PackageName, PlatformHeader, PlatformRequires, PlatformRigid, To, TypedIdent,
}; };
use roc_parse::{ use roc_parse::{
ast::{Def, Module}, ast::{Def, Module},
module::{self, module_defs}, module::{self, module_defs},
parser::{Parser, State, SyntaxError}, parser::{Parser, SyntaxError},
state::State,
}; };
use roc_region::all::Located; use roc_region::all::Loc;
use roc_reporting::{internal_error, user_error}; use roc_reporting::{internal_error, user_error};
pub fn format(files: std::vec::Vec<PathBuf>) { pub fn format(files: std::vec::Vec<PathBuf>) {
@ -105,7 +106,7 @@ pub fn format(files: std::vec::Vec<PathBuf>) {
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
struct Ast<'a> { struct Ast<'a> {
module: Module<'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>> { fn parse_all<'a>(arena: &'a Bump, src: &'a str) -> Result<Ast<'a>, SyntaxError<'a>> {
@ -228,16 +229,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 { fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self { match *self {
ExposesEntry::Exposed(a) => ExposesEntry::Exposed(a.remove_spaces(arena)), Spaced::Item(a) => Spaced::Item(a.remove_spaces(arena)),
ExposesEntry::SpaceBefore(a, _) => a.remove_spaces(arena), Spaced::SpaceBefore(a, _) => a.remove_spaces(arena),
ExposesEntry::SpaceAfter(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> { impl<'a> RemoveSpaces<'a> for ModuleName<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self { fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self *self
@ -261,18 +268,10 @@ impl<'a> RemoveSpaces<'a> for To<'a> {
impl<'a> RemoveSpaces<'a> for TypedIdent<'a> { impl<'a> RemoveSpaces<'a> for TypedIdent<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self { fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self { TypedIdent {
TypedIdent::Entry { ident: self.ident.remove_spaces(arena),
ident,
spaces_before_colon: _,
ann,
} => TypedIdent::Entry {
ident: ident.remove_spaces(arena),
spaces_before_colon: &[], spaces_before_colon: &[],
ann: ann.remove_spaces(arena), ann: self.ann.remove_spaces(arena),
},
TypedIdent::SpaceBefore(a, _) => a.remove_spaces(arena),
TypedIdent::SpaceAfter(a, _) => a.remove_spaces(arena),
} }
} }
} }
@ -287,38 +286,17 @@ impl<'a> RemoveSpaces<'a> for PlatformRequires<'a> {
} }
impl<'a> RemoveSpaces<'a> for PlatformRigid<'a> { impl<'a> RemoveSpaces<'a> for PlatformRigid<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self { fn remove_spaces(&self, _arena: &'a Bump) -> Self {
match *self { *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 PackageEntry<'a> { impl<'a> RemoveSpaces<'a> for PackageEntry<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self { fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self { PackageEntry {
PackageEntry::Entry { shorthand: self.shorthand,
shorthand,
spaces_after_shorthand: _,
package_or_path,
} => PackageEntry::Entry {
shorthand,
spaces_after_shorthand: &[], spaces_after_shorthand: &[],
package_or_path: package_or_path.remove_spaces(arena), package_name: self.package_name.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)),
} }
} }
} }
@ -328,8 +306,6 @@ impl<'a> RemoveSpaces<'a> for ImportsEntry<'a> {
match *self { match *self {
ImportsEntry::Module(a, b) => ImportsEntry::Module(a, b.remove_spaces(arena)), 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::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 +316,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 { fn remove_spaces(&self, arena: &'a Bump) -> Self {
let res = self.value.remove_spaces(arena); let res = self.value.remove_spaces(arena);
Located::new(0, 0, 0, 0, res) Loc::new(0, 0, 0, 0, res)
} }
} }
@ -398,9 +374,14 @@ impl<'a> RemoveSpaces<'a> for Def<'a> {
Def::Annotation(a, b) => { Def::Annotation(a, b) => {
Def::Annotation(a.remove_spaces(arena), b.remove_spaces(arena)) Def::Annotation(a.remove_spaces(arena), b.remove_spaces(arena))
} }
Def::Alias { name, vars, ann } => Def::Alias { Def::Alias {
header: AliasHeader { name, vars },
ann,
} => Def::Alias {
header: AliasHeader {
name: name.remove_spaces(arena), name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena), vars: vars.remove_spaces(arena),
},
ann: ann.remove_spaces(arena), ann: ann.remove_spaces(arena),
}, },
Def::Body(a, b) => Def::Body( Def::Body(a, b) => Def::Body(
@ -600,11 +581,9 @@ impl<'a> RemoveSpaces<'a> for TypeAnnotation<'a> {
), ),
TypeAnnotation::Apply(a, b, c) => TypeAnnotation::Apply(a, b, c.remove_spaces(arena)), TypeAnnotation::Apply(a, b, c) => TypeAnnotation::Apply(a, b, c.remove_spaces(arena)),
TypeAnnotation::BoundVariable(a) => TypeAnnotation::BoundVariable(a), TypeAnnotation::BoundVariable(a) => TypeAnnotation::BoundVariable(a),
TypeAnnotation::As(a, _, c) => TypeAnnotation::As( TypeAnnotation::As(a, _, c) => {
arena.alloc(a.remove_spaces(arena)), TypeAnnotation::As(arena.alloc(a.remove_spaces(arena)), &[], c)
&[], }
arena.alloc(c.remove_spaces(arena)),
),
TypeAnnotation::Record { fields, ext } => TypeAnnotation::Record { TypeAnnotation::Record { fields, ext } => TypeAnnotation::Record {
fields: fields.remove_spaces(arena), fields: fields.remove_spaces(arena),
ext: ext.remove_spaces(arena), ext: ext.remove_spaces(arena),

View file

@ -92,12 +92,12 @@ impl Validator for InputValidator {
Ok(ValidationResult::Incomplete) Ok(ValidationResult::Incomplete)
} else { } else {
let arena = bumpalo::Bump::new(); 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) { match roc_parse::expr::parse_loc_expr(0, &arena, state) {
// Special case some syntax errors to allow for multi-line inputs // Special case some syntax errors to allow for multi-line inputs
Err((_, EExpr::DefMissingFinalExpr(_, _), _)) Err((_, EExpr::DefMissingFinalExpr(_), _))
| Err((_, EExpr::DefMissingFinalExpr2(_, _, _), _)) => { | Err((_, EExpr::DefMissingFinalExpr2(_, _), _)) => {
Ok(ValidationResult::Incomplete) Ok(ValidationResult::Incomplete)
} }
_ => Ok(ValidationResult::Valid(None)), _ => Ok(ValidationResult::Valid(None)),

View file

@ -2,15 +2,20 @@ use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use libloading::Library; use libloading::Library;
use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_collections::all::MutMap;
use roc_gen_llvm::llvm::build::tag_pointer_tag_id_bits_and_mask;
use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type}; use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type};
use roc_module::called_via::CalledVia; use roc_module::called_via::CalledVia;
use roc_module::ident::TagName; use roc_module::ident::TagName;
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::ir::ProcLayout; use roc_mono::ir::ProcLayout;
use roc_mono::layout::{union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant}; use roc_mono::layout::{
union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant, WrappedVariant,
};
use roc_parse::ast::{AssignedField, Collection, Expr, StrLiteral}; use roc_parse::ast::{AssignedField, Collection, Expr, StrLiteral};
use roc_region::all::{Located, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable}; use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable};
use std::cmp::{max_by_key, min_by_key};
struct Env<'a, 'env> { struct Env<'a, 'env> {
arena: &'a Bump, arena: &'a Bump,
@ -38,10 +43,10 @@ pub unsafe fn jit_to_ast<'a>(
lib: Library, lib: Library,
main_fn_name: &str, main_fn_name: &str,
layout: ProcLayout<'a>, layout: ProcLayout<'a>,
content: &Content, content: &'a Content,
interns: &Interns, interns: &'a Interns,
home: ModuleId, home: ModuleId,
subs: &Subs, subs: &'a Subs,
ptr_bytes: u32, ptr_bytes: u32,
) -> Result<Expr<'a>, ToAstProblem> { ) -> Result<Expr<'a>, ToAstProblem> {
let env = Env { let env = Env {
@ -64,14 +69,166 @@ pub unsafe fn jit_to_ast<'a>(
} }
} }
fn jit_to_ast_help<'a>( // Unrolls tag unions that are newtypes (i.e. are singleton variants with one type argument).
// This is sometimes important in synchronizing `Content`s with `Layout`s, since `Layout`s will
// always unwrap newtypes and use the content of the underlying type.
fn unroll_newtypes<'a>(
env: &Env<'a, 'a>,
mut content: &'a Content,
) -> (Vec<'a, &'a TagName>, &'a Content) {
let mut newtype_tags = Vec::with_capacity_in(1, env.arena);
loop {
match content {
Content::Structure(FlatType::TagUnion(tags, _))
if tags.is_newtype_wrapper(env.subs) =>
{
let (tag_name, vars): (&TagName, &[Variable]) = tags
.unsorted_iterator(env.subs, Variable::EMPTY_TAG_UNION)
.next()
.unwrap();
newtype_tags.push(tag_name);
let var = vars[0];
content = env.subs.get_content_without_compacting(var);
}
_ => return (newtype_tags, content),
}
}
}
fn apply_newtypes<'a>(
env: &Env<'a, '_>, env: &Env<'a, '_>,
newtype_tags: Vec<'a, &'a TagName>,
mut expr: Expr<'a>,
) -> Expr<'a> {
for tag_name in newtype_tags.into_iter().rev() {
let tag_expr = tag_name_to_expr(env, tag_name);
let loc_tag_expr = &*env.arena.alloc(Loc::at_zero(tag_expr));
let loc_arg_expr = &*env.arena.alloc(Loc::at_zero(expr));
let loc_arg_exprs = env.arena.alloc_slice_copy(&[loc_arg_expr]);
expr = Expr::Apply(loc_tag_expr, loc_arg_exprs, CalledVia::Space);
}
expr
}
fn unroll_aliases<'a>(env: &Env<'a, 'a>, mut content: &'a Content) -> &'a Content {
while let Content::Alias(_, _, real) = content {
content = env.subs.get_content_without_compacting(*real);
}
content
}
fn unroll_recursion_var<'a>(env: &Env<'a, 'a>, mut content: &'a Content) -> &'a Content {
while let Content::RecursionVar { structure, .. } = content {
content = env.subs.get_content_without_compacting(*structure);
}
content
}
fn get_tags_vars_and_variant<'a>(
env: &Env<'a, '_>,
tags: &UnionTags,
opt_rec_var: Option<Variable>,
) -> (MutMap<TagName, std::vec::Vec<Variable>>, UnionVariant<'a>) {
let tags_vec: std::vec::Vec<(TagName, std::vec::Vec<Variable>)> = tags
.unsorted_iterator(env.subs, Variable::EMPTY_TAG_UNION)
.map(|(a, b)| (a.clone(), b.to_vec()))
.collect();
let vars_of_tag: MutMap<_, _> = tags_vec.iter().cloned().collect();
let union_variant =
union_sorted_tags_help(env.arena, tags_vec, opt_rec_var, env.subs, env.ptr_bytes);
(vars_of_tag, union_variant)
}
fn expr_of_tag<'a>(
env: &Env<'a, 'a>,
ptr_to_data: *const u8,
tag_name: &TagName,
arg_layouts: &'a [Layout<'a>],
arg_vars: &[Variable],
when_recursive: WhenRecursive<'a>,
) -> Expr<'a> {
let tag_expr = tag_name_to_expr(env, tag_name);
let loc_tag_expr = &*env.arena.alloc(Loc::at_zero(tag_expr));
debug_assert_eq!(arg_layouts.len(), arg_vars.len());
// NOTE assumes the data bytes are the first bytes
let it = arg_vars.iter().copied().zip(arg_layouts.iter());
let output = sequence_of_expr(env, ptr_to_data, it, when_recursive);
let output = output.into_bump_slice();
Expr::Apply(loc_tag_expr, output, CalledVia::Space)
}
/// Gets the tag ID of a union variant, assuming that the tag ID is stored alongside (after) the
/// tag data. The caller is expected to check that the tag ID is indeed stored this way.
fn tag_id_from_data(union_layout: UnionLayout, data_ptr: *const u8, ptr_bytes: u32) -> i64 {
let offset = union_layout.data_size_without_tag_id(ptr_bytes).unwrap();
unsafe {
match union_layout.tag_id_builtin() {
Builtin::Bool => *(data_ptr.add(offset as usize) as *const i8) as i64,
Builtin::Int(IntWidth::U8) => *(data_ptr.add(offset as usize) as *const i8) as i64,
Builtin::Int(IntWidth::U16) => *(data_ptr.add(offset as usize) as *const i16) as i64,
Builtin::Int(IntWidth::U64) => {
// used by non-recursive unions at the
// moment, remove if that is no longer the case
*(data_ptr.add(offset as usize) as *const i64) as i64
}
_ => unreachable!("invalid tag id layout"),
}
}
}
fn deref_ptr_of_ptr(ptr_of_ptr: *const u8, ptr_bytes: u32) -> *const u8 {
unsafe {
match ptr_bytes {
// Our LLVM codegen represents pointers as i32/i64s.
4 => *(ptr_of_ptr as *const i32) as *const u8,
8 => *(ptr_of_ptr as *const i64) as *const u8,
_ => unreachable!(),
}
}
}
/// Gets the tag ID of a union variant from its recursive pointer (that is, the pointer to the
/// pointer to the data of the union variant). Returns
/// - the tag ID
/// - the pointer to the data of the union variant, unmasked if the pointer held the tag ID
fn tag_id_from_recursive_ptr(
union_layout: UnionLayout,
rec_ptr: *const u8,
ptr_bytes: u32,
) -> (i64, *const u8) {
let tag_in_ptr = union_layout.stores_tag_id_in_pointer(ptr_bytes);
if tag_in_ptr {
let masked_ptr_to_data = deref_ptr_of_ptr(rec_ptr, ptr_bytes) as i64;
let (tag_id_bits, tag_id_mask) = tag_pointer_tag_id_bits_and_mask(ptr_bytes);
let tag_id = masked_ptr_to_data & (tag_id_mask as i64);
// Clear the tag ID data from the pointer
let ptr_to_data = ((masked_ptr_to_data >> tag_id_bits) << tag_id_bits) as *const u8;
(tag_id as i64, ptr_to_data)
} else {
let ptr_to_data = deref_ptr_of_ptr(rec_ptr, ptr_bytes);
let tag_id = tag_id_from_data(union_layout, ptr_to_data, ptr_bytes);
(tag_id, ptr_to_data)
}
}
fn jit_to_ast_help<'a>(
env: &Env<'a, 'a>,
lib: Library, lib: Library,
main_fn_name: &str, main_fn_name: &str,
layout: &Layout<'a>, layout: &Layout<'a>,
content: &Content, content: &'a Content,
) -> Result<Expr<'a>, ToAstProblem> { ) -> Result<Expr<'a>, ToAstProblem> {
match layout { let (newtype_tags, content) = unroll_newtypes(env, content);
let content = unroll_aliases(env, content);
let result = match layout {
Layout::Builtin(Builtin::Bool) => Ok(run_jit_function!(lib, main_fn_name, bool, |num| { Layout::Builtin(Builtin::Bool) => Ok(run_jit_function!(lib, main_fn_name, bool, |num| {
bool_to_ast(env, num, content) bool_to_ast(env, num, content)
})), })),
@ -200,151 +357,33 @@ fn jit_to_ast_help<'a>(
|bytes: *const u8| { ptr_to_ast(bytes as *const u8) } |bytes: *const u8| { ptr_to_ast(bytes as *const u8) }
) )
} }
Layout::Union(UnionLayout::NonRecursive(union_layouts)) => { Layout::Union(UnionLayout::NonRecursive(_)) => {
let union_layout = UnionLayout::NonRecursive(union_layouts);
match content {
Content::Structure(FlatType::TagUnion(tags, _)) => {
debug_assert_eq!(union_layouts.len(), tags.len());
let tags_vec: std::vec::Vec<(TagName, std::vec::Vec<Variable>)> = tags
.unsorted_iterator(env.subs, Variable::EMPTY_TAG_UNION)
.map(|(a, b)| (a.clone(), b.to_vec()))
.collect();
let tags_map: roc_collections::all::MutMap<_, _> =
tags_vec.iter().cloned().collect();
let union_variant =
union_sorted_tags_help(env.arena, tags_vec, None, env.subs, env.ptr_bytes);
let size = layout.stack_size(env.ptr_bytes); let size = layout.stack_size(env.ptr_bytes);
use roc_mono::layout::WrappedVariant::*;
match union_variant {
UnionVariant::Wrapped(variant) => {
match variant {
NonRecursive {
sorted_tag_layouts: tags_and_layouts,
} => {
Ok(run_jit_function_dynamic_type!( Ok(run_jit_function_dynamic_type!(
lib, lib,
main_fn_name, main_fn_name,
size as usize, size as usize,
|ptr: *const u8| { |ptr: *const u8| {
// Because this is a `Wrapped`, the first 8 bytes encode the tag ID ptr_to_ast(env, ptr, layout, WhenRecursive::Unreachable, content)
let offset = tags_and_layouts
.iter()
.map(|(_, fields)| {
fields
.iter()
.map(|l| l.stack_size(env.ptr_bytes))
.sum()
})
.max()
.unwrap_or(0);
let tag_id = match union_layout.tag_id_builtin() {
Builtin::Bool => {
*(ptr.add(offset as usize) as *const i8) as i64
}
Builtin::Int(IntWidth::U8) => {
*(ptr.add(offset as usize) as *const i8) as i64
}
Builtin::Int(IntWidth::U16) => {
*(ptr.add(offset as usize) as *const i16) as i64
}
Builtin::Int(IntWidth::U64) => {
// used by non-recursive unions at the
// moment, remove if that is no longer the case
*(ptr.add(offset as usize) as *const i64) as i64
}
_ => unreachable!("invalid tag id layout"),
};
// use the tag ID as an index, to get its name and layout of any arguments
let (tag_name, arg_layouts) =
&tags_and_layouts[tag_id as usize];
let tag_expr = tag_name_to_expr(env, tag_name);
let loc_tag_expr =
&*env.arena.alloc(Located::at_zero(tag_expr));
let variables = &tags_map[tag_name];
debug_assert_eq!(arg_layouts.len(), variables.len());
// NOTE assumes the data bytes are the first bytes
let it =
variables.iter().copied().zip(arg_layouts.iter());
let output = sequence_of_expr(env, ptr, it);
let output = output.into_bump_slice();
Expr::Apply(loc_tag_expr, output, CalledVia::Space)
} }
)) ))
} }
Recursive {
sorted_tag_layouts: tags_and_layouts,
} => {
Ok(run_jit_function_dynamic_type!(
lib,
main_fn_name,
size as usize,
|ptr: *const u8| {
// Because this is a `Wrapped`, the first 8 bytes encode the tag ID
let tag_id = *(ptr as *const i64);
// use the tag ID as an index, to get its name and layout of any arguments
let (tag_name, arg_layouts) =
&tags_and_layouts[tag_id as usize];
let tag_expr = tag_name_to_expr(env, tag_name);
let loc_tag_expr =
&*env.arena.alloc(Located::at_zero(tag_expr));
let variables = &tags_map[tag_name];
// because the arg_layouts include the tag ID, it is one longer
debug_assert_eq!(
arg_layouts.len() - 1,
variables.len()
);
// skip forward to the start of the first element, ignoring the tag id
let ptr = ptr.offset(8);
let it =
variables.iter().copied().zip(&arg_layouts[1..]);
let output = sequence_of_expr(env, ptr, it);
let output = output.into_bump_slice();
Expr::Apply(loc_tag_expr, output, CalledVia::Space)
}
))
}
_ => todo!(),
}
}
_ => unreachable!("any other variant would have a different layout"),
}
}
Content::Structure(FlatType::RecursiveTagUnion(_, _, _)) => {
todo!("print recursive tag unions in the REPL")
}
Content::Alias(_, _, actual) => {
let content = env.subs.get_content_without_compacting(*actual);
jit_to_ast_help(env, lib, main_fn_name, layout, content)
}
other => unreachable!("Weird content for Union layout: {:?}", other),
}
}
Layout::Union(UnionLayout::Recursive(_)) Layout::Union(UnionLayout::Recursive(_))
| Layout::Union(UnionLayout::NullableWrapped { .. })
| Layout::Union(UnionLayout::NullableUnwrapped { .. })
| Layout::Union(UnionLayout::NonNullableUnwrapped(_)) | Layout::Union(UnionLayout::NonNullableUnwrapped(_))
| Layout::RecursivePointer => { | Layout::Union(UnionLayout::NullableUnwrapped { .. })
todo!("add support for rendering recursive tag unions in the REPL") | Layout::Union(UnionLayout::NullableWrapped { .. }) => {
let size = layout.stack_size(env.ptr_bytes);
Ok(run_jit_function_dynamic_type!(
lib,
main_fn_name,
size as usize,
|ptr: *const u8| {
ptr_to_ast(env, ptr, layout, WhenRecursive::Loop(*layout), content)
}
))
}
Layout::RecursivePointer => {
unreachable!("RecursivePointers can only be inside structures")
} }
Layout::LambdaSet(lambda_set) => jit_to_ast_help( Layout::LambdaSet(lambda_set) => jit_to_ast_help(
env, env,
@ -353,7 +392,8 @@ fn jit_to_ast_help<'a>(
&lambda_set.runtime_representation(), &lambda_set.runtime_representation(),
content, content,
), ),
} };
result.map(|e| apply_newtypes(env, newtype_tags, e))
} }
fn tag_name_to_expr<'a>(env: &Env<'a, '_>, tag_name: &TagName) -> Expr<'a> { fn tag_name_to_expr<'a>(env: &Env<'a, '_>, tag_name: &TagName) -> Expr<'a> {
@ -370,11 +410,20 @@ fn tag_name_to_expr<'a>(env: &Env<'a, '_>, tag_name: &TagName) -> Expr<'a> {
} }
} }
/// Represents the layout of `RecursivePointer`s in a tag union, when recursive
/// tag unions are relevant.
#[derive(Clone, Copy, Debug, PartialEq)]
enum WhenRecursive<'a> {
Unreachable,
Loop(Layout<'a>),
}
fn ptr_to_ast<'a>( fn ptr_to_ast<'a>(
env: &Env<'a, '_>, env: &Env<'a, 'a>,
ptr: *const u8, ptr: *const u8,
layout: &Layout<'a>, layout: &Layout<'a>,
content: &Content, when_recursive: WhenRecursive<'a>,
content: &'a Content,
) -> Expr<'a> { ) -> Expr<'a> {
macro_rules! helper { macro_rules! helper {
($ty:ty) => {{ ($ty:ty) => {{
@ -384,7 +433,9 @@ fn ptr_to_ast<'a>(
}}; }};
} }
match layout { let (newtype_tags, content) = unroll_newtypes(env, content);
let content = unroll_aliases(env, content);
let expr = match layout {
Layout::Builtin(Builtin::Bool) => { Layout::Builtin(Builtin::Bool) => {
// TODO: bits are not as expected here. // TODO: bits are not as expected here.
// num is always false at the moment. // num is always false at the moment.
@ -453,17 +504,191 @@ fn ptr_to_ast<'a>(
); );
} }
}, },
Layout::RecursivePointer => {
match (content, when_recursive) {
(Content::RecursionVar {
structure,
opt_name: _,
}, WhenRecursive::Loop(union_layout)) => {
let content = env.subs.get_content_without_compacting(*structure);
ptr_to_ast(env, ptr, &union_layout, when_recursive, content)
}
other => unreachable!("Something had a RecursivePointer layout, but instead of being a RecursionVar and having a known recursive layout, I found {:?}", other),
}
}
Layout::Union(UnionLayout::NonRecursive(union_layouts)) => {
let union_layout = UnionLayout::NonRecursive(union_layouts);
let tags = match content {
Content::Structure(FlatType::TagUnion(tags, _)) => tags,
other => unreachable!("Weird content for nonrecursive Union layout: {:?}", other),
};
debug_assert_eq!(union_layouts.len(), tags.len());
let (vars_of_tag, union_variant) = get_tags_vars_and_variant(env, tags, None);
let tags_and_layouts = match union_variant {
UnionVariant::Wrapped(WrappedVariant::NonRecursive {
sorted_tag_layouts
}) => sorted_tag_layouts,
other => unreachable!("This layout tag union layout is nonrecursive but the variant isn't; found variant {:?}", other),
};
// Because this is a `NonRecursive`, the tag ID is definitely after the data.
let tag_id =
tag_id_from_data(union_layout, ptr, env.ptr_bytes);
// use the tag ID as an index, to get its name and layout of any arguments
let (tag_name, arg_layouts) =
&tags_and_layouts[tag_id as usize];
expr_of_tag(
env,
ptr,
tag_name,
arg_layouts,
&vars_of_tag[tag_name],
WhenRecursive::Unreachable,
)
}
Layout::Union(union_layout @ UnionLayout::Recursive(union_layouts)) => {
let (rec_var, tags) = match content {
Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags),
_ => unreachable!("any other content would have a different layout"),
};
debug_assert_eq!(union_layouts.len(), tags.len());
let (vars_of_tag, union_variant) =
get_tags_vars_and_variant(env, tags, Some(*rec_var));
let tags_and_layouts = match union_variant {
UnionVariant::Wrapped(WrappedVariant::Recursive {
sorted_tag_layouts
}) => sorted_tag_layouts,
_ => unreachable!("any other variant would have a different layout"),
};
let (tag_id, ptr_to_data) = tag_id_from_recursive_ptr(*union_layout, ptr, env.ptr_bytes);
let (tag_name, arg_layouts) = &tags_and_layouts[tag_id as usize];
expr_of_tag(
env,
ptr_to_data,
tag_name,
arg_layouts,
&vars_of_tag[tag_name],
when_recursive,
)
}
Layout::Union(UnionLayout::NonNullableUnwrapped(_)) => {
let (rec_var, tags) = match unroll_recursion_var(env, content) {
Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags),
other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other),
};
debug_assert_eq!(tags.len(), 1);
let (vars_of_tag, union_variant) = get_tags_vars_and_variant(env, tags, Some(*rec_var));
let (tag_name, arg_layouts) = match union_variant {
UnionVariant::Wrapped(WrappedVariant::NonNullableUnwrapped {
tag_name, fields,
}) => (tag_name, fields),
_ => unreachable!("any other variant would have a different layout"),
};
let ptr_to_data = deref_ptr_of_ptr(ptr, env.ptr_bytes);
expr_of_tag(
env,
ptr_to_data,
&tag_name,
arg_layouts,
&vars_of_tag[&tag_name],
when_recursive,
)
}
Layout::Union(UnionLayout::NullableUnwrapped { .. }) => {
let (rec_var, tags) = match unroll_recursion_var(env, content) {
Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags),
other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other),
};
debug_assert!(tags.len() <= 2);
let (vars_of_tag, union_variant) = get_tags_vars_and_variant(env, tags, Some(*rec_var));
let (nullable_name, other_name, other_arg_layouts) = match union_variant {
UnionVariant::Wrapped(WrappedVariant::NullableUnwrapped {
nullable_id: _,
nullable_name,
other_name,
other_fields,
}) => (nullable_name, other_name, other_fields),
_ => unreachable!("any other variant would have a different layout"),
};
let ptr_to_data = deref_ptr_of_ptr(ptr, env.ptr_bytes);
if ptr_to_data.is_null() {
tag_name_to_expr(env, &nullable_name)
} else {
expr_of_tag(
env,
ptr_to_data,
&other_name,
other_arg_layouts,
&vars_of_tag[&other_name],
when_recursive,
)
}
}
Layout::Union(union_layout @ UnionLayout::NullableWrapped { .. }) => {
let (rec_var, tags) = match unroll_recursion_var(env, content) {
Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags),
other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other),
};
let (vars_of_tag, union_variant) = get_tags_vars_and_variant(env, tags, Some(*rec_var));
let (nullable_id, nullable_name, tags_and_layouts) = match union_variant {
UnionVariant::Wrapped(WrappedVariant::NullableWrapped {
nullable_id,
nullable_name,
sorted_tag_layouts,
}) => (nullable_id, nullable_name, sorted_tag_layouts),
_ => unreachable!("any other variant would have a different layout"),
};
let ptr_to_data = deref_ptr_of_ptr(ptr, env.ptr_bytes);
if ptr_to_data.is_null() {
tag_name_to_expr(env, &nullable_name)
} else {
let (tag_id, ptr_to_data) = tag_id_from_recursive_ptr(*union_layout, ptr, env.ptr_bytes);
let tag_id = if tag_id > nullable_id.into() { tag_id - 1 } else { tag_id };
let (tag_name, arg_layouts) = &tags_and_layouts[tag_id as usize];
expr_of_tag(
env,
ptr_to_data,
tag_name,
arg_layouts,
&vars_of_tag[tag_name],
when_recursive,
)
}
}
other => { other => {
todo!( todo!(
"TODO add support for rendering pointer to {:?} in the REPL", "TODO add support for rendering pointer to {:?} in the REPL",
other other
); );
} }
} };
apply_newtypes(env, newtype_tags, expr)
} }
fn list_to_ast<'a>( fn list_to_ast<'a>(
env: &Env<'a, '_>, env: &Env<'a, 'a>,
ptr: *const u8, ptr: *const u8,
len: usize, len: usize,
elem_layout: &Layout<'a>, elem_layout: &Layout<'a>,
@ -493,8 +718,14 @@ fn list_to_ast<'a>(
for index in 0..len { for index in 0..len {
let offset_bytes = index * elem_size; let offset_bytes = index * elem_size;
let elem_ptr = unsafe { ptr.add(offset_bytes) }; let elem_ptr = unsafe { ptr.add(offset_bytes) };
let loc_expr = &*arena.alloc(Located { let loc_expr = &*arena.alloc(Loc {
value: ptr_to_ast(env, elem_ptr, elem_layout, elem_content), value: ptr_to_ast(
env,
elem_ptr,
elem_layout,
WhenRecursive::Unreachable,
elem_content,
),
region: Region::zero(), region: Region::zero(),
}); });
@ -507,7 +738,7 @@ fn list_to_ast<'a>(
} }
fn single_tag_union_to_ast<'a>( fn single_tag_union_to_ast<'a>(
env: &Env<'a, '_>, env: &Env<'a, 'a>,
ptr: *const u8, ptr: *const u8,
field_layouts: &'a [Layout<'a>], field_layouts: &'a [Layout<'a>],
tag_name: &TagName, tag_name: &TagName,
@ -516,15 +747,15 @@ fn single_tag_union_to_ast<'a>(
let arena = env.arena; let arena = env.arena;
let tag_expr = tag_name_to_expr(env, tag_name); let tag_expr = tag_name_to_expr(env, tag_name);
let loc_tag_expr = &*arena.alloc(Located::at_zero(tag_expr)); let loc_tag_expr = &*arena.alloc(Loc::at_zero(tag_expr));
let output = if field_layouts.len() == payload_vars.len() { let output = if field_layouts.len() == payload_vars.len() {
let it = payload_vars.iter().copied().zip(field_layouts); let it = payload_vars.iter().copied().zip(field_layouts);
sequence_of_expr(env, ptr as *const u8, it).into_bump_slice() sequence_of_expr(env, ptr as *const u8, it, WhenRecursive::Unreachable).into_bump_slice()
} else if field_layouts.is_empty() && !payload_vars.is_empty() { } else if field_layouts.is_empty() && !payload_vars.is_empty() {
// happens for e.g. `Foo Bar` where unit structures are nested and the inner one is dropped // happens for e.g. `Foo Bar` where unit structures are nested and the inner one is dropped
let it = payload_vars.iter().copied().zip([&Layout::Struct(&[])]); let it = payload_vars.iter().copied().zip([&Layout::Struct(&[])]);
sequence_of_expr(env, ptr as *const u8, it).into_bump_slice() sequence_of_expr(env, ptr as *const u8, it, WhenRecursive::Unreachable).into_bump_slice()
} else { } else {
unreachable!() unreachable!()
}; };
@ -533,10 +764,11 @@ fn single_tag_union_to_ast<'a>(
} }
fn sequence_of_expr<'a, I>( fn sequence_of_expr<'a, I>(
env: &Env<'a, '_>, env: &Env<'a, 'a>,
ptr: *const u8, ptr: *const u8,
sequence: I, sequence: I,
) -> Vec<'a, &'a Located<Expr<'a>>> when_recursive: WhenRecursive<'a>,
) -> Vec<'a, &'a Loc<Expr<'a>>>
where where
I: Iterator<Item = (Variable, &'a Layout<'a>)>, I: Iterator<Item = (Variable, &'a Layout<'a>)>,
I: ExactSizeIterator<Item = (Variable, &'a Layout<'a>)>, I: ExactSizeIterator<Item = (Variable, &'a Layout<'a>)>,
@ -550,8 +782,8 @@ where
for (var, layout) in sequence { for (var, layout) in sequence {
let content = subs.get_content_without_compacting(var); let content = subs.get_content_without_compacting(var);
let expr = ptr_to_ast(env, field_ptr, layout, content); let expr = ptr_to_ast(env, field_ptr, layout, when_recursive, content);
let loc_expr = Located::at_zero(expr); let loc_expr = Loc::at_zero(expr);
output.push(&*arena.alloc(loc_expr)); output.push(&*arena.alloc(loc_expr));
@ -563,7 +795,7 @@ where
} }
fn struct_to_ast<'a>( fn struct_to_ast<'a>(
env: &Env<'a, '_>, env: &Env<'a, 'a>,
ptr: *const u8, ptr: *const u8,
field_layouts: &'a [Layout<'a>], field_layouts: &'a [Layout<'a>],
record_fields: RecordFields, record_fields: RecordFields,
@ -583,16 +815,22 @@ fn struct_to_ast<'a>(
let inner_content = env.subs.get_content_without_compacting(field.into_inner()); let inner_content = env.subs.get_content_without_compacting(field.into_inner());
let loc_expr = &*arena.alloc(Located { let loc_expr = &*arena.alloc(Loc {
value: ptr_to_ast(env, ptr, &Layout::Struct(field_layouts), inner_content), value: ptr_to_ast(
env,
ptr,
&Layout::Struct(field_layouts),
WhenRecursive::Unreachable,
inner_content,
),
region: Region::zero(), region: Region::zero(),
}); });
let field_name = Located { let field_name = Loc {
value: &*arena.alloc_str(label.as_str()), value: &*arena.alloc_str(label.as_str()),
region: Region::zero(), region: Region::zero(),
}; };
let loc_field = Located { let loc_field = Loc {
value: AssignedField::RequiredValue(field_name, &[], loc_expr), value: AssignedField::RequiredValue(field_name, &[], loc_expr),
region: Region::zero(), region: Region::zero(),
}; };
@ -610,16 +848,22 @@ fn struct_to_ast<'a>(
let var = field.into_inner(); let var = field.into_inner();
let content = subs.get_content_without_compacting(var); let content = subs.get_content_without_compacting(var);
let loc_expr = &*arena.alloc(Located { let loc_expr = &*arena.alloc(Loc {
value: ptr_to_ast(env, field_ptr, field_layout, content), value: ptr_to_ast(
env,
field_ptr,
field_layout,
WhenRecursive::Unreachable,
content,
),
region: Region::zero(), region: Region::zero(),
}); });
let field_name = Located { let field_name = Loc {
value: &*arena.alloc_str(label.as_str()), value: &*arena.alloc_str(label.as_str()),
region: Region::zero(), region: Region::zero(),
}; };
let loc_field = Located { let loc_field = Loc {
value: AssignedField::RequiredValue(field_name, &[], loc_expr), value: AssignedField::RequiredValue(field_name, &[], loc_expr),
region: Region::zero(), region: Region::zero(),
}; };
@ -683,7 +927,7 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a
.next() .next()
.unwrap(); .unwrap();
let loc_label = Located { let loc_label = Loc {
value: &*arena.alloc_str(label.as_str()), value: &*arena.alloc_str(label.as_str()),
region: Region::zero(), region: Region::zero(),
}; };
@ -694,7 +938,7 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a
// so we need to do this recursively on the field type. // so we need to do this recursively on the field type.
let field_var = *field.as_inner(); let field_var = *field.as_inner();
let field_content = env.subs.get_content_without_compacting(field_var); let field_content = env.subs.get_content_without_compacting(field_var);
let loc_expr = Located { let loc_expr = Loc {
value: bool_to_ast(env, value, field_content), value: bool_to_ast(env, value, field_content),
region: Region::zero(), region: Region::zero(),
}; };
@ -702,7 +946,7 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a
AssignedField::RequiredValue(loc_label, &[], arena.alloc(loc_expr)) AssignedField::RequiredValue(loc_label, &[], arena.alloc(loc_expr))
}; };
let loc_assigned_field = Located { let loc_assigned_field = Loc {
value: assigned_field, value: assigned_field,
region: Region::zero(), region: Region::zero(),
}; };
@ -720,7 +964,7 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a
Expr::GlobalTag(arena.alloc_str(tag_name)) Expr::GlobalTag(arena.alloc_str(tag_name))
}; };
&*arena.alloc(Located { &*arena.alloc(Loc {
value: tag_expr, value: tag_expr,
region: Region::zero(), region: Region::zero(),
}) })
@ -734,7 +978,7 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a
let var = *payload_vars.iter().next().unwrap(); let var = *payload_vars.iter().next().unwrap();
let content = env.subs.get_content_without_compacting(var); let content = env.subs.get_content_without_compacting(var);
let loc_payload = &*arena.alloc(Located { let loc_payload = &*arena.alloc(Loc {
value: bool_to_ast(env, value, content), value: bool_to_ast(env, value, content),
region: Region::zero(), region: Region::zero(),
}); });
@ -795,7 +1039,7 @@ fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a>
.next() .next()
.unwrap(); .unwrap();
let loc_label = Located { let loc_label = Loc {
value: &*arena.alloc_str(label.as_str()), value: &*arena.alloc_str(label.as_str()),
region: Region::zero(), region: Region::zero(),
}; };
@ -806,7 +1050,7 @@ fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a>
// so we need to do this recursively on the field type. // so we need to do this recursively on the field type.
let field_var = *field.as_inner(); let field_var = *field.as_inner();
let field_content = env.subs.get_content_without_compacting(field_var); let field_content = env.subs.get_content_without_compacting(field_var);
let loc_expr = Located { let loc_expr = Loc {
value: byte_to_ast(env, value, field_content), value: byte_to_ast(env, value, field_content),
region: Region::zero(), region: Region::zero(),
}; };
@ -814,7 +1058,7 @@ fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a>
AssignedField::RequiredValue(loc_label, &[], arena.alloc(loc_expr)) AssignedField::RequiredValue(loc_label, &[], arena.alloc(loc_expr))
}; };
let loc_assigned_field = Located { let loc_assigned_field = Loc {
value: assigned_field, value: assigned_field,
region: Region::zero(), region: Region::zero(),
}; };
@ -832,7 +1076,7 @@ fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a>
Expr::GlobalTag(arena.alloc_str(tag_name)) Expr::GlobalTag(arena.alloc_str(tag_name))
}; };
&*arena.alloc(Located { &*arena.alloc(Loc {
value: tag_expr, value: tag_expr,
region: Region::zero(), region: Region::zero(),
}) })
@ -846,7 +1090,7 @@ fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a>
let var = *payload_vars.iter().next().unwrap(); let var = *payload_vars.iter().next().unwrap();
let content = env.subs.get_content_without_compacting(var); let content = env.subs.get_content_without_compacting(var);
let loc_payload = &*arena.alloc(Located { let loc_payload = &*arena.alloc(Loc {
value: byte_to_ast(env, value, content), value: byte_to_ast(env, value, content),
region: Region::zero(), region: Region::zero(),
}); });
@ -872,7 +1116,7 @@ fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a>
UnionVariant::ByteUnion(tagnames) => { UnionVariant::ByteUnion(tagnames) => {
let tag_name = &tagnames[value as usize]; let tag_name = &tagnames[value as usize];
let tag_expr = tag_name_to_expr(env, tag_name); let tag_expr = tag_name_to_expr(env, tag_name);
let loc_tag_expr = Located::at_zero(tag_expr); let loc_tag_expr = Loc::at_zero(tag_expr);
Expr::Apply(env.arena.alloc(loc_tag_expr), &[], CalledVia::Space) Expr::Apply(env.arena.alloc(loc_tag_expr), &[], CalledVia::Space)
} }
_ => unreachable!("invalid union variant for a Byte!"), _ => unreachable!("invalid union variant for a Byte!"),
@ -915,7 +1159,7 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E
.next() .next()
.unwrap(); .unwrap();
let loc_label = Located { let loc_label = Loc {
value: &*arena.alloc_str(label.as_str()), value: &*arena.alloc_str(label.as_str()),
region: Region::zero(), region: Region::zero(),
}; };
@ -926,14 +1170,14 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E
// so we need to do this recursively on the field type. // so we need to do this recursively on the field type.
let field_var = *field.as_inner(); let field_var = *field.as_inner();
let field_content = env.subs.get_content_without_compacting(field_var); let field_content = env.subs.get_content_without_compacting(field_var);
let loc_expr = Located { let loc_expr = Loc {
value: num_to_ast(env, num_expr, field_content), value: num_to_ast(env, num_expr, field_content),
region: Region::zero(), region: Region::zero(),
}; };
AssignedField::RequiredValue(loc_label, &[], arena.alloc(loc_expr)) AssignedField::RequiredValue(loc_label, &[], arena.alloc(loc_expr))
}; };
let loc_assigned_field = Located { let loc_assigned_field = Loc {
value: assigned_field, value: assigned_field,
region: Region::zero(), region: Region::zero(),
}; };
@ -960,7 +1204,7 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E
Expr::GlobalTag(arena.alloc_str(tag_name)) Expr::GlobalTag(arena.alloc_str(tag_name))
}; };
&*arena.alloc(Located { &*arena.alloc(Loc {
value: tag_expr, value: tag_expr,
region: Region::zero(), region: Region::zero(),
}) })
@ -974,7 +1218,7 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E
let var = *payload_vars.iter().next().unwrap(); let var = *payload_vars.iter().next().unwrap();
let content = env.subs.get_content_without_compacting(var); let content = env.subs.get_content_without_compacting(var);
let loc_payload = &*arena.alloc(Located { let loc_payload = &*arena.alloc(Loc {
value: num_to_ast(env, num_expr, content), value: num_to_ast(env, num_expr, content),
region: Region::zero(), region: Region::zero(),
}); });
@ -1043,30 +1287,3 @@ fn str_slice_to_ast<'a>(_arena: &'a Bump, string: &'a str) -> Expr<'a> {
Expr::Str(StrLiteral::PlainLine(string)) Expr::Str(StrLiteral::PlainLine(string))
} }
} }
// TODO this is currently nighly-only: use the implementation in std once it's stabilized
pub fn max_by<T, F: FnOnce(&T, &T) -> std::cmp::Ordering>(v1: T, v2: T, compare: F) -> T {
use std::cmp::Ordering;
match compare(&v1, &v2) {
Ordering::Less | Ordering::Equal => v2,
Ordering::Greater => v1,
}
}
pub fn min_by<T, F: FnOnce(&T, &T) -> std::cmp::Ordering>(v1: T, v2: T, compare: F) -> T {
use std::cmp::Ordering;
match compare(&v1, &v2) {
Ordering::Less | Ordering::Equal => v1,
Ordering::Greater => v2,
}
}
pub fn max_by_key<T, F: FnMut(&T) -> K, K: Ord>(v1: T, v2: T, mut f: F) -> T {
max_by(v1, v2, |v1, v2| f(v1).cmp(&f(v2)))
}
pub fn min_by_key<T, F: FnMut(&T) -> K, K: Ord>(v1: T, v2: T, mut f: F) -> T {
min_by(v1, v2, |v1, v2| f(v1).cmp(&f(v2)))
}

View file

@ -601,6 +601,14 @@ mod cli_run {
expected_ending: "", expected_ending: "",
use_valgrind: true, use_valgrind: true,
}, },
issue2279 => Example {
filename: "Issue2279.roc",
executable_filename: "issue2279",
stdin: &[],
input_file: None,
expected_ending: "Hello, world!\n",
use_valgrind: true,
},
quicksort_app => Example { quicksort_app => Example {
filename: "QuicksortApp.roc", filename: "QuicksortApp.roc",
executable_filename: "quicksortapp", executable_filename: "quicksortapp",

View file

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

View file

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

View file

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

View file

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

View file

@ -177,6 +177,51 @@ mod repl_eval {
expect_success("Foo Bar", "Foo Bar : [ Foo [ Bar ]* ]*"); 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] #[test]
fn tag_with_arguments() { fn tag_with_arguments() {
expect_success("True 1", "True 1 : [ True (Num *) ]*"); expect_success("True 1", "True 1 : [ True (Num *) ]*");
@ -567,6 +612,207 @@ mod repl_eval {
); );
} }
#[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] // #[test]
// fn parse_problem() { // fn parse_problem() {
// // can't find something that won't parse currently // // can't find something that won't parse currently

10
cli_utils/Cargo.lock generated
View file

@ -1310,13 +1310,13 @@ dependencies = [
name = "inkwell" name = "inkwell"
version = "0.1.0" version = "0.1.0"
dependencies = [ 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]] [[package]]
name = "inkwell" name = "inkwell"
version = "0.1.0" 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 = [ dependencies = [
"either", "either",
"inkwell_internals", "inkwell_internals",
@ -1324,13 +1324,12 @@ dependencies = [
"llvm-sys", "llvm-sys",
"once_cell", "once_cell",
"parking_lot", "parking_lot",
"regex",
] ]
[[package]] [[package]]
name = "inkwell_internals" name = "inkwell_internals"
version = "0.3.0" version = "0.5.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 = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2666,6 +2665,7 @@ dependencies = [
"roc_collections", "roc_collections",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
"roc_reporting",
"roc_std", "roc_std",
"target-lexicon", "target-lexicon",
] ]

View file

@ -32,7 +32,8 @@ pub fn path_to_roc_binary() -> PathBuf {
.or_else(|| { .or_else(|| {
env::current_exe().ok().map(|mut path| { env::current_exe().ok().map(|mut path| {
path.pop(); path.pop();
if path.ends_with("deps") { path.pop(); if path.ends_with("deps") {
path.pop();
} }
path path
}) })

View file

@ -11,6 +11,13 @@ use std::path::{Path, PathBuf};
use std::process::{Child, Command, Output}; use std::process::{Child, Command, Output};
use target_lexicon::{Architecture, OperatingSystem, Triple}; 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)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum LinkType { pub enum LinkType {
// These numbers correspond to the --lib flag; if it's present // 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>, shared_lib_path: Option<&Path>,
_target_valgrind: bool, _target_valgrind: bool,
) -> Output { ) -> Output {
let mut command = Command::new("zig"); let mut command = Command::new(&zig_executable());
command command
.env_clear() .env_clear()
.env("PATH", env_path) .env("PATH", env_path)
@ -151,7 +158,10 @@ pub fn build_zig_host_native(
use serde_json::Value; use serde_json::Value;
// Run `zig env` to find the location of zig's std/ directory // 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() { let zig_env_json = if zig_env_output.status.success() {
std::str::from_utf8(&zig_env_output.stdout).unwrap_or_else(|utf8_err| { 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("special");
zig_compiler_rt_path.push("compiler_rt.zig"); zig_compiler_rt_path.push("compiler_rt.zig");
let mut command = Command::new("zig"); let mut command = Command::new(&zig_executable());
command command
.env_clear() .env_clear()
.env("PATH", &env_path) .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 // we'd like to compile with `-target wasm32-wasi` but that is blocked on
// //
// https://github.com/ziglang/zig/issues/9414 // https://github.com/ziglang/zig/issues/9414
let mut command = Command::new("zig"); let mut command = Command::new(&zig_executable());
command command
.env_clear() .env_clear()
.env("PATH", env_path) .env("PATH", env_path)
@ -442,7 +452,7 @@ pub fn rebuild_host(
_ => panic!("Unsupported architecture {:?}", target.architecture), _ => panic!("Unsupported architecture {:?}", target.architecture),
}; };
validate_output("host.zig", "zig", output) validate_output("host.zig", &zig_executable(), output)
} else if cargo_host_src.exists() { } else if cargo_host_src.exists() {
// Compile and link Cargo.toml, if it exists // Compile and link Cargo.toml, if it exists
let cargo_dir = host_input_path.parent().unwrap(); let cargo_dir = host_input_path.parent().unwrap();
@ -646,7 +656,7 @@ fn link_linux(
if let Architecture::X86_32(_) = target.architecture { if let Architecture::X86_32(_) = target.architecture {
return Ok(( return Ok((
Command::new("zig") Command::new(&zig_executable())
.args(&["build-exe"]) .args(&["build-exe"])
.args(input_paths) .args(input_paths)
.args(&[ .args(&[
@ -895,7 +905,7 @@ fn link_wasm32(
let zig_str_path = find_zig_str_path(); let zig_str_path = find_zig_str_path();
let wasi_libc_path = find_wasi_libc_path(); let wasi_libc_path = find_wasi_libc_path();
let child = Command::new("zig") let child = Command::new(&zig_executable())
// .env_clear() // .env_clear()
// .env("PATH", &env_path) // .env("PATH", &env_path)
.args(&["build-exe"]) .args(&["build-exe"])

View file

@ -1,5 +1,6 @@
const std = @import("std"); const std = @import("std");
const str = @import("str.zig"); const str = @import("str.zig");
const num_ = @import("num.zig");
const utils = @import("utils.zig"); const utils = @import("utils.zig");
const math = std.math; const math = std.math;
@ -1052,6 +1053,14 @@ test "div: 10 / 3" {
// exports // exports
pub fn fromStr(arg: RocStr) callconv(.C) num_.NumParseResult(i128) {
if (@call(.{ .modifier = always_inline }, RocDec.fromStr, .{arg})) |dec| {
return .{ .errorcode = 0, .value = dec.num };
} else {
return .{ .errorcode = 1, .value = 0 };
}
}
pub fn fromF64C(arg: f64) callconv(.C) i128 { pub fn fromF64C(arg: f64) callconv(.C) i128 {
return if (@call(.{ .modifier = always_inline }, RocDec.fromF64, .{arg})) |dec| dec.num else @panic("TODO runtime exception failing convert f64 to RocDec"); return if (@call(.{ .modifier = always_inline }, RocDec.fromF64, .{arg})) |dec| dec.num else @panic("TODO runtime exception failing convert f64 to RocDec");
} }

View file

@ -216,7 +216,7 @@ pub const RocDict = extern struct {
} }
// otherwise, check if the refcount is one // 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; 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 // 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; return (ptr - 1)[0] == utils.REFCOUNT_ONE;
} }
@ -1282,7 +1282,7 @@ pub fn listSet(
// `if inBounds then LowLevelListGet input index item else input` // `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, // 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 // 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) { if ((ptr - 1)[0] == utils.REFCOUNT_ONE) {
return listSetInPlaceHelp(bytes, index, element, element_width, dec); return listSetInPlaceHelp(bytes, index, element, element_width, dec);

View file

@ -10,6 +10,7 @@ const STR = "str";
const dec = @import("dec.zig"); const dec = @import("dec.zig");
comptime { comptime {
exportDecFn(dec.fromStr, "from_str");
exportDecFn(dec.fromF64C, "from_f64"); exportDecFn(dec.fromF64C, "from_f64");
exportDecFn(dec.eqC, "eq"); exportDecFn(dec.eqC, "eq");
exportDecFn(dec.neqC, "neq"); exportDecFn(dec.neqC, "neq");
@ -131,6 +132,11 @@ comptime {
inline for (INTEGERS) |T| { inline for (INTEGERS) |T| {
str.exportFromInt(T, ROC_BUILTINS ++ "." ++ STR ++ ".from_int."); str.exportFromInt(T, ROC_BUILTINS ++ "." ++ STR ++ ".from_int.");
num.exportParseInt(T, ROC_BUILTINS ++ "." ++ STR ++ ".to_int.");
}
inline for (FLOATS) |T| {
num.exportParseFloat(T, ROC_BUILTINS ++ "." ++ STR ++ ".to_float.");
} }
} }

View file

@ -2,6 +2,48 @@ const std = @import("std");
const always_inline = std.builtin.CallOptions.Modifier.always_inline; const always_inline = std.builtin.CallOptions.Modifier.always_inline;
const math = std.math; const math = std.math;
const RocList = @import("list.zig").RocList; const RocList = @import("list.zig").RocList;
const RocStr = @import("str.zig").RocStr;
pub fn NumParseResult(comptime T: type) type {
// on the roc side we sort by alignment; putting the errorcode last
// always works out (no number with smaller alignment than 1)
return extern struct {
value: T,
errorcode: u8, // 0 indicates success
};
}
pub fn exportParseInt(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(buf: RocStr) callconv(.C) NumParseResult(T) {
// a radix of 0 will make zig determine the radix from the frefix:
// * A prefix of "0b" implies radix=2,
// * A prefix of "0o" implies radix=8,
// * A prefix of "0x" implies radix=16,
// * Otherwise radix=10 is assumed.
const radix = 0;
if (std.fmt.parseInt(T, buf.asSlice(), radix)) |success| {
return .{ .errorcode = 0, .value = success };
} else |err| {
return .{ .errorcode = 1, .value = 0 };
}
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportParseFloat(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(buf: RocStr) callconv(.C) NumParseResult(T) {
if (std.fmt.parseFloat(T, buf.asSlice())) |success| {
return .{ .errorcode = 0, .value = success };
} else |err| {
return .{ .errorcode = 1, .value = 0 };
}
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
pub fn exportPow(comptime T: type, comptime name: []const u8) void { pub fn exportPow(comptime T: type, comptime name: []const u8) void {
comptime var f = struct { comptime var f = struct {

View file

@ -16,7 +16,7 @@ const InPlace = enum(u8) {
}; };
const SMALL_STR_MAX_LENGTH = small_string_size - 1; 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); const blank_small_string: [@sizeOf(RocStr)]u8 = init_blank_small_string(small_string_size);
fn init_blank_small_string(comptime n: usize) [n]u8 { 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 const alignment = @alignOf(usize);
pub inline fn empty() RocStr { pub inline fn empty() RocStr {
const small_str_flag: isize = std.math.minInt(isize);
return RocStr{ return RocStr{
.str_len = 0, .str_len = @bitCast(usize, small_str_flag),
.str_bytes = null, .str_bytes = null,
}; };
} }
@ -80,7 +81,7 @@ pub const RocStr = extern struct {
} }
pub fn deinit(self: RocStr) void { 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); 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 { 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 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; return true;
} }
@ -121,28 +119,34 @@ pub const RocStr = extern struct {
return false; return false;
} }
const self_u8_ptr: [*]const u8 = @ptrCast([*]const u8, &self); // Now we have to look at the string contents
const other_u8_ptr: [*]const u8 = @ptrCast([*]const u8, &other); const self_bytes = self.asU8ptr();
const self_bytes: [*]const u8 = if (self.isSmallStr() or self.isEmpty()) self_u8_ptr else self_bytes_ptr orelse unreachable; const other_bytes = other.asU8ptr();
const other_bytes: [*]const u8 = if (other.isSmallStr() or other.isEmpty()) other_u8_ptr else other_bytes_ptr orelse unreachable;
var index: usize = 0; // 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
// TODO rewrite this into a for loop const self_words = @ptrCast([*]const usize, @alignCast(@alignOf(usize), self_bytes));
const length = self.len(); const other_words = @ptrCast([*]const usize, @alignCast(@alignOf(usize), other_bytes));
while (index < length) { var w: usize = 0;
if (self_bytes[index] != other_bytes[index]) { while (w < self_len / @sizeOf(usize)) : (w += 1) {
if (self_words[w] != other_words[w]) {
return false; 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; return true;
} }
pub fn clone(in_place: InPlace, str: RocStr) RocStr { pub fn clone(in_place: InPlace, str: RocStr) RocStr {
if (str.isSmallStr() or str.isEmpty()) { if (str.isSmallStr()) {
// just return the bytes // just return the bytes
return str; return str;
} else { } else {
@ -214,7 +218,8 @@ pub const RocStr = extern struct {
} }
pub fn isEmpty(self: RocStr) bool { 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 // If a string happens to be null-terminated already, then we can pass its
@ -246,7 +251,7 @@ pub const RocStr = extern struct {
} else { } else {
// This is a big string, and it's not empty, so we can safely // This is a big string, and it's not empty, so we can safely
// dereference the pointer. // 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]; const capacity_or_refcount: isize = (ptr - 1)[0];
// If capacity_or_refcount is positive, then it's a capacity value. // 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! // to first change its flag to mark it as a small string!
return longest_small_str; return longest_small_str;
} else { } 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]; const capacity_or_refcount: isize = (ptr - 1)[0];
if (capacity_or_refcount > 0) { if (capacity_or_refcount > 0) {
@ -294,11 +299,6 @@ pub const RocStr = extern struct {
} }
pub fn isUnique(self: RocStr) bool { 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 // small strings can be copied
if (self.isSmallStr()) { if (self.isSmallStr()) {
return true; return true;
@ -309,7 +309,7 @@ pub const RocStr = extern struct {
} }
fn isRefcountOne(self: RocStr) bool { 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; 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, // Since this conditional would be prone to branch misprediction,
// make sure it will compile to a cmov. // 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)); // return if (self.isSmallStr()) (&@bitCast([@sizeOf(RocStr)]u8, self)) else (@ptrCast([*]u8, self.str_bytes));
if (self.isSmallStr() or self.isEmpty()) { if (self.isSmallStr()) {
const as_int = @ptrToInt(&self); const as_int = @ptrToInt(&self);
const as_ptr = @intToPtr([*]u8, as_int); const as_ptr = @intToPtr([*]u8, as_int);
return as_ptr; return as_ptr;
@ -342,7 +342,7 @@ pub const RocStr = extern struct {
@memcpy(dest, src, self.len()); @memcpy(dest, src, self.len());
} }
test "RocStr.eq: equal" { test "RocStr.eq: small, equal" {
const str1_len = 3; const str1_len = 3;
var str1: [str1_len]u8 = "abc".*; var str1: [str1_len]u8 = "abc".*;
const str1_ptr: [*]u8 = &str1; const str1_ptr: [*]u8 = &str1;
@ -359,7 +359,7 @@ pub const RocStr = extern struct {
roc_str2.deinit(); roc_str2.deinit();
} }
test "RocStr.eq: not equal different length" { test "RocStr.eq: small, not equal, different length" {
const str1_len = 4; const str1_len = 4;
var str1: [str1_len]u8 = "abcd".*; var str1: [str1_len]u8 = "abcd".*;
const str1_ptr: [*]u8 = &str1; const str1_ptr: [*]u8 = &str1;
@ -378,7 +378,7 @@ pub const RocStr = extern struct {
try expect(!roc_str1.eq(roc_str2)); 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; const str1_len = 3;
var str1: [str1_len]u8 = "acb".*; var str1: [str1_len]u8 = "acb".*;
const str1_ptr: [*]u8 = &str1; const str1_ptr: [*]u8 = &str1;
@ -396,6 +396,67 @@ pub const RocStr = extern struct {
try expect(!roc_str1.eq(roc_str2)); 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 { 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_bytes_ptrs = delimiter.asU8ptr();
const delimiter_len = delimiter.len(); 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; const end_index: usize = str_len - delimiter_len + 1;
while (str_index <= end_index) { while (str_index <= end_index) {
var delimiter_index: usize = 0; 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); 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" { test "strSplitInPlace: no delimiter" {
// Str.split "abc" "!" == [ "abc" ] // Str.split "abc" "!" == [ "abc" ]
const str_arr = "abc"; const str_arr = "abc";
@ -673,7 +768,7 @@ pub fn countSegments(string: RocStr, delimiter: RocStr) callconv(.C) usize {
var count: usize = 1; var count: usize = 1;
if (str_len > delimiter_len) { if (str_len > delimiter_len and delimiter_len > 0) {
var str_index: usize = 0; var str_index: usize = 0;
const end_cond: usize = str_len - delimiter_len + 1; const end_cond: usize = str_len - delimiter_len + 1;

View file

@ -247,7 +247,7 @@ pub const RocResult = extern struct {
// - the tag is the first field // - the tag is the first field
// - the tag is usize bytes wide // - the tag is usize bytes wide
// - Ok has tag_id 1, because Err < Ok // - 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; return usizes[0] == 1;
} }

View file

@ -7,6 +7,13 @@ use std::path::Path;
use std::process::Command; use std::process::Command;
use std::str; use std::str;
fn zig_executable() -> String {
match std::env::var("ROC_ZIG") {
Ok(path) => path,
Err(_) => "zig".into(),
}
}
fn main() { fn main() {
println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=build.rs");
@ -89,7 +96,7 @@ fn generate_object_file(
run_command( run_command(
&bitcode_path, &bitcode_path,
"zig", &zig_executable(),
&["build", zig_object, "-Drelease=true"], &["build", zig_object, "-Drelease=true"],
); );
@ -113,7 +120,7 @@ fn generate_bc_file(
run_command( run_command(
&bitcode_path, &bitcode_path,
"zig", &zig_executable(),
&["build", zig_object, "-Drelease=true"], &["build", zig_object, "-Drelease=true"],
); );
@ -177,26 +184,3 @@ fn get_zig_files(dir: &Path, cb: &dyn Fn(&Path)) -> io::Result<()> {
} }
Ok(()) 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 interface Bool
exposes [ not, and, or, xor, isEq, isNotEq ] exposes [ and, isEq, isNotEq, not, or, xor ]
imports [] imports []
## Returns `False` when given `True`, and vice versa. ## Returns `False` when given `True`, and vice versa.

View file

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

View file

@ -2,41 +2,41 @@ interface List
exposes exposes
[ [
List, List,
isEmpty,
get,
set,
append, append,
len,
walkBackwards,
concat, concat,
first,
single,
repeat,
reverse,
prepend,
join,
keepIf,
contains, contains,
sum, drop,
walk, dropAt,
last, dropLast,
keepOks, first,
get,
isEmpty,
join,
keepErrs, keepErrs,
keepIf,
keepOks,
last,
len,
map, map,
map2, map2,
map3, map3,
map4, map4,
mapWithIndex,
mapOrDrop,
mapJoin, mapJoin,
mapOrDrop,
mapWithIndex,
prepend,
product, product,
walkUntil,
range, range,
repeat,
reverse,
set,
single,
sortWith, sortWith,
drop, sum,
dropAt, swap,
dropLast, walk,
swap walkBackwards,
walkUntil
] ]
imports [] imports []
@ -446,9 +446,6 @@ drop : List elem, Nat -> List elem
## To replace the element at a given index, instead of dropping it, see [List.set]. ## To replace the element at a given index, instead of dropping it, see [List.set].
dropAt : List elem, Nat -> List elem 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. ## Adds a new element to the end of the list.
## ##
## >>> List.append [ "a", "b" ] "c" ## >>> List.append [ "a", "b" ] "c"
@ -685,8 +682,6 @@ startsWith : List elem, List elem -> Bool
endsWith : 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 ## Run the given predicate on each element of the list, returning `True` if
## any of the elements satisfy it. ## any of the elements satisfy it.
any : List elem, (elem -> Bool) -> Bool any : List elem, (elem -> Bool) -> Bool
@ -698,3 +693,11 @@ all : List elem, (elem -> Bool) -> Bool
## Returns the first element of the list satisfying a predicate function. ## Returns the first element of the list satisfying a predicate function.
## If no satisfying element is found, an `Err NotFound` is returned. ## If no satisfying element is found, an `Err NotFound` is returned.
find : List elem, (elem -> Bool) -> Result elem [ NotFound ]* 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,91 @@ interface Num
exposes exposes
[ [
Num, 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, Binary64,
Binary32, 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, bitwiseAnd,
bitwiseXor,
bitwiseOr, bitwiseOr,
bitwiseXor,
ceiling,
compare,
cos,
div,
divFloor,
floor,
intCast,
isEven,
isGt,
isGte,
isLt,
isLte,
isMultipleOf,
isNegative,
isOdd,
isPositive,
isZero,
log,
maxFloat,
maxI128,
maxInt,
minFloat,
minInt,
modInt,
modFloat,
mul,
mulChecked,
mulWrap,
neg,
pow,
powInt,
rem,
round,
shiftLeftBy, shiftLeftBy,
shiftRightBy, shiftRightBy,
shiftRightZfBy, shiftRightZfBy,
subWrap, sin,
sub,
subChecked, subChecked,
mulWrap, subWrap,
mulChecked, sqrt,
intCast, tan,
maxI128, toFloat,
isMultipleOf toStr
] ]
imports [] imports []
@ -621,13 +621,13 @@ toStr : Num * -> Str
## Examples: ## Examples:
## ##
## In some countries (e.g. USA and UK), a comma is used to separate thousands: ## 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: ## 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: ## 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 : format :
Num *, Num *,
{ {
@ -824,15 +824,15 @@ maxF32 : F32
## If you go lower than this, your running Roc code will crash - so be careful not to! ## If you go lower than this, your running Roc code will crash - so be careful not to!
minF32 : F32 minF32 : F32
## The highest supported #F64 value you can have, which is approximately 1.8 × 10^308. ## 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! ## If you go higher than this, your running Roc code will crash - so be careful not to!
maxDec : Dec maxDec : Dec
## The lowest supported #F64 value you can have, which is approximately -1.8 × 10^308. ## The lowest supported #Dec value you can have, which is precisely -170_141_183_460_469_231_731.687303715884105728.
## ##
## If you go lower than this, your running Roc code will crash - so be careful not to! ## If you go lower than this, your running Roc code will crash - so be careful not to!
maxDec : Dec minDec : Dec
## Constants ## Constants

View file

@ -2,10 +2,12 @@ interface Result
exposes exposes
[ [
Result, Result,
after,
isOk,
isErr,
map, map,
mapErr, mapErr,
withDefault, withDefault
after
] ]
imports [] imports []
@ -13,6 +15,16 @@ interface Result
## okay, or else there was an error of some sort. ## okay, or else there was an error of some sort.
Result ok err : [ @Result ok err ] 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 ## If the result is `Ok`, return the value it holds. Otherwise, return
## the given default value. ## the given default value.
## ##

View file

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

View file

@ -2,19 +2,19 @@ interface Str
exposes exposes
[ [
Str, Str,
isEmpty,
append, append,
concat, concat,
joinWith,
split,
countGraphemes, countGraphemes,
startsWith,
endsWith, endsWith,
fromUtf8, fromUtf8,
Utf8Problem, isEmpty,
Utf8ByteProblem, joinWith,
split,
startsWith,
startsWithCodePt,
toUtf8, toUtf8,
startsWithCodePt Utf8Problem,
Utf8ByteProblem
] ]
imports [] imports []
@ -433,36 +433,33 @@ toDec : Str -> Result Dec [ InvalidDec ]*
## If the string represents a valid number, return that number. ## If the string represents a valid number, return that number.
## ##
## The exact number type to look for will be inferred from usage. Here's an ## The exact number type to look for will be inferred from usage.
## example where the `Err` branch matches `Integer Signed64`, which causes this to ## In the example below, the usage of I64 in the type signature will require that type instead of (Num *).
## parse an [I64] because [I64] is defined as `I64 : Num [ Integer [ Signed64 ] ]`.
## ##
## >>> when Str.toNum "12345" is ## >>> strToI64 : Str -> Result I64 [ InvalidNumStr ]*
## >>> Ok i64 -> "The I64 was: \(i64)" ## >>> strToI64 = \inputStr ->
## >>> Err (ExpectedNum (Integer Signed64)) -> "Not a valid I64!" ## >>> Str.toNum inputStr
## ##
## If the string is exactly `"NaN"`, `"∞"`, or `"-∞"`, they will be accepted ## If the string is exactly `"NaN"`, `"∞"`, or `"-∞"`, they will be accepted
## only when converting to [F64] or [F32] numbers, and will be translated accordingly. ## only when converting to [F64] or [F32] numbers, and will be translated accordingly.
## ##
## This never accepts numbers with underscores or commas in them. For more ## This never accepts numbers with underscores or commas in them. For more
## advanced options, see [parseNum]. ## advanced options, see [parseNum].
toNum : Str -> Result (Num a) [ ExpectedNum a ]* toNum : Str -> Result (Num *) [ InvalidNumStr ]*
## If the string begins with an [Int] or a [finite](Num.isFinite) [Frac], return ## If the string begins with an [Int] or a [finite](Num.isFinite) [Frac], return
## that number along with the rest of the string after it. ## that number along with the rest of the string after it.
## ##
## The exact number type to look for will be inferred from usage. Here's an ## The exact number type to look for will be inferred from usage.
## example where the `Err` branch matches `Float Binary64`, which causes this to ## In the example below, the usage of Float64 in the type signature will require that type instead of (Num *).
## parse an [F64] because [F64] is defined as `F64 : Num [ Fraction [ Float64 ] ]`.
## ##
## >>> when Str.parseNum input {} is ## >>> parseFloat64 : Str -> Result { val: Float64, rest: Str } [ InvalidNumStr ]*
## >>> Ok { val: f64, rest } -> "The F64 was: \(f64)" ## >>> Str.parseNum input {}
## >>> Err (ExpectedNum (Fraction Float64)) -> "Not a valid F64!"
## ##
## If the string begins with `"NaN"`, `"∞"`, and `"-∞"` (which do not represent ## If the string begins with `"NaN"`, `"∞"`, and `"-∞"` (which do not represent
## [finite](Num.isFinite) numbers), they will be accepted only when parsing ## [finite](Num.isFinite) numbers), they will be accepted only when parsing
## [F64] or [F32] numbers, and translated accordingly. ## [F64] or [F32] numbers, and translated accordingly.
# parseNum : Str, NumParseConfig -> Result { val : Num a, rest : Str } [ ExpectedNum a ]* # parseNum : Str, NumParseConfig -> Result { val : Num *, rest : Str } [ InvalidNumStr ]*
## Notes: ## Notes:
## * You can allow a decimal mark for integers; they'll only parse if the numbers after it are all 0. ## * You can allow a decimal mark for integers; they'll only parse if the numbers after it are all 0.

View file

@ -242,6 +242,9 @@ pub const STR_ENDS_WITH: &str = "roc_builtins.str.ends_with";
pub const STR_NUMBER_OF_BYTES: &str = "roc_builtins.str.number_of_bytes"; pub const STR_NUMBER_OF_BYTES: &str = "roc_builtins.str.number_of_bytes";
pub const STR_FROM_INT: IntrinsicName = int_intrinsic!("roc_builtins.str.from_int"); pub const STR_FROM_INT: IntrinsicName = int_intrinsic!("roc_builtins.str.from_int");
pub const STR_FROM_FLOAT: &str = "roc_builtins.str.from_float"; pub const STR_FROM_FLOAT: &str = "roc_builtins.str.from_float";
pub const STR_TO_INT: IntrinsicName = int_intrinsic!("roc_builtins.str.to_int");
pub const STR_TO_FLOAT: IntrinsicName = float_intrinsic!("roc_builtins.str.to_float");
pub const STR_TO_DECIMAL: &str = "roc_builtins.str.to_decimal";
pub const STR_EQUAL: &str = "roc_builtins.str.equal"; pub const STR_EQUAL: &str = "roc_builtins.str.equal";
pub const STR_TO_UTF8: &str = "roc_builtins.str.to_utf8"; pub const STR_TO_UTF8: &str = "roc_builtins.str.to_utf8";
pub const STR_FROM_UTF8: &str = "roc_builtins.str.from_utf8"; pub const STR_FROM_UTF8: &str = "roc_builtins.str.from_utf8";
@ -299,6 +302,7 @@ pub const LIST_ANY: &str = "roc_builtins.list.any";
pub const LIST_ALL: &str = "roc_builtins.list.all"; pub const LIST_ALL: &str = "roc_builtins.list.all";
pub const LIST_FIND_UNSAFE: &str = "roc_builtins.list.find_unsafe"; pub const LIST_FIND_UNSAFE: &str = "roc_builtins.list.find_unsafe";
pub const DEC_FROM_STR: &str = "roc_builtins.dec.from_str";
pub const DEC_FROM_F64: &str = "roc_builtins.dec.from_f64"; pub const DEC_FROM_F64: &str = "roc_builtins.dec.from_f64";
pub const DEC_EQ: &str = "roc_builtins.dec.eq"; pub const DEC_EQ: &str = "roc_builtins.dec.eq";
pub const DEC_NEQ: &str = "roc_builtins.dec.neq"; pub const DEC_NEQ: &str = "roc_builtins.dec.neq";

View file

@ -3,8 +3,9 @@ use roc_module::ident::TagName;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_region::all::Region; use roc_region::all::Region;
use roc_types::builtin_aliases::{ use roc_types::builtin_aliases::{
bool_type, dict_type, float_type, i128_type, int_type, list_type, nat_type, num_type, bool_type, dec_type, dict_type, f32_type, f64_type, float_type, i128_type, i16_type, i32_type,
ordering_type, result_type, set_type, str_type, str_utf8_byte_problem_type, u16_type, u32_type, i64_type, i8_type, int_type, list_type, nat_type, num_type, ordering_type, result_type,
set_type, str_type, str_utf8_byte_problem_type, u128_type, u16_type, u32_type, u64_type,
u8_type, u8_type,
}; };
use roc_types::solved_types::SolvedType; use roc_types::solved_types::SolvedType;
@ -702,6 +703,117 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(list_type(u8_type())) Box::new(list_type(u8_type()))
); );
// toNum : Str -> Result (Num a) [ InvalidNumStr ]
// Because toNum doesn't work with floats & decimals by default without
// a point of usage to be able to infer the proper layout
// we decided that separate functions for each sub num type
// is the best approach. These below all end up mapping to
// `str_to_num` in can `builtins.rs`
let invalid_str = || {
SolvedType::TagUnion(
vec![(TagName::Global("InvalidNumStr".into()), vec![])],
Box::new(SolvedType::Wildcard),
)
};
// toDec : Str -> Result Dec [ InvalidNumStr ]
add_top_level_function_type!(
Symbol::STR_TO_DEC,
vec![str_type()],
Box::new(result_type(dec_type(), invalid_str()))
);
// toF64 : Str -> Result F64 [ InvalidNumStr ]
add_top_level_function_type!(
Symbol::STR_TO_F64,
vec![str_type()],
Box::new(result_type(f64_type(), invalid_str()))
);
// toF32 : Str -> Result F32 [ InvalidNumStr ]
add_top_level_function_type!(
Symbol::STR_TO_F32,
vec![str_type()],
Box::new(result_type(f32_type(), invalid_str()))
);
// toNat : Str -> Result Nat [ InvalidNumStr ]
add_top_level_function_type!(
Symbol::STR_TO_NAT,
vec![str_type()],
Box::new(result_type(nat_type(), invalid_str()))
);
// toU128 : Str -> Result U128 [ InvalidNumStr ]
add_top_level_function_type!(
Symbol::STR_TO_U128,
vec![str_type()],
Box::new(result_type(u128_type(), invalid_str()))
);
// toI128 : Str -> Result I128 [ InvalidNumStr ]
add_top_level_function_type!(
Symbol::STR_TO_I128,
vec![str_type()],
Box::new(result_type(i128_type(), invalid_str()))
);
// toU64 : Str -> Result U64 [ InvalidNumStr ]
add_top_level_function_type!(
Symbol::STR_TO_U64,
vec![str_type()],
Box::new(result_type(u64_type(), invalid_str()))
);
// toI64 : Str -> Result I64 [ InvalidNumStr ]
add_top_level_function_type!(
Symbol::STR_TO_I64,
vec![str_type()],
Box::new(result_type(i64_type(), invalid_str()))
);
// toU32 : Str -> Result U32 [ InvalidNumStr ]
add_top_level_function_type!(
Symbol::STR_TO_U32,
vec![str_type()],
Box::new(result_type(u32_type(), invalid_str()))
);
// toI32 : Str -> Result I32 [ InvalidNumStr ]
add_top_level_function_type!(
Symbol::STR_TO_I32,
vec![str_type()],
Box::new(result_type(i32_type(), invalid_str()))
);
// toU16 : Str -> Result U16 [ InvalidNumStr ]
add_top_level_function_type!(
Symbol::STR_TO_U16,
vec![str_type()],
Box::new(result_type(u16_type(), invalid_str()))
);
// toI16 : Str -> Result I16 [ InvalidNumStr ]
add_top_level_function_type!(
Symbol::STR_TO_I16,
vec![str_type()],
Box::new(result_type(i16_type(), invalid_str()))
);
// toU8 : Str -> Result U8 [ InvalidNumStr ]
add_top_level_function_type!(
Symbol::STR_TO_U8,
vec![str_type()],
Box::new(result_type(u8_type(), invalid_str()))
);
// toI8 : Str -> Result I8 [ InvalidNumStr ]
add_top_level_function_type!(
Symbol::STR_TO_I8,
vec![str_type()],
Box::new(result_type(i8_type(), invalid_str()))
);
// List module // List module
// get : List elem, Nat -> Result elem [ OutOfBounds ]* // get : List elem, Nat -> Result elem [ OutOfBounds ]*
@ -1055,6 +1167,16 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(list_type(flex(TVAR1))), 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 // swap : List elem, Nat, Nat -> List elem
add_top_level_function_type!( add_top_level_function_type!(
Symbol::LIST_SWAP, Symbol::LIST_SWAP,

View file

@ -2,9 +2,9 @@ use crate::env::Env;
use crate::scope::Scope; use crate::scope::Scope;
use roc_collections::all::{ImMap, MutMap, MutSet, SendMap}; use roc_collections::all::{ImMap, MutMap, MutSet, SendMap};
use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_parse::ast::{AssignedField, Tag, TypeAnnotation}; use roc_parse::ast::{AliasHeader, AssignedField, Pattern, Tag, TypeAnnotation};
use roc_region::all::{Located, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
use roc_types::types::{Alias, LambdaSet, Problem, RecordField, Type}; 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)] #[allow(clippy::too_many_arguments)]
fn can_annotation_help( fn can_annotation_help(
env: &mut Env, env: &mut Env,
@ -150,30 +278,9 @@ fn can_annotation_help(
Type::Function(args, Box::new(closure), Box::new(ret)) Type::Function(args, Box::new(closure), Box::new(ret))
} }
Apply(module_name, ident, type_arguments) => { Apply(module_name, ident, type_arguments) => {
let symbol = if module_name.is_empty() { let symbol = match make_apply_symbol(env, region, scope, module_name, ident) {
// Since module_name was empty, this is an unqualified type. Err(problem) => return problem,
// Look it up in scope!
let ident: Ident = (*ident).into();
match scope.lookup(&ident, region) {
Ok(symbol) => symbol, 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 mut args = Vec::new(); let mut args = Vec::new();
@ -267,10 +374,16 @@ fn can_annotation_help(
} }
} }
} }
As(loc_inner, _spaces, loc_as) => match loc_as.value { As(
TypeAnnotation::Apply(module_name, ident, loc_vars) if module_name.is_empty() => { loc_inner,
_spaces,
AliasHeader {
name,
vars: loc_vars,
},
) => {
let symbol = match scope.introduce( let symbol = match scope.introduce(
ident.into(), name.value.into(),
&env.exposed_ident_ids, &env.exposed_ident_ids,
&mut env.ident_ids, &mut env.ident_ids,
region, region,
@ -304,28 +417,25 @@ fn can_annotation_help(
references.insert(symbol); references.insert(symbol);
for loc_var in loc_vars { for loc_var in *loc_vars {
match loc_var.value { let var = match loc_var.value {
BoundVariable(ident) => { Pattern::Identifier(name) if name.chars().next().unwrap().is_lowercase() => {
let var_name = Lowercase::from(ident); name
}
_ => unreachable!("I thought this was validated during parsing"),
};
let var_name = Lowercase::from(var);
if let Some(var) = introduced_variables.var_by_name(&var_name) { if let Some(var) = introduced_variables.var_by_name(&var_name) {
vars.push((var_name.clone(), Type::Variable(*var))); vars.push((var_name.clone(), Type::Variable(*var)));
lowercase_vars.push(Located::at(loc_var.region, (var_name, *var))); lowercase_vars.push(Loc::at(loc_var.region, (var_name, *var)));
} else { } else {
let var = var_store.fresh(); let var = var_store.fresh();
introduced_variables.insert_named(var_name.clone(), var); introduced_variables.insert_named(var_name.clone(), var);
vars.push((var_name.clone(), Type::Variable(var))); vars.push((var_name.clone(), Type::Variable(var)));
lowercase_vars.push(Located::at(loc_var.region, (var_name, var))); lowercase_vars.push(Loc::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);
}
} }
} }
@ -380,11 +490,6 @@ fn can_annotation_help(
} }
} }
} }
_ => {
// This is a syntactically invalid type alias.
Type::Erroneous(Problem::CanonicalizationProblem)
}
},
Record { fields, ext } => { Record { fields, ext } => {
let ext_type = match ext { let ext_type = match ext {
@ -530,7 +635,7 @@ where
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn can_assigned_fields<'a>( fn can_assigned_fields<'a>(
env: &mut Env, env: &mut Env,
fields: &&[Located<AssignedField<'a, TypeAnnotation<'a>>>], fields: &&[Loc<AssignedField<'a, TypeAnnotation<'a>>>],
region: Region, region: Region,
scope: &mut Scope, scope: &mut Scope,
var_store: &mut VarStore, var_store: &mut VarStore,
@ -640,7 +745,7 @@ fn can_assigned_fields<'a>(
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn can_tags<'a>( fn can_tags<'a>(
env: &mut Env, env: &mut Env,
tags: &'a [Located<Tag<'a>>], tags: &'a [Loc<Tag<'a>>],
region: Region, region: Region,
scope: &mut Scope, scope: &mut Scope,
var_store: &mut VarStore, var_store: &mut VarStore,

View file

@ -1,13 +1,13 @@
use crate::def::Def; use crate::def::Def;
use crate::expr::{ClosureData, Expr::*}; use crate::expr::{self, ClosureData, Expr::*};
use crate::expr::{Expr, Field, Recursive, WhenBranch}; use crate::expr::{Expr, Field, Recursive};
use crate::pattern::Pattern; use crate::pattern::Pattern;
use roc_collections::all::SendMap; use roc_collections::all::SendMap;
use roc_module::called_via::CalledVia; use roc_module::called_via::CalledVia;
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
macro_rules! macro_magic { macro_rules! macro_magic {
@ -68,6 +68,20 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
STR_TRIM => str_trim, STR_TRIM => str_trim,
STR_TRIM_LEFT => str_trim_left, STR_TRIM_LEFT => str_trim_left,
STR_TRIM_RIGHT => str_trim_right, STR_TRIM_RIGHT => str_trim_right,
STR_TO_DEC => str_to_num,
STR_TO_F64 => str_to_num,
STR_TO_F32 => str_to_num,
STR_TO_NAT => str_to_num,
STR_TO_U128 => str_to_num,
STR_TO_I128 => str_to_num,
STR_TO_U64 => str_to_num,
STR_TO_I64 => str_to_num,
STR_TO_U32 => str_to_num,
STR_TO_I32 => str_to_num,
STR_TO_U16 => str_to_num,
STR_TO_I16 => str_to_num,
STR_TO_U8 => str_to_num,
STR_TO_I8 => str_to_num,
LIST_LEN => list_len, LIST_LEN => list_len,
LIST_GET => list_get, LIST_GET => list_get,
LIST_SET => list_set, LIST_SET => list_set,
@ -99,6 +113,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_DROP => list_drop, LIST_DROP => list_drop,
LIST_DROP_AT => list_drop_at, LIST_DROP_AT => list_drop_at,
LIST_DROP_FIRST => list_drop_first, LIST_DROP_FIRST => list_drop_first,
LIST_DROP_IF => list_drop_if,
LIST_DROP_LAST => list_drop_last, LIST_DROP_LAST => list_drop_last,
LIST_SWAP => list_swap, LIST_SWAP => list_swap,
LIST_MAP_WITH_INDEX => list_map_with_index, LIST_MAP_WITH_INDEX => list_map_with_index,
@ -347,8 +362,8 @@ fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
Def { Def {
annotation: None, annotation: None,
expr_var: int_var, expr_var: int_var,
loc_expr: Located::at_zero(body), loc_expr: Loc::at_zero(body),
loc_pattern: Located::at_zero(Pattern::Identifier(symbol)), loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)),
pattern_vars: SendMap::default(), pattern_vars: SendMap::default(),
} }
} }
@ -362,8 +377,8 @@ fn num_min_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
Def { Def {
annotation: None, annotation: None,
expr_var: int_var, expr_var: int_var,
loc_expr: Located::at_zero(body), loc_expr: Loc::at_zero(body),
loc_pattern: Located::at_zero(Pattern::Identifier(symbol)), loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)),
pattern_vars: SendMap::default(), pattern_vars: SendMap::default(),
} }
} }
@ -1231,8 +1246,8 @@ fn num_max_i128(symbol: Symbol, var_store: &mut VarStore) -> Def {
Def { Def {
annotation: Some(annotation), annotation: Some(annotation),
expr_var: int_var, expr_var: int_var,
loc_expr: Located::at_zero(body), loc_expr: Loc::at_zero(body),
loc_pattern: Located::at_zero(Pattern::Identifier(symbol)), loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)),
pattern_vars: SendMap::default(), pattern_vars: SendMap::default(),
} }
} }
@ -1323,6 +1338,98 @@ fn str_trim_right(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_1(symbol, LowLevel::StrTrimRight, var_store) lowlevel_1(symbol, LowLevel::StrTrimRight, var_store)
} }
/// Str.toNum : Str -> Result (Num *) [ InvalidNumStr ]*
fn str_to_num(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh();
let str_var = var_store.fresh();
let num_var = var_store.fresh();
let ret_var = var_store.fresh();
let record_var = var_store.fresh();
let errorcode_var = var_store.fresh();
// let arg_2 = RunLowLevel StrToNum arg_1
//
// if arg_2.errorcode then
// Err InvalidNumStr
// else
// Ok arg_2.value
let cont = If {
branch_var: ret_var,
cond_var: bool_var,
branches: vec![(
// if-condition
no_region(RunLowLevel {
op: LowLevel::NumGt,
args: vec![
(
errorcode_var,
// arg_3.b
Access {
record_var,
ext_var: var_store.fresh(),
field: "b_errorcode".into(),
field_var: errorcode_var,
loc_expr: Box::new(no_region(Var(Symbol::ARG_2))),
},
),
(errorcode_var, int(errorcode_var, Variable::UNSIGNED8, 0)),
],
ret_var: bool_var,
}),
// overflow!
no_region(tag(
"Err",
vec![tag("InvalidNumStr", Vec::new(), var_store)],
var_store,
)),
)],
final_else: Box::new(
// all is well
no_region(
// Ok arg_2.value
tag(
"Ok",
vec![
// arg_3.a
Access {
record_var,
ext_var: var_store.fresh(),
field: "a_value".into(),
field_var: num_var,
loc_expr: Box::new(no_region(Var(Symbol::ARG_2))),
},
],
var_store,
),
),
),
};
let def = crate::def::Def {
loc_pattern: no_region(Pattern::Identifier(Symbol::ARG_2)),
loc_expr: no_region(RunLowLevel {
op: LowLevel::StrToNum,
args: vec![(str_var, Var(Symbol::ARG_1))],
ret_var: record_var,
}),
expr_var: record_var,
pattern_vars: SendMap::default(),
annotation: None,
};
let body = LetNonRec(Box::new(def), Box::new(no_region(cont)), ret_var);
defn(
symbol,
vec![(str_var, Symbol::ARG_1)],
var_store,
body,
ret_var,
)
}
/// Str.repeat : Str, Nat -> Str /// Str.repeat : Str, Nat -> Str
fn str_repeat(symbol: Symbol, var_store: &mut VarStore) -> Def { fn str_repeat(symbol: Symbol, var_store: &mut VarStore) -> Def {
let str_var = var_store.fresh(); let str_var = var_store.fresh();
@ -2376,6 +2483,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 { fn list_drop_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh(); let list_var = var_store.fresh();
let index_var = var_store.fresh(); let index_var = var_store.fresh();
@ -2400,6 +2508,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 /// List.dropLast: List elem -> List elem
fn list_drop_last(symbol: Symbol, var_store: &mut VarStore) -> Def { fn list_drop_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh(); let list_var = var_store.fresh();
@ -2985,7 +3148,7 @@ fn list_keep_errs(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::ListKeepErrs, var_store) 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 { fn list_range(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::ListRange, var_store) lowlevel_2(symbol, LowLevel::ListRange, var_store)
} }
@ -3143,8 +3306,8 @@ fn dict_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
Def { Def {
annotation: None, annotation: None,
expr_var: dict_var, expr_var: dict_var,
loc_expr: Located::at_zero(body), loc_expr: Loc::at_zero(body),
loc_pattern: Located::at_zero(Pattern::Identifier(symbol)), loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)),
pattern_vars: SendMap::default(), pattern_vars: SendMap::default(),
} }
} }
@ -3225,8 +3388,8 @@ fn dict_get(symbol: Symbol, var_store: &mut VarStore) -> Def {
let def = Def { let def = Def {
annotation: None, annotation: None,
expr_var: temp_record_var, expr_var: temp_record_var,
loc_expr: Located::at_zero(def_body), loc_expr: Loc::at_zero(def_body),
loc_pattern: Located::at_zero(Pattern::Identifier(temp_record)), loc_pattern: Loc::at_zero(Pattern::Identifier(temp_record)),
pattern_vars: Default::default(), pattern_vars: Default::default(),
}; };
@ -3318,8 +3481,8 @@ fn set_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
Def { Def {
annotation: None, annotation: None,
expr_var: set_var, expr_var: set_var,
loc_expr: Located::at_zero(body), loc_expr: Loc::at_zero(body),
loc_pattern: Located::at_zero(Pattern::Identifier(symbol)), loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)),
pattern_vars: SendMap::default(), pattern_vars: SendMap::default(),
} }
} }
@ -4001,7 +4164,7 @@ fn result_map(symbol: Symbol, var_store: &mut VarStore) -> Def {
)], )],
}; };
let branch = WhenBranch { let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)], patterns: vec![no_region(pattern)],
value: no_region(ok), value: no_region(ok),
guard: None, guard: None,
@ -4031,7 +4194,7 @@ fn result_map(symbol: Symbol, var_store: &mut VarStore) -> Def {
)], )],
}; };
let branch = WhenBranch { let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)], patterns: vec![no_region(pattern)],
value: no_region(err), value: no_region(err),
guard: None, guard: None,
@ -4098,7 +4261,7 @@ fn result_map_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
)], )],
}; };
let branch = WhenBranch { let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)], patterns: vec![no_region(pattern)],
value: no_region(ok), value: no_region(ok),
guard: None, guard: None,
@ -4128,7 +4291,7 @@ fn result_map_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
)], )],
}; };
let branch = WhenBranch { let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)], patterns: vec![no_region(pattern)],
value: no_region(err), value: no_region(err),
guard: None, guard: None,
@ -4171,7 +4334,7 @@ fn result_with_default(symbol: Symbol, var_store: &mut VarStore) -> Def {
arguments: vec![(ret_var, no_region(Pattern::Identifier(Symbol::ARG_3)))], arguments: vec![(ret_var, no_region(Pattern::Identifier(Symbol::ARG_3)))],
}; };
let branch = WhenBranch { let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)], patterns: vec![no_region(pattern)],
value: no_region(Var(Symbol::ARG_3)), value: no_region(Var(Symbol::ARG_3)),
guard: None, guard: None,
@ -4191,7 +4354,7 @@ fn result_with_default(symbol: Symbol, var_store: &mut VarStore) -> Def {
arguments: vec![(var_store.fresh(), no_region(Pattern::Underscore))], arguments: vec![(var_store.fresh(), no_region(Pattern::Underscore))],
}; };
let branch = WhenBranch { let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)], patterns: vec![no_region(pattern)],
value: no_region(Var(Symbol::ARG_2)), value: no_region(Var(Symbol::ARG_2)),
guard: None, guard: None,
@ -4241,7 +4404,7 @@ fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
arguments: vec![], arguments: vec![],
}; };
let branch = WhenBranch { let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)], patterns: vec![no_region(pattern)],
value: no_region(false_expr), value: no_region(false_expr),
guard: None, guard: None,
@ -4268,7 +4431,7 @@ fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
arguments: vec![], arguments: vec![],
}; };
let branch = WhenBranch { let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)], patterns: vec![no_region(pattern)],
value: no_region(true_expr), value: no_region(true_expr),
guard: None, guard: None,
@ -4318,7 +4481,7 @@ fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def {
arguments: vec![], arguments: vec![],
}; };
let branch = WhenBranch { let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)], patterns: vec![no_region(pattern)],
value: no_region(true_expr), value: no_region(true_expr),
guard: None, guard: None,
@ -4345,7 +4508,7 @@ fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def {
arguments: vec![], arguments: vec![],
}; };
let branch = WhenBranch { let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)], patterns: vec![no_region(pattern)],
value: no_region(false_expr), value: no_region(false_expr),
guard: None, guard: None,
@ -4407,7 +4570,7 @@ fn result_after(symbol: Symbol, var_store: &mut VarStore) -> Def {
)], )],
}; };
let branch = WhenBranch { let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)], patterns: vec![no_region(pattern)],
value: no_region(ok), value: no_region(ok),
guard: None, guard: None,
@ -4437,7 +4600,7 @@ fn result_after(symbol: Symbol, var_store: &mut VarStore) -> Def {
)], )],
}; };
let branch = WhenBranch { let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)], patterns: vec![no_region(pattern)],
value: no_region(err), value: no_region(err),
guard: None, guard: None,
@ -4464,8 +4627,8 @@ fn result_after(symbol: Symbol, var_store: &mut VarStore) -> Def {
} }
#[inline(always)] #[inline(always)]
fn no_region<T>(value: T) -> Located<T> { fn no_region<T>(value: T) -> Loc<T> {
Located { Loc {
region: Region::zero(), region: Region::zero(),
value, value,
} }
@ -4480,7 +4643,7 @@ fn tag(name: &'static str, args: Vec<Expr>, var_store: &mut VarStore) -> Expr {
arguments: args arguments: args
.into_iter() .into_iter()
.map(|expr| (var_store.fresh(), no_region(expr))) .map(|expr| (var_store.fresh(), no_region(expr)))
.collect::<Vec<(Variable, Located<Expr>)>>(), .collect::<Vec<(Variable, Loc<Expr>)>>(),
} }
} }
@ -4507,11 +4670,11 @@ fn defn(
let expr = defn_help(fn_name, args, var_store, body, ret_var); let expr = defn_help(fn_name, args, var_store, body, ret_var);
Def { Def {
loc_pattern: Located { loc_pattern: Loc {
region: Region::zero(), region: Region::zero(),
value: Pattern::Identifier(fn_name), value: Pattern::Identifier(fn_name),
}, },
loc_expr: Located { loc_expr: Loc {
region: Region::zero(), region: Region::zero(),
value: expr, value: expr,
}, },

View file

@ -1,10 +1,21 @@
use crate::expected::{Expected, PExpected}; use crate::expected::{Expected, PExpected};
use roc_collections::all::{MutSet, SendMap}; use roc_collections::all::{MutSet, SendMap};
use roc_module::symbol::Symbol; use roc_module::{ident::TagName, symbol::Symbol};
use roc_region::all::{Located, Region}; use roc_region::all::{Loc, Region};
use roc_types::types::{Category, PatternCategory, Type}; use roc_types::types::{Category, PatternCategory, Type};
use roc_types::{subs::Variable, types::VariableDetail}; 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)] #[derive(Debug, Clone, PartialEq)]
pub enum Constraint { pub enum Constraint {
Eq(Type, Expected<Type>, Category, Region), Eq(Type, Expected<Type>, Category, Region),
@ -15,13 +26,14 @@ pub enum Constraint {
SaveTheEnvironment, SaveTheEnvironment,
Let(Box<LetConstraint>), Let(Box<LetConstraint>),
And(Vec<Constraint>), And(Vec<Constraint>),
Present(Type, PresenceConstraint),
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct LetConstraint { pub struct LetConstraint {
pub rigid_vars: Vec<Variable>, pub rigid_vars: Vec<Variable>,
pub flex_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 defs_constraint: Constraint,
pub ret_constraint: Constraint, pub ret_constraint: Constraint,
} }
@ -74,6 +86,7 @@ impl Constraint {
|| boxed.defs_constraint.contains_save_the_environment() || boxed.defs_constraint.contains_save_the_environment()
} }
Constraint::And(cs) => cs.iter().any(|c| c.contains_save_the_environment()), Constraint::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); 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::ident::Lowercase;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_parse::ast; use roc_parse::ast;
use roc_parse::ast::AliasHeader;
use roc_parse::pattern::PatternType; use roc_parse::pattern::PatternType;
use roc_problem::can::{CycleEntry, Problem, RuntimeError}; 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::subs::{VarStore, Variable};
use roc_types::types::{Alias, Type}; use roc_types::types::{Alias, Type};
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::Debug; 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)] #[derive(Clone, Debug, PartialEq)]
pub struct Def { pub struct Def {
pub loc_pattern: Located<Pattern>, pub loc_pattern: Loc<Pattern>,
pub loc_expr: Located<Expr>, pub loc_expr: Loc<Expr>,
pub expr_var: Variable, pub expr_var: Variable,
pub pattern_vars: SendMap<Symbol, Variable>, pub pattern_vars: SendMap<Symbol, Variable>,
pub annotation: Option<Annotation>, pub annotation: Option<Annotation>,
@ -53,29 +54,29 @@ pub struct CanDefs {
enum PendingDef<'a> { enum PendingDef<'a> {
/// A standalone annotation with no body /// A standalone annotation with no body
AnnotationOnly( AnnotationOnly(
&'a Located<ast::Pattern<'a>>, &'a Loc<ast::Pattern<'a>>,
Located<Pattern>, Loc<Pattern>,
&'a Located<ast::TypeAnnotation<'a>>, &'a Loc<ast::TypeAnnotation<'a>>,
), ),
/// A body with no type annotation /// A body with no type annotation
Body( Body(
&'a Located<ast::Pattern<'a>>, &'a Loc<ast::Pattern<'a>>,
Located<Pattern>, Loc<Pattern>,
&'a Located<ast::Expr<'a>>, &'a Loc<ast::Expr<'a>>,
), ),
/// A body with a type annotation /// A body with a type annotation
TypedBody( TypedBody(
&'a Located<ast::Pattern<'a>>, &'a Loc<ast::Pattern<'a>>,
Located<Pattern>, Loc<Pattern>,
&'a Located<ast::TypeAnnotation<'a>>, &'a Loc<ast::TypeAnnotation<'a>>,
&'a Located<ast::Expr<'a>>, &'a Loc<ast::Expr<'a>>,
), ),
/// A type alias, e.g. `Ints : List Int` /// A type alias, e.g. `Ints : List Int`
Alias { Alias {
name: Located<Symbol>, name: Loc<Symbol>,
vars: Vec<Located<Lowercase>>, vars: Vec<Loc<Lowercase>>,
ann: &'a Located<ast::TypeAnnotation<'a>>, ann: &'a Loc<ast::TypeAnnotation<'a>>,
}, },
/// An invalid alias, that is ignored in the rest of the pipeline /// 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)] #[inline(always)]
pub fn canonicalize_defs<'a>( pub fn canonicalize_defs<'a>(
env: &mut Env<'a>, env: &mut Env<'a>,
mut output: Output, mut output: Output,
var_store: &mut VarStore, var_store: &mut VarStore,
original_scope: &Scope, original_scope: &Scope,
loc_defs: &'a [&'a Located<ast::Def<'a>>], loc_defs: &'a [&'a Loc<ast::Def<'a>>],
pattern_type: PatternType, pattern_type: PatternType,
) -> (CanDefs, Scope, Output, MutMap<Symbol, Region>) { ) -> (CanDefs, Scope, Output, MutMap<Symbol, Region>) {
// Canonicalizing defs while detecting shadowing involves a multi-step process: // Canonicalizing defs while detecting shadowing involves a multi-step process:
@ -179,9 +243,27 @@ pub fn canonicalize_defs<'a>(
let mut aliases = SendMap::default(); let mut aliases = SendMap::default();
let mut value_defs = Vec::new(); 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() { for pending_def in pending.into_iter() {
match pending_def { match pending_def {
PendingDef::Alias { name, vars, ann } => { PendingDef::Alias { name, vars, ann } => {
let symbols =
crate::annotation::find_alias_symbols(env.home, &mut env.ident_ids, &ann.value);
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 symbol = name.value;
let mut can_ann = let mut can_ann =
canonicalize_annotation(env, &mut scope, &ann.value, ann.region, var_store); canonicalize_annotation(env, &mut scope, &ann.value, ann.region, var_store);
@ -192,8 +274,7 @@ pub fn canonicalize_defs<'a>(
output.references.referenced_aliases.insert(symbol); output.references.referenced_aliases.insert(symbol);
} }
let mut can_vars: Vec<Located<(Lowercase, Variable)>> = let mut can_vars: Vec<Loc<(Lowercase, Variable)>> = Vec::with_capacity(vars.len());
Vec::with_capacity(vars.len());
let mut is_phantom = false; let mut is_phantom = false;
for loc_lowercase in vars { for loc_lowercase in vars {
@ -202,7 +283,7 @@ pub fn canonicalize_defs<'a>(
.var_by_name(&loc_lowercase.value) .var_by_name(&loc_lowercase.value)
{ {
// This is a valid lowercase rigid var for the alias. // This is a valid lowercase rigid var for the alias.
can_vars.push(Located { can_vars.push(Loc {
value: (loc_lowercase.value.clone(), *var), value: (loc_lowercase.value.clone(), *var),
region: loc_lowercase.region, region: loc_lowercase.region,
}); });
@ -238,9 +319,6 @@ pub fn canonicalize_defs<'a>(
let alias = scope.lookup_alias(symbol).expect("alias is added to scope"); let alias = scope.lookup_alias(symbol).expect("alias is added to scope");
aliases.insert(symbol, alias.clone()); aliases.insert(symbol, alias.clone());
} }
other => value_defs.push(other),
}
}
correct_mutual_recursive_type_alias(env, &mut aliases, var_store); correct_mutual_recursive_type_alias(env, &mut aliases, var_store);
@ -813,7 +891,7 @@ fn canonicalize_pending_def<'a>(
let value = Expr::RuntimeError(problem); let value = Expr::RuntimeError(problem);
let is_closure = arity > 0; let is_closure = arity > 0;
let loc_can_expr = if !is_closure { let loc_can_expr = if !is_closure {
Located { Loc {
value, value,
region: loc_ann.region, region: loc_ann.region,
} }
@ -825,7 +903,7 @@ fn canonicalize_pending_def<'a>(
let mut underscores = Vec::with_capacity(arity); let mut underscores = Vec::with_capacity(arity);
for _ in 0..arity { for _ in 0..arity {
let underscore: Located<Pattern> = Located { let underscore: Loc<Pattern> = Loc {
value: Pattern::Underscore, value: Pattern::Underscore,
region: Region::zero(), region: Region::zero(),
}; };
@ -833,12 +911,12 @@ fn canonicalize_pending_def<'a>(
underscores.push((var_store.fresh(), underscore)); underscores.push((var_store.fresh(), underscore));
} }
let body_expr = Located { let body_expr = Loc {
value, value,
region: loc_ann.region, region: loc_ann.region,
}; };
Located { Loc {
value: Closure(ClosureData { value: Closure(ClosureData {
function_type: var_store.fresh(), function_type: var_store.fresh(),
closure_type: var_store.fresh(), closure_type: var_store.fresh(),
@ -867,7 +945,7 @@ fn canonicalize_pending_def<'a>(
expr_var, expr_var,
// TODO try to remove this .clone()! // TODO try to remove this .clone()!
loc_pattern: loc_can_pattern.clone(), loc_pattern: loc_can_pattern.clone(),
loc_expr: Located { loc_expr: Loc {
region: loc_can_expr.region, region: loc_can_expr.region,
// TODO try to remove this .clone()! // TODO try to remove this .clone()!
value: loc_can_expr.value.clone(), value: loc_can_expr.value.clone(),
@ -897,7 +975,7 @@ fn canonicalize_pending_def<'a>(
output.references.referenced_aliases.insert(symbol); output.references.referenced_aliases.insert(symbol);
} }
let mut can_vars: Vec<Located<(Lowercase, Variable)>> = Vec::with_capacity(vars.len()); let mut can_vars: Vec<Loc<(Lowercase, Variable)>> = Vec::with_capacity(vars.len());
for loc_lowercase in vars { for loc_lowercase in vars {
if let Some(var) = can_ann if let Some(var) = can_ann
@ -905,7 +983,7 @@ fn canonicalize_pending_def<'a>(
.var_by_name(&loc_lowercase.value) .var_by_name(&loc_lowercase.value)
{ {
// This is a valid lowercase rigid var for the alias. // This is a valid lowercase rigid var for the alias.
can_vars.push(Located { can_vars.push(Loc {
value: (loc_lowercase.value.clone(), *var), value: (loc_lowercase.value.clone(), *var),
region: loc_lowercase.region, region: loc_lowercase.region,
}); });
@ -1088,7 +1166,7 @@ fn canonicalize_pending_def<'a>(
expr_var, expr_var,
// TODO try to remove this .clone()! // TODO try to remove this .clone()!
loc_pattern: loc_can_pattern.clone(), loc_pattern: loc_can_pattern.clone(),
loc_expr: Located { loc_expr: Loc {
region: loc_can_expr.region, region: loc_can_expr.region,
// TODO try to remove this .clone()! // TODO try to remove this .clone()!
value: loc_can_expr.value.clone(), value: loc_can_expr.value.clone(),
@ -1226,7 +1304,7 @@ fn canonicalize_pending_def<'a>(
expr_var, expr_var,
// TODO try to remove this .clone()! // TODO try to remove this .clone()!
loc_pattern: loc_can_pattern.clone(), loc_pattern: loc_can_pattern.clone(),
loc_expr: Located { loc_expr: Loc {
// TODO try to remove this .clone()! // TODO try to remove this .clone()!
region: loc_can_expr.region, region: loc_can_expr.region,
value: loc_can_expr.value.clone(), value: loc_can_expr.value.clone(),
@ -1249,8 +1327,8 @@ pub fn can_defs_with_return<'a>(
env: &mut Env<'a>, env: &mut Env<'a>,
var_store: &mut VarStore, var_store: &mut VarStore,
scope: Scope, scope: Scope,
loc_defs: &'a [&'a Located<ast::Def<'a>>], loc_defs: &'a [&'a Loc<ast::Def<'a>>],
loc_ret: &'a Located<ast::Expr<'a>>, loc_ret: &'a Loc<ast::Expr<'a>>,
) -> (Expr, Output) { ) -> (Expr, Output) {
let (unsorted, mut scope, defs_output, symbols_introduced) = canonicalize_defs( let (unsorted, mut scope, defs_output, symbols_introduced) = canonicalize_defs(
env, env,
@ -1283,10 +1361,10 @@ pub fn can_defs_with_return<'a>(
match can_defs { match can_defs {
Ok(decls) => { 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() { for declaration in decls.into_iter().rev() {
loc_expr = Located { loc_expr = Loc {
region: Region::zero(), region: Region::zero(),
value: decl_to_let(var_store, declaration, loc_expr), value: decl_to_let(var_store, declaration, loc_expr),
}; };
@ -1298,7 +1376,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 { match decl {
Declaration::Declare(def) => { Declaration::Declare(def) => {
Expr::LetNonRec(Box::new(def), Box::new(loc_ret), var_store.fresh()) Expr::LetNonRec(Box::new(def), Box::new(loc_ret), var_store.fresh())
@ -1428,7 +1506,9 @@ fn to_pending_def<'a>(
} }
Alias { Alias {
name, vars, ann, .. header: AliasHeader { name, vars },
ann,
..
} => { } => {
let region = Region::span_across(&name.region, &ann.region); let region = Region::span_across(&name.region, &ann.region);
@ -1439,7 +1519,7 @@ fn to_pending_def<'a>(
region, region,
) { ) {
Ok(symbol) => { 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() { for loc_var in vars.iter() {
match loc_var.value { match loc_var.value {
@ -1447,7 +1527,7 @@ fn to_pending_def<'a>(
if name.chars().next().unwrap().is_lowercase() => if name.chars().next().unwrap().is_lowercase() =>
{ {
let lowercase = Lowercase::from(name); let lowercase = Lowercase::from(name);
can_rigids.push(Located { can_rigids.push(Loc {
value: lowercase, value: lowercase,
region: loc_var.region, region: loc_var.region,
}); });
@ -1467,7 +1547,7 @@ fn to_pending_def<'a>(
Some(( Some((
Output::default(), Output::default(),
PendingDef::Alias { PendingDef::Alias {
name: Located { name: Loc {
region: name.region, region: name.region,
value: symbol, value: symbol,
}, },
@ -1500,9 +1580,9 @@ fn to_pending_def<'a>(
fn pending_typed_body<'a>( fn pending_typed_body<'a>(
env: &mut Env<'a>, env: &mut Env<'a>,
loc_pattern: &'a Located<ast::Pattern<'a>>, loc_pattern: &'a Loc<ast::Pattern<'a>>,
loc_ann: &'a Located<ast::TypeAnnotation<'a>>, loc_ann: &'a Loc<ast::TypeAnnotation<'a>>,
loc_expr: &'a Located<ast::Expr<'a>>, loc_expr: &'a Loc<ast::Expr<'a>>,
var_store: &mut VarStore, var_store: &mut VarStore,
scope: &mut Scope, scope: &mut Scope,
pattern_type: PatternType, 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::ident::{Ident, Lowercase, ModuleName};
use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_problem::can::{Problem, RuntimeError}; 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. /// The canonicalization environment for a particular module.
pub struct Env<'a> { pub struct Env<'a> {
@ -88,7 +88,7 @@ impl<'a> Env<'a> {
Ok(symbol) Ok(symbol)
} }
None => Err(RuntimeError::LookupNotInScope( None => Err(RuntimeError::LookupNotInScope(
Located { Loc {
value: ident, value: ident,
region, region,
}, },

View file

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

View file

@ -12,7 +12,7 @@ use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_parse::ast; use roc_parse::ast;
use roc_parse::pattern::PatternType; use roc_parse::pattern::PatternType;
use roc_problem::can::{Problem, RuntimeError}; use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Located, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
use roc_types::types::Alias; use roc_types::types::Alias;
@ -43,7 +43,7 @@ pub struct ModuleOutput {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn canonicalize_module_defs<'a, F>( pub fn canonicalize_module_defs<'a, F>(
arena: &Bump, arena: &Bump,
loc_defs: &'a [Located<ast::Def<'a>>], loc_defs: &'a [Loc<ast::Def<'a>>],
home: ModuleId, home: ModuleId,
module_ids: &ModuleIds, module_ids: &ModuleIds,
exposed_ident_ids: IdentIds, exposed_ident_ids: IdentIds,
@ -76,7 +76,7 @@ where
bumpalo::collections::Vec::with_capacity_in(loc_defs.len() + num_deps, arena); bumpalo::collections::Vec::with_capacity_in(loc_defs.len() + num_deps, arena);
for loc_def in loc_defs.iter() { for loc_def in loc_defs.iter() {
desugared.push(&*arena.alloc(Located { desugared.push(&*arena.alloc(Loc {
value: desugar_def(arena, &loc_def.value), value: desugar_def(arena, &loc_def.value),
region: loc_def.region, region: loc_def.region,
})); }));
@ -263,8 +263,8 @@ where
let runtime_error = RuntimeError::ExposedButNotDefined(symbol); let runtime_error = RuntimeError::ExposedButNotDefined(symbol);
let def = Def { let def = Def {
loc_pattern: Located::new(0, 0, 0, 0, Pattern::Identifier(symbol)), loc_pattern: Loc::new(0, 0, 0, 0, Pattern::Identifier(symbol)),
loc_expr: Located::new(0, 0, 0, 0, Expr::RuntimeError(runtime_error)), loc_expr: Loc::new(0, 0, 0, 0, Expr::RuntimeError(runtime_error)),
expr_var: var_store.fresh(), expr_var: var_store.fresh(),
pattern_vars, pattern_vars,
annotation: None, annotation: None,

View file

@ -7,7 +7,7 @@ use roc_module::called_via::{BinOp, CalledVia};
use roc_module::ident::ModuleName; use roc_module::ident::ModuleName;
use roc_parse::ast::Expr::{self, *}; use roc_parse::ast::Expr::{self, *};
use roc_parse::ast::{AssignedField, Def, WhenBranch}; 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 // BinOp precedence logic adapted from Gluon by Markus Westerlind
// https://github.com/gluon-lang/gluon - license information can be found in // 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>( fn new_op_call_expr<'a>(
arena: &'a Bump, arena: &'a Bump,
left: &'a Located<Expr<'a>>, left: &'a Loc<Expr<'a>>,
loc_op: Located<BinOp>, loc_op: Loc<BinOp>,
right: &'a Located<Expr<'a>>, right: &'a Loc<Expr<'a>>,
) -> Located<Expr<'a>> { ) -> Loc<Expr<'a>> {
let region = Region::span_across(&left.region, &right.region); let region = Region::span_across(&left.region, &right.region);
let value = match loc_op.value { let value = match loc_op.value {
@ -51,7 +51,7 @@ fn new_op_call_expr<'a>(
let args = arena.alloc([left, right]); let args = arena.alloc([left, right]);
let loc_expr = arena.alloc(Located { let loc_expr = arena.alloc(Loc {
value: Expr::Var { module_name, ident }, value: Expr::Var { module_name, ident },
region: loc_op.region, 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>( fn desugar_def_helps<'a>(
arena: &'a Bump, arena: &'a Bump,
region: Region, region: Region,
defs: &'a [&'a Located<Def<'a>>], defs: &'a [&'a Loc<Def<'a>>],
loc_ret: &'a Located<Expr<'a>>, loc_ret: &'a Loc<Expr<'a>>,
) -> &'a Located<Expr<'a>> { ) -> &'a Loc<Expr<'a>> {
let mut desugared_defs = Vec::with_capacity_in(defs.len(), arena); let mut desugared_defs = Vec::with_capacity_in(defs.len(), arena);
for loc_def in defs.iter() { for loc_def in defs.iter() {
let loc_def = Located { let loc_def = Loc {
value: desugar_def(arena, &loc_def.value), value: desugar_def(arena, &loc_def.value),
region: loc_def.region, region: loc_def.region,
}; };
@ -82,7 +82,7 @@ fn desugar_def_helps<'a>(
let desugared_defs = desugared_defs.into_bump_slice(); let desugared_defs = desugared_defs.into_bump_slice();
arena.alloc(Located { arena.alloc(Loc {
value: Defs(desugared_defs, desugar_expr(arena, loc_ret)), value: Defs(desugared_defs, desugar_expr(arena, loc_ret)),
region, 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, /// 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. /// 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 { match &loc_expr.value {
Float(_) Float(_)
| Num(_) | Num(_)
@ -136,13 +136,13 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
Access(sub_expr, paths) => { Access(sub_expr, paths) => {
let region = loc_expr.region; let region = loc_expr.region;
let loc_sub_expr = Located { let loc_sub_expr = Loc {
region, region,
value: **sub_expr, value: **sub_expr,
}; };
let value = Access(&desugar_expr(arena, arena.alloc(loc_sub_expr)).value, paths); 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) => { List(items) => {
let mut new_items = Vec::with_capacity_in(items.len(), arena); 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 new_items = new_items.into_bump_slice();
let value: Expr<'a> = List(items.replace_items(new_items)); let value: Expr<'a> = List(items.replace_items(new_items));
arena.alloc(Located { arena.alloc(Loc {
region: loc_expr.region, region: loc_expr.region,
value, value,
}) })
} }
Record(fields) => arena.alloc(Located { Record(fields) => arena.alloc(Loc {
region: loc_expr.region, region: loc_expr.region,
value: Record(fields.map_items(arena, |field| { value: Record(fields.map_items(arena, |field| {
let value = desugar_field(arena, &field.value); let value = desugar_field(arena, &field.value);
Located { Loc {
value, value,
region: field.region, 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 // NOTE the `update` field is always a `Var { .. }` and does not need to be desugared
let new_fields = fields.map_items(arena, |field| { let new_fields = fields.map_items(arena, |field| {
let value = desugar_field(arena, &field.value); let value = desugar_field(arena, &field.value);
Located { Loc {
value, value,
region: field.region, region: field.region,
} }
}); });
arena.alloc(Located { arena.alloc(Loc {
region: loc_expr.region, region: loc_expr.region,
value: RecordUpdate { value: RecordUpdate {
update: *update, 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, region: loc_expr.region,
value: Closure(loc_patterns, desugar_expr(arena, loc_ret)), 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 desugared_ret = desugar_expr(arena, loc_ret);
let closure = Expr::Closure(loc_patterns, desugared_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 { match &desugared_body.value {
Expr::Apply(function, arguments, called_via) => { 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); Vec::with_capacity_in(arguments.len() + 1, arena);
new_arguments.extend(arguments.iter()); new_arguments.extend(arguments.iter());
new_arguments.push(arena.alloc(loc_closure)); new_arguments.push(arena.alloc(loc_closure));
let call = Expr::Apply(function, new_arguments.into_bump_slice(), *called_via); 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) 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)]), arena.alloc([&*arena.alloc(loc_closure)]),
CalledVia::Space, CalledVia::Space,
); );
let loc_call = Located::at(loc_expr.region, call); let loc_call = Loc::at(loc_expr.region, call);
arena.alloc(loc_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(); 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), value: Apply(desugar_expr(arena, loc_fn), desugared_args, *called_via),
region: loc_expr.region, 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(); let desugared_branches = desugared_branches.into_bump_slice();
arena.alloc(Located { arena.alloc(Loc {
value: When(loc_desugared_cond, desugared_branches), value: When(loc_desugared_cond, desugared_branches),
region: loc_expr.region, 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", 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)]); 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)), value: Apply(loc_fn_var, desugared_args, CalledVia::UnaryOp(op)),
region: loc_expr.region, 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. // are no longer needed and should be dropped.
desugar_expr( desugar_expr(
arena, arena,
arena.alloc(Located { arena.alloc(Loc {
value: **expr, value: **expr,
region: loc_expr.region, 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), value: If(desugared_if_thens.into_bump_slice(), desugared_final_else),
region: loc_expr.region, 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) => { Expect(condition, continuation) => {
let desugared_condition = &*arena.alloc(desugar_expr(arena, condition)); let desugared_condition = &*arena.alloc(desugar_expr(arena, condition));
let desugared_continuation = &*arena.alloc(desugar_expr(arena, continuation)); let desugared_continuation = &*arena.alloc(desugar_expr(arena, continuation));
arena.alloc(Located { arena.alloc(Loc {
value: Expect(desugared_condition, desugared_continuation), value: Expect(desugared_condition, desugared_continuation),
region: loc_expr.region, region: loc_expr.region,
}) })
@ -350,7 +350,7 @@ fn desugar_field<'a>(
match field { match field {
RequiredValue(loc_str, spaces, loc_expr) => RequiredValue( RequiredValue(loc_str, spaces, loc_expr) => RequiredValue(
Located { Loc {
value: loc_str.value, value: loc_str.value,
region: loc_str.region, region: loc_str.region,
}, },
@ -358,7 +358,7 @@ fn desugar_field<'a>(
desugar_expr(arena, loc_expr), desugar_expr(arena, loc_expr),
), ),
OptionalValue(loc_str, spaces, loc_expr) => OptionalValue( OptionalValue(loc_str, spaces, loc_expr) => OptionalValue(
Located { Loc {
value: loc_str.value, value: loc_str.value,
region: loc_str.region, region: loc_str.region,
}, },
@ -367,7 +367,7 @@ fn desugar_field<'a>(
), ),
LabelOnly(loc_str) => { LabelOnly(loc_str) => {
// Desugar { x } into { x: x } // Desugar { x } into { x: x }
let loc_expr = Located { let loc_expr = Loc {
value: Var { value: Var {
module_name: "", module_name: "",
ident: loc_str.value, ident: loc_str.value,
@ -376,7 +376,7 @@ fn desugar_field<'a>(
}; };
RequiredValue( RequiredValue(
Located { Loc {
value: loc_str.value, value: loc_str.value,
region: loc_str.region, region: loc_str.region,
}, },
@ -423,11 +423,11 @@ fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) {
fn desugar_bin_ops<'a>( fn desugar_bin_ops<'a>(
arena: &'a Bump, arena: &'a Bump,
whole_region: Region, whole_region: Region,
lefts: &'a [(Located<Expr<'_>>, Located<BinOp>)], lefts: &'a [(Loc<Expr<'_>>, Loc<BinOp>)],
right: &'a Located<Expr<'_>>, right: &'a Loc<Expr<'_>>,
) -> &'a Located<Expr<'a>> { ) -> &'a Loc<Expr<'a>> {
let mut arg_stack: Vec<&'a Located<Expr>> = Vec::with_capacity_in(lefts.len() + 1, arena); let mut arg_stack: Vec<&'a Loc<Expr>> = Vec::with_capacity_in(lefts.len() + 1, arena);
let mut op_stack: Vec<Located<BinOp>> = Vec::with_capacity_in(lefts.len(), arena); let mut op_stack: Vec<Loc<BinOp>> = Vec::with_capacity_in(lefts.len(), arena);
for (loc_expr, loc_op) in lefts { for (loc_expr, loc_op) in lefts {
arg_stack.push(desugar_expr(arena, loc_expr)); arg_stack.push(desugar_expr(arena, loc_expr));
@ -447,18 +447,18 @@ fn desugar_bin_ops<'a>(
} }
enum Step<'a> { enum Step<'a> {
Error(&'a Located<Expr<'a>>), Error(&'a Loc<Expr<'a>>),
Push(Located<BinOp>), Push(Loc<BinOp>),
Skip, Skip,
} }
fn run_binop_step<'a>( fn run_binop_step<'a>(
arena: &'a Bump, arena: &'a Bump,
whole_region: Region, whole_region: Region,
arg_stack: &mut Vec<&'a Located<Expr<'a>>>, arg_stack: &mut Vec<&'a Loc<Expr<'a>>>,
op_stack: &mut Vec<Located<BinOp>>, op_stack: &mut Vec<Loc<BinOp>>,
next_op: Located<BinOp>, next_op: Loc<BinOp>,
) -> Result<(), &'a Located<Expr<'a>>> { ) -> Result<(), &'a Loc<Expr<'a>>> {
use Step::*; use Step::*;
match binop_step(arena, whole_region, arg_stack, op_stack, next_op) { 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>( fn binop_step<'a>(
arena: &'a Bump, arena: &'a Bump,
whole_region: Region, whole_region: Region,
arg_stack: &mut Vec<&'a Located<Expr<'a>>>, arg_stack: &mut Vec<&'a Loc<Expr<'a>>>,
op_stack: &mut Vec<Located<BinOp>>, op_stack: &mut Vec<Loc<BinOp>>,
next_op: Located<BinOp>, next_op: Loc<BinOp>,
) -> Step<'a> { ) -> Step<'a> {
use roc_module::called_via::Associativity::*; use roc_module::called_via::Associativity::*;
use std::cmp::Ordering; use std::cmp::Ordering;
@ -542,7 +542,7 @@ fn binop_step<'a>(
}; };
let value = Expr::PrecedenceConflict(arena.alloc(data)); 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::ast::{self, StrLiteral, StrSegment};
use roc_parse::pattern::PatternType; use roc_parse::pattern::PatternType;
use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError}; 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}; use roc_types::subs::{VarStore, Variable};
/// A pattern, including possible problems (e.g. shadowing) so that /// A pattern, including possible problems (e.g. shadowing) so that
/// codegen can generate a runtime error if this pattern is reached. /// codegen can generate a runtime error if this pattern is reached.
@ -18,12 +18,12 @@ pub enum Pattern {
whole_var: Variable, whole_var: Variable,
ext_var: Variable, ext_var: Variable,
tag_name: TagName, tag_name: TagName,
arguments: Vec<(Variable, Located<Pattern>)>, arguments: Vec<(Variable, Loc<Pattern>)>,
}, },
RecordDestructure { RecordDestructure {
whole_var: Variable, whole_var: Variable,
ext_var: Variable, ext_var: Variable,
destructs: Vec<Located<RecordDestruct>>, destructs: Vec<Loc<RecordDestruct>>,
}, },
IntLiteral(Variable, Box<str>, i64), IntLiteral(Variable, Box<str>, i64),
NumLiteral(Variable, Box<str>, i64), NumLiteral(Variable, Box<str>, i64),
@ -32,7 +32,7 @@ pub enum Pattern {
Underscore, Underscore,
// Runtime Exceptions // Runtime Exceptions
Shadowed(Region, Located<Ident>), Shadowed(Region, Loc<Ident>),
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments! // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
UnsupportedPattern(Region), UnsupportedPattern(Region),
// parse error patterns // parse error patterns
@ -50,8 +50,8 @@ pub struct RecordDestruct {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum DestructType { pub enum DestructType {
Required, Required,
Optional(Variable, Located<Expr>), Optional(Variable, Loc<Expr>),
Guard(Variable, Located<Pattern>), Guard(Variable, Loc<Pattern>),
} }
pub fn symbols_from_pattern(pattern: &Pattern) -> Vec<Symbol> { pub fn symbols_from_pattern(pattern: &Pattern) -> Vec<Symbol> {
@ -104,7 +104,7 @@ pub fn canonicalize_pattern<'a>(
pattern_type: PatternType, pattern_type: PatternType,
pattern: &ast::Pattern<'a>, pattern: &ast::Pattern<'a>,
region: Region, region: Region,
) -> (Output, Located<Pattern>) { ) -> (Output, Loc<Pattern>) {
use roc_parse::ast::Pattern::*; use roc_parse::ast::Pattern::*;
use PatternType::*; use PatternType::*;
@ -258,7 +258,7 @@ pub fn canonicalize_pattern<'a>(
Ok(symbol) => { Ok(symbol) => {
output.references.bound_symbols.insert(symbol); output.references.bound_symbols.insert(symbol);
destructs.push(Located { destructs.push(Loc {
region: loc_pattern.region, region: loc_pattern.region,
value: RecordDestruct { value: RecordDestruct {
var: var_store.fresh(), var: var_store.fresh(),
@ -297,7 +297,7 @@ pub fn canonicalize_pattern<'a>(
output.union(new_output); output.union(new_output);
destructs.push(Located { destructs.push(Loc {
region: loc_pattern.region, region: loc_pattern.region,
value: RecordDestruct { value: RecordDestruct {
var: var_store.fresh(), var: var_store.fresh(),
@ -329,7 +329,7 @@ pub fn canonicalize_pattern<'a>(
output.union(expr_output); output.union(expr_output);
destructs.push(Located { destructs.push(Loc {
region: loc_pattern.region, region: loc_pattern.region,
value: RecordDestruct { value: RecordDestruct {
var: var_store.fresh(), var: var_store.fresh(),
@ -391,7 +391,7 @@ pub fn canonicalize_pattern<'a>(
( (
output, output,
Located { Loc {
region, region,
value: can_pattern, value: can_pattern,
}, },
@ -432,7 +432,7 @@ fn malformed_pattern(env: &mut Env, problem: MalformedPatternProblem, region: Re
pub fn bindings_from_patterns<'a, I>(loc_patterns: I) -> Vec<(Symbol, Region)> pub fn bindings_from_patterns<'a, I>(loc_patterns: I) -> Vec<(Symbol, Region)>
where where
I: Iterator<Item = &'a Located<Pattern>>, I: Iterator<Item = &'a Loc<Pattern>>,
{ {
let mut answer = Vec::new(); let mut answer = Vec::new();
@ -464,7 +464,7 @@ fn add_bindings_from_patterns(
} }
} }
RecordDestructure { destructs, .. } => { RecordDestructure { destructs, .. } => {
for Located { for Loc {
region, region,
value: RecordDestruct { symbol, .. }, value: RecordDestruct { symbol, .. },
} in destructs } in destructs

View file

@ -2,7 +2,7 @@ use crate::expr::Expr;
use crate::pattern::Pattern; use crate::pattern::Pattern;
use roc_collections::all::ImSet; use roc_collections::all::ImSet;
use roc_module::symbol::Symbol; 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::subs::Variable;
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
@ -10,8 +10,8 @@ pub struct Procedure {
pub name: Option<Box<str>>, pub name: Option<Box<str>>,
pub is_self_tail_recursive: bool, pub is_self_tail_recursive: bool,
pub definition: Region, pub definition: Region,
pub args: Vec<Located<Pattern>>, pub args: Vec<Loc<Pattern>>,
pub body: Located<Expr>, pub body: Loc<Expr>,
pub references: References, pub references: References,
pub var: Variable, pub var: Variable,
pub ret_var: Variable, pub ret_var: Variable,
@ -20,8 +20,8 @@ pub struct Procedure {
impl Procedure { impl Procedure {
pub fn new( pub fn new(
definition: Region, definition: Region,
args: Vec<Located<Pattern>>, args: Vec<Loc<Pattern>>,
body: Located<Expr>, body: Loc<Expr>,
references: References, references: References,
var: Variable, var: Variable,
ret_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::ident::{Ident, Lowercase};
use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_problem::can::RuntimeError; 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::subs::{VarStore, Variable};
use roc_types::types::{Alias, Type}; use roc_types::types::{Alias, Type};
@ -41,7 +41,7 @@ impl Scope {
let mut type_variables: Vec<_> = free_vars.unnamed_vars.into_iter().collect(); let mut type_variables: Vec<_> = free_vars.unnamed_vars.into_iter().collect();
type_variables.sort(); type_variables.sort();
for (loc_name, (_, var)) in vars.iter().zip(type_variables) { 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 { let alias = Alias {
@ -87,7 +87,7 @@ impl Scope {
match self.idents.get(ident) { match self.idents.get(ident) {
Some((symbol, _)) => Ok(*symbol), Some((symbol, _)) => Ok(*symbol),
None => Err(RuntimeError::LookupNotInScope( None => Err(RuntimeError::LookupNotInScope(
Located { Loc {
region, region,
value: ident.clone(), value: ident.clone(),
}, },
@ -110,10 +110,10 @@ impl Scope {
exposed_ident_ids: &IdentIds, exposed_ident_ids: &IdentIds,
all_ident_ids: &mut IdentIds, all_ident_ids: &mut IdentIds,
region: Region, region: Region,
) -> Result<Symbol, (Region, Located<Ident>)> { ) -> Result<Symbol, (Region, Loc<Ident>)> {
match self.idents.get(&ident) { match self.idents.get(&ident) {
Some((_, original_region)) => { Some((_, original_region)) => {
let shadow = Located { let shadow = Loc {
value: ident, value: ident,
region, region,
}; };
@ -172,7 +172,7 @@ impl Scope {
&mut self, &mut self,
name: Symbol, name: Symbol,
region: Region, region: Region,
vars: Vec<Located<(Lowercase, Variable)>>, vars: Vec<Loc<(Lowercase, Variable)>>,
typ: Type, typ: Type,
) { ) {
let roc_types::types::VariableDetail { 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_line = state.line;
// let start_col = state.column + buf_len as u16; // let start_col = state.column + buf_len as u16;
// let end_line = start_line; // 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 } // 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_line = state.line;
// let start_col = state.column + buf_len as u16; // let start_col = state.column + buf_len as u16;
// let end_line = start_line; // 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>, // state: &State<'a>,
// buf_len: usize, // buf_len: usize,
// hex_str_len: usize, // hex_str_len: usize,
// ) -> Loc<V> { // ) -> Located<V> {
// let start_line = state.line; // let start_line = state.line;
// // +1 due to the `"` which precedes buf. // // +1 due to the `"` which precedes buf.
// let start_col = state.column + buf_len as u16 + 1; // 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_collections::all::MutMap;
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds}; use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds};
use roc_problem::can::Problem; use roc_problem::can::Problem;
use roc_region::all::{Located, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
use std::hash::Hash; use std::hash::Hash;
@ -23,7 +23,7 @@ pub fn can_expr(expr_str: &str) -> CanExprOut {
} }
pub struct CanExprOut { pub struct CanExprOut {
pub loc_expr: Located<Expr>, pub loc_expr: Loc<Expr>,
pub output: Output, pub output: Output,
pub problems: Vec<Problem>, pub problems: Vec<Problem>,
pub home: ModuleId, pub home: ModuleId,

View file

@ -17,7 +17,7 @@ mod test_can {
use roc_can::expr::Expr::{self, *}; use roc_can::expr::Expr::{self, *};
use roc_can::expr::{ClosureData, Recursive}; use roc_can::expr::{ClosureData, Recursive};
use roc_problem::can::{CycleEntry, FloatErrorKind, IntErrorKind, Problem, RuntimeError}; use roc_problem::can::{CycleEntry, FloatErrorKind, IntErrorKind, Problem, RuntimeError};
use roc_region::all::Region; use roc_region::all::{Position, Region};
use std::{f64, i64}; use std::{f64, i64};
fn assert_can(input: &str, expected: Expr) { fn assert_can(input: &str, expected: Expr) {
@ -943,8 +943,14 @@ mod test_can {
let problem = Problem::RuntimeError(RuntimeError::CircularDef(vec![CycleEntry { let problem = Problem::RuntimeError(RuntimeError::CircularDef(vec![CycleEntry {
symbol: interns.symbol(home, "x".into()), symbol: interns.symbol(home, "x".into()),
symbol_region: Region::new(0, 0, 0, 1), symbol_region: Region::new(
expr_region: Region::new(0, 0, 4, 5), Position { line: 0, column: 0 },
Position { line: 0, column: 1 },
),
expr_region: Region::new(
Position { line: 0, column: 4 },
Position { line: 0, column: 5 },
),
}])); }]));
assert_eq!(is_circular_def, true); assert_eq!(is_circular_def, true);
@ -974,18 +980,36 @@ mod test_can {
let problem = Problem::RuntimeError(RuntimeError::CircularDef(vec![ let problem = Problem::RuntimeError(RuntimeError::CircularDef(vec![
CycleEntry { CycleEntry {
symbol: interns.symbol(home, "x".into()), symbol: interns.symbol(home, "x".into()),
symbol_region: Region::new(0, 0, 0, 1), symbol_region: Region::new(
expr_region: Region::new(0, 0, 4, 5), Position { line: 0, column: 0 },
Position { line: 0, column: 1 },
),
expr_region: Region::new(
Position { line: 0, column: 4 },
Position { line: 0, column: 5 },
),
}, },
CycleEntry { CycleEntry {
symbol: interns.symbol(home, "y".into()), symbol: interns.symbol(home, "y".into()),
symbol_region: Region::new(1, 1, 0, 1), symbol_region: Region::new(
expr_region: Region::new(1, 1, 4, 5), Position { line: 1, column: 0 },
Position { line: 1, column: 1 },
),
expr_region: Region::new(
Position { line: 1, column: 4 },
Position { line: 1, column: 5 },
),
}, },
CycleEntry { CycleEntry {
symbol: interns.symbol(home, "z".into()), symbol: interns.symbol(home, "z".into()),
symbol_region: Region::new(2, 2, 0, 1), symbol_region: Region::new(
expr_region: Region::new(2, 2, 4, 5), Position { line: 2, column: 0 },
Position { line: 2, column: 1 },
),
expr_region: Region::new(
Position { line: 2, column: 4 },
Position { line: 2, column: 5 },
),
}, },
])); ]));

View file

@ -12,18 +12,17 @@ use roc_can::pattern::Pattern;
use roc_collections::all::{ImMap, Index, MutSet, SendMap}; use roc_collections::all::{ImMap, Index, MutSet, SendMap};
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::{ModuleId, Symbol}; 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::subs::Variable;
use roc_types::types::AnnotationSource::{self, *};
use roc_types::types::Type::{self, *}; use roc_types::types::Type::{self, *};
use roc_types::types::{Category, PReason, Reason, RecordField}; use roc_types::types::{AnnotationSource, Category, PReason, Reason, RecordField};
/// This is for constraining Defs /// This is for constraining Defs
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Info { pub struct Info {
pub vars: Vec<Variable>, pub vars: Vec<Variable>,
pub constraints: Vec<Constraint>, pub constraints: Vec<Constraint>,
pub def_types: SendMap<Symbol, Located<Type>>, pub def_types: SendMap<Symbol, Loc<Type>>,
} }
impl Info { impl Info {
@ -57,7 +56,7 @@ pub struct Env {
fn constrain_untyped_args( fn constrain_untyped_args(
env: &Env, env: &Env,
arguments: &[(Variable, Located<Pattern>)], arguments: &[(Variable, Loc<Pattern>)],
closure_type: Type, closure_type: Type,
return_type: Type, return_type: Type,
) -> (Vec<Variable>, PatternState, Type) { ) -> (Vec<Variable>, PatternState, Type) {
@ -78,6 +77,7 @@ fn constrain_untyped_args(
loc_pattern.region, loc_pattern.region,
pattern_expected, pattern_expected,
&mut pattern_state, &mut pattern_state,
true,
); );
vars.push(*pattern_var); vars.push(*pattern_var);
@ -604,7 +604,7 @@ pub fn constrain_expr(
FromAnnotation( FromAnnotation(
name.clone(), name.clone(),
*arity, *arity,
TypedWhenBranch { AnnotationSource::TypedWhenBranch {
index: Index::zero_based(index), index: Index::zero_based(index),
region: ann_source.region(), region: ann_source.region(),
}, },
@ -1040,6 +1040,7 @@ fn constrain_when_branch(
loc_pattern.region, loc_pattern.region,
pattern_expected.clone(), pattern_expected.clone(),
&mut state, &mut state,
true,
); );
} }
@ -1080,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_type = Variable(field_var);
let field_expected = NoExpectation(field_type.clone()); let field_expected = NoExpectation(field_type.clone());
let constraint = constrain_expr(env, loc_expr.region, &loc_expr.value, field_expected); let constraint = constrain_expr(env, loc_expr.region, &loc_expr.value, field_expected);
@ -1128,11 +1129,7 @@ pub fn constrain_decls(home: ModuleId, decls: &[Declaration]) -> Constraint {
constraint constraint
} }
fn constrain_def_pattern( fn constrain_def_pattern(env: &Env, loc_pattern: &Loc<Pattern>, expr_type: Type) -> PatternState {
env: &Env,
loc_pattern: &Located<Pattern>,
expr_type: Type,
) -> PatternState {
let pattern_expected = PExpected::NoExpectation(expr_type); let pattern_expected = PExpected::NoExpectation(expr_type);
let mut state = PatternState { let mut state = PatternState {
@ -1147,6 +1144,7 @@ fn constrain_def_pattern(
loc_pattern.region, loc_pattern.region,
pattern_expected, pattern_expected,
&mut state, &mut state,
true,
); );
state state
@ -1267,6 +1265,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
loc_pattern.region, loc_pattern.region,
pattern_expected, pattern_expected,
&mut state, &mut state,
false,
); );
} }
@ -1449,8 +1448,8 @@ fn instantiate_rigids(
introduced_vars: &IntroducedVariables, introduced_vars: &IntroducedVariables,
new_rigids: &mut Vec<Variable>, new_rigids: &mut Vec<Variable>,
ftv: &mut ImMap<Lowercase, Variable>, // rigids defined before the current annotation ftv: &mut ImMap<Lowercase, Variable>, // rigids defined before the current annotation
loc_pattern: &Located<Pattern>, loc_pattern: &Loc<Pattern>,
headers: &mut SendMap<Symbol, Located<Type>>, headers: &mut SendMap<Symbol, Loc<Type>>,
) -> Type { ) -> Type {
let mut annotation = annotation.clone(); let mut annotation = annotation.clone();
let mut rigid_substitution: ImMap<Variable, Type> = ImMap::default(); let mut rigid_substitution: ImMap<Variable, Type> = ImMap::default();
@ -1473,7 +1472,7 @@ fn instantiate_rigids(
if let Some(new_headers) = crate::pattern::headers_from_annotation( if let Some(new_headers) = crate::pattern::headers_from_annotation(
&loc_pattern.value, &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 (symbol, loc_type) in new_headers {
for var in loc_type.value.variables() { for var in loc_type.value.variables() {
@ -1634,6 +1633,7 @@ pub fn rec_defs_help(
loc_pattern.region, loc_pattern.region,
pattern_expected, pattern_expected,
&mut state, &mut state,
false,
); );
} }
@ -1770,7 +1770,7 @@ fn constrain_field_update(
var: Variable, var: Variable,
region: Region, region: Region,
field: Lowercase, field: Lowercase,
loc_expr: &Located<Expr>, loc_expr: &Loc<Expr>,
) -> (Variable, Type, Constraint) { ) -> (Variable, Type, Constraint) {
let field_type = Type::Variable(var); let field_type = Type::Variable(var);
let reason = Reason::RecordUpdateValue(field); let reason = Reason::RecordUpdateValue(field);

View file

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

View file

@ -1,19 +1,19 @@
use crate::builtins; use crate::builtins;
use crate::expr::{constrain_expr, Env}; 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::expected::{Expected, PExpected};
use roc_can::pattern::Pattern::{self, *}; use roc_can::pattern::Pattern::{self, *};
use roc_can::pattern::{DestructType, RecordDestruct}; use roc_can::pattern::{DestructType, RecordDestruct};
use roc_collections::all::{Index, SendMap}; use roc_collections::all::{Index, SendMap};
use roc_module::ident::Lowercase; use roc_module::ident::Lowercase;
use roc_module::symbol::Symbol; 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::subs::Variable;
use roc_types::types::{Category, PReason, PatternCategory, Reason, RecordField, Type}; use roc_types::types::{Category, PReason, PatternCategory, Reason, RecordField, Type};
#[derive(Default)] #[derive(Default)]
pub struct PatternState { pub struct PatternState {
pub headers: SendMap<Symbol, Located<Type>>, pub headers: SendMap<Symbol, Loc<Type>>,
pub vars: Vec<Variable>, pub vars: Vec<Variable>,
pub constraints: Vec<Constraint>, pub constraints: Vec<Constraint>,
} }
@ -27,8 +27,8 @@ pub struct PatternState {
/// definition has an annotation, we instead now add `x => Int`. /// definition has an annotation, we instead now add `x => Int`.
pub fn headers_from_annotation( pub fn headers_from_annotation(
pattern: &Pattern, pattern: &Pattern,
annotation: &Located<Type>, annotation: &Loc<Type>,
) -> Option<SendMap<Symbol, Located<Type>>> { ) -> Option<SendMap<Symbol, Loc<Type>>> {
let mut headers = SendMap::default(); let mut headers = SendMap::default();
// Check that the annotation structurally agrees with the pattern, preventing e.g. `{ x, y } : Int` // 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 // in such incorrect cases we don't put the full annotation in headers, just a variable, and let
@ -44,8 +44,8 @@ pub fn headers_from_annotation(
fn headers_from_annotation_help( fn headers_from_annotation_help(
pattern: &Pattern, pattern: &Pattern,
annotation: &Located<Type>, annotation: &Loc<Type>,
headers: &mut SendMap<Symbol, Located<Type>>, headers: &mut SendMap<Symbol, Loc<Type>>,
) -> bool { ) -> bool {
match pattern { match pattern {
Identifier(symbol) => { Identifier(symbol) => {
@ -76,7 +76,7 @@ fn headers_from_annotation_help(
if let Some(field_type) = fields.get(&destruct.label) { if let Some(field_type) = fields.get(&destruct.label) {
headers.insert( headers.insert(
destruct.symbol, destruct.symbol,
Located::at(annotation.region, field_type.clone().into_inner()), Loc::at(annotation.region, field_type.clone().into_inner()),
); );
} else { } else {
return false; return false;
@ -105,7 +105,7 @@ fn headers_from_annotation_help(
.all(|(arg_pattern, arg_type)| { .all(|(arg_pattern, arg_type)| {
headers_from_annotation_help( headers_from_annotation_help(
&arg_pattern.1.value, &arg_pattern.1.value,
&Located::at(annotation.region, arg_type.clone()), &Loc::at(annotation.region, arg_type.clone()),
headers, headers,
) )
}) })
@ -118,6 +118,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 /// This accepts PatternState (rather than returning it) so that the caller can
/// initialize the Vecs in PatternState using with_capacity /// initialize the Vecs in PatternState using with_capacity
/// based on its knowledge of their lengths. /// based on its knowledge of their lengths.
@ -127,16 +144,35 @@ pub fn constrain_pattern(
region: Region, region: Region,
expected: PExpected<Type>, expected: PExpected<Type>,
state: &mut PatternState, state: &mut PatternState,
destruct_position: bool,
) { ) {
match pattern { 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(_, _) => { Underscore | UnsupportedPattern(_) | MalformedPattern(_, _) | Shadowed(_, _) => {
// Neither the _ pattern nor erroneous ones add any constraints. // Neither the _ pattern nor erroneous ones add any constraints.
} }
Identifier(symbol) => { Identifier(symbol) => {
if destruct_position {
state.constraints.push(Constraint::Present(
expected.get_type_ref().clone(),
PresenceConstraint::IsOpen,
));
}
state.headers.insert( state.headers.insert(
*symbol, *symbol,
Located { Loc {
region, region,
value: expected.get_type(), value: expected.get_type(),
}, },
@ -192,7 +228,7 @@ pub fn constrain_pattern(
let mut field_types: SendMap<Lowercase, RecordField<Type>> = SendMap::default(); let mut field_types: SendMap<Lowercase, RecordField<Type>> = SendMap::default();
for Located { for Loc {
value: value:
RecordDestruct { RecordDestruct {
var, var,
@ -209,12 +245,12 @@ pub fn constrain_pattern(
if !state.headers.contains_key(symbol) { if !state.headers.contains_key(symbol) {
state state
.headers .headers
.insert(*symbol, Located::at(region, pat_type.clone())); .insert(*symbol, Loc::at(region, pat_type.clone()));
} }
let field_type = match typ { let field_type = match typ {
DestructType::Guard(guard_var, loc_guard) => { DestructType::Guard(guard_var, loc_guard) => {
state.constraints.push(Constraint::Pattern( state.constraints.push(make_pattern_constraint(
region, region,
PatternCategory::PatternGuard, PatternCategory::PatternGuard,
Type::Variable(*guard_var), Type::Variable(*guard_var),
@ -223,15 +259,23 @@ pub fn constrain_pattern(
pat_type.clone(), pat_type.clone(),
loc_guard.region, loc_guard.region,
), ),
destruct_position,
)); ));
state.vars.push(*guard_var); 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) RecordField::Demanded(pat_type)
} }
DestructType::Optional(expr_var, loc_expr) => { DestructType::Optional(expr_var, loc_expr) => {
state.constraints.push(Constraint::Pattern( state.constraints.push(make_pattern_constraint(
region, region,
PatternCategory::PatternDefault, PatternCategory::PatternDefault,
Type::Variable(*expr_var), Type::Variable(*expr_var),
@ -240,6 +284,7 @@ pub fn constrain_pattern(
pat_type.clone(), pat_type.clone(),
loc_expr.region, loc_expr.region,
), ),
destruct_position,
)); ));
state.vars.push(*expr_var); state.vars.push(*expr_var);
@ -276,11 +321,12 @@ pub fn constrain_pattern(
region, region,
); );
let record_con = Constraint::Pattern( let record_con = make_pattern_constraint(
region, region,
PatternCategory::Record, PatternCategory::Record,
Type::Variable(*whole_var), Type::Variable(*whole_var),
expected, expected,
destruct_position,
); );
state.constraints.push(whole_con); state.constraints.push(whole_con);
@ -307,10 +353,23 @@ pub fn constrain_pattern(
pattern_type, pattern_type,
region, 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( 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), Type::Variable(*whole_var),
Expected::NoExpectation(Type::TagUnion( Expected::NoExpectation(Type::TagUnion(
vec![(tag_name.clone(), argument_types)], vec![(tag_name.clone(), argument_types)],
@ -318,13 +377,15 @@ pub fn constrain_pattern(
)), )),
Category::Storage(std::file!(), std::line!()), Category::Storage(std::file!(), std::line!()),
region, region,
); )
};
let tag_con = Constraint::Pattern( let tag_con = make_pattern_constraint(
region, region,
PatternCategory::Ctor(tag_name.clone()), PatternCategory::Ctor(tag_name.clone()),
Type::Variable(*whole_var), Type::Variable(*whole_var),
expected, expected,
destruct_position,
); );
state.vars.push(*whole_var); state.vars.push(*whole_var);

View file

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

View file

@ -1,9 +1,10 @@
use crate::{ use crate::{
collection::fmt_collection,
spaces::{fmt_comments_only, fmt_spaces, NewlineAt, INDENT}, spaces::{fmt_comments_only, fmt_spaces, NewlineAt, INDENT},
Buf, Buf,
}; };
use roc_parse::ast::{AssignedField, Expr, Tag, TypeAnnotation}; use roc_parse::ast::{AliasHeader, AssignedField, Expr, Tag, TypeAnnotation};
use roc_region::all::Located; use roc_region::all::Loc;
/// Does an AST node need parens around it? /// Does an AST node need parens around it?
/// ///
@ -39,12 +40,12 @@ pub enum Newlines {
No, No,
} }
pub trait Formattable<'a> { pub trait Formattable {
fn is_multiline(&self) -> bool; fn is_multiline(&self) -> bool;
fn format_with_options( fn format_with_options<'buf>(
&self, &self,
buf: &mut Buf<'a>, buf: &mut Buf<'buf>,
_parens: Parens, _parens: Parens,
_newlines: Newlines, _newlines: Newlines,
indent: u16, indent: u16,
@ -52,23 +53,23 @@ pub trait Formattable<'a> {
self.format(buf, indent); 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); self.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
} }
} }
/// A reference to a formattable value is also formattable /// 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 where
T: Formattable<'a>, T: Formattable,
{ {
fn is_multiline(&self) -> bool { fn is_multiline(&self) -> bool {
(*self).is_multiline() (*self).is_multiline()
} }
fn format_with_options( fn format_with_options<'buf>(
&self, &self,
buf: &mut Buf<'a>, buf: &mut Buf<'buf>,
parens: Parens, parens: Parens,
newlines: Newlines, newlines: Newlines,
indent: u16, indent: u16,
@ -76,23 +77,23 @@ where
(*self).format_with_options(buf, parens, newlines, indent) (*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) (*self).format(buf, indent)
} }
} }
/// A Located formattable value is also formattable /// A Located formattable value is also formattable
impl<'a, T> Formattable<'a> for Located<T> impl<T> Formattable for Loc<T>
where where
T: Formattable<'a>, T: Formattable,
{ {
fn is_multiline(&self) -> bool { fn is_multiline(&self) -> bool {
self.value.is_multiline() self.value.is_multiline()
} }
fn format_with_options( fn format_with_options<'buf>(
&self, &self,
buf: &mut Buf<'a>, buf: &mut Buf<'buf>,
parens: Parens, parens: Parens,
newlines: Newlines, newlines: Newlines,
indent: u16, indent: u16,
@ -101,102 +102,12 @@ where
.format_with_options(buf, parens, newlines, indent) .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) self.value.format(buf, indent)
} }
} }
macro_rules! format_sequence { impl<'a> Formattable for TypeAnnotation<'a> {
($buf: expr, $indent:expr, $start:expr, $end:expr, $items:expr, $newline:expr, $t:ident) => {
$buf.indent($indent);
let is_multiline = $items.iter().any(|item| item.value.is_multiline())
|| !$items.final_comments().is_empty();
if is_multiline {
let braces_indent = $indent;
let item_indent = braces_indent + INDENT;
if ($newline == Newlines::Yes) {
$buf.newline();
}
$buf.indent(braces_indent);
$buf.push($start);
for item in $items.iter() {
match item.value {
$t::SpaceBefore(expr_below, spaces_above_expr) => {
$buf.newline();
fmt_comments_only(
$buf,
spaces_above_expr.iter(),
NewlineAt::Bottom,
item_indent,
);
match &expr_below {
$t::SpaceAfter(expr_above, spaces_below_expr) => {
expr_above.format($buf, item_indent);
$buf.push(',');
fmt_comments_only(
$buf,
spaces_below_expr.iter(),
NewlineAt::Top,
item_indent,
);
}
_ => {
expr_below.format($buf, item_indent);
$buf.push(',');
}
}
}
$t::SpaceAfter(sub_expr, spaces) => {
$buf.newline();
sub_expr.format($buf, item_indent);
$buf.push(',');
fmt_comments_only($buf, spaces.iter(), NewlineAt::Top, item_indent);
}
_ => {
$buf.newline();
item.format($buf, item_indent);
$buf.push(',');
}
}
}
fmt_comments_only(
$buf,
$items.final_comments().iter(),
NewlineAt::Top,
item_indent,
);
$buf.newline();
$buf.indent(braces_indent);
$buf.push($end);
} 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(' ');
item.format($buf, $indent);
if iter.peek().is_some() {
$buf.push(',');
}
}
if !$items.is_empty() {
$buf.push(' ');
}
$buf.push($end);
}
};
}
impl<'a> Formattable<'a> for TypeAnnotation<'a> {
fn is_multiline(&self) -> bool { fn is_multiline(&self) -> bool {
use roc_parse::ast::TypeAnnotation::*; use roc_parse::ast::TypeAnnotation::*;
@ -215,7 +126,7 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
|| args.iter().any(|loc_arg| (&loc_arg.value).is_multiline()) || args.iter().any(|loc_arg| (&loc_arg.value).is_multiline())
} }
Apply(_, _, args) => 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 } => { Record { fields, ext } => {
match ext { match ext {
@ -237,9 +148,9 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
} }
} }
fn format_with_options( fn format_with_options<'buf>(
&self, &self,
buf: &mut Buf<'a>, buf: &mut Buf<'buf>,
parens: Parens, parens: Parens,
newlines: Newlines, newlines: Newlines,
indent: u16, indent: u16,
@ -266,10 +177,12 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
if it.peek().is_some() { 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( (&result.value).format_with_options(
buf, buf,
@ -299,7 +212,7 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
buf.push_str(name); buf.push_str(name);
for argument in *arguments { for argument in *arguments {
buf.push(' '); buf.spaces(1);
(&argument.value).format_with_options( (&argument.value).format_with_options(
buf, buf,
Parens::InApply, Parens::InApply,
@ -317,7 +230,7 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
Inferred => buf.push('_'), Inferred => buf.push('_'),
TagUnion { tags, ext } => { TagUnion { tags, ext } => {
format_sequence!(buf, indent, '[', ']', tags, newlines, Tag); fmt_collection(buf, indent, '[', ']', *tags, newlines);
if let Some(loc_ext_ann) = *ext { if let Some(loc_ext_ann) = *ext {
loc_ext_ann.value.format(buf, indent); loc_ext_ann.value.format(buf, indent);
@ -325,18 +238,25 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
} }
Record { fields, ext } => { Record { fields, ext } => {
format_sequence!(buf, indent, '{', '}', fields, newlines, AssignedField); fmt_collection(buf, indent, '{', '}', *fields, newlines);
if let Some(loc_ext_ann) = *ext { if let Some(loc_ext_ann) = *ext {
loc_ext_ann.value.format(buf, indent); loc_ext_ann.value.format(buf, indent);
} }
} }
As(lhs, _spaces, rhs) => { As(lhs, _spaces, AliasHeader { name, vars }) => {
// TODO use spaces? // TODO use spaces?
lhs.value.format(buf, indent); lhs.value.format(buf, indent);
buf.spaces(1);
buf.push_str("as"); buf.push_str("as");
rhs.value.format(buf, indent); 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) => { SpaceBefore(ann, spaces) => {
@ -364,41 +284,41 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
/// > term: { x: 100, y: True } /// > term: { x: 100, y: True }
/// ///
/// So we need two instances, each having the specific separator /// 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 { fn is_multiline(&self) -> bool {
is_multiline_assigned_field_help(self) is_multiline_assigned_field_help(self)
} }
fn format_with_options( fn format_with_options<'buf>(
&self, &self,
buf: &mut Buf<'a>, buf: &mut Buf<'buf>,
parens: Parens, parens: Parens,
newlines: Newlines, newlines: Newlines,
indent: u16, indent: u16,
) { ) {
// we abuse the `Newlines` type to decide between multiline or single-line layout // 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 { fn is_multiline(&self) -> bool {
is_multiline_assigned_field_help(self) is_multiline_assigned_field_help(self)
} }
fn format_with_options( fn format_with_options<'buf>(
&self, &self,
buf: &mut Buf<'a>, buf: &mut Buf<'buf>,
parens: Parens, parens: Parens,
newlines: Newlines, newlines: Newlines,
indent: u16, indent: u16,
) { ) {
// we abuse the `Newlines` type to decide between multiline or single-line layout // 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::*; use self::AssignedField::*;
match afield { match afield {
@ -411,15 +331,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>, zelf: &AssignedField<'a, T>,
buf: &mut Buf<'a>, buf: &mut Buf<'buf>,
parens: Parens, parens: Parens,
indent: u16, indent: u16,
separator_prefix: &str, separator_spaces: usize,
is_multiline: bool, is_multiline: bool,
) where ) where
T: Formattable<'a>, T: Formattable,
{ {
use self::AssignedField::*; use self::AssignedField::*;
@ -436,8 +356,9 @@ fn format_assigned_field_help<'a, T>(
fmt_spaces(buf, spaces.iter(), indent); fmt_spaces(buf, spaces.iter(), indent);
} }
buf.push_str(separator_prefix); buf.spaces(separator_spaces);
buf.push_str(":"); buf.push_str(":");
buf.spaces(1);
ann.value.format(buf, indent); ann.value.format(buf, indent);
} }
OptionalValue(name, spaces, ann) => { OptionalValue(name, spaces, ann) => {
@ -452,7 +373,7 @@ fn format_assigned_field_help<'a, T>(
fmt_spaces(buf, spaces.iter(), indent); fmt_spaces(buf, spaces.iter(), indent);
} }
buf.push_str(separator_prefix); buf.spaces(separator_spaces);
buf.push('?'); buf.push('?');
ann.value.format(buf, indent); ann.value.format(buf, indent);
} }
@ -471,7 +392,7 @@ fn format_assigned_field_help<'a, T>(
buf, buf,
parens, parens,
indent, indent,
separator_prefix, separator_spaces,
is_multiline, is_multiline,
); );
} }
@ -481,7 +402,7 @@ fn format_assigned_field_help<'a, T>(
buf, buf,
parens, parens,
indent, indent,
separator_prefix, separator_spaces,
is_multiline, is_multiline,
); );
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent); fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
@ -492,7 +413,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 { fn is_multiline(&self) -> bool {
use self::Tag::*; use self::Tag::*;
@ -505,9 +426,9 @@ impl<'a> Formattable<'a> for Tag<'a> {
} }
} }
fn format_with_options( fn format_with_options<'buf>(
&self, &self,
buf: &mut Buf<'a>, buf: &mut Buf<'buf>,
_parens: Parens, _parens: Parens,
_newlines: Newlines, _newlines: Newlines,
indent: u16, indent: u16,
@ -527,7 +448,7 @@ impl<'a> Formattable<'a> for Tag<'a> {
} }
} else { } else {
for arg in *args { for arg in *args {
buf.push(' '); buf.spaces(1);
arg.format_with_options(buf, Parens::InApply, Newlines::No, indent); arg.format_with_options(buf, Parens::InApply, Newlines::No, indent);
} }
} }
@ -545,7 +466,7 @@ impl<'a> Formattable<'a> for Tag<'a> {
} }
} else { } else {
for arg in *args { for arg in *args {
buf.push(' '); buf.spaces(1);
arg.format_with_options(buf, Parens::InApply, Newlines::No, indent); arg.format_with_options(buf, Parens::InApply, Newlines::No, indent);
} }
} }

View file

@ -1,52 +1,74 @@
use roc_parse::ast::Collection; use roc_parse::ast::{Collection, ExtractSpaces};
use crate::{ use crate::{
annotation::{Formattable, Newlines, Parens}, annotation::{Formattable, Newlines},
spaces::{fmt_comments_only, NewlineAt, INDENT}, spaces::{fmt_comments_only, NewlineAt, INDENT},
Buf, Buf,
}; };
pub struct CollectionConfig { pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
pub begin: char, buf: &mut Buf<'buf>,
pub end: char, indent: u16,
pub delimiter: char, start: char,
end: char,
items: Collection<'a, T>,
newline: Newlines,
) where
<T as ExtractSpaces<'a>>::Item: Formattable,
{
buf.indent(indent);
let is_multiline =
items.iter().any(|item| item.is_multiline()) || !items.final_comments().is_empty();
if is_multiline {
let braces_indent = indent;
let item_indent = braces_indent + INDENT;
if newline == Newlines::Yes {
buf.newline();
}
buf.indent(braces_indent);
buf.push(start);
for item in items.iter() {
let item = item.extract_spaces();
buf.newline();
if !item.before.is_empty() {
fmt_comments_only(buf, item.before.iter(), NewlineAt::Bottom, item_indent);
} }
pub fn fmt_collection<'a, F: Formattable<'a>>( item.item.format(buf, item_indent);
buf: &mut Buf<'a>,
items: Collection<'_, F>, buf.push(',');
indent: u16,
config: CollectionConfig, if !item.after.is_empty() {
) { fmt_comments_only(buf, item.after.iter(), NewlineAt::Top, item_indent);
let loc_items = items.items;
let final_comments = items.final_comments();
buf.indent(indent);
buf.push(config.begin);
if !loc_items.is_empty() || !final_comments.iter().all(|c| c.is_newline()) {
let is_multiline = loc_items.iter().any(|item| item.is_multiline());
if is_multiline {
let item_indent = indent + INDENT;
for item in loc_items.iter() {
buf.newline();
item.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, item_indent);
buf.push(config.delimiter);
} }
fmt_comments_only(buf, final_comments.iter(), NewlineAt::Top, item_indent); }
fmt_comments_only(
buf,
items.final_comments().iter(),
NewlineAt::Top,
item_indent,
);
buf.newline(); buf.newline();
} else { } else {
// is_multiline == false // is_multiline == false
let mut iter = loc_items.iter().peekable(); // there is no comment to add
buf.push(start);
let mut iter = items.iter().peekable();
while let Some(item) = iter.next() { while let Some(item) = iter.next() {
buf.push(' '); buf.spaces(1);
item.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); item.format(buf, indent);
if iter.peek().is_some() { if iter.peek().is_some() {
buf.push(config.delimiter); buf.push(',');
}
}
if !items.is_empty() {
buf.spaces(1);
} }
} }
buf.indent(indent); buf.indent(indent);
buf.push(' '); buf.push(end);
}
}
buf.indent(indent);
buf.push(config.end);
} }

View file

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

View file

@ -1,4 +1,5 @@
use crate::annotation::{Formattable, Newlines, Parens}; use crate::annotation::{Formattable, Newlines, Parens};
use crate::collection::fmt_collection;
use crate::def::fmt_def; use crate::def::fmt_def;
use crate::pattern::fmt_pattern; use crate::pattern::fmt_pattern;
use crate::spaces::{fmt_comments_only, fmt_spaces, NewlineAt, INDENT}; use crate::spaces::{fmt_comments_only, fmt_spaces, NewlineAt, INDENT};
@ -8,9 +9,9 @@ use roc_parse::ast::{
AssignedField, Base, Collection, CommentOrNewline, Expr, Pattern, WhenBranch, AssignedField, Base, Collection, CommentOrNewline, Expr, Pattern, WhenBranch,
}; };
use roc_parse::ast::{StrLiteral, StrSegment}; 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 { fn is_multiline(&self) -> bool {
use roc_parse::ast::Expr::*; use roc_parse::ast::Expr::*;
// TODO cache these answers using a Map<Pointer, bool>, so // TODO cache these answers using a Map<Pointer, bool>, so
@ -105,9 +106,9 @@ impl<'a> Formattable<'a> for Expr<'a> {
} }
} }
fn format_with_options( fn format_with_options<'buf>(
&self, &self,
buf: &mut Buf<'a>, buf: &mut Buf<'buf>,
parens: Parens, parens: Parens,
newlines: Newlines, newlines: Newlines,
indent: u16, indent: u16,
@ -186,7 +187,7 @@ impl<'a> Formattable<'a> for Expr<'a> {
} }
} else { } else {
for loc_arg in loc_args.iter() { for loc_arg in loc_args.iter() {
buf.push(' '); buf.spaces(1);
loc_arg.format_with_options(buf, Parens::InApply, Newlines::Yes, indent); loc_arg.format_with_options(buf, Parens::InApply, Newlines::Yes, indent);
} }
} }
@ -256,9 +257,7 @@ impl<'a> Formattable<'a> for Expr<'a> {
fmt_if(buf, branches, final_else, self.is_multiline(), indent); fmt_if(buf, branches, final_else, self.is_multiline(), indent);
} }
When(loc_condition, branches) => fmt_when(buf, loc_condition, branches, indent), When(loc_condition, branches) => fmt_when(buf, loc_condition, branches, indent),
List(items) => { List(items) => fmt_collection(buf, indent, '[', ']', *items, Newlines::No),
fmt_list(buf, *items, indent);
}
BinOps(lefts, right) => fmt_bin_ops(buf, lefts, right, false, parens, indent), BinOps(lefts, right) => fmt_bin_ops(buf, lefts, right, false, parens, indent),
UnaryOp(sub_expr, unary_op) => { UnaryOp(sub_expr, unary_op) => {
buf.indent(indent); buf.indent(indent);
@ -290,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::*; use StrSegment::*;
match seg { match seg {
Plaintext(string) => { Plaintext(string) => {
buf.push_str(string); buf.push_str_allow_spaces(string);
} }
Unicode(loc_str) => { Unicode(loc_str) => {
buf.push_str("\\u("); buf.push_str("\\u(");
@ -345,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::*; use roc_parse::ast::StrLiteral::*;
buf.indent(indent); buf.indent(indent);
buf.push('"'); buf.push('"');
match literal { match literal {
PlainLine(string) => { PlainLine(string) => {
buf.push_str(string); buf.push_str_allow_spaces(string);
} }
Line(segments) => { Line(segments) => {
for seg in segments.iter() { for seg in segments.iter() {
@ -396,10 +395,10 @@ pub fn fmt_str_literal<'a>(buf: &mut Buf<'a>, literal: StrLiteral<'a>, indent: u
buf.push('"'); buf.push('"');
} }
fn fmt_bin_ops<'a>( fn fmt_bin_ops<'a, 'buf>(
buf: &mut Buf<'a>, buf: &mut Buf<'buf>,
lefts: &'a [(Located<Expr<'a>>, Located<BinOp>)], lefts: &'a [(Loc<Expr<'a>>, Loc<BinOp>)],
loc_right_side: &'a Located<Expr<'a>>, loc_right_side: &'a Loc<Expr<'a>>,
part_of_multi_line_bin_ops: bool, part_of_multi_line_bin_ops: bool,
apply_needs_parens: Parens, apply_needs_parens: Parens,
indent: u16, indent: u16,
@ -417,100 +416,17 @@ fn fmt_bin_ops<'a>(
buf.newline(); buf.newline();
buf.indent(indent + INDENT); buf.indent(indent + INDENT);
} else { } else {
buf.push(' '); buf.spaces(1);
} }
push_op(buf, bin_op); push_op(buf, bin_op);
buf.push(' '); buf.spaces(1);
} }
loc_right_side.format_with_options(buf, apply_needs_parens, Newlines::Yes, indent); loc_right_side.format_with_options(buf, apply_needs_parens, Newlines::Yes, indent);
} }
fn fmt_list<'a>(buf: &mut Buf<'a>, items: Collection<'a, &'a Located<Expr<'a>>>, indent: u16) {
let loc_items = items.items;
let final_comments = items.final_comments();
buf.indent(indent);
if loc_items.is_empty() && final_comments.iter().all(|c| c.is_newline()) {
buf.push_str("[]");
} else {
buf.push('[');
let is_multiline = loc_items.iter().any(|item| (&item.value).is_multiline());
if is_multiline {
let item_indent = indent + INDENT;
for item in loc_items.iter() {
match &item.value {
// TODO?? These SpaceAfter/SpaceBefore litany seems overcomplicated
// Can we simplify this? Or at least move this in a separate function.
Expr::SpaceBefore(expr_below, spaces_above_expr) => {
buf.newline();
fmt_comments_only(
buf,
spaces_above_expr.iter(),
NewlineAt::Bottom,
item_indent,
);
match &expr_below {
Expr::SpaceAfter(expr_above, spaces_below_expr) => {
expr_above.format(buf, item_indent);
buf.push(',');
fmt_comments_only(
buf,
spaces_below_expr.iter(),
NewlineAt::Top,
item_indent,
);
}
_ => {
expr_below.format(buf, item_indent);
buf.push(',');
}
}
}
Expr::SpaceAfter(sub_expr, spaces) => {
buf.newline();
sub_expr.format(buf, item_indent);
buf.push(',');
fmt_comments_only(buf, spaces.iter(), NewlineAt::Top, item_indent);
}
_ => {
buf.newline();
item.format_with_options(
buf,
Parens::NotNeeded,
Newlines::Yes,
item_indent,
);
buf.push(',');
}
}
}
fmt_comments_only(buf, final_comments.iter(), NewlineAt::Top, item_indent);
buf.newline();
buf.indent(indent);
buf.push(']');
} else {
// is_multiline == false
let mut iter = loc_items.iter().peekable();
while let Some(item) = iter.next() {
buf.push(' ');
item.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
if iter.peek().is_some() {
buf.push(',');
}
}
buf.push_str(" ]");
}
}
}
fn empty_line_before_expr<'a>(expr: &'a Expr<'a>) -> bool { fn empty_line_before_expr<'a>(expr: &'a Expr<'a>) -> bool {
use roc_parse::ast::Expr::*; use roc_parse::ast::Expr::*;
@ -538,9 +454,9 @@ fn empty_line_before_expr<'a>(expr: &'a Expr<'a>) -> bool {
} }
} }
fn fmt_when<'a>( fn fmt_when<'a, 'buf>(
buf: &mut Buf<'a>, buf: &mut Buf<'buf>,
loc_condition: &'a Located<Expr<'a>>, loc_condition: &'a Loc<Expr<'a>>,
branches: &[&'a WhenBranch<'a>], branches: &[&'a WhenBranch<'a>],
indent: u16, indent: u16,
) { ) {
@ -586,9 +502,9 @@ fn fmt_when<'a>(
} }
buf.indent(indent); buf.indent(indent);
} else { } else {
buf.push(' '); buf.spaces(1);
loc_condition.format(buf, indent); loc_condition.format(buf, indent);
buf.push(' '); buf.spaces(1);
} }
buf.push_str("is"); buf.push_str("is");
buf.newline(); buf.newline();
@ -600,7 +516,9 @@ fn fmt_when<'a>(
let (first_pattern, rest) = patterns.split_first().unwrap(); let (first_pattern, rest) = patterns.split_first().unwrap();
let is_multiline = match rest.last() { let is_multiline = match rest.last() {
None => false, None => false,
Some(last_pattern) => first_pattern.region.start_line != last_pattern.region.end_line, Some(last_pattern) => {
first_pattern.region.start().line != last_pattern.region.end().line
}
}; };
fmt_pattern( fmt_pattern(
@ -615,11 +533,13 @@ fn fmt_when<'a>(
buf.indent(indent + INDENT); buf.indent(indent + INDENT);
} }
buf.push_str(" |"); buf.push_str(" |");
buf.spaces(1);
fmt_pattern(buf, &when_pattern.value, indent + INDENT, Parens::NotNeeded); fmt_pattern(buf, &when_pattern.value, indent + INDENT, Parens::NotNeeded);
} }
if let Some(guard_expr) = &branch.guard { 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); guard_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT);
} }
@ -653,10 +573,10 @@ fn fmt_when<'a>(
} }
} }
fn fmt_expect<'a>( fn fmt_expect<'a, 'buf>(
buf: &mut Buf<'a>, buf: &mut Buf<'buf>,
condition: &'a Located<Expr<'a>>, condition: &'a Loc<Expr<'a>>,
continuation: &'a Located<Expr<'a>>, continuation: &'a Loc<Expr<'a>>,
is_multiline: bool, is_multiline: bool,
indent: u16, indent: u16,
) { ) {
@ -672,10 +592,10 @@ fn fmt_expect<'a>(
continuation.format(buf, return_indent); continuation.format(buf, return_indent);
} }
fn fmt_if<'a>( fn fmt_if<'a, 'buf>(
buf: &mut Buf<'a>, buf: &mut Buf<'buf>,
branches: &'a [(Located<Expr<'a>>, Located<Expr<'a>>)], branches: &'a [(Loc<Expr<'a>>, Loc<Expr<'a>>)],
final_else: &'a Located<Expr<'a>>, final_else: &'a Loc<Expr<'a>>,
is_multiline: bool, is_multiline: bool,
indent: u16, indent: u16,
) { ) {
@ -697,6 +617,7 @@ fn fmt_if<'a>(
if i > 0 { if i > 0 {
buf.push_str("else"); buf.push_str("else");
buf.spaces(1);
} }
buf.push_str("if"); buf.push_str("if");
@ -740,9 +661,9 @@ fn fmt_if<'a>(
} }
buf.indent(indent); buf.indent(indent);
} else { } else {
buf.push(' '); buf.spaces(1);
loc_condition.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); loc_condition.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
buf.push(' '); buf.spaces(1);
} }
buf.push_str("then"); buf.push_str("then");
@ -778,6 +699,7 @@ fn fmt_if<'a>(
} }
} else { } else {
buf.push_str(""); buf.push_str("");
buf.spaces(1);
loc_then.format(buf, return_indent); loc_then.format(buf, return_indent);
} }
} }
@ -788,15 +710,16 @@ fn fmt_if<'a>(
buf.newline(); buf.newline();
} else { } else {
buf.push_str(" else"); buf.push_str(" else");
buf.spaces(1);
} }
final_else.format(buf, return_indent); final_else.format(buf, return_indent);
} }
fn fmt_closure<'a>( fn fmt_closure<'a, 'buf>(
buf: &mut Buf<'a>, buf: &mut Buf<'buf>,
loc_patterns: &'a [Located<Pattern<'a>>], loc_patterns: &'a [Loc<Pattern<'a>>],
loc_ret: &'a Located<Expr<'a>>, loc_ret: &'a Loc<Expr<'a>>,
indent: u16, indent: u16,
) { ) {
use self::Expr::*; use self::Expr::*;
@ -827,6 +750,7 @@ fn fmt_closure<'a>(
buf.newline(); buf.newline();
} else { } else {
buf.push_str(","); buf.push_str(",");
buf.spaces(1);
} }
} }
} }
@ -835,7 +759,7 @@ fn fmt_closure<'a>(
buf.newline(); buf.newline();
buf.indent(indent); buf.indent(indent);
} else { } else {
buf.push(' '); buf.spaces(1);
} }
buf.push_str("->"); buf.push_str("->");
@ -859,18 +783,18 @@ fn fmt_closure<'a>(
} }
_ => { _ => {
// add a space after the `->` // add a space after the `->`
buf.push(' '); buf.spaces(1);
} }
}; };
loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, body_indent); loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, body_indent);
} }
fn fmt_backpassing<'a>( fn fmt_backpassing<'a, 'buf>(
buf: &mut Buf<'a>, buf: &mut Buf<'buf>,
loc_patterns: &'a [Located<Pattern<'a>>], loc_patterns: &'a [Loc<Pattern<'a>>],
loc_body: &'a Located<Expr<'a>>, loc_body: &'a Loc<Expr<'a>>,
loc_ret: &'a Located<Expr<'a>>, loc_ret: &'a Loc<Expr<'a>>,
indent: u16, indent: u16,
) { ) {
use self::Expr::*; use self::Expr::*;
@ -906,6 +830,7 @@ fn fmt_backpassing<'a>(
buf.newline(); buf.newline();
} else { } else {
buf.push_str(","); buf.push_str(",");
buf.spaces(1);
} }
} }
} }
@ -918,7 +843,7 @@ fn fmt_backpassing<'a>(
buf.newline(); buf.newline();
buf.indent(indent); buf.indent(indent);
} else { } else {
buf.push(' '); buf.spaces(1);
} }
buf.push_str("<-"); buf.push_str("<-");
@ -942,7 +867,7 @@ fn fmt_backpassing<'a>(
} }
_ => { _ => {
// add a space after the `<-` // add a space after the `<-`
buf.push(' '); buf.spaces(1);
} }
}; };
@ -960,10 +885,10 @@ fn pattern_needs_parens_when_backpassing(pat: &Pattern) -> bool {
} }
} }
fn fmt_record<'a>( fn fmt_record<'a, 'buf>(
buf: &mut Buf<'a>, buf: &mut Buf<'buf>,
update: Option<&'a Located<Expr<'a>>>, update: Option<&'a Loc<Expr<'a>>>,
fields: Collection<'a, Located<AssignedField<'a, Expr<'a>>>>, fields: Collection<'a, Loc<AssignedField<'a, Expr<'a>>>>,
indent: u16, indent: u16,
) { ) {
let loc_fields = fields.items; let loc_fields = fields.items;
@ -981,7 +906,7 @@ fn fmt_record<'a>(
// it this far. For example "{ 4 & hello = 9 }" // it this far. For example "{ 4 & hello = 9 }"
// doesnt make sense. // doesnt make sense.
Some(record_var) => { Some(record_var) => {
buf.push(' '); buf.spaces(1);
record_var.format(buf, indent); record_var.format(buf, indent);
buf.push_str(" &"); buf.push_str(" &");
} }
@ -1007,7 +932,7 @@ fn fmt_record<'a>(
buf.newline(); buf.newline();
} else { } else {
// is_multiline == false // is_multiline == false
buf.push(' '); buf.spaces(1);
let field_indent = indent; let field_indent = indent;
let mut iter = loc_fields.iter().peekable(); let mut iter = loc_fields.iter().peekable();
while let Some(field) = iter.next() { while let Some(field) = iter.next() {
@ -1015,9 +940,10 @@ fn fmt_record<'a>(
if iter.peek().is_some() { 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 // 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 // to add a comment. Anyway, it is not possible to have a single line record with
// a comment in it. // a comment in it.
@ -1029,13 +955,13 @@ fn fmt_record<'a>(
} }
} }
fn format_field_multiline<'a, T>( fn format_field_multiline<'a, 'buf, T>(
buf: &mut Buf<'a>, buf: &mut Buf<'buf>,
field: &AssignedField<'a, T>, field: &AssignedField<'a, T>,
indent: u16, indent: u16,
separator_prefix: &str, separator_prefix: &str,
) where ) where
T: Formattable<'a>, T: Formattable,
{ {
use self::AssignedField::*; use self::AssignedField::*;
match field { match field {
@ -1050,6 +976,7 @@ fn format_field_multiline<'a, T>(
buf.push_str(separator_prefix); buf.push_str(separator_prefix);
buf.push_str(":"); buf.push_str(":");
buf.spaces(1);
ann.value.format(buf, indent); ann.value.format(buf, indent);
buf.push(','); buf.push(',');
} }
@ -1064,6 +991,7 @@ fn format_field_multiline<'a, T>(
buf.push_str(separator_prefix); buf.push_str(separator_prefix);
buf.push_str("?"); buf.push_str("?");
buf.spaces(1);
ann.value.format(buf, indent); ann.value.format(buf, indent);
buf.push(','); buf.push(',');
} }

View file

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

@ -1,16 +1,16 @@
use crate::annotation::Formattable; use crate::annotation::{Formattable, Newlines};
use crate::collection::{fmt_collection, CollectionConfig}; use crate::collection::fmt_collection;
use crate::expr::fmt_str_literal; use crate::expr::fmt_str_literal;
use crate::spaces::{fmt_default_spaces, fmt_spaces, INDENT}; use crate::spaces::{fmt_default_spaces, fmt_spaces, INDENT};
use crate::Buf; use crate::Buf;
use roc_parse::ast::{Collection, Module}; use roc_parse::ast::{Collection, Module, Spaced};
use roc_parse::header::{ use roc_parse::header::{
AppHeader, Effects, ExposesEntry, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry, AppHeader, Effects, ExposedName, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
PackageName, PackageOrPath, PlatformHeader, PlatformRequires, PlatformRigid, To, TypedIdent, PackageName, PlatformHeader, PlatformRequires, PlatformRigid, To, TypedIdent,
}; };
use roc_region::all::Located; 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 { match module {
Module::Interface { header } => { Module::Interface { header } => {
fmt_interface_header(buf, header); fmt_interface_header(buf, header);
@ -24,404 +24,310 @@ 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; let indent = INDENT;
buf.indent(0); buf.indent(0);
buf.push_str("interface"); buf.push_str("interface");
// module name // 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()); buf.push_str(header.name.value.as_str());
// exposes // exposes
fmt_default_spaces(buf, header.before_exposes, " ", indent); fmt_default_spaces(buf, header.before_exposes, indent);
buf.indent(indent); buf.indent(indent);
buf.push_str("exposes"); 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); fmt_exposes(buf, header.exposes, indent);
// imports // imports
fmt_default_spaces(buf, header.before_imports, " ", indent); fmt_default_spaces(buf, header.before_imports, indent);
buf.indent(indent); buf.indent(indent);
buf.push_str("imports"); 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); 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; let indent = INDENT;
buf.indent(0); buf.indent(0);
buf.push_str("app"); 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); fmt_str_literal(buf, header.name.value, indent);
// packages // packages
fmt_default_spaces(buf, header.before_packages, " ", indent); fmt_default_spaces(buf, header.before_packages, indent);
buf.indent(indent); buf.indent(indent);
buf.push_str("packages"); 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); fmt_packages(buf, header.packages, indent);
// imports // imports
fmt_default_spaces(buf, header.before_imports, " ", indent); fmt_default_spaces(buf, header.before_imports, indent);
buf.indent(indent); buf.indent(indent);
buf.push_str("imports"); 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); fmt_imports(buf, header.imports, indent);
// provides // provides
fmt_default_spaces(buf, header.before_provides, " ", indent); fmt_default_spaces(buf, header.before_provides, indent);
buf.indent(indent); buf.indent(indent);
buf.push_str("provides"); buf.push_str("provides");
fmt_default_spaces(buf, header.after_provides, " ", indent); fmt_default_spaces(buf, header.after_provides, indent);
fmt_provides(buf, header.provides, indent); fmt_provides(buf, header.provides, indent);
fmt_default_spaces(buf, header.before_to, " ", indent); fmt_default_spaces(buf, header.before_to, indent);
buf.indent(indent); buf.indent(indent);
buf.push_str("to"); buf.push_str("to");
fmt_default_spaces(buf, header.after_to, " ", indent); fmt_default_spaces(buf, header.after_to, indent);
fmt_to(buf, header.to.value, 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; let indent = INDENT;
buf.indent(0); buf.indent(0);
buf.push_str("platform"); buf.push_str("platform");
fmt_default_spaces(buf, header.after_platform_keyword, " ", indent); fmt_default_spaces(buf, header.after_platform_keyword, indent);
fmt_package_name(buf, header.name.value); fmt_package_name(buf, header.name.value, indent);
// requires // requires
fmt_default_spaces(buf, header.before_requires, " ", indent); fmt_default_spaces(buf, header.before_requires, indent);
buf.indent(indent); buf.indent(indent);
buf.push_str("requires"); 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); fmt_requires(buf, &header.requires, indent);
// exposes // exposes
fmt_default_spaces(buf, header.before_exposes, " ", indent); fmt_default_spaces(buf, header.before_exposes, indent);
buf.indent(indent); buf.indent(indent);
buf.push_str("exposes"); 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); fmt_exposes(buf, header.exposes, indent);
// packages // packages
fmt_default_spaces(buf, header.before_packages, " ", indent); fmt_default_spaces(buf, header.before_packages, indent);
buf.indent(indent); buf.indent(indent);
buf.push_str("packages"); 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); fmt_packages(buf, header.packages, indent);
// imports // imports
fmt_default_spaces(buf, header.before_imports, " ", indent); fmt_default_spaces(buf, header.before_imports, indent);
buf.indent(indent); buf.indent(indent);
buf.push_str("imports"); 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); fmt_imports(buf, header.imports, indent);
// provides // provides
fmt_default_spaces(buf, header.before_provides, " ", indent); fmt_default_spaces(buf, header.before_provides, indent);
buf.indent(indent); buf.indent(indent);
buf.push_str("provides"); buf.push_str("provides");
fmt_default_spaces(buf, header.after_provides, " ", indent); fmt_default_spaces(buf, header.after_provides, indent);
fmt_provides(buf, header.provides, indent); fmt_provides(buf, header.provides, indent);
fmt_effects(buf, &header.effects, 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( fmt_collection(buf, indent, '{', '}', requires.rigids, Newlines::No);
buf,
requires.rigids,
indent,
CollectionConfig {
begin: '{',
end: '}',
delimiter: ',',
},
);
buf.push_str(" {"); buf.push_str(" {");
fmt_typed_ident(buf, &requires.signature.value, indent); buf.spaces(1);
requires.signature.value.format(buf, indent);
buf.push_str(" }"); buf.push_str(" }");
} }
fn fmt_effects<'a>(buf: &mut Buf<'a>, effects: &Effects<'a>, indent: u16) { fn fmt_effects<'a, 'buf>(buf: &mut Buf<'buf>, effects: &Effects<'a>, indent: u16) {
fmt_default_spaces(buf, effects.spaces_before_effects_keyword, " ", indent); fmt_default_spaces(buf, effects.spaces_before_effects_keyword, indent);
buf.indent(indent); buf.indent(indent);
buf.push_str("effects"); 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.indent(indent);
buf.push_str(effects.effect_shortname); buf.push_str(effects.effect_shortname);
buf.push('.'); buf.push('.');
buf.push_str(effects.effect_type_name); 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( fmt_collection(buf, indent, '{', '}', effects.entries, Newlines::No)
buf,
effects.entries,
indent,
CollectionConfig {
begin: '{',
end: '}',
delimiter: ',',
},
);
} }
fn fmt_typed_ident<'a>(buf: &mut Buf<'a>, entry: &TypedIdent<'a>, indent: u16) { impl<'a> Formattable for TypedIdent<'a> {
use TypedIdent::*; fn is_multiline(&self) -> bool {
match entry { false
Entry { }
ident,
spaces_before_colon, fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
ann,
} => {
buf.indent(indent); buf.indent(indent);
buf.push_str(ident.value); buf.push_str(self.ident.value);
fmt_default_spaces(buf, spaces_before_colon, " ", indent); fmt_default_spaces(buf, self.spaces_before_colon, indent);
buf.push_str(":"); buf.push_str(":");
ann.value.format(buf, indent); buf.spaces(1);
self.ann.value.format(buf, indent);
} }
SpaceBefore(sub_entry, spaces) => { }
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_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); fmt_spaces(buf, spaces.iter(), indent);
fmt_typed_ident(buf, sub_entry, indent); item.format_with_options(buf, parens, newlines, indent);
} }
SpaceAfter(sub_entry, spaces) => { Spaced::SpaceAfter(item, spaces) => {
fmt_typed_ident(buf, sub_entry, indent); item.format_with_options(buf, parens, newlines, indent);
fmt_spaces(buf, spaces.iter(), indent); fmt_spaces(buf, spaces.iter(), indent);
} }
} }
} }
}
impl<'a> Formattable<'a> for TypedIdent<'a> { impl<'a> Formattable for PlatformRigid<'a> {
fn is_multiline(&self) -> bool { fn is_multiline(&self) -> bool {
false false
} }
fn format(&self, buf: &mut Buf<'a>, indent: u16) { fn format<'buf>(&self, buf: &mut Buf<'buf>, _indent: u16) {
fmt_typed_ident(buf, self, indent); buf.push_str(self.rigid);
}
}
impl<'a> Formattable<'a> for PlatformRigid<'a> {
fn is_multiline(&self) -> bool {
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("=>");
buf.push_str(alias); buf.push_str(self.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 fmt_imports<'a>( fn fmt_imports<'a, 'buf>(
buf: &mut Buf<'a>, buf: &mut Buf<'buf>,
loc_entries: Collection<'a, Located<ImportsEntry<'a>>>, loc_entries: Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>,
indent: u16, indent: u16,
) { ) {
fmt_collection( fmt_collection(buf, indent, '[', ']', loc_entries, Newlines::No)
buf,
loc_entries,
indent,
CollectionConfig {
begin: '[',
end: ']',
delimiter: ',',
},
);
} }
fn fmt_provides<'a>( fn fmt_provides<'a, 'buf>(
buf: &mut Buf<'a>, buf: &mut Buf<'buf>,
loc_entries: Collection<'a, Located<ExposesEntry<'a, &'a str>>>, loc_entries: Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
indent: u16, indent: u16,
) { ) {
fmt_collection( fmt_collection(buf, indent, '[', ']', loc_entries, Newlines::No)
buf,
loc_entries,
indent,
CollectionConfig {
begin: '[',
end: ']',
delimiter: ',',
},
);
} }
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 { match to {
To::ExistingPackage(name) => { To::ExistingPackage(name) => {
buf.push_str(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 + 'a>( fn fmt_exposes<'buf, N: Formattable + Copy>(
buf: &mut Buf<'a>, buf: &mut Buf<'buf>,
loc_entries: Collection<'_, Located<ExposesEntry<'_, N>>>, loc_entries: Collection<'_, Loc<Spaced<'_, N>>>,
indent: u16, indent: u16,
) { ) {
fmt_collection( fmt_collection(buf, indent, '[', ']', loc_entries, Newlines::No)
buf,
loc_entries,
indent,
CollectionConfig {
begin: '[',
end: ']',
delimiter: ',',
},
);
}
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 { 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 { 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) buf.push_str(self)
} }
} }
impl<'a> FormatName for ModuleName<'a> { 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()); buf.push_str(self.as_str());
} }
} }
fn fmt_exposes_entry<'a, 'b, N: FormatName>( impl<'a> Formattable for ModuleName<'a> {
buf: &mut Buf<'a>,
entry: &ExposesEntry<'b, N>,
indent: u16,
) {
use roc_parse::header::ExposesEntry::*;
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 fmt_packages<'a>(
buf: &mut Buf<'a>,
loc_entries: Collection<'a, Located<PackageEntry<'a>>>,
indent: u16,
) {
fmt_collection(
buf,
loc_entries,
indent,
CollectionConfig {
begin: '{',
end: '}',
delimiter: ',',
},
);
}
impl<'a> Formattable<'a> for PackageEntry<'a> {
fn is_multiline(&self) -> bool { fn is_multiline(&self) -> bool {
false false
} }
fn format(&self, buf: &mut Buf<'a>, indent: u16) { fn format<'buf>(&self, buf: &mut Buf<'buf>, _indent: u16) {
buf.push_str(self.as_str());
}
}
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 for PackageEntry<'a> {
fn is_multiline(&self) -> bool {
false
}
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
fmt_packages_entry(buf, self, indent); fmt_packages_entry(buf, self, indent);
} }
} }
impl<'a> Formattable<'a> for ImportsEntry<'a> { impl<'a> Formattable for ImportsEntry<'a> {
fn is_multiline(&self) -> bool { fn is_multiline(&self) -> bool {
false 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); fmt_imports_entry(buf, self, indent);
} }
} }
fn fmt_packages_entry<'a>(buf: &mut Buf<'a>, entry: &PackageEntry<'a>, indent: u16) { fn fmt_packages_entry<'a, 'buf>(buf: &mut Buf<'buf>, entry: &PackageEntry<'a>, indent: u16) {
use PackageEntry::*; buf.push_str(entry.shorthand);
match entry {
Entry {
shorthand,
spaces_after_shorthand,
package_or_path,
} => {
buf.push_str(shorthand);
buf.push(':'); buf.push(':');
fmt_default_spaces(buf, spaces_after_shorthand, " ", indent); fmt_default_spaces(buf, entry.spaces_after_shorthand, indent);
fmt_package_or_path(buf, &package_or_path.value, indent); fmt_package_name(buf, entry.package_name.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_package_or_path<'a>(buf: &mut Buf<'a>, package_or_path: &PackageOrPath<'a>, indent: u16) { fn fmt_imports_entry<'a, 'buf>(buf: &mut Buf<'buf>, entry: &ImportsEntry<'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) {
use roc_parse::header::ImportsEntry::*; use roc_parse::header::ImportsEntry::*;
match entry { match entry {
@ -431,16 +337,7 @@ fn fmt_imports_entry<'a>(buf: &mut Buf<'a>, entry: &ImportsEntry<'a>, indent: u1
if !loc_exposes_entries.is_empty() { if !loc_exposes_entries.is_empty() {
buf.push('.'); buf.push('.');
fmt_collection( fmt_collection(buf, indent, '{', '}', *loc_exposes_entries, Newlines::No)
buf,
*loc_exposes_entries,
indent,
CollectionConfig {
begin: '{',
end: '}',
delimiter: ',',
},
);
} }
} }
@ -452,26 +349,8 @@ fn fmt_imports_entry<'a>(buf: &mut Buf<'a>, entry: &ImportsEntry<'a>, indent: u1
if !entries.is_empty() { if !entries.is_empty() {
buf.push('.'); buf.push('.');
fmt_collection( fmt_collection(buf, indent, '{', '}', *entries, Newlines::No)
buf, }
*entries,
indent,
CollectionConfig {
begin: '{',
end: '}',
delimiter: ',',
},
);
}
}
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 crate::Buf;
use roc_parse::ast::{Base, Pattern}; 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); 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 { fn is_multiline(&self) -> bool {
// Theory: a pattern should only be multiline when it contains a comment // Theory: a pattern should only be multiline when it contains a comment
match self { match self {
@ -37,9 +42,9 @@ impl<'a> Formattable<'a> for Pattern<'a> {
} }
} }
fn format_with_options( fn format_with_options<'buf>(
&self, &self,
buf: &mut Buf<'a>, buf: &mut Buf<'buf>,
parens: Parens, parens: Parens,
newlines: Newlines, newlines: Newlines,
indent: u16, indent: u16,
@ -68,7 +73,7 @@ impl<'a> Formattable<'a> for Pattern<'a> {
loc_pattern.format_with_options(buf, Parens::InApply, Newlines::No, indent); loc_pattern.format_with_options(buf, Parens::InApply, Newlines::No, indent);
for loc_arg in loc_arg_patterns.iter() { for loc_arg in loc_arg_patterns.iter() {
buf.push(' '); buf.spaces(1);
loc_arg.format_with_options(buf, Parens::InApply, Newlines::No, indent); loc_arg.format_with_options(buf, Parens::InApply, Newlines::No, indent);
} }
@ -79,6 +84,7 @@ impl<'a> Formattable<'a> for Pattern<'a> {
RecordDestructure(loc_patterns) => { RecordDestructure(loc_patterns) => {
buf.indent(indent); buf.indent(indent);
buf.push_str("{"); buf.push_str("{");
buf.spaces(1);
let mut it = loc_patterns.iter().peekable(); let mut it = loc_patterns.iter().peekable();
@ -87,6 +93,7 @@ impl<'a> Formattable<'a> for Pattern<'a> {
if it.peek().is_some() { if it.peek().is_some() {
buf.push_str(","); buf.push_str(",");
buf.spaces(1);
} }
} }
@ -97,6 +104,7 @@ impl<'a> Formattable<'a> for Pattern<'a> {
buf.indent(indent); buf.indent(indent);
buf.push_str(name); buf.push_str(name);
buf.push_str(":"); buf.push_str(":");
buf.spaces(1);
loc_pattern.format(buf, indent); loc_pattern.format(buf, indent);
} }
@ -104,6 +112,7 @@ impl<'a> Formattable<'a> for Pattern<'a> {
buf.indent(indent); buf.indent(indent);
buf.push_str(name); buf.push_str(name);
buf.push_str(" ?"); buf.push_str(" ?");
buf.spaces(1);
loc_pattern.format(buf, indent); loc_pattern.format(buf, indent);
} }

View file

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

View file

@ -11,11 +11,12 @@ mod test_fmt {
use roc_fmt::module::fmt_module; use roc_fmt::module::fmt_module;
use roc_fmt::Buf; use roc_fmt::Buf;
use roc_parse::module::{self, module_defs}; 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; 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 // 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(); let arena = Bump::new();
match roc_parse::test_helpers::parse_expr_with(&arena, input.trim()) { match roc_parse::test_helpers::parse_expr_with(&arena, input.trim()) {
Ok(actual) => { Ok(actual) => {
@ -34,21 +35,20 @@ mod test_fmt {
let expected = expected.trim_end(); let expected = expected.trim_end();
// First check that input formats to the expected version // 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 // Parse the expected result format it, asserting that it doesn't change
// It's important that formatting be stable / idempotent // 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) { fn expr_formats_same(input: &str) {
expr_formats_to(input, input); 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 arena = Bump::new();
let src = src.trim_end();
match module::parse_header(&arena, State::new(src.as_bytes())) { match module::parse_header(&arena, State::new(src.as_bytes())) {
Ok((actual, state)) => { Ok((actual, state)) => {
let mut buf = Buf::new_in(&arena); 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) 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()) 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) 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) { fn module_formats_same(input: &str) {
module_formats_to(input, input); module_formats_to(input, input);
} }
@ -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 // MODULES
#[test] #[test]
@ -2625,14 +2644,14 @@ mod test_fmt {
fn single_line_app() { fn single_line_app() {
module_formats_same(indoc!( module_formats_same(indoc!(
r#" r#"
app "Foo" packages { base: "platform" } imports [] provides [ main ] to base"# app "Foo" packages { pf: "platform" } imports [] provides [ main ] to pf"#
)); ));
} }
#[test] #[test]
fn single_line_platform() { fn single_line_platform() {
module_formats_same( module_formats_same(
"platform folkertdev/foo \ "platform \"folkertdev/foo\" \
requires { model=>Model, msg=>Msg } { main : Effect {} } \ requires { model=>Model, msg=>Msg } { main : Effect {} } \
exposes [] \ exposes [] \
packages {} \ packages {} \
@ -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 // this is a parse error atm
// #[test] // #[test]
// fn multiline_apply() { // fn multiline_apply() {

View file

@ -154,7 +154,7 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
} }
#[inline(always)] #[inline(always)]
fn float_callee_saved(_reg: &AArch64FloatReg) -> bool { fn float_callee_saved(_reg: &AArch64FloatReg) -> bool {
unimplemented!("AArch64 FloatRegs not implemented yet"); todo!("AArch64 FloatRegs");
} }
#[inline(always)] #[inline(always)]
@ -254,7 +254,7 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
_ret_layout: &Layout<'a>, _ret_layout: &Layout<'a>,
mut _stack_size: u32, mut _stack_size: u32,
) -> u32 { ) -> u32 {
unimplemented!("Loading args not yet implemented for AArch64"); todo!("Loading args for AArch64");
} }
#[inline(always)] #[inline(always)]
@ -265,7 +265,7 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
_arg_layouts: &[Layout<'a>], _arg_layouts: &[Layout<'a>],
_ret_layout: &Layout<'a>, _ret_layout: &Layout<'a>,
) -> u32 { ) -> u32 {
unimplemented!("Storing args not yet implemented for AArch64"); todo!("Storing args for AArch64");
} }
fn return_struct<'a>( fn return_struct<'a>(
@ -275,18 +275,18 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
_field_layouts: &[Layout<'a>], _field_layouts: &[Layout<'a>],
_ret_reg: Option<AArch64GeneralReg>, _ret_reg: Option<AArch64GeneralReg>,
) { ) {
unimplemented!("Returning structs not yet implemented for AArch64"); todo!("Returning structs for AArch64");
} }
fn returns_via_arg_pointer(_ret_layout: &Layout) -> bool { fn returns_via_arg_pointer(_ret_layout: &Layout) -> bool {
unimplemented!("Returning via arg pointer not yet implemented for AArch64"); todo!("Returning via arg pointer for AArch64");
} }
} }
impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler { impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
#[inline(always)] #[inline(always)]
fn abs_reg64_reg64(_buf: &mut Vec<'_, u8>, _dst: AArch64GeneralReg, _src: AArch64GeneralReg) { fn abs_reg64_reg64(_buf: &mut Vec<'_, u8>, _dst: AArch64GeneralReg, _src: AArch64GeneralReg) {
unimplemented!("abs_reg64_reg64 is not yet implement for AArch64"); todo!("abs_reg64_reg64 for AArch64");
} }
#[inline(always)] #[inline(always)]
@ -296,7 +296,7 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
_dst: AArch64FloatReg, _dst: AArch64FloatReg,
_src: AArch64FloatReg, _src: AArch64FloatReg,
) { ) {
unimplemented!("abs_reg64_reg64 is not yet implement for AArch64"); todo!("abs_reg64_reg64 for AArch64");
} }
#[inline(always)] #[inline(always)]
@ -307,13 +307,11 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
imm32: i32, imm32: i32,
) { ) {
if imm32 < 0 { if imm32 < 0 {
unimplemented!("immediate addition with values less than 0 are not yet implemented"); todo!("immediate addition with values less than 0");
} else if imm32 < 0xFFF { } else if imm32 < 0xFFF {
add_reg64_reg64_imm12(buf, dst, src, imm32 as u16); add_reg64_reg64_imm12(buf, dst, src, imm32 as u16);
} else { } else {
unimplemented!( todo!("immediate additions with values greater than 12bits");
"immediate additions with values greater than 12bits are not yet implemented"
);
} }
} }
#[inline(always)] #[inline(always)]
@ -332,12 +330,12 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
_src1: AArch64FloatReg, _src1: AArch64FloatReg,
_src2: AArch64FloatReg, _src2: AArch64FloatReg,
) { ) {
unimplemented!("adding floats not yet implemented for AArch64"); todo!("adding floats for AArch64");
} }
#[inline(always)] #[inline(always)]
fn call(_buf: &mut Vec<'_, u8>, _relocs: &mut Vec<'_, Relocation>, _fn_name: String) { fn call(_buf: &mut Vec<'_, u8>, _relocs: &mut Vec<'_, Relocation>, _fn_name: String) {
unimplemented!("calling functions literal not yet implemented for AArch64"); todo!("calling functions literal for AArch64");
} }
#[inline(always)] #[inline(always)]
@ -347,12 +345,12 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
_src1: AArch64GeneralReg, _src1: AArch64GeneralReg,
_src2: AArch64GeneralReg, _src2: AArch64GeneralReg,
) { ) {
unimplemented!("register multiplication not implemented yet for AArch64"); todo!("register multiplication for AArch64");
} }
#[inline(always)] #[inline(always)]
fn jmp_imm32(_buf: &mut Vec<'_, u8>, _offset: i32) -> usize { fn jmp_imm32(_buf: &mut Vec<'_, u8>, _offset: i32) -> usize {
unimplemented!("jump instructions not yet implemented for AArch64"); todo!("jump instructions for AArch64");
} }
#[inline(always)] #[inline(always)]
@ -368,7 +366,7 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
_imm: u64, _imm: u64,
_offset: i32, _offset: i32,
) -> usize { ) -> usize {
unimplemented!("jump not equal instructions not yet implemented for AArch64"); todo!("jump not equal instructions for AArch64");
} }
#[inline(always)] #[inline(always)]
@ -378,7 +376,7 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
_dst: AArch64FloatReg, _dst: AArch64FloatReg,
_imm: f32, _imm: f32,
) { ) {
unimplemented!("loading f32 literal not yet implemented for AArch64"); todo!("loading f32 literal for AArch64");
} }
#[inline(always)] #[inline(always)]
fn mov_freg64_imm64( fn mov_freg64_imm64(
@ -387,7 +385,7 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
_dst: AArch64FloatReg, _dst: AArch64FloatReg,
_imm: f64, _imm: f64,
) { ) {
unimplemented!("loading f64 literal not yet implemented for AArch64"); todo!("loading f64 literal for AArch64");
} }
#[inline(always)] #[inline(always)]
fn mov_reg64_imm64(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, imm: i64) { fn mov_reg64_imm64(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, imm: i64) {
@ -408,7 +406,7 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
} }
#[inline(always)] #[inline(always)]
fn mov_freg64_freg64(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _src: AArch64FloatReg) { fn mov_freg64_freg64(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _src: AArch64FloatReg) {
unimplemented!("moving data between float registers not yet implemented for AArch64"); todo!("moving data between float registers for AArch64");
} }
#[inline(always)] #[inline(always)]
fn mov_reg64_reg64(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, src: AArch64GeneralReg) { fn mov_reg64_reg64(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, src: AArch64GeneralReg) {
@ -417,70 +415,68 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
#[inline(always)] #[inline(always)]
fn mov_freg64_base32(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _offset: i32) { fn mov_freg64_base32(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _offset: i32) {
unimplemented!( todo!("loading floating point reg from base offset for AArch64");
"loading floating point reg from base offset not yet implemented for AArch64"
);
} }
#[inline(always)] #[inline(always)]
fn mov_reg64_base32(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, offset: i32) { fn mov_reg64_base32(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, offset: i32) {
if offset < 0 { if offset < 0 {
unimplemented!("negative base offsets are not yet implement for AArch64"); todo!("negative base offsets for AArch64");
} else if offset < (0xFFF << 8) { } else if offset < (0xFFF << 8) {
debug_assert!(offset % 8 == 0); debug_assert!(offset % 8 == 0);
ldr_reg64_imm12(buf, dst, AArch64GeneralReg::FP, (offset as u16) >> 3); ldr_reg64_imm12(buf, dst, AArch64GeneralReg::FP, (offset as u16) >> 3);
} else { } else {
unimplemented!("base offsets over 32k are not yet implement for AArch64"); todo!("base offsets over 32k for AArch64");
} }
} }
#[inline(always)] #[inline(always)]
fn mov_base32_freg64(_buf: &mut Vec<'_, u8>, _offset: i32, _src: AArch64FloatReg) { fn mov_base32_freg64(_buf: &mut Vec<'_, u8>, _offset: i32, _src: AArch64FloatReg) {
unimplemented!("saving floating point reg to base offset not yet implemented for AArch64"); todo!("saving floating point reg to base offset for AArch64");
} }
#[inline(always)] #[inline(always)]
fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: AArch64GeneralReg) { fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: AArch64GeneralReg) {
if offset < 0 { if offset < 0 {
unimplemented!("negative base offsets are not yet implement for AArch64"); todo!("negative base offsets for AArch64");
} else if offset < (0xFFF << 8) { } else if offset < (0xFFF << 8) {
debug_assert!(offset % 8 == 0); debug_assert!(offset % 8 == 0);
str_reg64_imm12(buf, src, AArch64GeneralReg::FP, (offset as u16) >> 3); str_reg64_imm12(buf, src, AArch64GeneralReg::FP, (offset as u16) >> 3);
} else { } else {
unimplemented!("base offsets over 32k are not yet implement for AArch64"); todo!("base offsets over 32k for AArch64");
} }
} }
#[inline(always)] #[inline(always)]
fn mov_freg64_stack32(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _offset: i32) { fn mov_freg64_stack32(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _offset: i32) {
unimplemented!("loading floating point reg from stack not yet implemented for AArch64"); todo!("loading floating point reg from stack for AArch64");
} }
#[inline(always)] #[inline(always)]
fn mov_reg64_stack32(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, offset: i32) { fn mov_reg64_stack32(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, offset: i32) {
if offset < 0 { if offset < 0 {
unimplemented!("negative stack offsets are not yet implement for AArch64"); todo!("negative stack offsets for AArch64");
} else if offset < (0xFFF << 8) { } else if offset < (0xFFF << 8) {
debug_assert!(offset % 8 == 0); debug_assert!(offset % 8 == 0);
ldr_reg64_imm12(buf, dst, AArch64GeneralReg::ZRSP, (offset as u16) >> 3); ldr_reg64_imm12(buf, dst, AArch64GeneralReg::ZRSP, (offset as u16) >> 3);
} else { } else {
unimplemented!("stack offsets over 32k are not yet implement for AArch64"); todo!("stack offsets over 32k for AArch64");
} }
} }
#[inline(always)] #[inline(always)]
fn mov_stack32_freg64(_buf: &mut Vec<'_, u8>, _offset: i32, _src: AArch64FloatReg) { fn mov_stack32_freg64(_buf: &mut Vec<'_, u8>, _offset: i32, _src: AArch64FloatReg) {
unimplemented!("saving floating point reg to stack not yet implemented for AArch64"); todo!("saving floating point reg to stack for AArch64");
} }
#[inline(always)] #[inline(always)]
fn mov_stack32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: AArch64GeneralReg) { fn mov_stack32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: AArch64GeneralReg) {
if offset < 0 { if offset < 0 {
unimplemented!("negative stack offsets are not yet implement for AArch64"); todo!("negative stack offsets for AArch64");
} else if offset < (0xFFF << 8) { } else if offset < (0xFFF << 8) {
debug_assert!(offset % 8 == 0); debug_assert!(offset % 8 == 0);
str_reg64_imm12(buf, src, AArch64GeneralReg::ZRSP, (offset as u16) >> 3); str_reg64_imm12(buf, src, AArch64GeneralReg::ZRSP, (offset as u16) >> 3);
} else { } else {
unimplemented!("stack offsets over 32k are not yet implement for AArch64"); todo!("stack offsets over 32k for AArch64");
} }
} }
#[inline(always)] #[inline(always)]
fn neg_reg64_reg64(_buf: &mut Vec<'_, u8>, _dst: AArch64GeneralReg, _src: AArch64GeneralReg) { fn neg_reg64_reg64(_buf: &mut Vec<'_, u8>, _dst: AArch64GeneralReg, _src: AArch64GeneralReg) {
unimplemented!("neg is not yet implement for AArch64"); todo!("neg for AArch64");
} }
#[inline(always)] #[inline(always)]
@ -491,15 +487,11 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
imm32: i32, imm32: i32,
) { ) {
if imm32 < 0 { if imm32 < 0 {
unimplemented!( todo!("immediate subtractions with values less than 0");
"immediate subtractions with values less than 0 are not yet implemented"
);
} else if imm32 < 0xFFF { } else if imm32 < 0xFFF {
sub_reg64_reg64_imm12(buf, dst, src, imm32 as u16); sub_reg64_reg64_imm12(buf, dst, src, imm32 as u16);
} else { } else {
unimplemented!( todo!("immediate subtractions with values greater than 12bits");
"immediate subtractions with values greater than 12bits are not yet implemented"
);
} }
} }
#[inline(always)] #[inline(always)]
@ -509,7 +501,7 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
_src1: AArch64GeneralReg, _src1: AArch64GeneralReg,
_src2: AArch64GeneralReg, _src2: AArch64GeneralReg,
) { ) {
unimplemented!("registers subtractions not implemented yet for AArch64"); todo!("registers subtractions for AArch64");
} }
#[inline(always)] #[inline(always)]
@ -519,7 +511,7 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
_src1: AArch64GeneralReg, _src1: AArch64GeneralReg,
_src2: AArch64GeneralReg, _src2: AArch64GeneralReg,
) { ) {
unimplemented!("registers equality not implemented yet for AArch64"); todo!("registers equality for AArch64");
} }
#[inline(always)] #[inline(always)]
@ -529,7 +521,7 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
_src1: AArch64GeneralReg, _src1: AArch64GeneralReg,
_src2: AArch64GeneralReg, _src2: AArch64GeneralReg,
) { ) {
unimplemented!("registers non-equality not implemented yet for AArch64"); todo!("registers non-equality for AArch64");
} }
#[inline(always)] #[inline(always)]
@ -539,7 +531,7 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
_src1: AArch64GeneralReg, _src1: AArch64GeneralReg,
_src2: AArch64GeneralReg, _src2: AArch64GeneralReg,
) { ) {
unimplemented!("registers less than not implemented yet for AArch64"); todo!("registers less than for AArch64");
} }
#[inline(always)] #[inline(always)]
@ -548,7 +540,7 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
_dst: AArch64FloatReg, _dst: AArch64FloatReg,
_src: AArch64GeneralReg, _src: AArch64GeneralReg,
) { ) {
unimplemented!("registers to float not implemented yet for AArch64"); todo!("registers to float for AArch64");
} }
#[inline(always)] #[inline(always)]
@ -557,7 +549,7 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
_dst: AArch64FloatReg, _dst: AArch64FloatReg,
_src: AArch64GeneralReg, _src: AArch64GeneralReg,
) { ) {
unimplemented!("registers to float not implemented yet for AArch64"); todo!("registers to float for AArch64");
} }
#[inline(always)] #[inline(always)]
@ -566,7 +558,7 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
_dst: AArch64FloatReg, _dst: AArch64FloatReg,
_src: AArch64FloatReg, _src: AArch64FloatReg,
) { ) {
unimplemented!("registers to float not implemented yet for AArch64"); todo!("registers to float for AArch64");
} }
#[inline(always)] #[inline(always)]
@ -575,7 +567,7 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
_dst: AArch64FloatReg, _dst: AArch64FloatReg,
_src: AArch64FloatReg, _src: AArch64FloatReg,
) { ) {
unimplemented!("registers to float not implemented yet for AArch64"); todo!("registers to float for AArch64");
} }
#[inline(always)] #[inline(always)]
@ -585,7 +577,7 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
_src1: AArch64GeneralReg, _src1: AArch64GeneralReg,
_src2: AArch64GeneralReg, _src2: AArch64GeneralReg,
) { ) {
unimplemented!("registers greater than or equal not implemented yet for AArch64"); todo!("registers greater than or equal for AArch64");
} }
#[inline(always)] #[inline(always)]

View file

@ -3,7 +3,7 @@ use bumpalo::collections::Vec;
use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::symbol::{Interns, Symbol}; 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::ir::{BranchInfo, JoinPointId, Literal, Param, ProcLayout, SelfRecursive, Stmt};
use roc_mono::layout::{Builtin, Layout}; use roc_mono::layout::{Builtin, Layout};
use roc_reporting::internal_error; use roc_reporting::internal_error;
@ -256,8 +256,8 @@ pub struct Backend64Bit<
phantom_cc: PhantomData<CC>, phantom_cc: PhantomData<CC>,
env: &'a Env<'a>, env: &'a Env<'a>,
interns: &'a mut Interns, interns: &'a mut Interns,
refcount_proc_gen: RefcountProcGenerator<'a>, helper_proc_gen: CodeGenHelp<'a>,
refcount_proc_symbols: Vec<'a, (Symbol, ProcLayout<'a>)>, helper_proc_symbols: Vec<'a, (Symbol, ProcLayout<'a>)>,
buf: Vec<'a, u8>, buf: Vec<'a, u8>,
relocs: Vec<'a, Relocation>, relocs: Vec<'a, Relocation>,
proc_name: Option<String>, proc_name: Option<String>,
@ -308,8 +308,8 @@ pub fn new_backend_64bit<
phantom_cc: PhantomData, phantom_cc: PhantomData,
env, env,
interns, interns,
refcount_proc_gen: RefcountProcGenerator::new(env.arena, IntWidth::I64, env.module_id), helper_proc_gen: CodeGenHelp::new(env.arena, IntWidth::I64, env.module_id),
refcount_proc_symbols: bumpalo::vec![in env.arena], helper_proc_symbols: bumpalo::vec![in env.arena],
proc_name: None, proc_name: None,
is_self_recursive: None, is_self_recursive: None,
buf: bumpalo::vec![in env.arena], buf: bumpalo::vec![in env.arena],
@ -346,19 +346,17 @@ impl<
fn interns(&self) -> &Interns { fn interns(&self) -> &Interns {
self.interns self.interns
} }
fn env_interns_refcount_mut( fn env_interns_helpers_mut(&mut self) -> (&Env<'a>, &mut Interns, &mut CodeGenHelp<'a>) {
&mut self, (self.env, self.interns, &mut self.helper_proc_gen)
) -> (&Env<'a>, &mut Interns, &mut RefcountProcGenerator<'a>) {
(self.env, self.interns, &mut self.refcount_proc_gen)
} }
fn refcount_proc_gen_mut(&mut self) -> &mut RefcountProcGenerator<'a> { fn helper_proc_gen_mut(&mut self) -> &mut CodeGenHelp<'a> {
&mut self.refcount_proc_gen &mut self.helper_proc_gen
} }
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>)> {
&mut self.refcount_proc_symbols &mut self.helper_proc_symbols
} }
fn refcount_proc_symbols(&self) -> &Vec<'a, (Symbol, ProcLayout<'a>)> { fn helper_proc_symbols(&self) -> &Vec<'a, (Symbol, ProcLayout<'a>)> {
&self.refcount_proc_symbols &self.helper_proc_symbols
} }
fn reset(&mut self, name: String, is_self_recursive: SelfRecursive) { fn reset(&mut self, name: String, is_self_recursive: SelfRecursive) {
@ -383,7 +381,7 @@ impl<
self.float_used_regs.clear(); self.float_used_regs.clear();
self.float_free_regs self.float_free_regs
.extend_from_slice(CC::FLOAT_DEFAULT_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>)> { fn literal_map(&mut self) -> &mut MutMap<Symbol, (*const Literal<'a>, *const Layout<'a>)> {
@ -572,7 +570,7 @@ impl<
Layout::Builtin(Builtin::Str) => { Layout::Builtin(Builtin::Str) => {
if CC::returns_via_arg_pointer(ret_layout) { if CC::returns_via_arg_pointer(ret_layout) {
// This will happen on windows, return via pointer here. // This will happen on windows, return via pointer here.
unimplemented!("FnCall: Returning strings via pointer not yet implemented"); todo!("FnCall: Returning strings via pointer");
} else { } else {
let offset = self.claim_stack_size(16); let offset = self.claim_stack_size(16);
self.symbol_storage_map.insert( self.symbol_storage_map.insert(
@ -590,10 +588,7 @@ impl<
Layout::Struct([]) => { Layout::Struct([]) => {
// Nothing needs to be done to load a returned empty struct. // Nothing needs to be done to load a returned empty struct.
} }
x => unimplemented!( x => todo!("FnCall: receiving return type, {:?}", x),
"FnCall: receiving return type, {:?}, is not yet implemented",
x
),
} }
} }
@ -637,10 +632,7 @@ impl<
self.buf[jne_location + i] = *byte; self.buf[jne_location + i] = *byte;
} }
} else { } else {
unimplemented!( todo!("Switch: branch info, {:?}", branch_info);
"Switch: branch info, {:?}, is not yet implemented",
branch_info
);
} }
} }
let (branch_info, stmt) = default_branch; let (branch_info, stmt) = default_branch;
@ -658,10 +650,7 @@ impl<
); );
} }
} else { } else {
unimplemented!( todo!("Switch: branch info, {:?}", branch_info);
"Switch: branch info, {:?}, is not yet implemented",
branch_info
);
} }
} }
@ -803,7 +792,7 @@ impl<
let src_reg = self.load_to_float_reg(src); let src_reg = self.load_to_float_reg(src);
ASM::abs_freg64_freg64(&mut self.buf, &mut self.relocs, dst_reg, src_reg); ASM::abs_freg64_freg64(&mut self.buf, &mut self.relocs, dst_reg, src_reg);
} }
x => unimplemented!("NumAbs: layout, {:?}, not implemented yet", x), x => todo!("NumAbs: layout, {:?}", x),
} }
} }
@ -821,7 +810,7 @@ impl<
let src2_reg = self.load_to_float_reg(src2); let src2_reg = self.load_to_float_reg(src2);
ASM::add_freg64_freg64_freg64(&mut self.buf, dst_reg, src1_reg, src2_reg); ASM::add_freg64_freg64_freg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
} }
x => unimplemented!("NumAdd: layout, {:?}, not implemented yet", x), x => todo!("NumAdd: layout, {:?}", x),
} }
} }
@ -833,7 +822,7 @@ impl<
let src2_reg = self.load_to_general_reg(src2); let src2_reg = self.load_to_general_reg(src2);
ASM::imul_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); ASM::imul_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
} }
x => unimplemented!("NumMul: layout, {:?}, not implemented yet", x), x => todo!("NumMul: layout, {:?}", x),
} }
} }
@ -844,7 +833,7 @@ impl<
let src_reg = self.load_to_general_reg(src); let src_reg = self.load_to_general_reg(src);
ASM::neg_reg64_reg64(&mut self.buf, dst_reg, src_reg); ASM::neg_reg64_reg64(&mut self.buf, dst_reg, src_reg);
} }
x => unimplemented!("NumNeg: layout, {:?}, not implemented yet", x), x => todo!("NumNeg: layout, {:?}", x),
} }
} }
@ -856,7 +845,7 @@ impl<
let src2_reg = self.load_to_general_reg(src2); let src2_reg = self.load_to_general_reg(src2);
ASM::sub_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); ASM::sub_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
} }
x => unimplemented!("NumSub: layout, {:?}, not implemented yet", x), x => todo!("NumSub: layout, {:?}", x),
} }
} }
@ -868,7 +857,7 @@ impl<
let src2_reg = self.load_to_general_reg(src2); let src2_reg = self.load_to_general_reg(src2);
ASM::eq_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); ASM::eq_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
} }
x => unimplemented!("NumEq: layout, {:?}, not implemented yet", x), x => todo!("NumEq: layout, {:?}", x),
} }
} }
@ -880,7 +869,7 @@ impl<
let src2_reg = self.load_to_general_reg(src2); let src2_reg = self.load_to_general_reg(src2);
ASM::neq_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); ASM::neq_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
} }
x => unimplemented!("NumNeq: layout, {:?}, not implemented yet", x), x => todo!("NumNeq: layout, {:?}", x),
} }
} }
@ -898,7 +887,7 @@ impl<
let src2_reg = self.load_to_general_reg(src2); let src2_reg = self.load_to_general_reg(src2);
ASM::lt_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); ASM::lt_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
} }
x => unimplemented!("NumLt: layout, {:?}, not implemented yet", x), x => todo!("NumLt: layout, {:?}", x),
} }
} }
@ -953,11 +942,7 @@ impl<
let src_reg = self.load_to_float_reg(src); let src_reg = self.load_to_float_reg(src);
ASM::mov_freg64_freg64(&mut self.buf, dst_reg, src_reg); ASM::mov_freg64_freg64(&mut self.buf, dst_reg, src_reg);
} }
(a, r) => unimplemented!( (a, r) => todo!("NumToFloat: layout, arg {:?}, ret {:?}", a, r),
"NumToFloat: layout, arg {:?}, ret {:?}, not implemented yet",
a,
r
),
} }
} }
@ -975,15 +960,17 @@ impl<
let src2_reg = self.load_to_general_reg(src2); let src2_reg = self.load_to_general_reg(src2);
ASM::gte_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); ASM::gte_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
} }
x => unimplemented!("NumGte: layout, {:?}, not implemented yet", x), x => todo!("NumGte: layout, {:?}", x),
} }
} }
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 dst_reg = self.claim_general_reg(dst);
let src_reg = self.load_to_general_reg(src); let src_reg = self.load_to_general_reg(src);
// The refcount pointer is the value before the pointer. ASM::mov_reg64_reg64(&mut self.buf, dst_reg, src_reg);
ASM::sub_reg64_reg64_imm32(&mut self.buf, dst_reg, src_reg, PTR_SIZE as i32);
} }
fn create_struct(&mut self, sym: &Symbol, layout: &Layout<'a>, fields: &'a [Symbol]) { fn create_struct(&mut self, sym: &Symbol, layout: &Layout<'a>, fields: &'a [Symbol]) {
@ -1115,7 +1102,7 @@ impl<
ASM::mov_reg64_imm64(&mut self.buf, reg, num); ASM::mov_reg64_imm64(&mut self.buf, reg, num);
ASM::mov_base32_reg64(&mut self.buf, offset + 8, reg); ASM::mov_base32_reg64(&mut self.buf, offset + 8, reg);
} }
x => unimplemented!("loading literal, {:?}, is not yet implemented", x), x => todo!("loading literal, {:?}", x),
} }
} }
@ -1236,7 +1223,7 @@ impl<
Layout::Builtin(Builtin::Str) => { Layout::Builtin(Builtin::Str) => {
if self.symbol_storage_map.contains_key(&Symbol::RET_POINTER) { if self.symbol_storage_map.contains_key(&Symbol::RET_POINTER) {
// This will happen on windows, return via pointer here. // This will happen on windows, return via pointer here.
unimplemented!("Returning strings via pointer not yet implemented"); todo!("Returning strings via pointer");
} else { } else {
ASM::mov_reg64_base32(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *offset); ASM::mov_reg64_base32(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *offset);
ASM::mov_reg64_base32( ASM::mov_reg64_base32(
@ -1259,12 +1246,9 @@ impl<
CC::return_struct(&mut self.buf, offset, size, field_layouts, ret_reg); CC::return_struct(&mut self.buf, offset, size, field_layouts, ret_reg);
} }
} }
x => unimplemented!( x => todo!("returning symbol with layout, {:?}", x),
"returning symbol with layout, {:?}, is not yet implemented",
x
),
}, },
Some(x) => unimplemented!("returning symbol storage, {:?}, is not yet implemented", x), Some(x) => todo!("returning symbol storage, {:?}", x),
None if layout == &Layout::Struct(&[]) => { None if layout == &Layout::Struct(&[]) => {
// Empty struct is not defined and does nothing. // Empty struct is not defined and does nothing.
} }
@ -1596,10 +1580,7 @@ impl<
internal_error!("unknown struct: {:?}", sym); internal_error!("unknown struct: {:?}", sym);
} }
} }
x => unimplemented!( x => todo!("copying data to the stack with layout, {:?}", x),
"copying data to the stack with layout, {:?}, not implemented yet",
x
),
} }
} }

View file

@ -265,12 +265,12 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
); );
general_i += 2; general_i += 2;
} else { } else {
unimplemented!("loading strings args on the stack is not yet implemented"); todo!("loading strings args on the stack");
} }
} }
Layout::Struct(&[]) => {} Layout::Struct(&[]) => {}
x => { x => {
unimplemented!("Loading args with layout {:?} not yet implemented", x); todo!("Loading args with layout {:?}", x);
} }
} }
} }
@ -295,7 +295,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
// Nothing needs to be done for any of these cases. // Nothing needs to be done for any of these cases.
} }
x => { x => {
unimplemented!("receiving return type, {:?}, is not yet implemented", x); todo!("receiving return type, {:?}", x);
} }
} }
for (i, layout) in arg_layouts.iter().enumerate() { for (i, layout) in arg_layouts.iter().enumerate() {
@ -426,14 +426,12 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
} }
general_i += 2; general_i += 2;
} else { } else {
unimplemented!( todo!("calling functions with strings on the stack");
"calling functions with strings on the stack is not yet implemented"
);
} }
} }
Layout::Struct(&[]) => {} Layout::Struct(&[]) => {}
x => { x => {
unimplemented!("calling with arg type, {:?}, is not yet implemented", x); todo!("calling with arg type, {:?}", x);
} }
} }
} }
@ -447,7 +445,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
_field_layouts: &[Layout<'a>], _field_layouts: &[Layout<'a>],
_ret_reg: Option<X86_64GeneralReg>, _ret_reg: Option<X86_64GeneralReg>,
) { ) {
unimplemented!("Returning structs not yet implemented for X86_64"); todo!("Returning structs for X86_64");
} }
fn returns_via_arg_pointer(ret_layout: &Layout) -> bool { fn returns_via_arg_pointer(ret_layout: &Layout) -> bool {
@ -603,20 +601,18 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
} }
Layout::Builtin(Builtin::Str) => { Layout::Builtin(Builtin::Str) => {
// I think this just needs to be passed on the stack, so not a huge deal. // I think this just needs to be passed on the stack, so not a huge deal.
unimplemented!( todo!("Passing str args with Windows fast call");
"Passing str args with Windows fast call not yet implemented."
);
} }
Layout::Struct(&[]) => {} Layout::Struct(&[]) => {}
x => { x => {
unimplemented!("Loading args with layout {:?} not yet implemented", x); todo!("Loading args with layout {:?}", x);
} }
} }
} else { } else {
arg_offset += match layout { arg_offset += match layout {
single_register_builtins!() => 8, single_register_builtins!() => 8,
x => { x => {
unimplemented!("Loading args with layout {:?} not yet implemented", x); todo!("Loading args with layout {:?}", x);
} }
}; };
symbol_map.insert( symbol_map.insert(
@ -648,7 +644,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
// Nothing needs to be done for any of these cases. // Nothing needs to be done for any of these cases.
} }
x => { x => {
unimplemented!("receiving return type, {:?}, is not yet implemented", x); todo!("receiving return type, {:?}", x);
} }
} }
for (i, layout) in arg_layouts.iter().enumerate() { for (i, layout) in arg_layouts.iter().enumerate() {
@ -722,7 +718,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
} }
SymbolStorage::GeneralReg(_) SymbolStorage::GeneralReg(_)
| SymbolStorage::BaseAndGeneralReg { .. } => { | SymbolStorage::BaseAndGeneralReg { .. } => {
unimplemented!("Cannot load general symbol into FloatReg") internal_error!("Cannot load general symbol into FloatReg")
} }
} }
} else { } else {
@ -747,7 +743,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
} }
SymbolStorage::GeneralReg(_) SymbolStorage::GeneralReg(_)
| SymbolStorage::BaseAndGeneralReg { .. } => { | SymbolStorage::BaseAndGeneralReg { .. } => {
unimplemented!("Cannot load general symbol into FloatReg") internal_error!("Cannot load general symbol into FloatReg")
} }
} }
stack_offset += 8; stack_offset += 8;
@ -755,11 +751,11 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
} }
Layout::Builtin(Builtin::Str) => { Layout::Builtin(Builtin::Str) => {
// I think this just needs to be passed on the stack, so not a huge deal. // I think this just needs to be passed on the stack, so not a huge deal.
unimplemented!("Passing str args with Windows fast call not yet implemented."); todo!("Passing str args with Windows fast call");
} }
Layout::Struct(&[]) => {} Layout::Struct(&[]) => {}
x => { x => {
unimplemented!("calling with arg type, {:?}, is not yet implemented", x); todo!("calling with arg type, {:?}", x);
} }
} }
} }
@ -773,7 +769,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
_field_layouts: &[Layout<'a>], _field_layouts: &[Layout<'a>],
_ret_reg: Option<X86_64GeneralReg>, _ret_reg: Option<X86_64GeneralReg>,
) { ) {
unimplemented!("Returning structs not yet implemented for X86_64WindowsFastCall"); todo!("Returning structs for X86_64WindowsFastCall");
} }
fn returns_via_arg_pointer(ret_layout: &Layout) -> bool { fn returns_via_arg_pointer(ret_layout: &Layout) -> bool {
@ -977,9 +973,7 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
) -> usize { ) -> usize {
buf.reserve(13); buf.reserve(13);
if imm > i32::MAX as u64 { if imm > i32::MAX as u64 {
unimplemented!( todo!("comparison with values greater than i32::max");
"comparison with values greater than i32::max not yet implemented for jne"
);
} }
cmp_reg64_imm32(buf, reg, imm as i32); cmp_reg64_imm32(buf, reg, imm as i32);
jne_imm32(buf, offset); jne_imm32(buf, offset);

View file

@ -6,9 +6,9 @@ use bumpalo::{collections::Vec, Bump};
use roc_builtins::bitcode::{self, FloatWidth, IntWidth}; use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::{ModuleName, TagName}; use roc_module::ident::{ModuleName, TagName};
use roc_module::low_level::LowLevel; use roc_module::low_level::{LowLevel, LowLevelWrapperType};
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::gen_refcount::RefcountProcGenerator; use roc_mono::code_gen_help::CodeGenHelp;
use roc_mono::ir::{ use roc_mono::ir::{
BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, Param, Proc, ProcLayout, BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, Param, Proc, ProcLayout,
SelfRecursive, Stmt, SelfRecursive, Stmt,
@ -62,9 +62,7 @@ trait Backend<'a> {
// This method is suboptimal, but it seems to be the only way to make rust understand // 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, // 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. // rust understands that they are part of a single use of mutable self.
fn env_interns_refcount_mut( fn env_interns_helpers_mut(&mut self) -> (&Env<'a>, &mut Interns, &mut CodeGenHelp<'a>);
&mut self,
) -> (&Env<'a>, &mut Interns, &mut RefcountProcGenerator<'a>);
fn symbol_to_string(&self, symbol: Symbol, layout_id: LayoutId) -> String { fn symbol_to_string(&self, symbol: Symbol, layout_id: LayoutId) -> String {
layout_id.to_symbol_string(symbol, self.interns()) layout_id.to_symbol_string(symbol, self.interns())
@ -76,11 +74,11 @@ trait Backend<'a> {
.starts_with(ModuleName::APP) .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. /// 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. /// 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.scan_ast(&proc.body);
self.create_free_map(); self.create_free_map();
self.build_stmt(&proc.body, &proc.ret_layout); self.build_stmt(&proc.body, &proc.ret_layout);
let mut rc_proc_names = bumpalo::vec![in self.env().arena]; let mut helper_proc_names = bumpalo::vec![in self.env().arena];
rc_proc_names.reserve(self.refcount_proc_symbols().len()); helper_proc_names.reserve(self.helper_proc_symbols().len());
for (rc_proc_sym, rc_proc_layout) in self.refcount_proc_symbols() { for (rc_proc_sym, rc_proc_layout) in self.helper_proc_symbols() {
let name = layout_ids let name = layout_ids
.get_toplevel(*rc_proc_sym, rc_proc_layout) .get_toplevel(*rc_proc_sym, rc_proc_layout)
.to_symbol_string(*rc_proc_sym, self.interns()); .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(); 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. /// 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 // 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 // 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. // 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 (rc_stmt, new_specializations) = {
let (env, interns, rc_proc_gen) = self.env_interns_refcount_mut(); let (env, interns, rc_proc_gen) = self.env_interns_helpers_mut();
let module_id = env.module_id; let module_id = env.module_id;
let ident_ids = interns.all_ident_ids.get_mut(&module_id).unwrap(); let ident_ids = interns.all_ident_ids.get_mut(&module_id).unwrap();
rc_proc_gen.expand_refcount_stmt(ident_ids, layout, modify, *following) rc_proc_gen.expand_refcount_stmt(ident_ids, layout, modify, *following)
}; };
if let Some((rc_proc_symbol, rc_proc_layout)) = new_proc_info { for spec in new_specializations.into_iter() {
self.refcount_proc_symbols_mut() self.helper_proc_symbols_mut().push(spec);
.push((rc_proc_symbol, rc_proc_layout));
} }
self.build_stmt(&rc_stmt, ret_layout) self.build_stmt(rc_stmt, ret_layout)
} }
Stmt::Switch { Stmt::Switch {
cond_symbol, cond_symbol,
@ -209,7 +206,7 @@ trait Backend<'a> {
self.build_jump(id, args, arg_layouts.into_bump_slice(), ret_layout); self.build_jump(id, args, arg_layouts.into_bump_slice(), ret_layout);
self.free_symbols(stmt); self.free_symbols(stmt);
} }
x => unimplemented!("the statement, {:?}, is not yet implemented", x), x => todo!("the statement, {:?}", x),
} }
} }
// build_switch generates a instructions for a switch statement. // build_switch generates a instructions for a switch statement.
@ -263,8 +260,9 @@ trait Backend<'a> {
ret_layout, ret_layout,
.. ..
} => { } => {
// If this function is just a lowlevel wrapper, then inline it if let LowLevelWrapperType::CanBeReplacedBy(lowlevel) =
if let Some(lowlevel) = LowLevel::from_inlined_wrapper(*func_sym) { LowLevelWrapperType::from_symbol(*func_sym)
{
self.build_run_low_level( self.build_run_low_level(
sym, sym,
&lowlevel, &lowlevel,
@ -309,7 +307,7 @@ trait Backend<'a> {
layout, layout,
) )
} }
x => unimplemented!("the call type, {:?}, is not yet implemented", x), x => todo!("the call type, {:?}", x),
} }
} }
Expr::Struct(fields) => { Expr::Struct(fields) => {
@ -323,7 +321,7 @@ trait Backend<'a> {
} => { } => {
self.load_struct_at_index(sym, structure, *index, field_layouts); self.load_struct_at_index(sym, structure, *index, field_layouts);
} }
x => unimplemented!("the expression, {:?}, is not yet implemented", x), x => todo!("the expression, {:?}", x),
} }
} }
@ -534,13 +532,13 @@ trait Backend<'a> {
arg_layouts, arg_layouts,
ret_layout, ret_layout,
), ),
LowLevel::RefCountGetPtr => { LowLevel::PtrCast => {
debug_assert_eq!( debug_assert_eq!(
1, 1,
args.len(), 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( LowLevel::RefCountDec => self.build_fn_call(
sym, sym,
@ -556,7 +554,7 @@ trait Backend<'a> {
arg_layouts, arg_layouts,
ret_layout, ret_layout,
), ),
x => unimplemented!("low level, {:?}. is not yet implemented", x), x => todo!("low level, {:?}", x),
} }
} }
@ -587,7 +585,7 @@ trait Backend<'a> {
self.build_eq(sym, &args[0], &Symbol::DEV_TMP, &arg_layouts[0]); self.build_eq(sym, &args[0], &Symbol::DEV_TMP, &arg_layouts[0]);
self.free_symbol(&Symbol::DEV_TMP) self.free_symbol(&Symbol::DEV_TMP)
} }
_ => unimplemented!("the function, {:?}, is not yet implemented", func_sym), _ => todo!("the function, {:?}", func_sym),
} }
} }
@ -645,7 +643,7 @@ trait Backend<'a> {
); );
/// build_refcount_getptr loads the pointer to the reference count of src into dst. /// 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. /// 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>)>; fn literal_map(&mut self) -> &mut MutMap<Symbol, (*const Literal<'a>, *const Layout<'a>)>;

View file

@ -104,7 +104,7 @@ pub fn build_module<'a>(
), ),
) )
} }
x => unimplemented!("the target, {:?}, is not yet implemented", x), x => unimplemented!("the target, {:?}", x),
} }
} }
@ -157,7 +157,7 @@ fn generate_wrapper<'a, B: Backend<'a>>(
Err(e) => internal_error!("{:?}", e), Err(e) => internal_error!("{:?}", e),
} }
} else { } else {
unimplemented!("failed to find fn symbol for {:?}", wraps); internal_error!("failed to find fn symbol for {:?}", wraps);
} }
} }
@ -240,38 +240,38 @@ fn build_object<'a, B: Backend<'a>>(
) )
} }
let rc_procs = { // Generate IR for specialized helper procs (refcounting & equality)
let helper_procs = {
let module_id = backend.env().module_id; let module_id = backend.env().module_id;
let (env, interns, rc_proc_gen) = backend.env_interns_refcount_mut(); let (env, interns, helper_proc_gen) = backend.env_interns_helpers_mut();
// Generate IR for refcounting procedures
let ident_ids = interns.all_ident_ids.get_mut(&module_id).unwrap(); let ident_ids = interns.all_ident_ids.get_mut(&module_id).unwrap();
let rc_procs = rc_proc_gen.generate_refcount_procs(arena, ident_ids); let helper_procs = helper_proc_gen.take_procs();
env.module_id.register_debug_idents(ident_ids); env.module_id.register_debug_idents(ident_ids);
rc_procs helper_procs
}; };
let empty = bumpalo::collections::Vec::new_in(arena); let empty = bumpalo::collections::Vec::new_in(arena);
let rc_symbols_and_layouts = std::mem::replace(backend.refcount_proc_symbols_mut(), empty); let helper_symbols_and_layouts = std::mem::replace(backend.helper_proc_symbols_mut(), empty);
let mut rc_names_symbols_procs = Vec::with_capacity_in(rc_procs.len(), arena); let mut helper_names_symbols_procs = Vec::with_capacity_in(helper_procs.len(), arena);
// Names and linker data for refcounting procedures // Names and linker data for helpers
for ((sym, layout), proc) in rc_symbols_and_layouts.into_iter().zip(rc_procs) { for ((sym, layout), proc) in helper_symbols_and_layouts.into_iter().zip(helper_procs) {
let layout_id = layout_ids.get_toplevel(sym, &layout); let layout_id = layout_ids.get_toplevel(sym, &layout);
let fn_name = backend.symbol_to_string(sym, layout_id); let fn_name = backend.symbol_to_string(sym, layout_id);
if let Some(proc_id) = output.symbol_id(fn_name.as_bytes()) { if let Some(proc_id) = output.symbol_id(fn_name.as_bytes()) {
if let SymbolSection::Section(section_id) = output.symbol(proc_id).section { if let SymbolSection::Section(section_id) = output.symbol(proc_id).section {
rc_names_symbols_procs.push((fn_name, section_id, proc_id, proc)); helper_names_symbols_procs.push((fn_name, section_id, proc_id, proc));
continue; continue;
} }
} }
internal_error!("failed to create rc fn for symbol {:?}", sym); internal_error!("failed to create rc fn for symbol {:?}", sym);
} }
// Build refcounting procedures // Build helpers
for (fn_name, section_id, proc_id, proc) in rc_names_symbols_procs { for (fn_name, section_id, proc_id, proc) in helper_names_symbols_procs {
build_proc( build_proc(
&mut output, &mut output,
&mut backend, &mut backend,
@ -285,7 +285,7 @@ fn build_object<'a, B: Backend<'a>>(
) )
} }
// Relocations for all procedures (user code & refcounting) // Relocations for all procedures (user code & helpers)
for (section_id, reloc) in relocations { for (section_id, reloc) in relocations {
match output.add_relocation(section_id, reloc) { match output.add_relocation(section_id, reloc) {
Ok(obj) => obj, Ok(obj) => obj,

View file

@ -10,6 +10,7 @@ edition = "2018"
roc_collections = { path = "../collections" } roc_collections = { path = "../collections" }
roc_module = { path = "../module" } roc_module = { path = "../module" }
roc_builtins = { path = "../builtins" } roc_builtins = { path = "../builtins" }
roc_reporting = { path = "../../reporting" }
roc_mono = { path = "../mono" } roc_mono = { path = "../mono" }
roc_std = { path = "../../roc_std" } roc_std = { path = "../../roc_std" }
morphic_lib = { path = "../../vendor/morphic_lib" } morphic_lib = { path = "../../vendor/morphic_lib" }

View file

@ -46,12 +46,15 @@ fn call_bitcode_fn_help<'a, 'ctx, 'env>(
args: &[BasicValueEnum<'ctx>], args: &[BasicValueEnum<'ctx>],
fn_name: &str, fn_name: &str,
) -> CallSiteValue<'ctx> { ) -> CallSiteValue<'ctx> {
let it = args.iter().map(|x| (*x).into());
let arguments = bumpalo::collections::Vec::from_iter_in(it, env.arena);
let fn_val = env let fn_val = env
.module .module
.get_function(fn_name) .get_function(fn_name)
.unwrap_or_else(|| panic!("Unrecognized builtin function: {:?} - if you're working on the Roc compiler, do you need to rebuild the bitcode? See compiler/builtins/bitcode/README.md", fn_name)); .unwrap_or_else(|| panic!("Unrecognized builtin function: {:?} - if you're working on the Roc compiler, do you need to rebuild the bitcode? See compiler/builtins/bitcode/README.md", fn_name));
let call = env.builder.build_call(fn_val, args, "call_builtin"); let call = env.builder.build_call(fn_val, &arguments, "call_builtin");
call.set_call_convention(fn_val.get_call_conventions()); call.set_call_convention(fn_val.get_call_conventions());
call call
@ -595,7 +598,7 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>(
let value1 = env.builder.build_load(value_cast1, "load_opaque"); let value1 = env.builder.build_load(value_cast1, "load_opaque");
let value2 = env.builder.build_load(value_cast2, "load_opaque"); let value2 = env.builder.build_load(value_cast2, "load_opaque");
let default = [value1, value2]; let default = [value1.into(), value2.into()];
let arguments_cast = match closure_data_layout.runtime_representation() { let arguments_cast = match closure_data_layout.runtime_representation() {
Layout::Struct(&[]) => { Layout::Struct(&[]) => {
@ -613,7 +616,9 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>(
let closure_data = env.builder.build_load(closure_cast, "load_opaque"); let closure_data = env.builder.build_load(closure_cast, "load_opaque");
env.arena.alloc([value1, value2, closure_data]) as &[_] env.arena
.alloc([value1.into(), value2.into(), closure_data.into()])
as &[_]
} }
}; };

View file

@ -15,8 +15,8 @@ use crate::llvm::build_list::{
list_single, list_sort_with, list_sublist, list_swap, list_single, list_sort_with, list_sublist, list_swap,
}; };
use crate::llvm::build_str::{ use crate::llvm::build_str::{
empty_str, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, str_from_utf8,
str_from_utf8, str_from_utf8_range, str_join_with, str_number_of_bytes, str_repeat, str_split, str_from_utf8_range, str_join_with, str_number_of_bytes, str_repeat, str_split,
str_starts_with, str_starts_with_code_point, str_to_utf8, str_trim, str_trim_left, str_starts_with, str_starts_with_code_point, str_to_utf8, str_trim, str_trim_left,
str_trim_right, str_trim_right,
}; };
@ -39,11 +39,13 @@ use inkwell::debug_info::{
use inkwell::memory_buffer::MemoryBuffer; use inkwell::memory_buffer::MemoryBuffer;
use inkwell::module::{Linkage, Module}; use inkwell::module::{Linkage, Module};
use inkwell::passes::{PassManager, PassManagerBuilder}; use inkwell::passes::{PassManager, PassManagerBuilder};
use inkwell::types::{BasicType, BasicTypeEnum, FunctionType, IntType, StructType}; use inkwell::types::{
BasicMetadataTypeEnum, BasicType, BasicTypeEnum, FunctionType, IntType, StructType,
};
use inkwell::values::BasicValueEnum::{self, *}; use inkwell::values::BasicValueEnum::{self, *};
use inkwell::values::{ use inkwell::values::{
BasicValue, CallSiteValue, CallableValue, FloatValue, FunctionValue, InstructionOpcode, BasicMetadataValueEnum, BasicValue, CallSiteValue, CallableValue, FloatValue, FunctionValue,
InstructionValue, IntValue, PointerValue, StructValue, InstructionOpcode, InstructionValue, IntValue, PointerValue, StructValue,
}; };
use inkwell::OptimizationLevel; use inkwell::OptimizationLevel;
use inkwell::{AddressSpace, IntPredicate}; use inkwell::{AddressSpace, IntPredicate};
@ -60,6 +62,7 @@ use roc_mono::ir::{
ModifyRc, OptLevel, ProcLayout, ModifyRc, OptLevel, ProcLayout,
}; };
use roc_mono::layout::{Builtin, LambdaSet, Layout, LayoutIds, TagIdIntType, UnionLayout}; use roc_mono::layout::{Builtin, LambdaSet, Layout, LayoutIds, TagIdIntType, UnionLayout};
use roc_reporting::internal_error;
use target_lexicon::{Architecture, OperatingSystem, Triple}; use target_lexicon::{Architecture, OperatingSystem, Triple};
/// This is for Inkwell's FunctionValue::verify - we want to know the verification /// This is for Inkwell's FunctionValue::verify - we want to know the verification
@ -226,10 +229,11 @@ impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> {
.get_function(intrinsic_name) .get_function(intrinsic_name)
.unwrap_or_else(|| panic!("Unrecognized intrinsic function: {}", intrinsic_name)); .unwrap_or_else(|| panic!("Unrecognized intrinsic function: {}", intrinsic_name));
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity_in(args.len(), self.arena); let mut arg_vals: Vec<BasicMetadataValueEnum> =
Vec::with_capacity_in(args.len(), self.arena);
for arg in args.iter() { for arg in args.iter() {
arg_vals.push(*arg); arg_vals.push((*arg).into());
} }
let call = self let call = self
@ -775,9 +779,6 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
Bool(b) => env.context.bool_type().const_int(*b as u64, false).into(), Bool(b) => env.context.bool_type().const_int(*b as u64, false).into(),
Byte(b) => env.context.i8_type().const_int(*b as u64, false).into(), Byte(b) => env.context.i8_type().const_int(*b as u64, false).into(),
Str(str_literal) => { Str(str_literal) => {
if str_literal.is_empty() {
empty_str(env)
} else {
let ctx = env.context; let ctx = env.context;
let builder = env.builder; let builder = env.builder;
let number_of_chars = str_literal.len() as u64; let number_of_chars = str_literal.len() as u64;
@ -836,9 +837,8 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
.const_int(*character as u64, false) .const_int(*character as u64, false)
.as_basic_value_enum(); .as_basic_value_enum();
let index_val = ctx.i64_type().const_int(index as u64, false); let index_val = ctx.i64_type().const_int(index as u64, false);
let elem_ptr = unsafe { let elem_ptr =
builder.build_in_bounds_gep(array_alloca, &[index_val], "index") unsafe { builder.build_in_bounds_gep(array_alloca, &[index_val], "index") };
};
builder.build_store(elem_ptr, val); builder.build_store(elem_ptr, val);
} }
@ -881,12 +881,7 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
) )
.unwrap(); .unwrap();
builder.build_bitcast( builder.build_bitcast(struct_val.into_struct_value(), str_type, "cast_collection")
struct_val.into_struct_value(),
str_type,
"cast_collection",
)
}
} }
} }
} }
@ -1399,6 +1394,18 @@ fn build_wrapped_tag<'a, 'ctx, 'env>(
); );
field_vals.push(ptr); field_vals.push(ptr);
} else if matches!(
tag_field_layout,
Layout::Union(UnionLayout::NonRecursive(_))
) {
debug_assert!(val.is_pointer_value());
// We store non-recursive unions without any indirection.
let reified = env
.builder
.build_load(val.into_pointer_value(), "load_non_recursive");
field_vals.push(reified);
} else { } else {
// this check fails for recursive tag unions, but can be helpful while debugging // this check fails for recursive tag unions, but can be helpful while debugging
// debug_assert_eq!(tag_field_layout, val_layout); // debug_assert_eq!(tag_field_layout, val_layout);
@ -1754,8 +1761,8 @@ fn tag_pointer_set_tag_id<'a, 'ctx, 'env>(
tag_id: u8, tag_id: u8,
pointer: PointerValue<'ctx>, pointer: PointerValue<'ctx>,
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
// we only have 3 bits, so can encode only 0..7 // we only have 3 bits, so can encode only 0..7 (or on 32-bit targets, 2 bits to encode 0..3)
debug_assert!(tag_id < 8); debug_assert!((tag_id as u32) < env.ptr_bytes);
let ptr_int = env.ptr_int(); let ptr_int = env.ptr_int();
@ -1768,16 +1775,19 @@ fn tag_pointer_set_tag_id<'a, 'ctx, 'env>(
.build_int_to_ptr(combined, pointer.get_type(), "to_ptr") .build_int_to_ptr(combined, pointer.get_type(), "to_ptr")
} }
pub fn tag_pointer_tag_id_bits_and_mask(ptr_bytes: u32) -> (u64, u64) {
match ptr_bytes {
8 => (3, 0b0000_0111),
4 => (2, 0b0000_0011),
_ => unreachable!(),
}
}
pub fn tag_pointer_read_tag_id<'a, 'ctx, 'env>( pub fn tag_pointer_read_tag_id<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
pointer: PointerValue<'ctx>, pointer: PointerValue<'ctx>,
) -> IntValue<'ctx> { ) -> IntValue<'ctx> {
let mask: u64 = match env.ptr_bytes { let (_, mask) = tag_pointer_tag_id_bits_and_mask(env.ptr_bytes);
8 => 0b0000_0111,
4 => 0b0000_0011,
_ => unreachable!(),
};
let ptr_int = env.ptr_int(); let ptr_int = env.ptr_int();
let as_int = env.builder.build_ptr_to_int(pointer, ptr_int, "to_int"); let as_int = env.builder.build_ptr_to_int(pointer, ptr_int, "to_int");
@ -1795,11 +1805,7 @@ pub fn tag_pointer_clear_tag_id<'a, 'ctx, 'env>(
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
let ptr_int = env.ptr_int(); let ptr_int = env.ptr_int();
let tag_id_bits_mask = match env.ptr_bytes { let (tag_id_bits_mask, _) = tag_pointer_tag_id_bits_and_mask(env.ptr_bytes);
8 => 3,
4 => 2,
_ => unreachable!(),
};
let as_int = env.builder.build_ptr_to_int(pointer, ptr_int, "to_int"); let as_int = env.builder.build_ptr_to_int(pointer, ptr_int, "to_int");
@ -2214,6 +2220,24 @@ pub fn allocate_with_refcount_help<'a, 'ctx, 'env>(
data_ptr data_ptr
} }
macro_rules! dict_key_value_layout {
($dict_layout:expr) => {
match $dict_layout {
Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => (key_layout, value_layout),
_ => unreachable!("invalid dict layout"),
}
};
}
macro_rules! list_element_layout {
($list_layout:expr) => {
match $list_layout {
Layout::Builtin(Builtin::List(list_layout)) => *list_layout,
_ => unreachable!("invalid list layout"),
}
};
}
fn list_literal<'a, 'ctx, 'env>( fn list_literal<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>, scope: &Scope<'a, 'ctx>,
@ -3287,7 +3311,9 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>(
let output_type = return_type.ptr_type(AddressSpace::Generic); let output_type = return_type.ptr_type(AddressSpace::Generic);
argument_types.push(output_type.into()); argument_types.push(output_type.into());
env.context.void_type().fn_type(&argument_types, false) env.context
.void_type()
.fn_type(&function_arguments(env, &argument_types), false)
} }
}; };
@ -3392,7 +3418,9 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>(
let c_function_type = { let c_function_type = {
let output_type = return_type.ptr_type(AddressSpace::Generic); let output_type = return_type.ptr_type(AddressSpace::Generic);
argument_types.push(output_type.into()); argument_types.push(output_type.into());
env.context.void_type().fn_type(&argument_types, false) env.context
.void_type()
.fn_type(&function_arguments(env, &argument_types), false)
}; };
let c_function = add_func( let c_function = add_func(
@ -3440,9 +3468,11 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>(
let arguments_for_call = &arguments_for_call.into_bump_slice(); let arguments_for_call = &arguments_for_call.into_bump_slice();
let call_result = { let call_result = {
let last_block = builder.get_insert_block().unwrap();
let roc_wrapper_function = make_exception_catcher(env, roc_function, return_layout); let roc_wrapper_function = make_exception_catcher(env, roc_function, return_layout);
builder.position_at_end(entry); builder.position_at_end(last_block);
call_roc_function( call_roc_function(
env, env,
@ -3536,12 +3566,17 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
let cc_return = to_cc_return(env, &return_layout); let cc_return = to_cc_return(env, &return_layout);
let c_function_type = match cc_return { let c_function_type = match cc_return {
CCReturn::Void => env.context.void_type().fn_type(&argument_types, false), CCReturn::Void => env
CCReturn::Return => return_type.fn_type(&argument_types, false), .context
.void_type()
.fn_type(&function_arguments(env, &argument_types), false),
CCReturn::Return => return_type.fn_type(&function_arguments(env, &argument_types), false),
CCReturn::ByPointer => { CCReturn::ByPointer => {
let output_type = return_type.ptr_type(AddressSpace::Generic); let output_type = return_type.ptr_type(AddressSpace::Generic);
argument_types.push(output_type.into()); argument_types.push(output_type.into());
env.context.void_type().fn_type(&argument_types, false) env.context
.void_type()
.fn_type(&function_arguments(env, &argument_types), false)
} }
}; };
@ -3902,7 +3937,8 @@ fn make_exception_catching_wrapper<'a, 'ctx, 'env>(
// argument_types.push(wrapper_return_type.ptr_type(AddressSpace::Generic).into()); // argument_types.push(wrapper_return_type.ptr_type(AddressSpace::Generic).into());
// let wrapper_function_type = env.context.void_type().fn_type(&argument_types, false); // let wrapper_function_type = env.context.void_type().fn_type(&argument_types, false);
let wrapper_function_type = wrapper_return_type.fn_type(&argument_types, false); let wrapper_function_type =
wrapper_return_type.fn_type(&function_arguments(env, &argument_types), false);
// Add main to the module. // Add main to the module.
let wrapper_function = add_func( let wrapper_function = add_func(
@ -4134,11 +4170,13 @@ fn build_proc_header<'a, 'ctx, 'env>(
} }
let fn_type = match RocReturn::from_layout(env, &proc.ret_layout) { let fn_type = match RocReturn::from_layout(env, &proc.ret_layout) {
RocReturn::Return => ret_type.fn_type(&arg_basic_types, false), RocReturn::Return => ret_type.fn_type(&function_arguments(env, &arg_basic_types), false),
RocReturn::ByPointer => { RocReturn::ByPointer => {
// println!( "{:?} will return void instead of {:?}", symbol, proc.ret_layout); // println!( "{:?} will return void instead of {:?}", symbol, proc.ret_layout);
arg_basic_types.push(ret_type.ptr_type(AddressSpace::Generic).into()); arg_basic_types.push(ret_type.ptr_type(AddressSpace::Generic).into());
env.context.void_type().fn_type(&arg_basic_types, false) env.context
.void_type()
.fn_type(&function_arguments(env, &arg_basic_types), false)
} }
}; };
@ -4542,7 +4580,8 @@ pub fn call_roc_function<'a, 'ctx, 'env>(
match RocReturn::from_layout(env, result_layout) { match RocReturn::from_layout(env, result_layout) {
RocReturn::ByPointer if !pass_by_pointer => { RocReturn::ByPointer if !pass_by_pointer => {
// WARNING this is a hack!! // WARNING this is a hack!!
let mut arguments = Vec::from_iter_in(arguments.iter().copied(), env.arena); let it = arguments.iter().map(|x| (*x).into());
let mut arguments = Vec::from_iter_in(it, env.arena);
arguments.pop(); arguments.pop();
let result_type = basic_type_from_layout(env, result_layout); let result_type = basic_type_from_layout(env, result_layout);
@ -4563,7 +4602,8 @@ pub fn call_roc_function<'a, 'ctx, 'env>(
env.builder.build_load(result_alloca, "load_result") env.builder.build_load(result_alloca, "load_result")
} }
RocReturn::ByPointer => { RocReturn::ByPointer => {
let mut arguments = Vec::from_iter_in(arguments.iter().copied(), env.arena); let it = arguments.iter().map(|x| (*x).into());
let mut arguments = Vec::from_iter_in(it, env.arena);
let result_type = basic_type_from_layout(env, result_layout); let result_type = basic_type_from_layout(env, result_layout);
let result_alloca = tag_alloca(env, result_type, "result_value"); let result_alloca = tag_alloca(env, result_type, "result_value");
@ -4592,7 +4632,10 @@ pub fn call_roc_function<'a, 'ctx, 'env>(
roc_function.get_type().get_param_types().len(), roc_function.get_type().get_param_types().len(),
arguments.len() arguments.len()
); );
let call = env.builder.build_call(roc_function, arguments, "call"); let it = arguments.iter().map(|x| (*x).into());
let arguments = Vec::from_iter_in(it, env.arena);
let call = env.builder.build_call(roc_function, &arguments, "call");
// roc functions should have the fast calling convention // roc functions should have the fast calling convention
debug_assert_eq!(roc_function.get_call_conventions(), FAST_CALL_CONV); debug_assert_eq!(roc_function.get_call_conventions(), FAST_CALL_CONV);
@ -5272,6 +5315,31 @@ fn run_low_level<'a, 'ctx, 'env>(
str_ends_with(env, scope, args[0], args[1]) str_ends_with(env, scope, args[0], args[1])
} }
StrToNum => {
// Str.toNum : Str -> Result (Num *) {}
debug_assert_eq!(args.len(), 1);
let (string, _string_layout) = load_symbol_and_layout(scope, &args[0]);
if let Layout::Struct(struct_layout) = layout {
// match on the return layout to figure out which zig builtin we need
let intrinsic = match struct_layout[0] {
Layout::Builtin(Builtin::Int(int_width)) => &bitcode::STR_TO_INT[int_width],
Layout::Builtin(Builtin::Float(float_width)) => {
&bitcode::STR_TO_FLOAT[float_width]
}
Layout::Builtin(Builtin::Decimal) => bitcode::DEC_FROM_STR,
_ => unreachable!(),
};
let string =
complex_bitcast(env.builder, string, env.str_list_c_abi().into(), "to_utf8");
call_bitcode_fn(env, &[string], intrinsic)
} else {
unreachable!()
}
}
StrFromInt => { StrFromInt => {
// Str.fromInt : Int -> Str // Str.fromInt : Int -> Str
debug_assert_eq!(args.len(), 1); debug_assert_eq!(args.len(), 1);
@ -5398,7 +5466,9 @@ fn run_low_level<'a, 'ctx, 'env>(
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]); let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
list_reverse(env, list, list_layout, update_mode) let element_layout = list_element_layout!(list_layout);
list_reverse(env, list, element_layout, update_mode)
} }
ListConcat => { ListConcat => {
debug_assert_eq!(args.len(), 2); debug_assert_eq!(args.len(), 2);
@ -5407,7 +5477,9 @@ fn run_low_level<'a, 'ctx, 'env>(
let second_list = load_symbol(scope, &args[1]); let second_list = load_symbol(scope, &args[1]);
list_concat(env, parent, first_list, second_list, list_layout) let element_layout = list_element_layout!(list_layout);
list_concat(env, first_list, second_list, element_layout)
} }
ListContains => { ListContains => {
// List.contains : List elem, elem -> Bool // List.contains : List elem, elem -> Bool
@ -5452,17 +5524,15 @@ fn run_low_level<'a, 'ctx, 'env>(
let index_1 = load_symbol(scope, &args[1]); let index_1 = load_symbol(scope, &args[1]);
let index_2 = load_symbol(scope, &args[2]); let index_2 = load_symbol(scope, &args[2]);
match list_layout { let element_layout = list_element_layout!(list_layout);
Layout::Builtin(Builtin::List(element_layout)) => list_swap( list_swap(
env, env,
original_wrapper, original_wrapper,
index_1.into_int_value(), index_1.into_int_value(),
index_2.into_int_value(), index_2.into_int_value(),
element_layout, element_layout,
update_mode, update_mode,
), )
_ => unreachable!("Invalid layout {:?} in List.swap", list_layout),
}
} }
ListSublist => { ListSublist => {
// List.sublist : List elem, { start : Nat, len : Nat } -> List elem // List.sublist : List elem, { start : Nat, len : Nat } -> List elem
@ -5477,17 +5547,15 @@ fn run_low_level<'a, 'ctx, 'env>(
let start = load_symbol(scope, &args[1]); let start = load_symbol(scope, &args[1]);
let len = load_symbol(scope, &args[2]); let len = load_symbol(scope, &args[2]);
match list_layout { let element_layout = list_element_layout!(list_layout);
Layout::Builtin(Builtin::List(element_layout)) => list_sublist( list_sublist(
env, env,
layout_ids, layout_ids,
original_wrapper, original_wrapper,
start.into_int_value(), start.into_int_value(),
len.into_int_value(), len.into_int_value(),
element_layout, element_layout,
), )
_ => unreachable!("Invalid layout {:?} in List.sublist", list_layout),
}
} }
ListDropAt => { ListDropAt => {
// List.dropAt : List elem, Nat -> List elem // List.dropAt : List elem, Nat -> List elem
@ -5498,16 +5566,14 @@ fn run_low_level<'a, 'ctx, 'env>(
let count = load_symbol(scope, &args[1]); let count = load_symbol(scope, &args[1]);
match list_layout { let element_layout = list_element_layout!(list_layout);
Layout::Builtin(Builtin::List(element_layout)) => list_drop_at( list_drop_at(
env, env,
layout_ids, layout_ids,
original_wrapper, original_wrapper,
count.into_int_value(), count.into_int_value(),
element_layout, element_layout,
), )
_ => unreachable!("Invalid layout {:?} in List.dropAt", list_layout),
}
} }
ListPrepend => { ListPrepend => {
// List.prepend : List elem, elem -> List elem // List.prepend : List elem, elem -> List elem
@ -5524,7 +5590,44 @@ fn run_low_level<'a, 'ctx, 'env>(
let (list, outer_list_layout) = load_symbol_and_layout(scope, &args[0]); let (list, outer_list_layout) = load_symbol_and_layout(scope, &args[0]);
list_join(env, parent, list, outer_list_layout) let inner_list_layout = list_element_layout!(outer_list_layout);
let element_layout = list_element_layout!(inner_list_layout);
list_join(env, list, element_layout)
}
ListGetUnsafe => {
// List.get : List elem, Nat -> [ Ok elem, OutOfBounds ]*
debug_assert_eq!(args.len(), 2);
let (wrapper_struct, list_layout) = load_symbol_and_layout(scope, &args[0]);
let wrapper_struct = wrapper_struct.into_struct_value();
let elem_index = load_symbol(scope, &args[1]).into_int_value();
let element_layout = list_element_layout!(list_layout);
list_get_unsafe(
env,
layout_ids,
parent,
element_layout,
elem_index,
wrapper_struct,
)
}
ListSet => {
let list = load_symbol(scope, &args[0]);
let index = load_symbol(scope, &args[1]);
let (element, element_layout) = load_symbol_and_layout(scope, &args[2]);
list_set(
env,
layout_ids,
list,
index.into_int_value(),
element,
element_layout,
update_mode,
)
} }
NumToStr => { NumToStr => {
// Num.toStr : Num a -> Str // Num.toStr : Num a -> Str
@ -5559,9 +5662,13 @@ fn run_low_level<'a, 'ctx, 'env>(
let int_type = convert::int_type_from_int_width(env, *int_width); let int_type = convert::int_type_from_int_width(env, *int_width);
build_int_unary_op(env, arg.into_int_value(), int_type, op) build_int_unary_op(env, arg.into_int_value(), int_type, op)
} }
Float(float_width) => { Float(float_width) => build_float_unary_op(
build_float_unary_op(env, arg.into_float_value(), op, *float_width) env,
} layout,
arg.into_float_value(),
op,
*float_width,
),
_ => { _ => {
unreachable!("Compiler bug: tried to run numeric operation {:?} on invalid builtin layout: ({:?})", op, arg_layout); unreachable!("Compiler bug: tried to run numeric operation {:?} on invalid builtin layout: ({:?})", op, arg_layout);
} }
@ -5807,41 +5914,6 @@ fn run_low_level<'a, 'ctx, 'env>(
BasicValueEnum::IntValue(bool_val) BasicValueEnum::IntValue(bool_val)
} }
ListGetUnsafe => {
// List.get : List elem, Nat -> [ Ok elem, OutOfBounds ]*
debug_assert_eq!(args.len(), 2);
let (wrapper_struct, list_layout) = load_symbol_and_layout(scope, &args[0]);
let wrapper_struct = wrapper_struct.into_struct_value();
let elem_index = load_symbol(scope, &args[1]).into_int_value();
list_get_unsafe(
env,
layout_ids,
parent,
list_layout,
elem_index,
wrapper_struct,
)
}
ListSet => {
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
let (index, _) = load_symbol_and_layout(scope, &args[1]);
let (element, _) = load_symbol_and_layout(scope, &args[2]);
match list_layout {
Layout::Builtin(Builtin::List(element_layout)) => list_set(
env,
layout_ids,
list,
index.into_int_value(),
element,
element_layout,
update_mode,
),
_ => unreachable!("invalid dict layout"),
}
}
Hash => { Hash => {
debug_assert_eq!(args.len(), 2); debug_assert_eq!(args.len(), 2);
let seed = load_symbol(scope, &args[0]); let seed = load_symbol(scope, &args[0]);
@ -5871,116 +5943,80 @@ fn run_low_level<'a, 'ctx, 'env>(
debug_assert_eq!(args.len(), 2); debug_assert_eq!(args.len(), 2);
let (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]); let (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]);
let (key, key_layout) = load_symbol_and_layout(scope, &args[1]); let key = load_symbol(scope, &args[1]);
match dict_layout { let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
Layout::Builtin(Builtin::Dict(_, value_layout)) => {
dict_remove(env, layout_ids, dict, key, key_layout, value_layout) dict_remove(env, layout_ids, dict, key, key_layout, value_layout)
} }
_ => unreachable!("invalid dict layout"),
}
}
DictContains => { DictContains => {
debug_assert_eq!(args.len(), 2); debug_assert_eq!(args.len(), 2);
let (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]); let (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]);
let (key, key_layout) = load_symbol_and_layout(scope, &args[1]); let key = load_symbol(scope, &args[1]);
match dict_layout { let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
Layout::Builtin(Builtin::Dict(_, value_layout)) => {
dict_contains(env, layout_ids, dict, key, key_layout, value_layout) dict_contains(env, layout_ids, dict, key, key_layout, value_layout)
} }
_ => unreachable!("invalid dict layout"),
}
}
DictGetUnsafe => { DictGetUnsafe => {
debug_assert_eq!(args.len(), 2); debug_assert_eq!(args.len(), 2);
let (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]); let (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]);
let (key, key_layout) = load_symbol_and_layout(scope, &args[1]); let key = load_symbol(scope, &args[1]);
match dict_layout { let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
Layout::Builtin(Builtin::Dict(_, value_layout)) => {
dict_get(env, layout_ids, dict, key, key_layout, value_layout) dict_get(env, layout_ids, dict, key, key_layout, value_layout)
} }
_ => unreachable!("invalid dict layout"),
}
}
DictKeys => { DictKeys => {
debug_assert_eq!(args.len(), 1); debug_assert_eq!(args.len(), 1);
let (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]); let (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]);
match dict_layout { let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => {
dict_keys(env, layout_ids, dict, key_layout, value_layout) dict_keys(env, layout_ids, dict, key_layout, value_layout)
} }
_ => unreachable!("invalid dict layout"),
}
}
DictValues => { DictValues => {
debug_assert_eq!(args.len(), 1); debug_assert_eq!(args.len(), 1);
let (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]); let (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]);
match dict_layout { let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => {
dict_values(env, layout_ids, dict, key_layout, value_layout) dict_values(env, layout_ids, dict, key_layout, value_layout)
} }
_ => unreachable!("invalid dict layout"),
}
}
DictUnion => { DictUnion => {
debug_assert_eq!(args.len(), 2); debug_assert_eq!(args.len(), 2);
let (dict1, dict_layout) = load_symbol_and_layout(scope, &args[0]); let (dict1, dict_layout) = load_symbol_and_layout(scope, &args[0]);
let (dict2, _) = load_symbol_and_layout(scope, &args[1]); let (dict2, _) = load_symbol_and_layout(scope, &args[1]);
match dict_layout { let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => {
dict_union(env, layout_ids, dict1, dict2, key_layout, value_layout) dict_union(env, layout_ids, dict1, dict2, key_layout, value_layout)
} }
_ => unreachable!("invalid dict layout"),
}
}
DictDifference => { DictDifference => {
debug_assert_eq!(args.len(), 2); debug_assert_eq!(args.len(), 2);
let (dict1, dict_layout) = load_symbol_and_layout(scope, &args[0]); let (dict1, dict_layout) = load_symbol_and_layout(scope, &args[0]);
let (dict2, _) = load_symbol_and_layout(scope, &args[1]); let (dict2, _) = load_symbol_and_layout(scope, &args[1]);
match dict_layout { let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => {
dict_difference(env, layout_ids, dict1, dict2, key_layout, value_layout) dict_difference(env, layout_ids, dict1, dict2, key_layout, value_layout)
} }
_ => unreachable!("invalid dict layout"),
}
}
DictIntersection => { DictIntersection => {
debug_assert_eq!(args.len(), 2); debug_assert_eq!(args.len(), 2);
let (dict1, dict_layout) = load_symbol_and_layout(scope, &args[0]); let (dict1, dict_layout) = load_symbol_and_layout(scope, &args[0]);
let (dict2, _) = load_symbol_and_layout(scope, &args[1]); let (dict2, _) = load_symbol_and_layout(scope, &args[1]);
match dict_layout { let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => {
dict_intersection(env, layout_ids, dict1, dict2, key_layout, value_layout) dict_intersection(env, layout_ids, dict1, dict2, key_layout, value_layout)
} }
_ => unreachable!("invalid dict layout"),
}
}
SetFromList => { SetFromList => {
debug_assert_eq!(args.len(), 1); debug_assert_eq!(args.len(), 1);
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]); let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
match list_layout { let key_layout = list_element_layout!(list_layout);
Layout::Builtin(Builtin::List(key_layout)) => {
set_from_list(env, layout_ids, list, key_layout) set_from_list(env, layout_ids, list, key_layout)
} }
_ => unreachable!("invalid dict layout"),
}
}
ExpectTrue => { ExpectTrue => {
debug_assert_eq!(args.len(), 1); debug_assert_eq!(args.len(), 1);
@ -6043,8 +6079,8 @@ fn run_low_level<'a, 'ctx, 'env>(
unreachable!("these are higher order, and are handled elsewhere") unreachable!("these are higher order, and are handled elsewhere")
} }
RefCountGetPtr | RefCountInc | RefCountDec => { PtrCast | RefCountInc | RefCountDec => {
unreachable!("LLVM backend does not use lowlevels for refcounting"); unreachable!("Not used in LLVM backend: {:?}", op);
} }
} }
} }
@ -6135,6 +6171,14 @@ fn to_cc_return<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>)
} }
} }
fn function_arguments<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
arguments: &[BasicTypeEnum<'ctx>],
) -> Vec<'a, BasicMetadataTypeEnum<'ctx>> {
let it = arguments.iter().map(|x| (*x).into());
Vec::from_iter_in(it, env.arena)
}
fn build_foreign_symbol<'a, 'ctx, 'env>( fn build_foreign_symbol<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
scope: &mut Scope<'a, 'ctx>, scope: &mut Scope<'a, 'ctx>,
@ -6189,17 +6233,25 @@ fn build_foreign_symbol<'a, 'ctx, 'env>(
} }
let cc_type = match cc_return { let cc_type = match cc_return {
CCReturn::Void => env.context.void_type().fn_type(&cc_argument_types, false), CCReturn::Void => env
.context
.void_type()
.fn_type(&function_arguments(env, &cc_argument_types), false),
CCReturn::ByPointer => { CCReturn::ByPointer => {
cc_argument_types.push(return_type.ptr_type(AddressSpace::Generic).into()); cc_argument_types.push(return_type.ptr_type(AddressSpace::Generic).into());
env.context.void_type().fn_type(&cc_argument_types, false) env.context
.void_type()
.fn_type(&function_arguments(env, &cc_argument_types), false)
}
CCReturn::Return => {
return_type.fn_type(&function_arguments(env, &cc_argument_types), false)
} }
CCReturn::Return => return_type.fn_type(&cc_argument_types, false),
}; };
let cc_function = get_foreign_symbol(env, foreign.clone(), cc_type); let cc_function = get_foreign_symbol(env, foreign.clone(), cc_type);
let fastcc_type = return_type.fn_type(&fastcc_argument_types, false); let fastcc_type =
return_type.fn_type(&function_arguments(env, &fastcc_argument_types), false);
let fastcc_function = add_func( let fastcc_function = add_func(
env.module, env.module,
@ -6220,14 +6272,14 @@ fn build_foreign_symbol<'a, 'ctx, 'env>(
let mut cc_arguments = let mut cc_arguments =
Vec::with_capacity_in(fastcc_parameters.len() + 1, env.arena); Vec::with_capacity_in(fastcc_parameters.len() + 1, env.arena);
for (param, cc_type) in fastcc_parameters.into_iter().zip(cc_argument_types.iter()) let it = fastcc_parameters.into_iter().zip(cc_argument_types.iter());
{ for (param, cc_type) in it {
if param.get_type() == *cc_type { if param.get_type() == *cc_type {
cc_arguments.push(param); cc_arguments.push(param.into());
} else { } else {
let as_cc_type = let as_cc_type =
complex_bitcast(env.builder, param, *cc_type, "to_cc_type"); complex_bitcast(env.builder, param, *cc_type, "to_cc_type");
cc_arguments.push(as_cc_type); cc_arguments.push(as_cc_type.into());
} }
} }
@ -6482,17 +6534,6 @@ pub fn build_num_binop<'a, 'ctx, 'env>(
{ {
use roc_mono::layout::Builtin::*; use roc_mono::layout::Builtin::*;
let float_binop = |float_width| {
build_float_binop(
env,
parent,
float_width,
lhs_arg.into_float_value(),
rhs_arg.into_float_value(),
op,
)
};
match lhs_builtin { match lhs_builtin {
Int(int_width) => build_int_binop( Int(int_width) => build_int_binop(
env, env,
@ -6503,7 +6544,14 @@ pub fn build_num_binop<'a, 'ctx, 'env>(
op, op,
), ),
Float(float_width) => float_binop(*float_width), Float(float_width) => build_float_binop(
env,
parent,
*float_width,
lhs_arg.into_float_value(),
rhs_arg.into_float_value(),
op,
),
Decimal => { Decimal => {
build_dec_binop(env, parent, lhs_arg, lhs_layout, rhs_arg, rhs_layout, op) build_dec_binop(env, parent, lhs_arg, lhs_layout, rhs_arg, rhs_layout, op)
@ -6922,9 +6970,10 @@ fn int_abs_with_overflow<'a, 'ctx, 'env>(
fn build_float_unary_op<'a, 'ctx, 'env>( fn build_float_unary_op<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout: &Layout<'a>,
arg: FloatValue<'ctx>, arg: FloatValue<'ctx>,
op: LowLevel, op: LowLevel,
float_width: FloatWidth, float_width: FloatWidth, // arg width
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
use roc_module::low_level::LowLevel::*; use roc_module::low_level::LowLevel::*;
@ -6936,7 +6985,35 @@ fn build_float_unary_op<'a, 'ctx, 'env>(
NumAbs => env.call_intrinsic(&LLVM_FABS[float_width], &[arg.into()]), NumAbs => env.call_intrinsic(&LLVM_FABS[float_width], &[arg.into()]),
NumSqrtUnchecked => env.call_intrinsic(&LLVM_SQRT[float_width], &[arg.into()]), NumSqrtUnchecked => env.call_intrinsic(&LLVM_SQRT[float_width], &[arg.into()]),
NumLogUnchecked => env.call_intrinsic(&LLVM_LOG[float_width], &[arg.into()]), NumLogUnchecked => env.call_intrinsic(&LLVM_LOG[float_width], &[arg.into()]),
NumToFloat => arg.into(), /* Converting from Float to Float is a no-op */ NumToFloat => {
let return_width = match layout {
Layout::Builtin(Builtin::Float(return_width)) => *return_width,
_ => internal_error!("Layout for returning is not Float : {:?}", layout),
};
match (float_width, return_width) {
(FloatWidth::F32, FloatWidth::F32) => arg.into(),
(FloatWidth::F32, FloatWidth::F64) => bd.build_cast(
InstructionOpcode::FPExt,
arg,
env.context.f64_type(),
"f32_to_f64",
),
(FloatWidth::F64, FloatWidth::F32) => bd.build_cast(
InstructionOpcode::FPTrunc,
arg,
env.context.f32_type(),
"f64_to_f32",
),
(FloatWidth::F64, FloatWidth::F64) => arg.into(),
(FloatWidth::F128, FloatWidth::F128) => arg.into(),
(FloatWidth::F128, _) => {
unimplemented!("I cannot handle F128 with Num.toFloat yet")
}
(_, FloatWidth::F128) => {
unimplemented!("I cannot handle F128 with Num.toFloat yet")
}
}
}
NumCeiling => env.builder.build_cast( NumCeiling => env.builder.build_cast(
InstructionOpcode::FPToSI, InstructionOpcode::FPToSI,
env.call_intrinsic(&LLVM_CEILING[float_width], &[arg.into()]), env.call_intrinsic(&LLVM_CEILING[float_width], &[arg.into()]),

View file

@ -27,7 +27,9 @@ impl Alignment {
let value_align = value.alignment_bytes(ptr_bytes); let value_align = value.alignment_bytes(ptr_bytes);
let mut bits = key_align.max(value_align) as u8; let mut bits = key_align.max(value_align) as u8;
debug_assert!(bits == 4 || bits == 8 || bits == 16);
// alignment must be a power of 2
debug_assert!(bits.is_power_of_two());
let value_before_key_flag = 0b1000_0000; let value_before_key_flag = 0b1000_0000;

View file

@ -346,7 +346,7 @@ fn build_hash_tag<'a, 'ctx, 'env>(
.set_current_debug_location(env.context, di_location); .set_current_debug_location(env.context, di_location);
let call = env let call = env
.builder .builder
.build_call(function, &[seed.into(), value], "struct_hash"); .build_call(function, &[seed.into(), value.into()], "struct_hash");
call.set_call_convention(FAST_CALL_CONV); call.set_call_convention(FAST_CALL_CONV);

View file

@ -145,12 +145,9 @@ pub fn list_repeat<'a, 'ctx, 'env>(
/// List.join : List (List elem) -> List elem /// List.join : List (List elem) -> List elem
pub fn list_join<'a, 'ctx, 'env>( pub fn list_join<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
_parent: FunctionValue<'ctx>,
outer_list: BasicValueEnum<'ctx>, outer_list: BasicValueEnum<'ctx>,
outer_list_layout: &Layout<'a>, element_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
match outer_list_layout {
Layout::Builtin(Builtin::List(Layout::Builtin(Builtin::List(element_layout)))) => {
call_bitcode_fn_returns_list( call_bitcode_fn_returns_list(
env, env,
&[ &[
@ -161,31 +158,20 @@ pub fn list_join<'a, 'ctx, 'env>(
bitcode::LIST_JOIN, bitcode::LIST_JOIN,
) )
} }
_ => {
unreachable!("Invalid List layout for List.join {:?}", outer_list_layout);
}
}
}
/// List.reverse : List elem -> List elem /// List.reverse : List elem -> List elem
pub fn list_reverse<'a, 'ctx, 'env>( pub fn list_reverse<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
list: BasicValueEnum<'ctx>, list: BasicValueEnum<'ctx>,
list_layout: &Layout<'a>, element_layout: &Layout<'a>,
update_mode: UpdateMode, update_mode: UpdateMode,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let element_layout = match *list_layout {
Layout::Builtin(Builtin::List(elem_layout)) => *elem_layout,
_ => unreachable!("Invalid layout {:?} in List.reverse", list_layout),
};
call_bitcode_fn_returns_list( call_bitcode_fn_returns_list(
env, env,
&[ &[
pass_list_cc(env, list), pass_list_cc(env, list),
env.alignment_intvalue(&element_layout), env.alignment_intvalue(element_layout),
layout_width(env, &element_layout), layout_width(env, element_layout),
pass_update_mode(env, update_mode), pass_update_mode(env, update_mode),
], ],
bitcode::LIST_REVERSE, bitcode::LIST_REVERSE,
@ -196,39 +182,28 @@ pub fn list_get_unsafe<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
parent: FunctionValue<'ctx>, parent: FunctionValue<'ctx>,
list_layout: &Layout<'a>, element_layout: &Layout<'a>,
elem_index: IntValue<'ctx>, elem_index: IntValue<'ctx>,
wrapper_struct: StructValue<'ctx>, wrapper_struct: StructValue<'ctx>,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let builder = env.builder; let builder = env.builder;
match list_layout { let elem_type = basic_type_from_layout(env, element_layout);
Layout::Builtin(Builtin::List(elem_layout)) => {
let elem_type = basic_type_from_layout(env, elem_layout);
let ptr_type = elem_type.ptr_type(AddressSpace::Generic); let ptr_type = elem_type.ptr_type(AddressSpace::Generic);
// Load the pointer to the array data // Load the pointer to the array data
let array_data_ptr = load_list_ptr(builder, wrapper_struct, ptr_type); let array_data_ptr = load_list_ptr(builder, wrapper_struct, ptr_type);
// Assume the bounds have already been checked earlier // Assume the bounds have already been checked earlier
// (e.g. by List.get or List.first, which wrap List.#getUnsafe) // (e.g. by List.get or List.first, which wrap List.#getUnsafe)
let elem_ptr = unsafe { let elem_ptr =
builder.build_in_bounds_gep(array_data_ptr, &[elem_index], "list_get_element") unsafe { builder.build_in_bounds_gep(array_data_ptr, &[elem_index], "list_get_element") };
};
let result = load_roc_value(env, **elem_layout, elem_ptr, "list_get_load_element"); let result = load_roc_value(env, *element_layout, elem_ptr, "list_get_load_element");
increment_refcount_layout(env, parent, layout_ids, 1, result, elem_layout); increment_refcount_layout(env, parent, layout_ids, 1, result, element_layout);
result result
} }
_ => {
unreachable!(
"Invalid List layout for ListGetUnsafe operation: {:?}",
list_layout
);
}
}
}
/// List.append : List elem, elem -> List elem /// List.append : List elem, elem -> List elem
pub fn list_append<'a, 'ctx, 'env>( pub fn list_append<'a, 'ctx, 'env>(
@ -346,7 +321,7 @@ pub fn list_set<'a, 'ctx, 'env>(
list: BasicValueEnum<'ctx>, list: BasicValueEnum<'ctx>,
index: IntValue<'ctx>, index: IntValue<'ctx>,
element: BasicValueEnum<'ctx>, element: BasicValueEnum<'ctx>,
element_layout: &'a Layout<'a>, element_layout: &Layout<'a>,
update_mode: UpdateMode, update_mode: UpdateMode,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout); let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout);
@ -880,26 +855,20 @@ pub fn list_map4<'a, 'ctx, 'env>(
/// List.concat : List elem, List elem -> List elem /// List.concat : List elem, List elem -> List elem
pub fn list_concat<'a, 'ctx, 'env>( pub fn list_concat<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
_parent: FunctionValue<'ctx>,
first_list: BasicValueEnum<'ctx>, first_list: BasicValueEnum<'ctx>,
second_list: BasicValueEnum<'ctx>, second_list: BasicValueEnum<'ctx>,
list_layout: &Layout<'a>, element_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
match list_layout { call_bitcode_fn_returns_list(
Layout::Builtin(Builtin::List(elem_layout)) => call_bitcode_fn_returns_list(
env, env,
&[ &[
pass_list_cc(env, first_list), pass_list_cc(env, first_list),
pass_list_cc(env, second_list), pass_list_cc(env, second_list),
env.alignment_intvalue(elem_layout), env.alignment_intvalue(element_layout),
layout_width(env, elem_layout), layout_width(env, element_layout),
], ],
bitcode::LIST_CONCAT, bitcode::LIST_CONCAT,
), )
_ => {
unreachable!("Invalid List layout for List.concat {:?}", list_layout);
}
}
} }
/// List.any : List elem, \(elem -> Bool) -> Bool /// List.any : List elem, \(elem -> Bool) -> Bool

View file

@ -432,12 +432,3 @@ pub fn str_equal<'a, 'ctx, 'env>(
bitcode::STR_EQUAL, bitcode::STR_EQUAL,
) )
} }
// TODO investigate: does this cause problems when the layout is known? this value is now not refcounted!
pub fn empty_str<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValueEnum<'ctx> {
let struct_type = super::convert::zig_str_type(env);
// The pointer should be null (aka zero) and the length should be zero,
// so the whole struct should be a const_zero
BasicValueEnum::StructValue(struct_type.const_zero())
}

View file

@ -804,7 +804,9 @@ fn build_tag_eq<'a, 'ctx, 'env>(
env.builder.position_at_end(block); env.builder.position_at_end(block);
env.builder env.builder
.set_current_debug_location(env.context, di_location); .set_current_debug_location(env.context, di_location);
let call = env.builder.build_call(function, &[tag1, tag2], "tag_eq"); let call = env
.builder
.build_call(function, &[tag1.into(), tag2.into()], "tag_eq");
call.set_call_convention(FAST_CALL_CONV); call.set_call_convention(FAST_CALL_CONV);

View file

@ -203,22 +203,6 @@ pub fn block_of_memory_slices<'ctx>(
block_of_memory_help(context, union_size) block_of_memory_help(context, union_size)
} }
pub fn union_data_is_struct<'a, 'ctx, 'env>(
env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
layouts: &[Layout<'_>],
) -> StructType<'ctx> {
let data_type = basic_type_from_record(env, layouts);
union_data_is_struct_type(env.context, data_type.into_struct_type())
}
pub fn union_data_is_struct_type<'ctx>(
context: &'ctx Context,
struct_type: StructType<'ctx>,
) -> StructType<'ctx> {
let tag_id_type = context.i64_type();
context.struct_type(&[struct_type.into(), tag_id_type.into()], false)
}
pub fn block_of_memory<'ctx>( pub fn block_of_memory<'ctx>(
context: &'ctx Context, context: &'ctx Context,
layout: &Layout<'_>, layout: &Layout<'_>,
@ -246,14 +230,18 @@ fn block_of_memory_help(context: &Context, union_size: u32) -> BasicTypeEnum<'_>
let num_i64 = union_size / 8; let num_i64 = union_size / 8;
let num_i8 = union_size % 8; let num_i8 = union_size % 8;
let i8_array_type = context.i8_type().array_type(num_i8).as_basic_type_enum();
let i64_array_type = context.i64_type().array_type(num_i64).as_basic_type_enum(); let i64_array_type = context.i64_type().array_type(num_i64).as_basic_type_enum();
if num_i8 == 0 { if num_i64 == 0 {
// the object fits perfectly in some number of i64's // The object fits perfectly in some number of i8s
context.struct_type(&[i8_array_type], false).into()
} else if num_i8 == 0 {
// The object fits perfectly in some number of i64s
// (i.e. the size is a multiple of 8 bytes) // (i.e. the size is a multiple of 8 bytes)
context.struct_type(&[i64_array_type], false).into() context.struct_type(&[i64_array_type], false).into()
} else { } else {
// there are some trailing bytes at the end // There are some trailing bytes at the end
let i8_array_type = context.i8_type().array_type(num_i8).as_basic_type_enum(); let i8_array_type = context.i8_type().array_type(num_i8).as_basic_type_enum();
context context

View file

@ -96,7 +96,7 @@ pub fn add_default_roc_externs(env: &Env<'_, '_, '_>) {
// Call libc realloc() // Call libc realloc()
let call = builder.build_call( let call = builder.build_call(
libc_realloc_val, libc_realloc_val,
&[ptr_arg, new_size_arg], &[ptr_arg.into(), new_size_arg.into()],
"call_libc_realloc", "call_libc_realloc",
); );

View file

@ -10,7 +10,7 @@ use bumpalo::collections::Vec;
use inkwell::basic_block::BasicBlock; use inkwell::basic_block::BasicBlock;
use inkwell::context::Context; use inkwell::context::Context;
use inkwell::module::Linkage; use inkwell::module::Linkage;
use inkwell::types::{AnyTypeEnum, BasicType, BasicTypeEnum}; use inkwell::types::{AnyTypeEnum, BasicMetadataTypeEnum, BasicType, BasicTypeEnum};
use inkwell::values::{ use inkwell::values::{
BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue, BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue,
}; };
@ -567,9 +567,11 @@ fn call_help<'a, 'ctx, 'env>(
let call = match call_mode { let call = match call_mode {
CallMode::Inc(inc_amount) => { CallMode::Inc(inc_amount) => {
env.builder env.builder
.build_call(function, &[value, inc_amount.into()], "increment") .build_call(function, &[value.into(), inc_amount.into()], "increment")
} }
CallMode::Dec => env.builder.build_call(function, &[value], "decrement"), CallMode::Dec => env
.builder
.build_call(function, &[value.into()], "decrement"),
}; };
call.set_call_convention(FAST_CALL_CONV); call.set_call_convention(FAST_CALL_CONV);
@ -1053,6 +1055,11 @@ pub fn build_header_help<'a, 'ctx, 'env>(
arguments: &[BasicTypeEnum<'ctx>], arguments: &[BasicTypeEnum<'ctx>],
) -> FunctionValue<'ctx> { ) -> FunctionValue<'ctx> {
use inkwell::types::AnyTypeEnum::*; use inkwell::types::AnyTypeEnum::*;
let it = arguments.iter().map(|x| BasicMetadataTypeEnum::from(*x));
let vec = Vec::from_iter_in(it, env.arena);
let arguments = vec.as_slice();
let fn_type = match return_type { let fn_type = match return_type {
ArrayType(t) => t.fn_type(arguments, false), ArrayType(t) => t.fn_type(arguments, false),
FloatType(t) => t.fn_type(arguments, false), FloatType(t) => t.fn_type(arguments, false),

Binary file not shown.

File diff suppressed because it is too large Load diff

View file

@ -8,9 +8,9 @@ use bumpalo::{self, collections::Vec, Bump};
use roc_builtins::bitcode::IntWidth; use roc_builtins::bitcode::IntWidth;
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevelWrapperType;
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::gen_refcount::RefcountProcGenerator; use roc_mono::code_gen_help::CodeGenHelp;
use roc_mono::ir::{Proc, ProcLayout}; use roc_mono::ir::{Proc, ProcLayout};
use roc_mono::layout::LayoutIds; use roc_mono::layout::LayoutIds;
use roc_reporting::internal_error; use roc_reporting::internal_error;
@ -26,7 +26,7 @@ const PTR_TYPE: ValueType = ValueType::I32;
pub const STACK_POINTER_GLOBAL_ID: u32 = 0; pub const STACK_POINTER_GLOBAL_ID: u32 = 0;
pub const FRAME_ALIGNMENT_BYTES: i32 = 16; pub const FRAME_ALIGNMENT_BYTES: i32 = 16;
pub const MEMORY_NAME: &str = "memory"; pub const MEMORY_NAME: &str = "memory";
pub const BUILTINS_IMPORT_MODULE_NAME: &str = "builtins"; pub const BUILTINS_IMPORT_MODULE_NAME: &str = "env";
pub const STACK_POINTER_NAME: &str = "__stack_pointer"; pub const STACK_POINTER_NAME: &str = "__stack_pointer";
pub struct Env<'a> { pub struct Env<'a> {
@ -62,7 +62,10 @@ pub fn build_module_help<'a>(
// and filter out procs we're going to inline // and filter out procs we're going to inline
let mut fn_index: u32 = 0; let mut fn_index: u32 = 0;
for ((sym, layout), proc) in procedures.into_iter() { for ((sym, layout), proc) in procedures.into_iter() {
if LowLevel::from_inlined_wrapper(sym).is_some() { if matches!(
LowLevelWrapperType::from_symbol(sym),
LowLevelWrapperType::CanBeReplacedBy(_)
) {
continue; continue;
} }
procs.push(proc); procs.push(proc);
@ -94,14 +97,14 @@ pub fn build_module_help<'a>(
proc_symbols, proc_symbols,
linker_symbols, linker_symbols,
exports, exports,
RefcountProcGenerator::new(env.arena, IntWidth::I32, env.module_id), CodeGenHelp::new(env.arena, IntWidth::I32, env.module_id),
); );
if false { if DEBUG_LOG_SETTINGS.user_procs_ir {
println!("## procs"); println!("## procs");
for proc in procs.iter() { for proc in procs.iter() {
println!("{}", proc.to_pretty(200)); println!("{}", proc.to_pretty(200));
println!("{:#?}", proc); // println!("{:#?}", proc);
} }
} }
@ -110,21 +113,21 @@ pub fn build_module_help<'a>(
backend.build_proc(proc); backend.build_proc(proc);
} }
// Generate IR for refcounting procs // Generate specialized helpers for refcounting & equality
let refcount_procs = backend.generate_refcount_procs(); let helper_procs = backend.generate_helpers();
backend.register_symbol_debug_names(); backend.register_symbol_debug_names();
if false { if DEBUG_LOG_SETTINGS.helper_procs_ir {
println!("## refcount_procs"); println!("## helper_procs");
for proc in refcount_procs.iter() { for proc in helper_procs.iter() {
println!("{}", proc.to_pretty(200)); println!("{}", proc.to_pretty(200));
println!("{:#?}", proc); // println!("{:#?}", proc);
} }
} }
// Generate Wasm for refcounting procs // Generate Wasm for refcounting procs
for proc in refcount_procs.iter() { for proc in helper_procs.iter() {
backend.build_proc(proc); backend.build_proc(proc);
} }
@ -176,22 +179,43 @@ pub fn copy_memory(code_builder: &mut CodeBuilder, config: CopyMemoryConfig) {
} }
/// Round up to alignment_bytes (which must be a power of 2) /// Round up to alignment_bytes (which must be a power of 2)
pub fn round_up_to_alignment(unaligned: i32, alignment_bytes: i32) -> i32 { #[macro_export]
if alignment_bytes <= 1 { macro_rules! round_up_to_alignment {
return unaligned; ($unaligned: expr, $alignment_bytes: expr) => {
} if $alignment_bytes <= 1 {
if alignment_bytes.count_ones() != 1 { $unaligned
internal_error!( } else if $alignment_bytes.count_ones() != 1 {
panic!(
"Cannot align to {} bytes. Not a power of 2.", "Cannot align to {} bytes. Not a power of 2.",
alignment_bytes $alignment_bytes
); );
} } else {
let mut aligned = unaligned; let mut aligned = $unaligned;
aligned += alignment_bytes - 1; // if lower bits are non-zero, push it over the next boundary aligned += $alignment_bytes - 1; // if lower bits are non-zero, push it over the next boundary
aligned &= -alignment_bytes; // mask with a flag that has upper bits 1, lower bits 0 aligned &= !$alignment_bytes + 1; // mask with a flag that has upper bits 1, lower bits 0
aligned aligned
} }
};
}
pub fn debug_panic<E: std::fmt::Debug>(error: E) { pub fn debug_panic<E: std::fmt::Debug>(error: E) {
internal_error!("{:?}", error); internal_error!("{:?}", error);
} }
pub struct WasmDebugLogSettings {
proc_start_end: bool,
user_procs_ir: bool,
helper_procs_ir: bool,
let_stmt_ir: bool,
instructions: bool,
pub keep_test_binary: bool,
}
pub const DEBUG_LOG_SETTINGS: WasmDebugLogSettings = WasmDebugLogSettings {
proc_start_end: false && cfg!(debug_assertions),
user_procs_ir: false && cfg!(debug_assertions),
helper_procs_ir: false && cfg!(debug_assertions),
let_stmt_ir: false && cfg!(debug_assertions),
instructions: false && cfg!(debug_assertions),
keep_test_binary: false && cfg!(debug_assertions),
};

View file

@ -5,19 +5,20 @@ use roc_reporting::internal_error;
use crate::layout::{StackMemoryFormat::*, WasmLayout}; use crate::layout::{StackMemoryFormat::*, WasmLayout};
use crate::storage::{Storage, StoredValue}; use crate::storage::{Storage, StoredValue};
use crate::wasm_module::{CodeBuilder, ValueType::*}; use crate::wasm_module::{Align, CodeBuilder, ValueType::*};
#[derive(Debug)]
pub enum LowlevelBuildResult { pub enum LowlevelBuildResult {
Done, Done,
BuiltinCall(&'static str), BuiltinCall(&'static str),
NotImplemented, NotImplemented,
} }
pub fn decode_low_level<'a>( pub fn dispatch_low_level<'a>(
code_builder: &mut CodeBuilder<'a>, code_builder: &mut CodeBuilder<'a>,
storage: &mut Storage<'a>, storage: &mut Storage<'a>,
lowlevel: LowLevel, lowlevel: LowLevel,
args: &'a [Symbol], args: &[Symbol],
ret_layout: &WasmLayout, ret_layout: &WasmLayout,
) -> LowlevelBuildResult { ) -> LowlevelBuildResult {
use LowlevelBuildResult::*; use LowlevelBuildResult::*;
@ -26,8 +27,9 @@ pub fn decode_low_level<'a>(
|| internal_error!("Invalid return layout for {:?}: {:?}", lowlevel, ret_layout); || internal_error!("Invalid return layout for {:?}: {:?}", lowlevel, ret_layout);
match lowlevel { match lowlevel {
// Str
StrConcat => return BuiltinCall(bitcode::STR_CONCAT), StrConcat => return BuiltinCall(bitcode::STR_CONCAT),
StrJoinWith => return NotImplemented, // needs Array StrJoinWith => return BuiltinCall(bitcode::STR_JOIN_WITH),
StrIsEmpty => { StrIsEmpty => {
code_builder.i64_const(i64::MIN); code_builder.i64_const(i64::MIN);
code_builder.i64_eq(); code_builder.i64_eq();
@ -35,24 +37,46 @@ pub fn decode_low_level<'a>(
StrStartsWith => return BuiltinCall(bitcode::STR_STARTS_WITH), StrStartsWith => return BuiltinCall(bitcode::STR_STARTS_WITH),
StrStartsWithCodePt => return BuiltinCall(bitcode::STR_STARTS_WITH_CODE_PT), StrStartsWithCodePt => return BuiltinCall(bitcode::STR_STARTS_WITH_CODE_PT),
StrEndsWith => return BuiltinCall(bitcode::STR_ENDS_WITH), StrEndsWith => return BuiltinCall(bitcode::STR_ENDS_WITH),
StrSplit => return NotImplemented, // needs Array StrSplit => {
StrCountGraphemes => return NotImplemented, // test needs Array // Roughly we need to:
StrFromInt => return NotImplemented, // choose builtin based on storage size // 1. count segments
StrFromUtf8 => return NotImplemented, // needs Array // 2. make a new pointer
StrTrimLeft => return BuiltinCall(bitcode::STR_TRIM_LEFT), // 3. split that pointer in place
StrTrimRight => return BuiltinCall(bitcode::STR_TRIM_RIGHT), // see: build_str.rs line 31
StrFromUtf8Range => return NotImplemented, // needs Array return NotImplemented;
StrToUtf8 => return NotImplemented, // needs Array }
StrRepeat => return BuiltinCall(bitcode::STR_REPEAT), StrCountGraphemes => return BuiltinCall(bitcode::STR_COUNT_GRAPEHEME_CLUSTERS),
StrToNum => return NotImplemented, // choose builtin based on storage size
StrFromInt => {
// This does not get exposed in user space. We switched to NumToStr instead.
// We can probably just leave this as NotImplemented. We may want remove this LowLevel.
// see: https://github.com/rtfeldman/roc/pull/2108
return NotImplemented;
}
StrFromFloat => { StrFromFloat => {
// linker errors for __ashlti3, __fixunsdfti, __multi3, __udivti3, __umodti3 // linker errors for __ashlti3, __fixunsdfti, __multi3, __udivti3, __umodti3
// https://gcc.gnu.org/onlinedocs/gccint/Integer-library-routines.html // https://gcc.gnu.org/onlinedocs/gccint/Integer-library-routines.html
// https://gcc.gnu.org/onlinedocs/gccint/Soft-float-library-routines.html // https://gcc.gnu.org/onlinedocs/gccint/Soft-float-library-routines.html
return NotImplemented; return NotImplemented;
} }
StrFromUtf8 => return BuiltinCall(bitcode::STR_FROM_UTF8),
StrTrimLeft => return BuiltinCall(bitcode::STR_TRIM_LEFT),
StrTrimRight => return BuiltinCall(bitcode::STR_TRIM_RIGHT),
StrFromUtf8Range => return BuiltinCall(bitcode::STR_FROM_UTF8_RANGE), // refcounting errors
StrToUtf8 => return BuiltinCall(bitcode::STR_TO_UTF8), // refcounting errors
StrRepeat => return BuiltinCall(bitcode::STR_REPEAT),
StrTrim => return BuiltinCall(bitcode::STR_TRIM), StrTrim => return BuiltinCall(bitcode::STR_TRIM),
ListLen | ListGetUnsafe | ListSet | ListSingle | ListRepeat | ListReverse | ListConcat // List
ListLen => {
// List structure has already been loaded as i64 (Zig calling convention)
// We want the second (more significant) 32 bits. Shift and convert to i32.
code_builder.i64_const(32);
code_builder.i64_shr_u();
code_builder.i32_wrap_i64();
}
ListGetUnsafe | ListSet | ListSingle | ListRepeat | ListReverse | ListConcat
| ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListMap | ListMap2 | ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListMap | ListMap2
| ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk | ListWalkUntil | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk | ListWalkUntil
| ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith | ListSublist | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith | ListSublist
@ -62,6 +86,7 @@ pub fn decode_low_level<'a>(
return NotImplemented; return NotImplemented;
} }
// Num
NumAdd => match ret_layout { NumAdd => match ret_layout {
WasmLayout::Primitive(value_type, _) => match value_type { WasmLayout::Primitive(value_type, _) => match value_type {
I32 => code_builder.i32_add(), I32 => code_builder.i32_add(),
@ -80,8 +105,6 @@ pub fn decode_low_level<'a>(
WasmLayout::Primitive(value_type, size) => match value_type { WasmLayout::Primitive(value_type, size) => match value_type {
I32 => { I32 => {
code_builder.i32_add(); code_builder.i32_add();
// TODO: is *deliberate* wrapping really in the spirit of things?
// The point of choosing NumAddWrap is to go fast by skipping checks, but we're making it slower.
wrap_i32(code_builder, *size); wrap_i32(code_builder, *size);
} }
I64 => code_builder.i64_add(), I64 => code_builder.i64_add(),
@ -346,27 +369,56 @@ pub fn decode_low_level<'a>(
}, },
WasmLayout::StackMemory { .. } => return NotImplemented, WasmLayout::StackMemory { .. } => return NotImplemented,
}, },
NumIsFinite => match ret_layout { NumIsFinite => {
WasmLayout::Primitive(value_type, _) => match value_type { use StoredValue::*;
I32 => code_builder.i32_const(1), match storage.get(&args[0]) {
I64 => code_builder.i32_const(1), VirtualMachineStack { value_type, .. } | Local { value_type, .. } => {
match value_type {
I32 | I64 => code_builder.i32_const(1), // always true for integers
F32 => { F32 => {
code_builder.i32_reinterpret_f32(); code_builder.i32_reinterpret_f32();
code_builder.i32_const(0x7f800000); code_builder.i32_const(0x7f80_0000);
code_builder.i32_and(); code_builder.i32_and();
code_builder.i32_const(0x7f800000); code_builder.i32_const(0x7f80_0000);
code_builder.i32_ne(); code_builder.i32_ne();
} }
F64 => { F64 => {
code_builder.i64_reinterpret_f64(); code_builder.i64_reinterpret_f64();
code_builder.i64_const(0x7ff0000000000000); code_builder.i64_const(0x7ff0_0000_0000_0000);
code_builder.i64_and(); code_builder.i64_and();
code_builder.i64_const(0x7ff0000000000000); code_builder.i64_const(0x7ff0_0000_0000_0000);
code_builder.i64_ne(); code_builder.i64_ne();
} }
}, }
WasmLayout::StackMemory { .. } => return NotImplemented, }
}, StackMemory {
format, location, ..
} => {
let (local_id, offset) = location.local_and_offset(storage.stack_frame_pointer);
match format {
Int128 => code_builder.i32_const(1),
Float128 => {
code_builder.get_local(local_id);
code_builder.i64_load(Align::Bytes4, offset + 8);
code_builder.i64_const(0x7fff_0000_0000_0000);
code_builder.i64_and();
code_builder.i64_const(0x7fff_0000_0000_0000);
code_builder.i64_ne();
}
Decimal => {
code_builder.get_local(local_id);
code_builder.i64_load(Align::Bytes4, offset + 8);
code_builder.i64_const(0x7100_0000_0000_0000);
code_builder.i64_and();
code_builder.i64_const(0x7100_0000_0000_0000);
code_builder.i64_ne();
}
DataStructure => return NotImplemented,
}
}
}
}
NumAtan => { NumAtan => {
let width = float_width_from_layout(ret_layout); let width = float_width_from_layout(ret_layout);
return BuiltinCall(&bitcode::NUM_ATAN[width]); return BuiltinCall(&bitcode::NUM_ATAN[width]);
@ -467,37 +519,15 @@ pub fn decode_low_level<'a>(
WasmLayout::StackMemory { .. } => return NotImplemented, WasmLayout::StackMemory { .. } => return NotImplemented,
} }
} }
Eq => match storage.get(&args[0]) {
StoredValue::VirtualMachineStack { value_type, .. }
| StoredValue::Local { value_type, .. } => match value_type {
I32 => code_builder.i32_eq(),
I64 => code_builder.i64_eq(),
F32 => code_builder.f32_eq(),
F64 => code_builder.f64_eq(),
},
StoredValue::StackMemory { .. } => return NotImplemented,
},
NotEq => match storage.get(&args[0]) {
StoredValue::VirtualMachineStack { value_type, .. }
| StoredValue::Local { value_type, .. } => match value_type {
I32 => code_builder.i32_ne(),
I64 => code_builder.i64_ne(),
F32 => code_builder.f32_ne(),
F64 => code_builder.f64_ne(),
},
StoredValue::StackMemory { .. } => return NotImplemented,
},
And => code_builder.i32_and(), And => code_builder.i32_and(),
Or => code_builder.i32_or(), Or => code_builder.i32_or(),
Not => code_builder.i32_eqz(), Not => code_builder.i32_eqz(),
Hash => return NotImplemented,
ExpectTrue => return NotImplemented, ExpectTrue => return NotImplemented,
RefCountGetPtr => {
code_builder.i32_const(4);
code_builder.i32_sub();
}
RefCountInc => return BuiltinCall(bitcode::UTILS_INCREF), RefCountInc => return BuiltinCall(bitcode::UTILS_INCREF),
RefCountDec => return BuiltinCall(bitcode::UTILS_DECREF), RefCountDec => return BuiltinCall(bitcode::UTILS_DECREF),
Eq | NotEq | Hash | PtrCast => {
internal_error!("{:?} should be handled in backend.rs", lowlevel)
}
} }
Done Done
} }

View file

@ -3,6 +3,7 @@ use bumpalo::Bump;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::layout::Layout;
use roc_reporting::internal_error; use roc_reporting::internal_error;
use crate::layout::{ use crate::layout::{
@ -82,8 +83,10 @@ impl StoredValue {
/// including the VM stack, local variables, and linear memory /// including the VM stack, local variables, and linear memory
#[derive(Debug)] #[derive(Debug)]
pub struct Storage<'a> { pub struct Storage<'a> {
pub return_var: Option<LocalId>,
pub arg_types: Vec<'a, ValueType>, pub arg_types: Vec<'a, ValueType>,
pub local_types: Vec<'a, ValueType>, pub local_types: Vec<'a, ValueType>,
pub symbol_layouts: MutMap<Symbol, Layout<'a>>,
pub symbol_storage_map: MutMap<Symbol, StoredValue>, pub symbol_storage_map: MutMap<Symbol, StoredValue>,
pub stack_frame_pointer: Option<LocalId>, pub stack_frame_pointer: Option<LocalId>,
pub stack_frame_size: i32, pub stack_frame_size: i32,
@ -92,8 +95,10 @@ pub struct Storage<'a> {
impl<'a> Storage<'a> { impl<'a> Storage<'a> {
pub fn new(arena: &'a Bump) -> Self { pub fn new(arena: &'a Bump) -> Self {
Storage { Storage {
return_var: None,
arg_types: Vec::with_capacity_in(8, arena), arg_types: Vec::with_capacity_in(8, arena),
local_types: Vec::with_capacity_in(32, arena), local_types: Vec::with_capacity_in(32, arena),
symbol_layouts: MutMap::default(),
symbol_storage_map: MutMap::default(), symbol_storage_map: MutMap::default(),
stack_frame_pointer: None, stack_frame_pointer: None,
stack_frame_size: 0, stack_frame_size: 0,
@ -101,18 +106,26 @@ impl<'a> Storage<'a> {
} }
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.return_var = None;
self.arg_types.clear(); self.arg_types.clear();
self.local_types.clear(); self.local_types.clear();
self.symbol_layouts.clear();
self.symbol_storage_map.clear(); self.symbol_storage_map.clear();
self.stack_frame_pointer = None; self.stack_frame_pointer = None;
self.stack_frame_size = 0; self.stack_frame_size = 0;
} }
/// Internal use only. If you think you want it externally, you really want `allocate` /// Internal use only. See `allocate` or `create_anonymous_local`
fn get_next_local_id(&self) -> LocalId { fn get_next_local_id(&self) -> LocalId {
LocalId((self.arg_types.len() + self.local_types.len()) as u32) LocalId((self.arg_types.len() + self.local_types.len()) as u32)
} }
pub fn create_anonymous_local(&mut self, value_type: ValueType) -> LocalId {
let id = self.get_next_local_id();
self.local_types.push(value_type);
id
}
/// Allocate storage for a Roc value /// Allocate storage for a Roc value
/// ///
/// Wasm primitives (i32, i64, f32, f64) are allocated "storage" on the VM stack. /// Wasm primitives (i32, i64, f32, f64) are allocated "storage" on the VM stack.
@ -125,26 +138,28 @@ impl<'a> Storage<'a> {
/// They are allocated a certain offset and size in the stack frame. /// They are allocated a certain offset and size in the stack frame.
pub fn allocate( pub fn allocate(
&mut self, &mut self,
wasm_layout: &WasmLayout, layout: Layout<'a>,
symbol: Symbol, symbol: Symbol,
kind: StoredValueKind, kind: StoredValueKind,
) -> StoredValue { ) -> StoredValue {
let next_local_id = self.get_next_local_id(); let next_local_id = self.get_next_local_id();
let wasm_layout = WasmLayout::new(&layout);
self.symbol_layouts.insert(symbol, layout);
let storage = match wasm_layout { let storage = match wasm_layout {
WasmLayout::Primitive(value_type, size) => match kind { WasmLayout::Primitive(value_type, size) => match kind {
StoredValueKind::Parameter => { StoredValueKind::Parameter => {
self.arg_types.push(*value_type); self.arg_types.push(value_type);
StoredValue::Local { StoredValue::Local {
local_id: next_local_id, local_id: next_local_id,
value_type: *value_type, value_type,
size: *size, size,
} }
} }
_ => StoredValue::VirtualMachineStack { _ => StoredValue::VirtualMachineStack {
vm_state: VmSymbolState::NotYetPushed, vm_state: VmSymbolState::NotYetPushed,
value_type: *value_type, value_type,
size: *size, size,
}, },
}, },
@ -155,7 +170,7 @@ impl<'a> Storage<'a> {
} => { } => {
let location = match kind { let location = match kind {
StoredValueKind::Parameter => { StoredValueKind::Parameter => {
if *size > 0 { if size > 0 {
self.arg_types.push(PTR_TYPE); self.arg_types.push(PTR_TYPE);
StackMemoryLocation::PointerArg(next_local_id) StackMemoryLocation::PointerArg(next_local_id)
} else { } else {
@ -166,15 +181,15 @@ impl<'a> Storage<'a> {
} }
StoredValueKind::Variable => { StoredValueKind::Variable => {
if self.stack_frame_pointer.is_none() && *size > 0 { if self.stack_frame_pointer.is_none() && size > 0 {
self.stack_frame_pointer = Some(next_local_id); self.stack_frame_pointer = Some(next_local_id);
self.local_types.push(PTR_TYPE); self.local_types.push(PTR_TYPE);
} }
let offset = let offset =
round_up_to_alignment(self.stack_frame_size, *alignment_bytes as i32); round_up_to_alignment!(self.stack_frame_size, alignment_bytes as i32);
self.stack_frame_size = offset + (*size as i32); self.stack_frame_size = offset + (size as i32);
StackMemoryLocation::FrameOffset(offset as u32) StackMemoryLocation::FrameOffset(offset as u32)
} }
@ -184,9 +199,9 @@ impl<'a> Storage<'a> {
StoredValue::StackMemory { StoredValue::StackMemory {
location, location,
size: *size, size,
alignment_bytes: *alignment_bytes, alignment_bytes,
format: *format, format,
} }
} }
}; };
@ -313,9 +328,11 @@ impl<'a> Storage<'a> {
code_builder.i64_load(align, offset); code_builder.i64_load(align, offset);
} else if *size <= 12 && BUILTINS_ZIG_VERSION == ZigVersion::Zig9 { } else if *size <= 12 && BUILTINS_ZIG_VERSION == ZigVersion::Zig9 {
code_builder.i64_load(align, offset); code_builder.i64_load(align, offset);
code_builder.get_local(local_id);
code_builder.i32_load(align, offset + 8); code_builder.i32_load(align, offset + 8);
} else { } else {
code_builder.i64_load(align, offset); code_builder.i64_load(align, offset);
code_builder.get_local(local_id);
code_builder.i64_load(align, offset + 8); code_builder.i64_load(align, offset + 8);
} }
} }
@ -502,6 +519,10 @@ impl<'a> Storage<'a> {
alignment_bytes, alignment_bytes,
.. ..
} => { } => {
if self.stack_frame_pointer.is_none() {
self.stack_frame_pointer = Some(self.get_next_local_id());
}
let (to_ptr, to_offset) = location.local_and_offset(self.stack_frame_pointer); let (to_ptr, to_offset) = location.local_and_offset(self.stack_frame_pointer);
copy_memory( copy_memory(
code_builder, code_builder,
@ -638,7 +659,8 @@ impl<'a> Storage<'a> {
} }
} }
/// Ensure a StoredValue has an associated local /// Ensure a StoredValue has an associated local (which could be the frame pointer!)
///
/// This is useful when a value needs to be accessed from a more deeply-nested block. /// This is useful when a value needs to be accessed from a more deeply-nested block.
/// In that case we want to make sure it's not just stored in the VM stack, because /// In that case we want to make sure it's not just stored in the VM stack, because
/// blocks can't access the VM stack from outer blocks, but they can access locals. /// blocks can't access the VM stack from outer blocks, but they can access locals.
@ -655,15 +677,12 @@ impl<'a> Storage<'a> {
size, size,
} = storage } = storage
{ {
let local_id = self.get_next_local_id(); let next_local_id = self.get_next_local_id();
if vm_state != VmSymbolState::NotYetPushed { code_builder.store_symbol_to_local(symbol, vm_state, next_local_id);
code_builder.load_symbol(symbol, vm_state, local_id);
code_builder.set_local(local_id);
}
self.local_types.push(value_type); self.local_types.push(value_type);
let new_storage = StoredValue::Local { let new_storage = StoredValue::Local {
local_id, local_id: next_local_id,
value_type, value_type,
size, size,
}; };

View file

@ -8,12 +8,13 @@ use roc_module::symbol::Symbol;
use super::linking::{IndexRelocType, OffsetRelocType, RelocationEntry}; use super::linking::{IndexRelocType, OffsetRelocType, RelocationEntry};
use super::opcodes::{OpCode, OpCode::*}; use super::opcodes::{OpCode, OpCode::*};
use super::serialize::{SerialBuffer, Serialize}; use super::serialize::{SerialBuffer, Serialize};
use crate::{round_up_to_alignment, FRAME_ALIGNMENT_BYTES, STACK_POINTER_GLOBAL_ID}; use crate::{
round_up_to_alignment, DEBUG_LOG_SETTINGS, FRAME_ALIGNMENT_BYTES, STACK_POINTER_GLOBAL_ID,
};
const ENABLE_DEBUG_LOG: bool = false;
macro_rules! log_instruction { macro_rules! log_instruction {
($($x: expr),+) => { ($($x: expr),+) => {
if ENABLE_DEBUG_LOG { println!($($x,)*); } if DEBUG_LOG_SETTINGS.instructions { println!($($x,)*); }
}; };
} }
@ -36,29 +37,7 @@ impl Serialize for ValueType {
} }
} }
#[derive(PartialEq, Eq, Debug)] const BLOCK_NO_RESULT: u8 = 0x40;
pub enum BlockType {
NoResult,
Value(ValueType),
}
impl BlockType {
pub fn as_byte(&self) -> u8 {
match self {
Self::NoResult => 0x40,
Self::Value(t) => *t as u8,
}
}
}
impl From<Option<ValueType>> for BlockType {
fn from(opt: Option<ValueType>) -> Self {
match opt {
Some(ty) => BlockType::Value(ty),
None => BlockType::NoResult,
}
}
}
/// A control block in our model of the VM /// A control block in our model of the VM
/// Child blocks cannot "see" values from their parent block /// Child blocks cannot "see" values from their parent block
@ -67,25 +46,17 @@ struct VmBlock<'a> {
opcode: OpCode, opcode: OpCode,
/// the stack of values for this block /// the stack of values for this block
value_stack: Vec<'a, Symbol>, value_stack: Vec<'a, Symbol>,
/// whether this block pushes a result value to its parent
has_result: bool,
} }
impl std::fmt::Debug for VmBlock<'_> { impl std::fmt::Debug for VmBlock<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!( f.write_fmt(format_args!("{:?}", self.opcode))
"{:?} {}",
self.opcode,
if self.has_result {
"Result"
} else {
"NoResult"
}
))
} }
} }
/// Wasm memory alignment. (Rust representation matches Wasm encoding) /// Wasm memory alignment for load/store instructions.
/// Rust representation matches Wasm encoding.
/// It's an error to specify alignment higher than the "natural" alignment of the instruction
#[repr(u8)] #[repr(u8)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum Align { pub enum Align {
@ -93,10 +64,6 @@ pub enum Align {
Bytes2 = 1, Bytes2 = 1,
Bytes4 = 2, Bytes4 = 2,
Bytes8 = 3, Bytes8 = 3,
Bytes16 = 4,
Bytes32 = 5,
Bytes64 = 6,
// ... we can add more if we need them ...
} }
impl From<u32> for Align { impl From<u32> for Align {
@ -105,11 +72,13 @@ impl From<u32> for Align {
1 => Align::Bytes1, 1 => Align::Bytes1,
2 => Align::Bytes2, 2 => Align::Bytes2,
4 => Align::Bytes4, 4 => Align::Bytes4,
8 => Align::Bytes8, _ => {
16 => Align::Bytes16, if x.count_ones() == 1 {
32 => Align::Bytes32, Align::Bytes8 // Max value supported by any Wasm instruction
64 => Align::Bytes64, } else {
_ => internal_error!("{:?}-byte alignment not supported", x), internal_error!("Cannot align to {} bytes", x);
}
}
} }
} }
} }
@ -193,7 +162,6 @@ impl<'a> CodeBuilder<'a> {
let mut vm_block_stack = Vec::with_capacity_in(8, arena); let mut vm_block_stack = Vec::with_capacity_in(8, arena);
let function_block = VmBlock { let function_block = VmBlock {
opcode: BLOCK, opcode: BLOCK,
has_result: true,
value_stack: Vec::with_capacity_in(8, arena), value_stack: Vec::with_capacity_in(8, arena),
}; };
vm_block_stack.push(function_block); vm_block_stack.push(function_block);
@ -312,30 +280,12 @@ impl<'a> CodeBuilder<'a> {
_ => { _ => {
// Symbol is not on top of the stack. // Symbol is not on top of the stack.
// We should have saved it to a local, so go back and do that now. // We should have saved it to a local, so go back and do that now.
self.store_pushed_symbol_to_local(
// It should still be on the stack in the block where it was assigned. Remove it. symbol,
let mut found = false; vm_state,
for block in self.vm_block_stack.iter_mut() { pushed_at,
if let Some(found_index) = next_local_id,
block.value_stack.iter().position(|&s| s == symbol)
{
block.value_stack.remove(found_index);
found = true;
}
}
// Go back to the code position where it was pushed, and save it to a local
if found {
self.add_insertion(pushed_at, SETLOCAL, next_local_id.0);
} else {
if ENABLE_DEBUG_LOG {
println!(
"{:?} has been popped implicitly. Leaving it on the stack.",
symbol
); );
}
self.add_insertion(pushed_at, TEELOCAL, next_local_id.0);
}
// Recover the value again at the current position // Recover the value again at the current position
self.get_local(next_local_id); self.get_local(next_local_id);
@ -363,6 +313,60 @@ impl<'a> CodeBuilder<'a> {
} }
} }
/// Go back and store a Symbol in a local variable, without loading it at the current position
pub fn store_symbol_to_local(
&mut self,
symbol: Symbol,
vm_state: VmSymbolState,
next_local_id: LocalId,
) {
use VmSymbolState::*;
match vm_state {
NotYetPushed => {
// Nothing to do
}
Pushed { pushed_at } => {
self.store_pushed_symbol_to_local(symbol, vm_state, pushed_at, next_local_id)
}
Popped { pushed_at } => {
self.add_insertion(pushed_at, TEELOCAL, next_local_id.0);
}
}
}
fn store_pushed_symbol_to_local(
&mut self,
symbol: Symbol,
vm_state: VmSymbolState,
pushed_at: usize,
local_id: LocalId,
) {
debug_assert!(matches!(vm_state, VmSymbolState::Pushed { .. }));
// Update our stack model at the position where we're going to set the SETLOCAL
let mut found = false;
for block in self.vm_block_stack.iter_mut() {
if let Some(found_index) = block.value_stack.iter().position(|&s| s == symbol) {
block.value_stack.remove(found_index);
found = true;
}
}
// Go back to the code position where it was pushed, and save it to a local
if found {
self.add_insertion(pushed_at, SETLOCAL, local_id.0);
} else {
if DEBUG_LOG_SETTINGS.instructions {
println!(
"{:?} has been popped implicitly. Leaving it on the stack.",
symbol
);
}
self.add_insertion(pushed_at, TEELOCAL, local_id.0);
}
}
/********************************************************** /**********************************************************
FUNCTION HEADER FUNCTION HEADER
@ -435,7 +439,7 @@ impl<'a> CodeBuilder<'a> {
/// Build the function header: local declarations, stack frame push/pop code, and function length /// Build the function header: local declarations, stack frame push/pop code, and function length
/// After this, all bytes have been generated (but not yet serialized) and we know the final size. /// After this, all bytes have been generated (but not yet serialized) and we know the final size.
pub fn build_fn_header( pub fn build_fn_header_and_footer(
&mut self, &mut self,
local_types: &[ValueType], local_types: &[ValueType],
frame_size: i32, frame_size: i32,
@ -445,9 +449,9 @@ impl<'a> CodeBuilder<'a> {
if frame_size != 0 { if frame_size != 0 {
if let Some(frame_ptr_id) = frame_pointer { if let Some(frame_ptr_id) = frame_pointer {
let aligned_size = round_up_to_alignment(frame_size, FRAME_ALIGNMENT_BYTES); let aligned_size = round_up_to_alignment!(frame_size, FRAME_ALIGNMENT_BYTES);
self.build_stack_frame_push(aligned_size, frame_ptr_id); self.build_stack_frame_push(aligned_size, frame_ptr_id);
self.build_stack_frame_pop(aligned_size, frame_ptr_id); self.build_stack_frame_pop(aligned_size, frame_ptr_id); // footer
} }
} }
@ -526,7 +530,16 @@ impl<'a> CodeBuilder<'a> {
/// Emits the opcode and simulates VM stack push/pop /// Emits the opcode and simulates VM stack push/pop
fn inst_base(&mut self, opcode: OpCode, pops: usize, push: bool) { fn inst_base(&mut self, opcode: OpCode, pops: usize, push: bool) {
let current_stack = self.current_stack_mut(); let current_stack = self.current_stack_mut();
let new_len = current_stack.len() - pops as usize; let stack_size = current_stack.len();
debug_assert!(
stack_size >= pops,
"Wasm value stack underflow. Tried to pop {} but only {} available",
pops,
stack_size
);
let new_len = stack_size - pops as usize;
current_stack.truncate(new_len); current_stack.truncate(new_len);
if push { if push {
current_stack.push(Symbol::WASM_TMP); current_stack.push(Symbol::WASM_TMP);
@ -545,23 +558,19 @@ impl<'a> CodeBuilder<'a> {
} }
/// Block instruction /// Block instruction
fn inst_block(&mut self, opcode: OpCode, pops: usize, block_type: BlockType) { fn inst_block(&mut self, opcode: OpCode, pops: usize) {
self.inst_base(opcode, pops, false); self.inst_base(opcode, pops, false);
self.code.push(block_type.as_byte());
// We don't support block result types. Too hard to track types through arbitrary control flow.
self.code.push(BLOCK_NO_RESULT);
// Start a new block with a fresh value stack // Start a new block with a fresh value stack
self.vm_block_stack.push(VmBlock { self.vm_block_stack.push(VmBlock {
opcode, opcode,
value_stack: Vec::with_capacity_in(8, self.arena), value_stack: Vec::with_capacity_in(8, self.arena),
has_result: block_type != BlockType::NoResult,
}); });
log_instruction!( log_instruction!("{:10}\t{:?}", format!("{:?}", opcode), &self.vm_block_stack);
"{:10} {:?}\t{:?}",
format!("{:?}", opcode),
block_type,
&self.vm_block_stack
);
} }
fn inst_imm32(&mut self, opcode: OpCode, pops: usize, push: bool, immediate: u32) { fn inst_imm32(&mut self, opcode: OpCode, pops: usize, push: bool, immediate: u32) {
@ -614,14 +623,14 @@ impl<'a> CodeBuilder<'a> {
instruction_no_args!(unreachable_, UNREACHABLE, 0, false); instruction_no_args!(unreachable_, UNREACHABLE, 0, false);
instruction_no_args!(nop, NOP, 0, false); instruction_no_args!(nop, NOP, 0, false);
pub fn block(&mut self, ty: BlockType) { pub fn block(&mut self) {
self.inst_block(BLOCK, 0, ty); self.inst_block(BLOCK, 0);
} }
pub fn loop_(&mut self, ty: BlockType) { pub fn loop_(&mut self) {
self.inst_block(LOOP, 0, ty); self.inst_block(LOOP, 0);
} }
pub fn if_(&mut self, ty: BlockType) { pub fn if_(&mut self) {
self.inst_block(IF, 1, ty); self.inst_block(IF, 1);
} }
pub fn else_(&mut self) { pub fn else_(&mut self) {
// Reuse the 'then' block but clear its value stack // Reuse the 'then' block but clear its value stack
@ -630,14 +639,21 @@ impl<'a> CodeBuilder<'a> {
} }
pub fn end(&mut self) { pub fn end(&mut self) {
self.inst_base(END, 0, false); // We need to drop any unused values from the VM stack in order to pass Wasm validation.
// This happens, for example, in test `gen_tags::if_guard_exhaustiveness`
let n_unused = self
.vm_block_stack
.last()
.map(|block| block.value_stack.len())
.unwrap_or(0);
let ended_block = self.vm_block_stack.pop().unwrap(); for _ in 0..n_unused {
if ended_block.has_result { self.drop_();
let result = ended_block.value_stack.last().unwrap();
self.current_stack_mut().push(*result)
} }
self.inst_base(END, 0, false);
self.vm_block_stack.pop();
log_instruction!("END \t\t{:?}", &self.vm_block_stack); log_instruction!("END \t\t{:?}", &self.vm_block_stack);
} }
pub fn br(&mut self, levels: u32) { pub fn br(&mut self, levels: u32) {
@ -901,4 +917,17 @@ impl<'a> CodeBuilder<'a> {
instruction_no_args!(i64_reinterpret_f64, I64REINTERPRETF64, 1, true); instruction_no_args!(i64_reinterpret_f64, I64REINTERPRETF64, 1, true);
instruction_no_args!(f32_reinterpret_i32, F32REINTERPRETI32, 1, true); instruction_no_args!(f32_reinterpret_i32, F32REINTERPRETI32, 1, true);
instruction_no_args!(f64_reinterpret_i64, F64REINTERPRETI64, 1, true); instruction_no_args!(f64_reinterpret_i64, F64REINTERPRETI64, 1, true);
/// Generate a debug assertion for an expected i32 value
pub fn _debug_assert_i32(&mut self, expected: i32) {
self.i32_const(expected);
self.i32_eq();
self.i32_eqz();
self.if_();
self.unreachable_(); // Tell Wasm runtime to throw an exception
self.end();
// It matches. Restore the original value to the VM stack and continue the program.
// We know it matched the expected value, so just use that!
self.i32_const(expected);
}
} }

View file

@ -4,6 +4,6 @@ pub mod opcodes;
pub mod sections; pub mod sections;
pub mod serialize; pub mod serialize;
pub use code_builder::{Align, BlockType, CodeBuilder, LocalId, ValueType, VmSymbolState}; pub use code_builder::{Align, CodeBuilder, LocalId, ValueType, VmSymbolState};
pub use linking::{LinkingSubSection, SymInfo}; pub use linking::{LinkingSubSection, SymInfo};
pub use sections::{ConstExpr, Export, ExportType, Global, GlobalType, Signature, WasmModule}; pub use sections::{ConstExpr, Export, ExportType, Global, GlobalType, Signature, WasmModule};

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