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:
pull_request:
paths-ignore:
- '**.md'
on: [pull_request]
name: Benchmarks

View File

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

View File

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

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.
- 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.
- 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?

228
Cargo.lock generated
View File

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

View File

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

View File

@ -30,6 +30,8 @@ For NQueens, input 10 in the terminal and press enter.
[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 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
Currently our `addAndStringify` funcion takes two arguments. We can instead make
Currently our `addAndStringify` function takes two arguments. We can instead make
it take one argument like so:
```coffee
@ -1053,7 +1053,7 @@ Let's take a closer look at the part of `Hello.roc` above `main`:
```coffee
app "hello"
packages [ pf: "examples/cli/platform" ]
packages { pf: "examples/cli/platform" }
imports [ pf.Stdout ]
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:
```coffee
packages [ pf: "examples/cli/platform" ]
packages { pf: "examples/cli/platform" }
imports [ pf.Stdout ]
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 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.
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
`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_parse::parser::SyntaxError;
use roc_region::all::{Located, Region};
use roc_region::all::{Loc, Region};
use snafu::{Backtrace, Snafu};
use crate::lang::core::ast::ASTNodeId;
@ -56,8 +56,8 @@ impl From<ModuleError> for ASTError {
}
}
impl From<(Region, Located<Ident>)> for ASTError {
fn from(ident_exists_err: (Region, Located<Ident>)) -> Self {
impl From<(Region, Loc<Ident>)> for ASTError {
fn from(ident_exists_err: (Region, Loc<Ident>)) -> Self {
Self::IdentExistsError {
msg: format!("{:?}", ident_exists_err),
}

View File

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

View File

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

View File

@ -30,6 +30,17 @@ use crate::{
mem_pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone},
};
/// A presence constraint is an additive constraint that defines the lower bound
/// of a type. For example, `Present(t1, IncludesTag(A, []))` means that the
/// type `t1` must contain at least the tag `A`. The additive nature of these
/// constraints makes them behaviorally different from unification-based constraints.
#[derive(Debug)]
pub enum PresenceConstraint<'a> {
IncludesTag(TagName, BumpVec<'a, Type2>),
IsOpen,
Pattern(Region, PatternCategory, PExpected<Type2>),
}
#[derive(Debug)]
pub enum Constraint<'a> {
Eq(Type2, Expected<Type2>, Category, Region),
@ -40,6 +51,7 @@ pub enum Constraint<'a> {
Let(&'a LetConstraint<'a>),
// SaveTheEnvironment,
True, // Used for things that always unify, e.g. blanks and runtime errors
Present(Type2, PresenceConstraint<'a>),
}
#[derive(Debug)]
@ -782,7 +794,15 @@ pub fn constrain_expr<'a>(
constraints: BumpVec::with_capacity_in(1, arena),
};
constrain_pattern(arena, env, pattern, region, pattern_expected, &mut state);
constrain_pattern(
arena,
env,
pattern,
region,
pattern_expected,
&mut state,
false,
);
state.vars.push(*expr_var);
let def_expr = env.pool.get(*expr_id);
@ -1302,6 +1322,7 @@ fn constrain_when_branch<'a>(
region,
pattern_expected.shallow_clone(),
&mut state,
true,
);
}
@ -1346,6 +1367,23 @@ fn constrain_when_branch<'a>(
}
}
fn make_pattern_constraint(
region: Region,
category: PatternCategory,
actual: Type2,
expected: PExpected<Type2>,
presence_con: bool,
) -> Constraint<'static> {
if presence_con {
Constraint::Present(
actual,
PresenceConstraint::Pattern(region, category, expected),
)
} else {
Constraint::Pattern(region, category, actual, expected)
}
}
/// This accepts PatternState (rather than returning it) so that the caller can
/// initialize the Vecs in PatternState using with_capacity
/// based on its knowledge of their lengths.
@ -1356,15 +1394,35 @@ pub fn constrain_pattern<'a>(
region: Region,
expected: PExpected<Type2>,
state: &mut PatternState2<'a>,
destruct_position: bool,
) {
use Pattern2::*;
match pattern {
Underscore if destruct_position => {
// This is an underscore in a position where we destruct a variable,
// like a when expression:
// when x is
// A -> ""
// _ -> ""
// so, we know that "x" (in this case, a tag union) must be open.
state.constraints.push(Constraint::Present(
expected.get_type(),
PresenceConstraint::IsOpen,
));
}
Underscore | UnsupportedPattern(_) | MalformedPattern(_, _) | Shadowed { .. } => {
// Neither the _ pattern nor erroneous ones add any constraints.
}
Identifier(symbol) => {
if destruct_position {
state.constraints.push(Constraint::Present(
expected.get_type_ref().shallow_clone(),
PresenceConstraint::IsOpen,
));
}
state.headers.insert(*symbol, expected.get_type());
}
@ -1446,7 +1504,7 @@ pub fn constrain_pattern<'a>(
let field_type = match destruct_type {
DestructType::Guard(guard_var, guard_id) => {
state.constraints.push(Constraint::Pattern(
state.constraints.push(make_pattern_constraint(
region,
PatternCategory::PatternGuard,
Type2::Variable(*guard_var),
@ -1456,6 +1514,7 @@ pub fn constrain_pattern<'a>(
// TODO: region should be from guard_id
region,
),
destruct_position,
));
state.vars.push(*guard_var);
@ -1463,12 +1522,20 @@ pub fn constrain_pattern<'a>(
let guard = env.pool.get(*guard_id);
// TODO: region should be from guard_id
constrain_pattern(arena, env, guard, region, expected, state);
constrain_pattern(
arena,
env,
guard,
region,
expected,
state,
destruct_position,
);
types::RecordField::Demanded(env.pool.add(pat_type))
}
DestructType::Optional(expr_var, expr_id) => {
state.constraints.push(Constraint::Pattern(
state.constraints.push(make_pattern_constraint(
region,
PatternCategory::PatternDefault,
Type2::Variable(*expr_var),
@ -1478,6 +1545,7 @@ pub fn constrain_pattern<'a>(
// TODO: region should be from expr_id
region,
),
destruct_position,
));
state.vars.push(*expr_var);
@ -1521,11 +1589,12 @@ pub fn constrain_pattern<'a>(
region,
);
let record_con = Constraint::Pattern(
let record_con = make_pattern_constraint(
region,
PatternCategory::Record,
Type2::Variable(*whole_var),
expected,
destruct_position,
);
state.constraints.push(whole_con);
@ -1540,7 +1609,16 @@ pub fn constrain_pattern<'a>(
let tag_name = TagName::Global(name.as_str(env.pool).into());
constrain_tag_pattern(
arena, env, region, expected, state, *whole_var, *ext_var, arguments, tag_name,
arena,
env,
region,
expected,
state,
*whole_var,
*ext_var,
arguments,
tag_name,
destruct_position,
);
}
PrivateTag {
@ -1552,7 +1630,16 @@ pub fn constrain_pattern<'a>(
let tag_name = TagName::Private(*name);
constrain_tag_pattern(
arena, env, region, expected, state, *whole_var, *ext_var, arguments, tag_name,
arena,
env,
region,
expected,
state,
*whole_var,
*ext_var,
arguments,
tag_name,
destruct_position,
);
}
}
@ -1569,6 +1656,7 @@ fn constrain_tag_pattern<'a>(
ext_var: Variable,
arguments: &PoolVec<(Variable, PatternId)>,
tag_name: TagName,
destruct_position: bool,
) {
let mut argument_types = Vec::with_capacity(arguments.len());
@ -1591,31 +1679,42 @@ fn constrain_tag_pattern<'a>(
);
// TODO region should come from pattern
constrain_pattern(arena, env, pattern, region, expected, state);
constrain_pattern(arena, env, pattern, region, expected, state, false);
}
let whole_con = Constraint::Eq(
Type2::Variable(whole_var),
Expected::NoExpectation(Type2::TagUnion(
PoolVec::new(
vec![(
tag_name.clone(),
PoolVec::new(argument_types.into_iter(), env.pool),
)]
.into_iter(),
env.pool,
let whole_con = if destruct_position {
Constraint::Present(
expected.get_type_ref().shallow_clone(),
PresenceConstraint::IncludesTag(
tag_name.clone(),
BumpVec::from_iter_in(argument_types.into_iter(), arena),
),
env.pool.add(Type2::Variable(ext_var)),
)),
Category::Storage(std::file!(), std::line!()),
region,
);
)
} else {
Constraint::Eq(
Type2::Variable(whole_var),
Expected::NoExpectation(Type2::TagUnion(
PoolVec::new(
vec![(
tag_name.clone(),
PoolVec::new(argument_types.into_iter(), env.pool),
)]
.into_iter(),
env.pool,
),
env.pool.add(Type2::Variable(ext_var)),
)),
Category::Storage(std::file!(), std::line!()),
region,
)
};
let tag_con = Constraint::Pattern(
let tag_con = make_pattern_constraint(
region,
PatternCategory::Ctor(tag_name),
Type2::Variable(whole_var),
expected,
destruct_position,
);
state.vars.push(whole_var);
@ -1660,6 +1759,7 @@ fn constrain_untyped_args<'a>(
Region::zero(),
pattern_expected,
&mut pattern_state,
false,
);
vars.push(*pattern_var);
@ -2612,4 +2712,168 @@ pub mod test_constrain {
"{ email : Str, name : Str }a -> { email : Str, name : Str }a",
)
}
#[test]
fn infer_union_input_position1() {
infer_eq(
indoc!(
r#"
\tag ->
when tag is
A -> X
B -> Y
"#
),
"[ A, B ] -> [ X, Y ]*",
)
}
#[test]
fn infer_union_input_position2() {
infer_eq(
indoc!(
r#"
\tag ->
when tag is
A -> X
B -> Y
_ -> Z
"#
),
"[ A, B ]* -> [ X, Y, Z ]*",
)
}
#[test]
fn infer_union_input_position3() {
infer_eq(
indoc!(
r#"
\tag ->
when tag is
A M -> X
A N -> Y
"#
),
"[ A [ M, N ] ] -> [ X, Y ]*",
)
}
#[test]
fn infer_union_input_position4() {
infer_eq(
indoc!(
r#"
\tag ->
when tag is
A M -> X
A N -> Y
A _ -> Z
"#
),
"[ A [ M, N ]* ] -> [ X, Y, Z ]*",
)
}
#[test]
#[ignore = "TODO: currently [ A [ M [ J ]*, N [ K ]* ] ] -> [ X ]*"]
fn infer_union_input_position5() {
infer_eq(
indoc!(
r#"
\tag ->
when tag is
A (M J) -> X
A (N K) -> X
"#
),
"[ A [ M [ J ], N [ K ] ] ] -> [ X ]*",
)
}
#[test]
fn infer_union_input_position6() {
infer_eq(
indoc!(
r#"
\tag ->
when tag is
A M -> X
B -> X
A N -> X
"#
),
"[ A [ M, N ], B ] -> [ X ]*",
)
}
#[test]
#[ignore = "TODO: currently [ A ]* -> [ A, X ]*"]
fn infer_union_input_position7() {
infer_eq(
indoc!(
r#"
\tag ->
when tag is
A -> X
t -> t
"#
),
// TODO: we could be a bit smarter by subtracting "A" as a possible
// tag in the union known by t, which would yield the principal type
// [ A, ]a -> [ X ]a
"[ A, X ]a -> [ A, X ]a",
)
}
#[test]
fn infer_union_input_position8() {
infer_eq(
indoc!(
r#"
\opt ->
when opt is
Some ({tag: A}) -> 1
Some ({tag: B}) -> 1
None -> 0
"#
),
"[ None, Some { tag : [ A, B ] }* ] -> Num *",
)
}
#[test]
#[ignore = "TODO: panicked at 'Invalid Cycle', ast/src/lang/core/def/def.rs:1208:21"]
fn infer_union_input_position9() {
infer_eq(
indoc!(
r#"
opt : [ Some Str, None ]
opt = Some ""
rcd = { opt }
when rcd is
{ opt: Some s } -> s
{ opt: None } -> "?"
"#
),
"Str",
)
}
#[test]
#[ignore = "TODO: currently <type mismatch> -> Num a"]
fn infer_union_input_position10() {
infer_eq(
indoc!(
r#"
\r ->
when r is
{ x: Blue, y ? 3 } -> y
{ x: Red, y ? 5 } -> y
"#
),
"{ x : [ Blue, Red ], y ? Num a }* -> Num a",
)
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,7 +14,7 @@ path = "src/main.rs"
test = false
bench = false
[features]
[features]
default = ["target-aarch64", "target-x86_64", "target-wasm32", "llvm", "editor"]
wasm32-cli-run = ["target-wasm32", "run-wasm32"]
@ -66,8 +66,8 @@ roc_editor = { path = "../editor", optional = true }
roc_linker = { path = "../linker" }
clap = { version = "= 3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] }
const_format = "0.2.22"
rustyline = { git = "https://github.com/rtfeldman/rustyline", tag = "prompt-fix" }
rustyline-derive = { git = "https://github.com/rtfeldman/rustyline", tag = "prompt-fix" }
rustyline = { git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1" }
rustyline-derive = { git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1" }
bumpalo = { version = "3.8.0", features = ["collections"] }
libloading = "0.7.1"
mimalloc = { version = "0.1.26", default-features = false }

View File

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

View File

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

View File

@ -2,15 +2,20 @@ use bumpalo::collections::Vec;
use bumpalo::Bump;
use libloading::Library;
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_module::called_via::CalledVia;
use roc_module::ident::TagName;
use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::ir::ProcLayout;
use roc_mono::layout::{union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant};
use roc_mono::layout::{
union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant, WrappedVariant,
};
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 std::cmp::{max_by_key, min_by_key};
struct Env<'a, 'env> {
arena: &'a Bump,
@ -38,10 +43,10 @@ pub unsafe fn jit_to_ast<'a>(
lib: Library,
main_fn_name: &str,
layout: ProcLayout<'a>,
content: &Content,
interns: &Interns,
content: &'a Content,
interns: &'a Interns,
home: ModuleId,
subs: &Subs,
subs: &'a Subs,
ptr_bytes: u32,
) -> Result<Expr<'a>, ToAstProblem> {
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, '_>,
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,
main_fn_name: &str,
layout: &Layout<'a>,
content: &Content,
content: &'a Content,
) -> 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| {
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) }
)
}
Layout::Union(UnionLayout::NonRecursive(union_layouts)) => {
let union_layout = UnionLayout::NonRecursive(union_layouts);
match content {
Content::Structure(FlatType::TagUnion(tags, _)) => {
debug_assert_eq!(union_layouts.len(), tags.len());
let tags_vec: std::vec::Vec<(TagName, std::vec::Vec<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);
use roc_mono::layout::WrappedVariant::*;
match union_variant {
UnionVariant::Wrapped(variant) => {
match variant {
NonRecursive {
sorted_tag_layouts: tags_and_layouts,
} => {
Ok(run_jit_function_dynamic_type!(
lib,
main_fn_name,
size as usize,
|ptr: *const u8| {
// Because this is a `Wrapped`, the first 8 bytes encode the tag ID
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"),
}
Layout::Union(UnionLayout::NonRecursive(_)) => {
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::Unreachable, content)
}
Content::Structure(FlatType::RecursiveTagUnion(_, _, _)) => {
todo!("print recursive tag unions in the REPL")
}
Content::Alias(_, _, actual) => {
let content = env.subs.get_content_without_compacting(*actual);
jit_to_ast_help(env, lib, main_fn_name, layout, content)
}
other => unreachable!("Weird content for Union layout: {:?}", other),
}
))
}
Layout::Union(UnionLayout::Recursive(_))
| Layout::Union(UnionLayout::NullableWrapped { .. })
| Layout::Union(UnionLayout::NullableUnwrapped { .. })
| Layout::Union(UnionLayout::NonNullableUnwrapped(_))
| Layout::RecursivePointer => {
todo!("add support for rendering recursive tag unions in the REPL")
| Layout::Union(UnionLayout::NullableUnwrapped { .. })
| 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(
env,
@ -353,7 +392,8 @@ fn jit_to_ast_help<'a>(
&lambda_set.runtime_representation(),
content,
),
}
};
result.map(|e| apply_newtypes(env, newtype_tags, e))
}
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>(
env: &Env<'a, '_>,
env: &Env<'a, 'a>,
ptr: *const u8,
layout: &Layout<'a>,
content: &Content,
when_recursive: WhenRecursive<'a>,
content: &'a Content,
) -> Expr<'a> {
macro_rules! helper {
($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) => {
// TODO: bits are not as expected here.
// 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 => {
todo!(
"TODO add support for rendering pointer to {:?} in the REPL",
other
);
}
}
};
apply_newtypes(env, newtype_tags, expr)
}
fn list_to_ast<'a>(
env: &Env<'a, '_>,
env: &Env<'a, 'a>,
ptr: *const u8,
len: usize,
elem_layout: &Layout<'a>,
@ -493,8 +718,14 @@ fn list_to_ast<'a>(
for index in 0..len {
let offset_bytes = index * elem_size;
let elem_ptr = unsafe { ptr.add(offset_bytes) };
let loc_expr = &*arena.alloc(Located {
value: ptr_to_ast(env, elem_ptr, elem_layout, elem_content),
let loc_expr = &*arena.alloc(Loc {
value: ptr_to_ast(
env,
elem_ptr,
elem_layout,
WhenRecursive::Unreachable,
elem_content,
),
region: Region::zero(),
});
@ -507,7 +738,7 @@ fn list_to_ast<'a>(
}
fn single_tag_union_to_ast<'a>(
env: &Env<'a, '_>,
env: &Env<'a, 'a>,
ptr: *const u8,
field_layouts: &'a [Layout<'a>],
tag_name: &TagName,
@ -516,15 +747,15 @@ fn single_tag_union_to_ast<'a>(
let arena = env.arena;
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 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() {
// happens for e.g. `Foo Bar` where unit structures are nested and the inner one is dropped
let it = payload_vars.iter().copied().zip([&Layout::Struct(&[])]);
sequence_of_expr(env, ptr as *const u8, it).into_bump_slice()
sequence_of_expr(env, ptr as *const u8, it, WhenRecursive::Unreachable).into_bump_slice()
} else {
unreachable!()
};
@ -533,10 +764,11 @@ fn single_tag_union_to_ast<'a>(
}
fn sequence_of_expr<'a, I>(
env: &Env<'a, '_>,
env: &Env<'a, 'a>,
ptr: *const u8,
sequence: I,
) -> Vec<'a, &'a Located<Expr<'a>>>
when_recursive: WhenRecursive<'a>,
) -> Vec<'a, &'a Loc<Expr<'a>>>
where
I: Iterator<Item = (Variable, &'a Layout<'a>)>,
I: ExactSizeIterator<Item = (Variable, &'a Layout<'a>)>,
@ -550,8 +782,8 @@ where
for (var, layout) in sequence {
let content = subs.get_content_without_compacting(var);
let expr = ptr_to_ast(env, field_ptr, layout, content);
let loc_expr = Located::at_zero(expr);
let expr = ptr_to_ast(env, field_ptr, layout, when_recursive, content);
let loc_expr = Loc::at_zero(expr);
output.push(&*arena.alloc(loc_expr));
@ -563,7 +795,7 @@ where
}
fn struct_to_ast<'a>(
env: &Env<'a, '_>,
env: &Env<'a, 'a>,
ptr: *const u8,
field_layouts: &'a [Layout<'a>],
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 loc_expr = &*arena.alloc(Located {
value: ptr_to_ast(env, ptr, &Layout::Struct(field_layouts), inner_content),
let loc_expr = &*arena.alloc(Loc {
value: ptr_to_ast(
env,
ptr,
&Layout::Struct(field_layouts),
WhenRecursive::Unreachable,
inner_content,
),
region: Region::zero(),
});
let field_name = Located {
let field_name = Loc {
value: &*arena.alloc_str(label.as_str()),
region: Region::zero(),
};
let loc_field = Located {
let loc_field = Loc {
value: AssignedField::RequiredValue(field_name, &[], loc_expr),
region: Region::zero(),
};
@ -610,16 +848,22 @@ fn struct_to_ast<'a>(
let var = field.into_inner();
let content = subs.get_content_without_compacting(var);
let loc_expr = &*arena.alloc(Located {
value: ptr_to_ast(env, field_ptr, field_layout, content),
let loc_expr = &*arena.alloc(Loc {
value: ptr_to_ast(
env,
field_ptr,
field_layout,
WhenRecursive::Unreachable,
content,
),
region: Region::zero(),
});
let field_name = Located {
let field_name = Loc {
value: &*arena.alloc_str(label.as_str()),
region: Region::zero(),
};
let loc_field = Located {
let loc_field = Loc {
value: AssignedField::RequiredValue(field_name, &[], loc_expr),
region: Region::zero(),
};
@ -683,7 +927,7 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a
.next()
.unwrap();
let loc_label = Located {
let loc_label = Loc {
value: &*arena.alloc_str(label.as_str()),
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.
let field_var = *field.as_inner();
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),
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))
};
let loc_assigned_field = Located {
let loc_assigned_field = Loc {
value: assigned_field,
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))
};
&*arena.alloc(Located {
&*arena.alloc(Loc {
value: tag_expr,
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 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),
region: Region::zero(),
});
@ -795,7 +1039,7 @@ fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a>
.next()
.unwrap();
let loc_label = Located {
let loc_label = Loc {
value: &*arena.alloc_str(label.as_str()),
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.
let field_var = *field.as_inner();
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),
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))
};
let loc_assigned_field = Located {
let loc_assigned_field = Loc {
value: assigned_field,
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))
};
&*arena.alloc(Located {
&*arena.alloc(Loc {
value: tag_expr,
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 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),
region: Region::zero(),
});
@ -872,7 +1116,7 @@ fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a>
UnionVariant::ByteUnion(tagnames) => {
let tag_name = &tagnames[value as usize];
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)
}
_ => 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()
.unwrap();
let loc_label = Located {
let loc_label = Loc {
value: &*arena.alloc_str(label.as_str()),
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.
let field_var = *field.as_inner();
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),
region: Region::zero(),
};
AssignedField::RequiredValue(loc_label, &[], arena.alloc(loc_expr))
};
let loc_assigned_field = Located {
let loc_assigned_field = Loc {
value: assigned_field,
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))
};
&*arena.alloc(Located {
&*arena.alloc(Loc {
value: tag_expr,
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 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),
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))
}
}
// 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: "",
use_valgrind: true,
},
issue2279 => Example {
filename: "Issue2279.roc",
executable_filename: "issue2279",
stdin: &[],
input_file: None,
expected_ending: "Hello, world!\n",
use_valgrind: true,
},
quicksort_app => Example {
filename: "QuicksortApp.roc",
executable_filename: "quicksortapp",

View File

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

View File

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

View File

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

View File

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

View File

@ -177,6 +177,51 @@ mod repl_eval {
expect_success("Foo Bar", "Foo Bar : [ Foo [ Bar ]* ]*");
}
#[test]
fn newtype_of_big_data() {
expect_success(
indoc!(
r#"
Either a b : [ Left a, Right b ]
lefty : Either Str Str
lefty = Left "loosey"
A lefty
"#
),
r#"A (Left "loosey") : [ A (Either Str Str) ]*"#,
)
}
#[test]
fn newtype_nested() {
expect_success(
indoc!(
r#"
Either a b : [ Left a, Right b ]
lefty : Either Str Str
lefty = Left "loosey"
A (B (C lefty))
"#
),
r#"A (B (C (Left "loosey"))) : [ A [ B [ C (Either Str Str) ]* ]* ]*"#,
)
}
#[test]
fn newtype_of_big_of_newtype() {
expect_success(
indoc!(
r#"
Big a : [ Big a [ Wrapper [ Newtype a ] ] ]
big : Big Str
big = Big "s" (Wrapper (Newtype "t"))
A big
"#
),
r#"A (Big "s" (Wrapper (Newtype "t"))) : [ A (Big Str) ]*"#,
)
}
#[test]
fn tag_with_arguments() {
expect_success("True 1", "True 1 : [ True (Num *) ]*");
@ -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]
// fn parse_problem() {
// // can't find something that won't parse currently

10
cli_utils/Cargo.lock generated
View File

@ -1310,13 +1310,13 @@ dependencies = [
name = "inkwell"
version = "0.1.0"
dependencies = [
"inkwell 0.1.0 (git+https://github.com/rtfeldman/inkwell?tag=llvm12-0.release8)",
"inkwell 0.1.0 (git+https://github.com/rtfeldman/inkwell?tag=llvm13-0.release1)",
]
[[package]]
name = "inkwell"
version = "0.1.0"
source = "git+https://github.com/rtfeldman/inkwell?tag=llvm12-0.release8#14b78d96d2dbc95694e181be66e4cd53df3fc02f"
source = "git+https://github.com/rtfeldman/inkwell?tag=llvm13-0.release1#e15d665227b2acad4ca949820d80048e09f3f4e5"
dependencies = [
"either",
"inkwell_internals",
@ -1324,13 +1324,12 @@ dependencies = [
"llvm-sys",
"once_cell",
"parking_lot",
"regex",
]
[[package]]
name = "inkwell_internals"
version = "0.3.0"
source = "git+https://github.com/rtfeldman/inkwell?tag=llvm12-0.release8#14b78d96d2dbc95694e181be66e4cd53df3fc02f"
version = "0.5.0"
source = "git+https://github.com/rtfeldman/inkwell?tag=llvm13-0.release1#e15d665227b2acad4ca949820d80048e09f3f4e5"
dependencies = [
"proc-macro2",
"quote",
@ -2666,6 +2665,7 @@ dependencies = [
"roc_collections",
"roc_module",
"roc_mono",
"roc_reporting",
"roc_std",
"target-lexicon",
]

View File

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

View File

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

View File

@ -1,5 +1,6 @@
const std = @import("std");
const str = @import("str.zig");
const num_ = @import("num.zig");
const utils = @import("utils.zig");
const math = std.math;
@ -1052,6 +1053,14 @@ test "div: 10 / 3" {
// 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 {
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
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(8, self.dict_bytes));
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(@alignOf(usize), self.dict_bytes));
return (ptr - 1)[0] == utils.REFCOUNT_ONE;
}

View File

@ -36,7 +36,7 @@ pub const RocList = extern struct {
}
// otherwise, check if the refcount is one
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(8, self.bytes));
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(@alignOf(usize), self.bytes));
return (ptr - 1)[0] == utils.REFCOUNT_ONE;
}
@ -1282,7 +1282,7 @@ pub fn listSet(
// `if inBounds then LowLevelListGet input index item else input`
// so we don't do a bounds check here. Hence, the list is also non-empty,
// because inserting into an empty list is always out of bounds
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(8, bytes));
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(@alignOf(usize), bytes));
if ((ptr - 1)[0] == utils.REFCOUNT_ONE) {
return listSetInPlaceHelp(bytes, index, element, element_width, dec);

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
interface Bool
exposes [ not, and, or, xor, isEq, isNotEq ]
exposes [ and, isEq, isNotEq, not, or, xor ]
imports []
## Returns `False` when given `True`, and vice versa.

View File

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

View File

@ -2,41 +2,41 @@ interface List
exposes
[
List,
isEmpty,
get,
set,
append,
len,
walkBackwards,
concat,
first,
single,
repeat,
reverse,
prepend,
join,
keepIf,
contains,
sum,
walk,
last,
keepOks,
drop,
dropAt,
dropLast,
first,
get,
isEmpty,
join,
keepErrs,
keepIf,
keepOks,
last,
len,
map,
map2,
map3,
map4,
mapWithIndex,
mapOrDrop,
mapJoin,
mapOrDrop,
mapWithIndex,
prepend,
product,
walkUntil,
range,
repeat,
reverse,
set,
single,
sortWith,
drop,
dropAt,
dropLast,
swap
sum,
swap,
walk,
walkBackwards,
walkUntil
]
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].
dropAt : List elem, Nat -> List elem
## Drops the last element in a List.
dropLast : List elem -> List elem
## Adds a new element to the end of the list.
##
## >>> List.append [ "a", "b" ] "c"
@ -685,8 +682,6 @@ startsWith : List elem, List elem -> Bool
endsWith : List elem, List elem -> Bool
all : List elem, (elem -> Bool) -> Bool
## Run the given predicate on each element of the list, returning `True` if
## any of the elements satisfy it.
any : List elem, (elem -> Bool) -> Bool
@ -698,3 +693,11 @@ all : List elem, (elem -> Bool) -> Bool
## Returns the first element of the list satisfying a predicate function.
## If no satisfying element is found, an `Err NotFound` is returned.
find : List elem, (elem -> Bool) -> Result elem [ NotFound ]*
## Apply a function that returns a Result on a list, only successful
## Results are kept and returned unwrapped.
keepOks : List before, (before -> Result after *) -> List after
## Apply a function that returns a Result on a list, only unsuccessful
## Results are kept and returned unwrapped.
keepErrs : List before, (before -> Result * after) -> List after

View File

@ -2,91 +2,91 @@ interface Num
exposes
[
Num,
Int,
Float,
Natural,
Nat,
Decimal,
Dec,
Integer,
FloatingPoint,
I128,
U128,
I64,
U64,
I32,
U32,
I16,
U16,
I8,
U8,
F64,
F32,
maxInt,
minInt,
maxFloat,
minFloat,
abs,
neg,
add,
sub,
mul,
isLt,
isLte,
isGt,
isGte,
toFloat,
sin,
cos,
tan,
isZero,
isEven,
isOdd,
isPositive,
isNegative,
rem,
div,
divFloor,
modInt,
modFloat,
sqrt,
log,
round,
compare,
pow,
ceiling,
powInt,
floor,
addWrap,
addChecked,
atan,
acos,
toStr,
Signed128,
Signed64,
Signed32,
Signed16,
Signed8,
Unsigned128,
Unsigned64,
Unsigned32,
Unsigned16,
Unsigned8,
Binary64,
Binary32,
Dec,
Decimal,
Float,
FloatingPoint,
F32,
F64,
I8,
I16,
I32,
I64,
I128,
Int,
Integer,
Nat,
Natural,
Signed8,
Signed16,
Signed32,
Signed64,
Signed128,
U8,
U16,
U32,
U64,
U128,
Unsigned8,
Unsigned16,
Unsigned32,
Unsigned64,
Unsigned128,
abs,
acos,
add,
addChecked,
addWrap,
atan,
bitwiseAnd,
bitwiseXor,
bitwiseOr,
bitwiseXor,
ceiling,
compare,
cos,
div,
divFloor,
floor,
intCast,
isEven,
isGt,
isGte,
isLt,
isLte,
isMultipleOf,
isNegative,
isOdd,
isPositive,
isZero,
log,
maxFloat,
maxI128,
maxInt,
minFloat,
minInt,
modInt,
modFloat,
mul,
mulChecked,
mulWrap,
neg,
pow,
powInt,
rem,
round,
shiftLeftBy,
shiftRightBy,
shiftRightZfBy,
subWrap,
sin,
sub,
subChecked,
mulWrap,
mulChecked,
intCast,
maxI128,
isMultipleOf
subWrap,
sqrt,
tan,
toFloat,
toStr
]
imports []
@ -621,13 +621,13 @@ toStr : Num * -> Str
## Examples:
##
## In some countries (e.g. USA and UK), a comma is used to separate thousands:
## >>> Num.format 1_000_000 { base: Decimal, wholeSep: { mark: ",", places: 3 } }
## >>> Num.format 1_000_000 { pf: Decimal, wholeSep: { mark: ",", places: 3 } }
##
## Sometimes when rendering bits, it's nice to group them into groups of 4:
## >>> Num.format 1_000_000 { base: Binary, wholeSep: { mark: " ", places: 4 } }
## >>> Num.format 1_000_000 { pf: Binary, wholeSep: { mark: " ", places: 4 } }
##
## It's also common to render hexadecimal in groups of 2:
## >>> Num.format 1_000_000 { base: Hexadecimal, wholeSep: { mark: " ", places: 2 } }
## >>> Num.format 1_000_000 { pf: Hexadecimal, wholeSep: { mark: " ", places: 2 } }
format :
Num *,
{
@ -824,15 +824,15 @@ maxF32 : F32
## If you go lower than this, your running Roc code will crash - so be careful not to!
minF32 : F32
## The highest supported #F64 value you can have, which is approximately 1.8 × 10^308.
## The highest supported #Dec value you can have, which is precisely 170_141_183_460_469_231_731.687303715884105727.
##
## If you go higher than this, your running Roc code will crash - so be careful not to!
maxDec : Dec
## 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!
maxDec : Dec
minDec : Dec
## Constants

View File

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

View File

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

View File

@ -2,19 +2,19 @@ interface Str
exposes
[
Str,
isEmpty,
append,
concat,
joinWith,
split,
countGraphemes,
startsWith,
endsWith,
fromUtf8,
Utf8Problem,
Utf8ByteProblem,
isEmpty,
joinWith,
split,
startsWith,
startsWithCodePt,
toUtf8,
startsWithCodePt
Utf8Problem,
Utf8ByteProblem
]
imports []
@ -433,36 +433,33 @@ toDec : Str -> Result Dec [ InvalidDec ]*
## 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
## example where the `Err` branch matches `Integer Signed64`, which causes this to
## parse an [I64] because [I64] is defined as `I64 : Num [ Integer [ Signed64 ] ]`.
## The exact number type to look for will be inferred from usage.
## In the example below, the usage of I64 in the type signature will require that type instead of (Num *).
##
## >>> when Str.toNum "12345" is
## >>> Ok i64 -> "The I64 was: \(i64)"
## >>> Err (ExpectedNum (Integer Signed64)) -> "Not a valid I64!"
## >>> strToI64 : Str -> Result I64 [ InvalidNumStr ]*
## >>> strToI64 = \inputStr ->
## >>> Str.toNum inputStr
##
## If the string is exactly `"NaN"`, `"∞"`, or `"-∞"`, they will be accepted
## 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
## 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
## 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
## example where the `Err` branch matches `Float Binary64`, which causes this to
## parse an [F64] because [F64] is defined as `F64 : Num [ Fraction [ Float64 ] ]`.
## The exact number type to look for will be inferred from usage.
## In the example below, the usage of Float64 in the type signature will require that type instead of (Num *).
##
## >>> when Str.parseNum input {} is
## >>> Ok { val: f64, rest } -> "The F64 was: \(f64)"
## >>> Err (ExpectedNum (Fraction Float64)) -> "Not a valid F64!"
## >>> parseFloat64 : Str -> Result { val: Float64, rest: Str } [ InvalidNumStr ]*
## >>> Str.parseNum input {}
##
## If the string begins with `"NaN"`, `"∞"`, and `"-∞"` (which do not represent
## [finite](Num.isFinite) numbers), they will be accepted only when parsing
## [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:
## * 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_FROM_INT: IntrinsicName = int_intrinsic!("roc_builtins.str.from_int");
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_TO_UTF8: &str = "roc_builtins.str.to_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_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_EQ: &str = "roc_builtins.dec.eq";
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_region::all::Region;
use roc_types::builtin_aliases::{
bool_type, dict_type, float_type, i128_type, int_type, list_type, nat_type, num_type,
ordering_type, result_type, set_type, str_type, str_utf8_byte_problem_type, u16_type, u32_type,
bool_type, dec_type, dict_type, f32_type, f64_type, float_type, i128_type, i16_type, i32_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,
};
use roc_types::solved_types::SolvedType;
@ -702,6 +703,117 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
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
// get : List elem, Nat -> Result elem [ OutOfBounds ]*
@ -1055,6 +1167,16 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(list_type(flex(TVAR1))),
);
// dropIf : List elem, (elem -> Bool) -> List elem
add_top_level_function_type!(
Symbol::LIST_DROP_IF,
vec![
list_type(flex(TVAR1)),
closure(vec![flex(TVAR1)], TVAR2, Box::new(bool_type())),
],
Box::new(list_type(flex(TVAR1))),
);
// swap : List elem, Nat, Nat -> List elem
add_top_level_function_type!(
Symbol::LIST_SWAP,

View File

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

View File

@ -1,13 +1,13 @@
use crate::def::Def;
use crate::expr::{ClosureData, Expr::*};
use crate::expr::{Expr, Field, Recursive, WhenBranch};
use crate::expr::{self, ClosureData, Expr::*};
use crate::expr::{Expr, Field, Recursive};
use crate::pattern::Pattern;
use roc_collections::all::SendMap;
use roc_module::called_via::CalledVia;
use roc_module::ident::{Lowercase, TagName};
use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region};
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
macro_rules! macro_magic {
@ -68,6 +68,20 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
STR_TRIM => str_trim,
STR_TRIM_LEFT => str_trim_left,
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_GET => list_get,
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_AT => list_drop_at,
LIST_DROP_FIRST => list_drop_first,
LIST_DROP_IF => list_drop_if,
LIST_DROP_LAST => list_drop_last,
LIST_SWAP => list_swap,
LIST_MAP_WITH_INDEX => list_map_with_index,
@ -347,8 +362,8 @@ fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
Def {
annotation: None,
expr_var: int_var,
loc_expr: Located::at_zero(body),
loc_pattern: Located::at_zero(Pattern::Identifier(symbol)),
loc_expr: Loc::at_zero(body),
loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)),
pattern_vars: SendMap::default(),
}
}
@ -362,8 +377,8 @@ fn num_min_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
Def {
annotation: None,
expr_var: int_var,
loc_expr: Located::at_zero(body),
loc_pattern: Located::at_zero(Pattern::Identifier(symbol)),
loc_expr: Loc::at_zero(body),
loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)),
pattern_vars: SendMap::default(),
}
}
@ -1231,8 +1246,8 @@ fn num_max_i128(symbol: Symbol, var_store: &mut VarStore) -> Def {
Def {
annotation: Some(annotation),
expr_var: int_var,
loc_expr: Located::at_zero(body),
loc_pattern: Located::at_zero(Pattern::Identifier(symbol)),
loc_expr: Loc::at_zero(body),
loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)),
pattern_vars: SendMap::default(),
}
}
@ -1323,6 +1338,98 @@ fn str_trim_right(symbol: Symbol, var_store: &mut VarStore) -> Def {
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
fn str_repeat(symbol: Symbol, var_store: &mut VarStore) -> Def {
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 {
let list_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
fn list_drop_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
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)
}
/// List.keepErrs: List before, (before -> Result * after) -> List after
/// List.range: Int a, Int a -> List (Int a)
fn list_range(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::ListRange, var_store)
}
@ -3143,8 +3306,8 @@ fn dict_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
Def {
annotation: None,
expr_var: dict_var,
loc_expr: Located::at_zero(body),
loc_pattern: Located::at_zero(Pattern::Identifier(symbol)),
loc_expr: Loc::at_zero(body),
loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)),
pattern_vars: SendMap::default(),
}
}
@ -3225,8 +3388,8 @@ fn dict_get(symbol: Symbol, var_store: &mut VarStore) -> Def {
let def = Def {
annotation: None,
expr_var: temp_record_var,
loc_expr: Located::at_zero(def_body),
loc_pattern: Located::at_zero(Pattern::Identifier(temp_record)),
loc_expr: Loc::at_zero(def_body),
loc_pattern: Loc::at_zero(Pattern::Identifier(temp_record)),
pattern_vars: Default::default(),
};
@ -3318,8 +3481,8 @@ fn set_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
Def {
annotation: None,
expr_var: set_var,
loc_expr: Located::at_zero(body),
loc_pattern: Located::at_zero(Pattern::Identifier(symbol)),
loc_expr: Loc::at_zero(body),
loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)),
pattern_vars: SendMap::default(),
}
}
@ -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)],
value: no_region(ok),
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)],
value: no_region(err),
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)],
value: no_region(ok),
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)],
value: no_region(err),
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)))],
};
let branch = WhenBranch {
let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(Var(Symbol::ARG_3)),
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))],
};
let branch = WhenBranch {
let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(Var(Symbol::ARG_2)),
guard: None,
@ -4241,7 +4404,7 @@ fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
arguments: vec![],
};
let branch = WhenBranch {
let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(false_expr),
guard: None,
@ -4268,7 +4431,7 @@ fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
arguments: vec![],
};
let branch = WhenBranch {
let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(true_expr),
guard: None,
@ -4318,7 +4481,7 @@ fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def {
arguments: vec![],
};
let branch = WhenBranch {
let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(true_expr),
guard: None,
@ -4345,7 +4508,7 @@ fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def {
arguments: vec![],
};
let branch = WhenBranch {
let branch = expr::WhenBranch {
patterns: vec![no_region(pattern)],
value: no_region(false_expr),
guard: None,
@ -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)],
value: no_region(ok),
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)],
value: no_region(err),
guard: None,
@ -4464,8 +4627,8 @@ fn result_after(symbol: Symbol, var_store: &mut VarStore) -> Def {
}
#[inline(always)]
fn no_region<T>(value: T) -> Located<T> {
Located {
fn no_region<T>(value: T) -> Loc<T> {
Loc {
region: Region::zero(),
value,
}
@ -4480,7 +4643,7 @@ fn tag(name: &'static str, args: Vec<Expr>, var_store: &mut VarStore) -> Expr {
arguments: args
.into_iter()
.map(|expr| (var_store.fresh(), no_region(expr)))
.collect::<Vec<(Variable, Located<Expr>)>>(),
.collect::<Vec<(Variable, Loc<Expr>)>>(),
}
}
@ -4507,11 +4670,11 @@ fn defn(
let expr = defn_help(fn_name, args, var_store, body, ret_var);
Def {
loc_pattern: Located {
loc_pattern: Loc {
region: Region::zero(),
value: Pattern::Identifier(fn_name),
},
loc_expr: Located {
loc_expr: Loc {
region: Region::zero(),
value: expr,
},

View File

@ -1,10 +1,21 @@
use crate::expected::{Expected, PExpected};
use roc_collections::all::{MutSet, SendMap};
use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region};
use roc_module::{ident::TagName, symbol::Symbol};
use roc_region::all::{Loc, Region};
use roc_types::types::{Category, PatternCategory, Type};
use roc_types::{subs::Variable, types::VariableDetail};
/// A presence constraint is an additive constraint that defines the lower bound
/// of a type. For example, `Present(t1, IncludesTag(A, []))` means that the
/// type `t1` must contain at least the tag `A`. The additive nature of these
/// constraints makes them behaviorally different from unification-based constraints.
#[derive(Debug, Clone, PartialEq)]
pub enum PresenceConstraint {
IncludesTag(TagName, Vec<Type>),
IsOpen,
Pattern(Region, PatternCategory, PExpected<Type>),
}
#[derive(Debug, Clone, PartialEq)]
pub enum Constraint {
Eq(Type, Expected<Type>, Category, Region),
@ -15,13 +26,14 @@ pub enum Constraint {
SaveTheEnvironment,
Let(Box<LetConstraint>),
And(Vec<Constraint>),
Present(Type, PresenceConstraint),
}
#[derive(Debug, Clone, PartialEq)]
pub struct LetConstraint {
pub rigid_vars: Vec<Variable>,
pub flex_vars: Vec<Variable>,
pub def_types: SendMap<Symbol, Located<Type>>,
pub def_types: SendMap<Symbol, Loc<Type>>,
pub defs_constraint: Constraint,
pub ret_constraint: Constraint,
}
@ -74,6 +86,7 @@ impl Constraint {
|| boxed.defs_constraint.contains_save_the_environment()
}
Constraint::And(cs) => cs.iter().any(|c| c.contains_save_the_environment()),
Constraint::Present(_, _) => false,
}
}
}
@ -143,5 +156,20 @@ fn validate_help(constraint: &Constraint, declared: &Declared, accum: &mut Varia
validate_help(c, declared, accum);
}
}
Constraint::Present(typ, constr) => {
subtract(declared, &typ.variables_detail(), accum);
match constr {
PresenceConstraint::IncludesTag(_, tys) => {
for ty in tys {
subtract(declared, &ty.variables_detail(), accum);
}
}
PresenceConstraint::IsOpen => {}
PresenceConstraint::Pattern(_, _, expected) => {
subtract(declared, &typ.variables_detail(), accum);
subtract(declared, &expected.get_type_ref().variables_detail(), accum);
}
}
}
}
}

View File

@ -14,19 +14,20 @@ use roc_collections::all::{default_hasher, ImMap, ImSet, MutMap, MutSet, SendMap
use roc_module::ident::Lowercase;
use roc_module::symbol::Symbol;
use roc_parse::ast;
use roc_parse::ast::AliasHeader;
use roc_parse::pattern::PatternType;
use roc_problem::can::{CycleEntry, Problem, RuntimeError};
use roc_region::all::{Located, Region};
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::{Alias, Type};
use std::collections::HashMap;
use std::fmt::Debug;
use ven_graph::{strongly_connected_components, topological_sort_into_groups};
use ven_graph::{strongly_connected_components, topological_sort, topological_sort_into_groups};
#[derive(Clone, Debug, PartialEq)]
pub struct Def {
pub loc_pattern: Located<Pattern>,
pub loc_expr: Located<Expr>,
pub loc_pattern: Loc<Pattern>,
pub loc_expr: Loc<Expr>,
pub expr_var: Variable,
pub pattern_vars: SendMap<Symbol, Variable>,
pub annotation: Option<Annotation>,
@ -53,29 +54,29 @@ pub struct CanDefs {
enum PendingDef<'a> {
/// A standalone annotation with no body
AnnotationOnly(
&'a Located<ast::Pattern<'a>>,
Located<Pattern>,
&'a Located<ast::TypeAnnotation<'a>>,
&'a Loc<ast::Pattern<'a>>,
Loc<Pattern>,
&'a Loc<ast::TypeAnnotation<'a>>,
),
/// A body with no type annotation
Body(
&'a Located<ast::Pattern<'a>>,
Located<Pattern>,
&'a Located<ast::Expr<'a>>,
&'a Loc<ast::Pattern<'a>>,
Loc<Pattern>,
&'a Loc<ast::Expr<'a>>,
),
/// A body with a type annotation
TypedBody(
&'a Located<ast::Pattern<'a>>,
Located<Pattern>,
&'a Located<ast::TypeAnnotation<'a>>,
&'a Located<ast::Expr<'a>>,
&'a Loc<ast::Pattern<'a>>,
Loc<Pattern>,
&'a Loc<ast::TypeAnnotation<'a>>,
&'a Loc<ast::Expr<'a>>,
),
/// A type alias, e.g. `Ints : List Int`
Alias {
name: Located<Symbol>,
vars: Vec<Located<Lowercase>>,
ann: &'a Located<ast::TypeAnnotation<'a>>,
name: Loc<Symbol>,
vars: Vec<Loc<Lowercase>>,
ann: &'a Loc<ast::TypeAnnotation<'a>>,
},
/// An invalid alias, that is ignored in the rest of the pipeline
@ -106,13 +107,76 @@ impl Declaration {
}
}
/// Returns a topologically sorted sequence of alias names
fn sort_aliases_before_introduction(mut alias_symbols: MutMap<Symbol, Vec<Symbol>>) -> Vec<Symbol> {
let defined_symbols: Vec<Symbol> = alias_symbols.keys().copied().collect();
// find the strongly connected components and their relations
let sccs = {
// only retain symbols from the current alias_defs
for v in alias_symbols.iter_mut() {
v.1.retain(|x| defined_symbols.iter().any(|s| s == x));
}
let all_successors_with_self = |symbol: &Symbol| alias_symbols[symbol].iter().copied();
strongly_connected_components(&defined_symbols, all_successors_with_self)
};
// then sort the strongly connected components
let groups: Vec<_> = (0..sccs.len()).collect();
let mut group_symbols: Vec<Vec<Symbol>> = vec![Vec::new(); groups.len()];
let mut symbol_to_group_index = MutMap::default();
let mut group_to_groups = vec![Vec::new(); groups.len()];
for (index, group) in sccs.iter().enumerate() {
for s in group {
symbol_to_group_index.insert(*s, index);
}
}
for (index, group) in sccs.iter().enumerate() {
for s in group {
let reachable = &alias_symbols[s];
for r in reachable {
let new_index = symbol_to_group_index[r];
if new_index != index {
group_to_groups[index].push(new_index);
}
}
}
}
for v in group_symbols.iter_mut() {
v.sort();
v.dedup();
}
let all_successors_with_self = |group: &usize| group_to_groups[*group].iter().copied();
// split into self-recursive and mutually recursive
match topological_sort(&groups, all_successors_with_self) {
Ok(result) => result
.iter()
.rev()
.map(|group_index| sccs[*group_index].iter())
.flatten()
.copied()
.collect(),
Err(_loop_detected) => unreachable!("the groups cannot recurse"),
}
}
#[inline(always)]
pub fn canonicalize_defs<'a>(
env: &mut Env<'a>,
mut output: Output,
var_store: &mut VarStore,
original_scope: &Scope,
loc_defs: &'a [&'a Located<ast::Def<'a>>],
loc_defs: &'a [&'a Loc<ast::Def<'a>>],
pattern_type: PatternType,
) -> (CanDefs, Scope, Output, MutMap<Symbol, Region>) {
// Canonicalizing defs while detecting shadowing involves a multi-step process:
@ -179,69 +243,83 @@ pub fn canonicalize_defs<'a>(
let mut aliases = SendMap::default();
let mut value_defs = Vec::new();
let mut alias_defs = MutMap::default();
let mut alias_symbols = MutMap::default();
for pending_def in pending.into_iter() {
match pending_def {
PendingDef::Alias { name, vars, ann } => {
let symbol = name.value;
let mut can_ann =
canonicalize_annotation(env, &mut scope, &ann.value, ann.region, var_store);
let symbols =
crate::annotation::find_alias_symbols(env.home, &mut env.ident_ids, &ann.value);
// Record all the annotation's references in output.references.lookups
for symbol in can_ann.references {
output.references.lookups.insert(symbol);
output.references.referenced_aliases.insert(symbol);
}
let mut can_vars: Vec<Located<(Lowercase, Variable)>> =
Vec::with_capacity(vars.len());
let mut is_phantom = false;
for loc_lowercase in vars {
if let Some(var) = can_ann
.introduced_variables
.var_by_name(&loc_lowercase.value)
{
// This is a valid lowercase rigid var for the alias.
can_vars.push(Located {
value: (loc_lowercase.value.clone(), *var),
region: loc_lowercase.region,
});
} else {
is_phantom = true;
env.problems.push(Problem::PhantomTypeArgument {
alias: symbol,
variable_region: loc_lowercase.region,
variable_name: loc_lowercase.value.clone(),
});
}
}
if is_phantom {
// Bail out
continue;
}
if can_ann.typ.contains_symbol(symbol) {
make_tag_union_recursive(
env,
symbol,
name.region,
vec![],
&mut can_ann.typ,
var_store,
&mut false,
);
}
scope.add_alias(symbol, ann.region, can_vars.clone(), can_ann.typ.clone());
let alias = scope.lookup_alias(symbol).expect("alias is added to scope");
aliases.insert(symbol, alias.clone());
alias_symbols.insert(name.value, symbols);
alias_defs.insert(name.value, (name, vars, ann));
}
other => value_defs.push(other),
}
}
let sorted = sort_aliases_before_introduction(alias_symbols);
for alias_name in sorted {
let (name, vars, ann) = alias_defs.remove(&alias_name).unwrap();
let symbol = name.value;
let mut can_ann =
canonicalize_annotation(env, &mut scope, &ann.value, ann.region, var_store);
// Record all the annotation's references in output.references.lookups
for symbol in can_ann.references {
output.references.lookups.insert(symbol);
output.references.referenced_aliases.insert(symbol);
}
let mut can_vars: Vec<Loc<(Lowercase, Variable)>> = Vec::with_capacity(vars.len());
let mut is_phantom = false;
for loc_lowercase in vars {
if let Some(var) = can_ann
.introduced_variables
.var_by_name(&loc_lowercase.value)
{
// This is a valid lowercase rigid var for the alias.
can_vars.push(Loc {
value: (loc_lowercase.value.clone(), *var),
region: loc_lowercase.region,
});
} else {
is_phantom = true;
env.problems.push(Problem::PhantomTypeArgument {
alias: symbol,
variable_region: loc_lowercase.region,
variable_name: loc_lowercase.value.clone(),
});
}
}
if is_phantom {
// Bail out
continue;
}
if can_ann.typ.contains_symbol(symbol) {
make_tag_union_recursive(
env,
symbol,
name.region,
vec![],
&mut can_ann.typ,
var_store,
&mut false,
);
}
scope.add_alias(symbol, ann.region, can_vars.clone(), can_ann.typ.clone());
let alias = scope.lookup_alias(symbol).expect("alias is added to scope");
aliases.insert(symbol, alias.clone());
}
correct_mutual_recursive_type_alias(env, &mut aliases, var_store);
// Now that we have the scope completely assembled, and shadowing resolved,
@ -813,7 +891,7 @@ fn canonicalize_pending_def<'a>(
let value = Expr::RuntimeError(problem);
let is_closure = arity > 0;
let loc_can_expr = if !is_closure {
Located {
Loc {
value,
region: loc_ann.region,
}
@ -825,7 +903,7 @@ fn canonicalize_pending_def<'a>(
let mut underscores = Vec::with_capacity(arity);
for _ in 0..arity {
let underscore: Located<Pattern> = Located {
let underscore: Loc<Pattern> = Loc {
value: Pattern::Underscore,
region: Region::zero(),
};
@ -833,12 +911,12 @@ fn canonicalize_pending_def<'a>(
underscores.push((var_store.fresh(), underscore));
}
let body_expr = Located {
let body_expr = Loc {
value,
region: loc_ann.region,
};
Located {
Loc {
value: Closure(ClosureData {
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
@ -867,7 +945,7 @@ fn canonicalize_pending_def<'a>(
expr_var,
// TODO try to remove this .clone()!
loc_pattern: loc_can_pattern.clone(),
loc_expr: Located {
loc_expr: Loc {
region: loc_can_expr.region,
// TODO try to remove this .clone()!
value: loc_can_expr.value.clone(),
@ -897,7 +975,7 @@ fn canonicalize_pending_def<'a>(
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 {
if let Some(var) = can_ann
@ -905,7 +983,7 @@ fn canonicalize_pending_def<'a>(
.var_by_name(&loc_lowercase.value)
{
// This is a valid lowercase rigid var for the alias.
can_vars.push(Located {
can_vars.push(Loc {
value: (loc_lowercase.value.clone(), *var),
region: loc_lowercase.region,
});
@ -1088,7 +1166,7 @@ fn canonicalize_pending_def<'a>(
expr_var,
// TODO try to remove this .clone()!
loc_pattern: loc_can_pattern.clone(),
loc_expr: Located {
loc_expr: Loc {
region: loc_can_expr.region,
// TODO try to remove this .clone()!
value: loc_can_expr.value.clone(),
@ -1226,7 +1304,7 @@ fn canonicalize_pending_def<'a>(
expr_var,
// TODO try to remove this .clone()!
loc_pattern: loc_can_pattern.clone(),
loc_expr: Located {
loc_expr: Loc {
// TODO try to remove this .clone()!
region: loc_can_expr.region,
value: loc_can_expr.value.clone(),
@ -1249,8 +1327,8 @@ pub fn can_defs_with_return<'a>(
env: &mut Env<'a>,
var_store: &mut VarStore,
scope: Scope,
loc_defs: &'a [&'a Located<ast::Def<'a>>],
loc_ret: &'a Located<ast::Expr<'a>>,
loc_defs: &'a [&'a Loc<ast::Def<'a>>],
loc_ret: &'a Loc<ast::Expr<'a>>,
) -> (Expr, Output) {
let (unsorted, mut scope, defs_output, symbols_introduced) = canonicalize_defs(
env,
@ -1283,10 +1361,10 @@ pub fn can_defs_with_return<'a>(
match can_defs {
Ok(decls) => {
let mut loc_expr: Located<Expr> = ret_expr;
let mut loc_expr: Loc<Expr> = ret_expr;
for declaration in decls.into_iter().rev() {
loc_expr = Located {
loc_expr = Loc {
region: Region::zero(),
value: decl_to_let(var_store, declaration, loc_expr),
};
@ -1298,7 +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 {
Declaration::Declare(def) => {
Expr::LetNonRec(Box::new(def), Box::new(loc_ret), var_store.fresh())
@ -1428,7 +1506,9 @@ fn to_pending_def<'a>(
}
Alias {
name, vars, ann, ..
header: AliasHeader { name, vars },
ann,
..
} => {
let region = Region::span_across(&name.region, &ann.region);
@ -1439,7 +1519,7 @@ fn to_pending_def<'a>(
region,
) {
Ok(symbol) => {
let mut can_rigids: Vec<Located<Lowercase>> = Vec::with_capacity(vars.len());
let mut can_rigids: Vec<Loc<Lowercase>> = Vec::with_capacity(vars.len());
for loc_var in vars.iter() {
match loc_var.value {
@ -1447,7 +1527,7 @@ fn to_pending_def<'a>(
if name.chars().next().unwrap().is_lowercase() =>
{
let lowercase = Lowercase::from(name);
can_rigids.push(Located {
can_rigids.push(Loc {
value: lowercase,
region: loc_var.region,
});
@ -1467,7 +1547,7 @@ fn to_pending_def<'a>(
Some((
Output::default(),
PendingDef::Alias {
name: Located {
name: Loc {
region: name.region,
value: symbol,
},
@ -1500,9 +1580,9 @@ fn to_pending_def<'a>(
fn pending_typed_body<'a>(
env: &mut Env<'a>,
loc_pattern: &'a Located<ast::Pattern<'a>>,
loc_ann: &'a Located<ast::TypeAnnotation<'a>>,
loc_expr: &'a Located<ast::Expr<'a>>,
loc_pattern: &'a Loc<ast::Pattern<'a>>,
loc_ann: &'a Loc<ast::TypeAnnotation<'a>>,
loc_expr: &'a Loc<ast::Expr<'a>>,
var_store: &mut VarStore,
scope: &mut Scope,
pattern_type: PatternType,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,7 +7,7 @@ use roc_module::symbol::Symbol;
use roc_parse::ast::{self, StrLiteral, StrSegment};
use roc_parse::pattern::PatternType;
use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError};
use roc_region::all::{Located, Region};
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
/// A pattern, including possible problems (e.g. shadowing) so that
/// codegen can generate a runtime error if this pattern is reached.
@ -18,12 +18,12 @@ pub enum Pattern {
whole_var: Variable,
ext_var: Variable,
tag_name: TagName,
arguments: Vec<(Variable, Located<Pattern>)>,
arguments: Vec<(Variable, Loc<Pattern>)>,
},
RecordDestructure {
whole_var: Variable,
ext_var: Variable,
destructs: Vec<Located<RecordDestruct>>,
destructs: Vec<Loc<RecordDestruct>>,
},
IntLiteral(Variable, Box<str>, i64),
NumLiteral(Variable, Box<str>, i64),
@ -32,7 +32,7 @@ pub enum Pattern {
Underscore,
// Runtime Exceptions
Shadowed(Region, Located<Ident>),
Shadowed(Region, Loc<Ident>),
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
UnsupportedPattern(Region),
// parse error patterns
@ -50,8 +50,8 @@ pub struct RecordDestruct {
#[derive(Clone, Debug, PartialEq)]
pub enum DestructType {
Required,
Optional(Variable, Located<Expr>),
Guard(Variable, Located<Pattern>),
Optional(Variable, Loc<Expr>),
Guard(Variable, Loc<Pattern>),
}
pub fn symbols_from_pattern(pattern: &Pattern) -> Vec<Symbol> {
@ -104,7 +104,7 @@ pub fn canonicalize_pattern<'a>(
pattern_type: PatternType,
pattern: &ast::Pattern<'a>,
region: Region,
) -> (Output, Located<Pattern>) {
) -> (Output, Loc<Pattern>) {
use roc_parse::ast::Pattern::*;
use PatternType::*;
@ -258,7 +258,7 @@ pub fn canonicalize_pattern<'a>(
Ok(symbol) => {
output.references.bound_symbols.insert(symbol);
destructs.push(Located {
destructs.push(Loc {
region: loc_pattern.region,
value: RecordDestruct {
var: var_store.fresh(),
@ -297,7 +297,7 @@ pub fn canonicalize_pattern<'a>(
output.union(new_output);
destructs.push(Located {
destructs.push(Loc {
region: loc_pattern.region,
value: RecordDestruct {
var: var_store.fresh(),
@ -329,7 +329,7 @@ pub fn canonicalize_pattern<'a>(
output.union(expr_output);
destructs.push(Located {
destructs.push(Loc {
region: loc_pattern.region,
value: RecordDestruct {
var: var_store.fresh(),
@ -391,7 +391,7 @@ pub fn canonicalize_pattern<'a>(
(
output,
Located {
Loc {
region,
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)>
where
I: Iterator<Item = &'a Located<Pattern>>,
I: Iterator<Item = &'a Loc<Pattern>>,
{
let mut answer = Vec::new();
@ -464,7 +464,7 @@ fn add_bindings_from_patterns(
}
}
RecordDestructure { destructs, .. } => {
for Located {
for Loc {
region,
value: RecordDestruct { symbol, .. },
} in destructs

View File

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

View File

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

View File

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

View File

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

View File

@ -17,7 +17,7 @@ mod test_can {
use roc_can::expr::Expr::{self, *};
use roc_can::expr::{ClosureData, Recursive};
use roc_problem::can::{CycleEntry, FloatErrorKind, IntErrorKind, Problem, RuntimeError};
use roc_region::all::Region;
use roc_region::all::{Position, Region};
use std::{f64, i64};
fn assert_can(input: &str, expected: Expr) {
@ -943,8 +943,14 @@ mod test_can {
let problem = Problem::RuntimeError(RuntimeError::CircularDef(vec![CycleEntry {
symbol: interns.symbol(home, "x".into()),
symbol_region: Region::new(0, 0, 0, 1),
expr_region: Region::new(0, 0, 4, 5),
symbol_region: Region::new(
Position { 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);
@ -974,18 +980,36 @@ mod test_can {
let problem = Problem::RuntimeError(RuntimeError::CircularDef(vec![
CycleEntry {
symbol: interns.symbol(home, "x".into()),
symbol_region: Region::new(0, 0, 0, 1),
expr_region: Region::new(0, 0, 4, 5),
symbol_region: Region::new(
Position { line: 0, column: 0 },
Position { line: 0, column: 1 },
),
expr_region: Region::new(
Position { line: 0, column: 4 },
Position { line: 0, column: 5 },
),
},
CycleEntry {
symbol: interns.symbol(home, "y".into()),
symbol_region: Region::new(1, 1, 0, 1),
expr_region: Region::new(1, 1, 4, 5),
symbol_region: Region::new(
Position { line: 1, column: 0 },
Position { line: 1, column: 1 },
),
expr_region: Region::new(
Position { line: 1, column: 4 },
Position { line: 1, column: 5 },
),
},
CycleEntry {
symbol: interns.symbol(home, "z".into()),
symbol_region: Region::new(2, 2, 0, 1),
expr_region: Region::new(2, 2, 4, 5),
symbol_region: Region::new(
Position { 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_module::ident::{Lowercase, TagName};
use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::{Located, Region};
use roc_region::all::{Loc, Region};
use roc_types::subs::Variable;
use roc_types::types::AnnotationSource::{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
#[derive(Default, Debug)]
pub struct Info {
pub vars: Vec<Variable>,
pub constraints: Vec<Constraint>,
pub def_types: SendMap<Symbol, Located<Type>>,
pub def_types: SendMap<Symbol, Loc<Type>>,
}
impl Info {
@ -57,7 +56,7 @@ pub struct Env {
fn constrain_untyped_args(
env: &Env,
arguments: &[(Variable, Located<Pattern>)],
arguments: &[(Variable, Loc<Pattern>)],
closure_type: Type,
return_type: Type,
) -> (Vec<Variable>, PatternState, Type) {
@ -78,6 +77,7 @@ fn constrain_untyped_args(
loc_pattern.region,
pattern_expected,
&mut pattern_state,
true,
);
vars.push(*pattern_var);
@ -604,7 +604,7 @@ pub fn constrain_expr(
FromAnnotation(
name.clone(),
*arity,
TypedWhenBranch {
AnnotationSource::TypedWhenBranch {
index: Index::zero_based(index),
region: ann_source.region(),
},
@ -1040,6 +1040,7 @@ fn constrain_when_branch(
loc_pattern.region,
pattern_expected.clone(),
&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_expected = NoExpectation(field_type.clone());
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
}
fn constrain_def_pattern(
env: &Env,
loc_pattern: &Located<Pattern>,
expr_type: Type,
) -> PatternState {
fn constrain_def_pattern(env: &Env, loc_pattern: &Loc<Pattern>, expr_type: Type) -> PatternState {
let pattern_expected = PExpected::NoExpectation(expr_type);
let mut state = PatternState {
@ -1147,6 +1144,7 @@ fn constrain_def_pattern(
loc_pattern.region,
pattern_expected,
&mut state,
true,
);
state
@ -1267,6 +1265,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
loc_pattern.region,
pattern_expected,
&mut state,
false,
);
}
@ -1449,8 +1448,8 @@ fn instantiate_rigids(
introduced_vars: &IntroducedVariables,
new_rigids: &mut Vec<Variable>,
ftv: &mut ImMap<Lowercase, Variable>, // rigids defined before the current annotation
loc_pattern: &Located<Pattern>,
headers: &mut SendMap<Symbol, Located<Type>>,
loc_pattern: &Loc<Pattern>,
headers: &mut SendMap<Symbol, Loc<Type>>,
) -> Type {
let mut annotation = annotation.clone();
let mut rigid_substitution: ImMap<Variable, Type> = ImMap::default();
@ -1473,7 +1472,7 @@ fn instantiate_rigids(
if let Some(new_headers) = crate::pattern::headers_from_annotation(
&loc_pattern.value,
&Located::at(loc_pattern.region, annotation.clone()),
&Loc::at(loc_pattern.region, annotation.clone()),
) {
for (symbol, loc_type) in new_headers {
for var in loc_type.value.variables() {
@ -1634,6 +1633,7 @@ pub fn rec_defs_help(
loc_pattern.region,
pattern_expected,
&mut state,
false,
);
}
@ -1770,7 +1770,7 @@ fn constrain_field_update(
var: Variable,
region: Region,
field: Lowercase,
loc_expr: &Located<Expr>,
loc_expr: &Loc<Expr>,
) -> (Variable, Type, Constraint) {
let field_type = Type::Variable(var);
let reason = Reason::RecordUpdateValue(field);

View File

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

View File

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

View File

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

View File

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

View File

@ -1,52 +1,74 @@
use roc_parse::ast::Collection;
use roc_parse::ast::{Collection, ExtractSpaces};
use crate::{
annotation::{Formattable, Newlines, Parens},
annotation::{Formattable, Newlines},
spaces::{fmt_comments_only, NewlineAt, INDENT},
Buf,
};
pub struct CollectionConfig {
pub begin: char,
pub end: char,
pub delimiter: char,
}
pub fn fmt_collection<'a, F: Formattable<'a>>(
buf: &mut Buf<'a>,
items: Collection<'_, F>,
pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
buf: &mut Buf<'buf>,
indent: u16,
config: CollectionConfig,
) {
let loc_items = items.items;
let final_comments = items.final_comments();
start: char,
end: char,
items: Collection<'a, T>,
newline: Newlines,
) where
<T as ExtractSpaces<'a>>::Item: Formattable,
{
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);
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();
} 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(config.delimiter);
}
}
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);
}
buf.indent(indent);
buf.push(' ');
item.item.format(buf, item_indent);
buf.push(',');
if !item.after.is_empty() {
fmt_comments_only(buf, item.after.iter(), NewlineAt::Top, item_indent);
}
}
fmt_comments_only(
buf,
items.final_comments().iter(),
NewlineAt::Top,
item_indent,
);
buf.newline();
} 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.spaces(1);
item.format(buf, indent);
if iter.peek().is_some() {
buf.push(',');
}
}
if !items.is_empty() {
buf.spaces(1);
}
}
buf.indent(indent);
buf.push(config.end);
buf.push(end);
}

View File

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

View File

@ -1,4 +1,5 @@
use crate::annotation::{Formattable, Newlines, Parens};
use crate::collection::fmt_collection;
use crate::def::fmt_def;
use crate::pattern::fmt_pattern;
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,
};
use roc_parse::ast::{StrLiteral, StrSegment};
use roc_region::all::Located;
use roc_region::all::Loc;
impl<'a> Formattable<'a> for Expr<'a> {
impl<'a> Formattable for Expr<'a> {
fn is_multiline(&self) -> bool {
use roc_parse::ast::Expr::*;
// TODO cache these answers using a Map<Pointer, bool>, so
@ -105,9 +106,9 @@ impl<'a> Formattable<'a> for Expr<'a> {
}
}
fn format_with_options(
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'a>,
buf: &mut Buf<'buf>,
parens: Parens,
newlines: Newlines,
indent: u16,
@ -186,7 +187,7 @@ impl<'a> Formattable<'a> for Expr<'a> {
}
} else {
for loc_arg in loc_args.iter() {
buf.push(' ');
buf.spaces(1);
loc_arg.format_with_options(buf, Parens::InApply, Newlines::Yes, indent);
}
}
@ -256,9 +257,7 @@ impl<'a> Formattable<'a> for Expr<'a> {
fmt_if(buf, branches, final_else, self.is_multiline(), indent);
}
When(loc_condition, branches) => fmt_when(buf, loc_condition, branches, indent),
List(items) => {
fmt_list(buf, *items, indent);
}
List(items) => fmt_collection(buf, indent, '[', ']', *items, Newlines::No),
BinOps(lefts, right) => fmt_bin_ops(buf, lefts, right, false, parens, indent),
UnaryOp(sub_expr, unary_op) => {
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::*;
match seg {
Plaintext(string) => {
buf.push_str(string);
buf.push_str_allow_spaces(string);
}
Unicode(loc_str) => {
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::*;
buf.indent(indent);
buf.push('"');
match literal {
PlainLine(string) => {
buf.push_str(string);
buf.push_str_allow_spaces(string);
}
Line(segments) => {
for seg in segments.iter() {
@ -396,10 +395,10 @@ pub fn fmt_str_literal<'a>(buf: &mut Buf<'a>, literal: StrLiteral<'a>, indent: u
buf.push('"');
}
fn fmt_bin_ops<'a>(
buf: &mut Buf<'a>,
lefts: &'a [(Located<Expr<'a>>, Located<BinOp>)],
loc_right_side: &'a Located<Expr<'a>>,
fn fmt_bin_ops<'a, 'buf>(
buf: &mut Buf<'buf>,
lefts: &'a [(Loc<Expr<'a>>, Loc<BinOp>)],
loc_right_side: &'a Loc<Expr<'a>>,
part_of_multi_line_bin_ops: bool,
apply_needs_parens: Parens,
indent: u16,
@ -417,100 +416,17 @@ fn fmt_bin_ops<'a>(
buf.newline();
buf.indent(indent + INDENT);
} else {
buf.push(' ');
buf.spaces(1);
}
push_op(buf, bin_op);
buf.push(' ');
buf.spaces(1);
}
loc_right_side.format_with_options(buf, apply_needs_parens, Newlines::Yes, indent);
}
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 {
use roc_parse::ast::Expr::*;
@ -538,9 +454,9 @@ fn empty_line_before_expr<'a>(expr: &'a Expr<'a>) -> bool {
}
}
fn fmt_when<'a>(
buf: &mut Buf<'a>,
loc_condition: &'a Located<Expr<'a>>,
fn fmt_when<'a, 'buf>(
buf: &mut Buf<'buf>,
loc_condition: &'a Loc<Expr<'a>>,
branches: &[&'a WhenBranch<'a>],
indent: u16,
) {
@ -586,9 +502,9 @@ fn fmt_when<'a>(
}
buf.indent(indent);
} else {
buf.push(' ');
buf.spaces(1);
loc_condition.format(buf, indent);
buf.push(' ');
buf.spaces(1);
}
buf.push_str("is");
buf.newline();
@ -600,7 +516,9 @@ fn fmt_when<'a>(
let (first_pattern, rest) = patterns.split_first().unwrap();
let is_multiline = match rest.last() {
None => false,
Some(last_pattern) => first_pattern.region.start_line != last_pattern.region.end_line,
Some(last_pattern) => {
first_pattern.region.start().line != last_pattern.region.end().line
}
};
fmt_pattern(
@ -614,12 +532,14 @@ fn fmt_when<'a>(
buf.newline();
buf.indent(indent + INDENT);
}
buf.push_str(" | ");
buf.push_str(" |");
buf.spaces(1);
fmt_pattern(buf, &when_pattern.value, indent + INDENT, Parens::NotNeeded);
}
if let Some(guard_expr) = &branch.guard {
buf.push_str(" if ");
buf.push_str(" if");
buf.spaces(1);
guard_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT);
}
@ -653,10 +573,10 @@ fn fmt_when<'a>(
}
}
fn fmt_expect<'a>(
buf: &mut Buf<'a>,
condition: &'a Located<Expr<'a>>,
continuation: &'a Located<Expr<'a>>,
fn fmt_expect<'a, 'buf>(
buf: &mut Buf<'buf>,
condition: &'a Loc<Expr<'a>>,
continuation: &'a Loc<Expr<'a>>,
is_multiline: bool,
indent: u16,
) {
@ -672,10 +592,10 @@ fn fmt_expect<'a>(
continuation.format(buf, return_indent);
}
fn fmt_if<'a>(
buf: &mut Buf<'a>,
branches: &'a [(Located<Expr<'a>>, Located<Expr<'a>>)],
final_else: &'a Located<Expr<'a>>,
fn fmt_if<'a, 'buf>(
buf: &mut Buf<'buf>,
branches: &'a [(Loc<Expr<'a>>, Loc<Expr<'a>>)],
final_else: &'a Loc<Expr<'a>>,
is_multiline: bool,
indent: u16,
) {
@ -696,7 +616,8 @@ fn fmt_if<'a>(
buf.indent(indent);
if i > 0 {
buf.push_str("else ");
buf.push_str("else");
buf.spaces(1);
}
buf.push_str("if");
@ -740,9 +661,9 @@ fn fmt_if<'a>(
}
buf.indent(indent);
} else {
buf.push(' ');
buf.spaces(1);
loc_condition.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
buf.push(' ');
buf.spaces(1);
}
buf.push_str("then");
@ -777,7 +698,8 @@ fn fmt_if<'a>(
}
}
} else {
buf.push_str(" ");
buf.push_str("");
buf.spaces(1);
loc_then.format(buf, return_indent);
}
}
@ -787,16 +709,17 @@ fn fmt_if<'a>(
buf.push_str("else");
buf.newline();
} else {
buf.push_str(" else ");
buf.push_str(" else");
buf.spaces(1);
}
final_else.format(buf, return_indent);
}
fn fmt_closure<'a>(
buf: &mut Buf<'a>,
loc_patterns: &'a [Located<Pattern<'a>>],
loc_ret: &'a Located<Expr<'a>>,
fn fmt_closure<'a, 'buf>(
buf: &mut Buf<'buf>,
loc_patterns: &'a [Loc<Pattern<'a>>],
loc_ret: &'a Loc<Expr<'a>>,
indent: u16,
) {
use self::Expr::*;
@ -826,7 +749,8 @@ fn fmt_closure<'a>(
buf.push(',');
buf.newline();
} else {
buf.push_str(", ");
buf.push_str(",");
buf.spaces(1);
}
}
}
@ -835,7 +759,7 @@ fn fmt_closure<'a>(
buf.newline();
buf.indent(indent);
} else {
buf.push(' ');
buf.spaces(1);
}
buf.push_str("->");
@ -859,18 +783,18 @@ fn fmt_closure<'a>(
}
_ => {
// add a space after the `->`
buf.push(' ');
buf.spaces(1);
}
};
loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, body_indent);
}
fn fmt_backpassing<'a>(
buf: &mut Buf<'a>,
loc_patterns: &'a [Located<Pattern<'a>>],
loc_body: &'a Located<Expr<'a>>,
loc_ret: &'a Located<Expr<'a>>,
fn fmt_backpassing<'a, 'buf>(
buf: &mut Buf<'buf>,
loc_patterns: &'a [Loc<Pattern<'a>>],
loc_body: &'a Loc<Expr<'a>>,
loc_ret: &'a Loc<Expr<'a>>,
indent: u16,
) {
use self::Expr::*;
@ -905,7 +829,8 @@ fn fmt_backpassing<'a>(
buf.push(',');
buf.newline();
} else {
buf.push_str(", ");
buf.push_str(",");
buf.spaces(1);
}
}
}
@ -918,7 +843,7 @@ fn fmt_backpassing<'a>(
buf.newline();
buf.indent(indent);
} else {
buf.push(' ');
buf.spaces(1);
}
buf.push_str("<-");
@ -942,7 +867,7 @@ fn fmt_backpassing<'a>(
}
_ => {
// 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>(
buf: &mut Buf<'a>,
update: Option<&'a Located<Expr<'a>>>,
fields: Collection<'a, Located<AssignedField<'a, Expr<'a>>>>,
fn fmt_record<'a, 'buf>(
buf: &mut Buf<'buf>,
update: Option<&'a Loc<Expr<'a>>>,
fields: Collection<'a, Loc<AssignedField<'a, Expr<'a>>>>,
indent: u16,
) {
let loc_fields = fields.items;
@ -981,7 +906,7 @@ fn fmt_record<'a>(
// it this far. For example "{ 4 & hello = 9 }"
// doesnt make sense.
Some(record_var) => {
buf.push(' ');
buf.spaces(1);
record_var.format(buf, indent);
buf.push_str(" &");
}
@ -1007,17 +932,18 @@ fn fmt_record<'a>(
buf.newline();
} else {
// is_multiline == false
buf.push(' ');
buf.spaces(1);
let field_indent = indent;
let mut iter = loc_fields.iter().peekable();
while let Some(field) = iter.next() {
field.format_with_options(buf, Parens::NotNeeded, Newlines::No, field_indent);
if iter.peek().is_some() {
buf.push_str(", ");
buf.push_str(",");
buf.spaces(1);
}
}
buf.push(' ');
buf.spaces(1);
// if we are here, that means that `final_comments` is empty, thus we don't have
// to add a comment. Anyway, it is not possible to have a single line record with
// a comment in it.
@ -1029,13 +955,13 @@ fn fmt_record<'a>(
}
}
fn format_field_multiline<'a, T>(
buf: &mut Buf<'a>,
fn format_field_multiline<'a, 'buf, T>(
buf: &mut Buf<'buf>,
field: &AssignedField<'a, T>,
indent: u16,
separator_prefix: &str,
) where
T: Formattable<'a>,
T: Formattable,
{
use self::AssignedField::*;
match field {
@ -1049,7 +975,8 @@ fn format_field_multiline<'a, T>(
}
buf.push_str(separator_prefix);
buf.push_str(": ");
buf.push_str(":");
buf.spaces(1);
ann.value.format(buf, indent);
buf.push(',');
}
@ -1063,7 +990,8 @@ fn format_field_multiline<'a, T>(
}
buf.push_str(separator_prefix);
buf.push_str("? ");
buf.push_str("?");
buf.spaces(1);
ann.value.format(buf, indent);
buf.push(',');
}

View File

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

View File

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

View File

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

View File

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

View File

@ -11,11 +11,12 @@ mod test_fmt {
use roc_fmt::module::fmt_module;
use roc_fmt::Buf;
use roc_parse::module::{self, module_defs};
use roc_parse::parser::{Parser, State};
use roc_parse::parser::Parser;
use roc_parse::state::State;
use roc_test_utils::assert_multiline_str_eq;
// Not intended to be used directly in tests; please use expr_formats_to or expr_formats_same
fn expect_format_helper(input: &str, expected: &str) {
fn expect_format_expr_helper(input: &str, expected: &str) {
let arena = Bump::new();
match roc_parse::test_helpers::parse_expr_with(&arena, input.trim()) {
Ok(actual) => {
@ -34,21 +35,20 @@ mod test_fmt {
let expected = expected.trim_end();
// First check that input formats to the expected version
expect_format_helper(input, expected);
expect_format_expr_helper(input, expected);
// Parse the expected result format it, asserting that it doesn't change
// It's important that formatting be stable / idempotent
expect_format_helper(expected, expected);
expect_format_expr_helper(expected, expected);
}
fn expr_formats_same(input: &str) {
expr_formats_to(input, input);
}
fn module_formats_to(src: &str, expected: &str) {
// Not intended to be used directly in tests; please use module_formats_to or module_formats_same
fn expect_format_module_helper(src: &str, expected: &str) {
let arena = Bump::new();
let src = src.trim_end();
match module::parse_header(&arena, State::new(src.as_bytes())) {
Ok((actual, state)) => {
let mut buf = Buf::new_in(&arena);
@ -63,13 +63,21 @@ mod test_fmt {
}
Err(error) => panic!("Unexpected parse failure when parsing this for defs formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, error)
}
assert_multiline_str_eq!(expected, buf.as_str())
}
Err(error) => panic!("Unexpected parse failure when parsing this for module header formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, error)
};
}
fn module_formats_to(input: &str, expected: &str) {
// First check that input formats to the expected version
expect_format_module_helper(input, expected);
// Parse the expected result format it, asserting that it doesn't change
// It's important that formatting be stable / idempotent
expect_format_module_helper(expected, expected);
}
fn module_formats_same(input: &str) {
module_formats_to(input, input);
}
@ -2563,6 +2571,17 @@ mod test_fmt {
);
}
#[test]
fn pipline_op_with_apply() {
expr_formats_same(indoc!(
r#"
output
|> List.set (offset + 0) b
|> List.set (offset + 1) a
"#
));
}
// MODULES
#[test]
@ -2625,14 +2644,14 @@ mod test_fmt {
fn single_line_app() {
module_formats_same(indoc!(
r#"
app "Foo" packages { base: "platform" } imports [] provides [ main ] to base"#
app "Foo" packages { pf: "platform" } imports [] provides [ main ] to pf"#
));
}
#[test]
fn single_line_platform() {
module_formats_same(
"platform folkertdev/foo \
"platform \"folkertdev/foo\" \
requires { model=>Model, msg=>Msg } { main : Effect {} } \
exposes [] \
packages {} \
@ -2706,7 +2725,7 @@ mod test_fmt {
fn multiline_tag_union_annotation_beginning_on_same_line() {
expr_formats_same(indoc!(
r#"
Expr : [
Expr : [
Add Expr Expr,
Mul Expr Expr,
Val I64,
@ -2919,6 +2938,38 @@ mod test_fmt {
));
}
#[test]
/// Test that everything under examples/ is formatted correctly
/// If this test fails on your diff, it probably means you need to re-format the examples.
/// Try this:
/// `cargo run -- format $(find examples -name \*.roc)`
fn test_fmt_examples() {
let mut count = 0;
let mut root = std::env::current_dir()
.unwrap()
.parent()
.unwrap()
.parent()
.unwrap()
.to_owned();
root.push("examples");
for entry in walkdir::WalkDir::new(&root) {
let entry = entry.unwrap();
let path = entry.path();
if path.extension() == Some(&std::ffi::OsStr::new("roc")) {
count += 1;
let src = std::fs::read_to_string(path).unwrap();
println!("Now trying to format {}", path.display());
module_formats_same(&src);
}
}
assert!(
count > 0,
"Expecting to find at least 1 .roc file to format under {}",
root.display()
);
}
// this is a parse error atm
// #[test]
// fn multiline_apply() {

View File

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

View File

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

View File

@ -265,12 +265,12 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
);
general_i += 2;
} else {
unimplemented!("loading strings args on the stack is not yet implemented");
todo!("loading strings args on the stack");
}
}
Layout::Struct(&[]) => {}
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.
}
x => {
unimplemented!("receiving return type, {:?}, is not yet implemented", x);
todo!("receiving return type, {:?}", x);
}
}
for (i, layout) in arg_layouts.iter().enumerate() {
@ -426,14 +426,12 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
}
general_i += 2;
} else {
unimplemented!(
"calling functions with strings on the stack is not yet implemented"
);
todo!("calling functions with strings on the stack");
}
}
Layout::Struct(&[]) => {}
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>],
_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 {
@ -603,20 +601,18 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
}
Layout::Builtin(Builtin::Str) => {
// 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(&[]) => {}
x => {
unimplemented!("Loading args with layout {:?} not yet implemented", x);
todo!("Loading args with layout {:?}", x);
}
}
} else {
arg_offset += match layout {
single_register_builtins!() => 8,
x => {
unimplemented!("Loading args with layout {:?} not yet implemented", x);
todo!("Loading args with layout {:?}", x);
}
};
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.
}
x => {
unimplemented!("receiving return type, {:?}, is not yet implemented", x);
todo!("receiving return type, {:?}", x);
}
}
for (i, layout) in arg_layouts.iter().enumerate() {
@ -722,7 +718,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
}
SymbolStorage::GeneralReg(_)
| SymbolStorage::BaseAndGeneralReg { .. } => {
unimplemented!("Cannot load general symbol into FloatReg")
internal_error!("Cannot load general symbol into FloatReg")
}
}
} else {
@ -747,7 +743,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
}
SymbolStorage::GeneralReg(_)
| SymbolStorage::BaseAndGeneralReg { .. } => {
unimplemented!("Cannot load general symbol into FloatReg")
internal_error!("Cannot load general symbol into FloatReg")
}
}
stack_offset += 8;
@ -755,11 +751,11 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
}
Layout::Builtin(Builtin::Str) => {
// 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(&[]) => {}
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>],
_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 {
@ -977,9 +973,7 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
) -> usize {
buf.reserve(13);
if imm > i32::MAX as u64 {
unimplemented!(
"comparison with values greater than i32::max not yet implemented for jne"
);
todo!("comparison with values greater than i32::max");
}
cmp_reg64_imm32(buf, reg, imm as i32);
jne_imm32(buf, offset);

View File

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

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),
}
} 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 (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 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);
rc_procs
helper_procs
};
let empty = bumpalo::collections::Vec::new_in(arena);
let rc_symbols_and_layouts = std::mem::replace(backend.refcount_proc_symbols_mut(), empty);
let mut rc_names_symbols_procs = Vec::with_capacity_in(rc_procs.len(), arena);
let helper_symbols_and_layouts = std::mem::replace(backend.helper_proc_symbols_mut(), empty);
let mut helper_names_symbols_procs = Vec::with_capacity_in(helper_procs.len(), arena);
// Names and linker data for refcounting procedures
for ((sym, layout), proc) in rc_symbols_and_layouts.into_iter().zip(rc_procs) {
// Names and linker data for helpers
for ((sym, layout), proc) in helper_symbols_and_layouts.into_iter().zip(helper_procs) {
let layout_id = layout_ids.get_toplevel(sym, &layout);
let fn_name = backend.symbol_to_string(sym, layout_id);
if let Some(proc_id) = output.symbol_id(fn_name.as_bytes()) {
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;
}
}
internal_error!("failed to create rc fn for symbol {:?}", sym);
}
// Build refcounting procedures
for (fn_name, section_id, proc_id, proc) in rc_names_symbols_procs {
// Build helpers
for (fn_name, section_id, proc_id, proc) in helper_names_symbols_procs {
build_proc(
&mut output,
&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 {
match output.add_relocation(section_id, reloc) {
Ok(obj) => obj,

View File

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

View File

@ -46,12 +46,15 @@ fn call_bitcode_fn_help<'a, 'ctx, 'env>(
args: &[BasicValueEnum<'ctx>],
fn_name: &str,
) -> 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
.module
.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));
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
@ -595,7 +598,7 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>(
let value1 = env.builder.build_load(value_cast1, "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() {
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");
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,
};
use crate::llvm::build_str::{
empty_str, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int,
str_from_utf8, str_from_utf8_range, str_join_with, str_number_of_bytes, str_repeat, str_split,
str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, str_from_utf8,
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_trim_right,
};
@ -39,11 +39,13 @@ use inkwell::debug_info::{
use inkwell::memory_buffer::MemoryBuffer;
use inkwell::module::{Linkage, Module};
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::{
BasicValue, CallSiteValue, CallableValue, FloatValue, FunctionValue, InstructionOpcode,
InstructionValue, IntValue, PointerValue, StructValue,
BasicMetadataValueEnum, BasicValue, CallSiteValue, CallableValue, FloatValue, FunctionValue,
InstructionOpcode, InstructionValue, IntValue, PointerValue, StructValue,
};
use inkwell::OptimizationLevel;
use inkwell::{AddressSpace, IntPredicate};
@ -60,6 +62,7 @@ use roc_mono::ir::{
ModifyRc, OptLevel, ProcLayout,
};
use roc_mono::layout::{Builtin, LambdaSet, Layout, LayoutIds, TagIdIntType, UnionLayout};
use roc_reporting::internal_error;
use target_lexicon::{Architecture, OperatingSystem, Triple};
/// 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)
.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() {
arg_vals.push(*arg);
arg_vals.push((*arg).into());
}
let call = self
@ -775,118 +779,109 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
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(),
Str(str_literal) => {
if str_literal.is_empty() {
empty_str(env)
} else {
let ctx = env.context;
let builder = env.builder;
let number_of_chars = str_literal.len() as u64;
let ctx = env.context;
let builder = env.builder;
let number_of_chars = str_literal.len() as u64;
let str_type = super::convert::zig_str_type(env);
let str_type = super::convert::zig_str_type(env);
if str_literal.len() < env.small_str_bytes() as usize {
// TODO support big endian systems
if str_literal.len() < env.small_str_bytes() as usize {
// TODO support big endian systems
let array_alloca = builder.build_array_alloca(
ctx.i8_type(),
ctx.i8_type().const_int(env.small_str_bytes() as u64, false),
"alloca_small_str",
);
let array_alloca = builder.build_array_alloca(
ctx.i8_type(),
ctx.i8_type().const_int(env.small_str_bytes() as u64, false),
"alloca_small_str",
);
// Zero out all the bytes. If we don't do this, then
// small strings would have uninitialized bytes, which could
// cause string equality checks to fail randomly.
//
// We're running memset over *all* the bytes, even though
// the final one is about to be manually overridden, on
// the theory that LLVM will optimize the memset call
// into two instructions to move appropriately-sized
// zero integers into the appropriate locations instead
// of doing any iteration.
//
// TODO: look at the compiled output to verify this theory!
env.call_memset(
// Zero out all the bytes. If we don't do this, then
// small strings would have uninitialized bytes, which could
// cause string equality checks to fail randomly.
//
// We're running memset over *all* the bytes, even though
// the final one is about to be manually overridden, on
// the theory that LLVM will optimize the memset call
// into two instructions to move appropriately-sized
// zero integers into the appropriate locations instead
// of doing any iteration.
//
// TODO: look at the compiled output to verify this theory!
env.call_memset(
array_alloca,
ctx.i8_type().const_zero(),
env.ptr_int().const_int(env.small_str_bytes() as u64, false),
);
let final_byte = (str_literal.len() as u8) | 0b1000_0000;
let final_byte_ptr = unsafe {
builder.build_in_bounds_gep(
array_alloca,
ctx.i8_type().const_zero(),
env.ptr_int().const_int(env.small_str_bytes() as u64, false),
);
let final_byte = (str_literal.len() as u8) | 0b1000_0000;
let final_byte_ptr = unsafe {
builder.build_in_bounds_gep(
array_alloca,
&[ctx
.i8_type()
.const_int(env.small_str_bytes() as u64 - 1, false)],
"str_literal_final_byte",
)
};
builder.build_store(
final_byte_ptr,
ctx.i8_type().const_int(final_byte as u64, false),
);
// Copy the elements from the list literal into the array
for (index, character) in str_literal.as_bytes().iter().enumerate() {
let val = env
.context
&[ctx
.i8_type()
.const_int(*character as u64, false)
.as_basic_value_enum();
let index_val = ctx.i64_type().const_int(index as u64, false);
let elem_ptr = unsafe {
builder.build_in_bounds_gep(array_alloca, &[index_val], "index")
};
builder.build_store(elem_ptr, val);
}
builder.build_load(
builder
.build_bitcast(
array_alloca,
str_type.ptr_type(AddressSpace::Generic),
"cast_collection",
)
.into_pointer_value(),
"small_str_array",
.const_int(env.small_str_bytes() as u64 - 1, false)],
"str_literal_final_byte",
)
} else {
let ptr = define_global_str_literal_ptr(env, *str_literal);
let number_of_elements = env.ptr_int().const_int(number_of_chars, false);
};
let struct_type = str_type;
builder.build_store(
final_byte_ptr,
ctx.i8_type().const_int(final_byte as u64, false),
);
let mut struct_val;
// Copy the elements from the list literal into the array
for (index, character) in str_literal.as_bytes().iter().enumerate() {
let val = env
.context
.i8_type()
.const_int(*character as u64, false)
.as_basic_value_enum();
let index_val = ctx.i64_type().const_int(index as u64, false);
let elem_ptr =
unsafe { builder.build_in_bounds_gep(array_alloca, &[index_val], "index") };
// Store the pointer
struct_val = builder
.build_insert_value(
struct_type.get_undef(),
ptr,
Builtin::WRAPPER_PTR,
"insert_ptr_str_literal",
)
.unwrap();
// Store the length
struct_val = builder
.build_insert_value(
struct_val,
number_of_elements,
Builtin::WRAPPER_LEN,
"insert_len",
)
.unwrap();
builder.build_bitcast(
struct_val.into_struct_value(),
str_type,
"cast_collection",
)
builder.build_store(elem_ptr, val);
}
builder.build_load(
builder
.build_bitcast(
array_alloca,
str_type.ptr_type(AddressSpace::Generic),
"cast_collection",
)
.into_pointer_value(),
"small_str_array",
)
} else {
let ptr = define_global_str_literal_ptr(env, *str_literal);
let number_of_elements = env.ptr_int().const_int(number_of_chars, false);
let struct_type = str_type;
let mut struct_val;
// Store the pointer
struct_val = builder
.build_insert_value(
struct_type.get_undef(),
ptr,
Builtin::WRAPPER_PTR,
"insert_ptr_str_literal",
)
.unwrap();
// Store the length
struct_val = builder
.build_insert_value(
struct_val,
number_of_elements,
Builtin::WRAPPER_LEN,
"insert_len",
)
.unwrap();
builder.build_bitcast(struct_val.into_struct_value(), str_type, "cast_collection")
}
}
}
@ -1399,6 +1394,18 @@ fn build_wrapped_tag<'a, 'ctx, 'env>(
);
field_vals.push(ptr);
} else if matches!(
tag_field_layout,
Layout::Union(UnionLayout::NonRecursive(_))
) {
debug_assert!(val.is_pointer_value());
// We store non-recursive unions without any indirection.
let reified = env
.builder
.build_load(val.into_pointer_value(), "load_non_recursive");
field_vals.push(reified);
} else {
// this check fails for recursive tag unions, but can be helpful while debugging
// debug_assert_eq!(tag_field_layout, val_layout);
@ -1754,8 +1761,8 @@ fn tag_pointer_set_tag_id<'a, 'ctx, 'env>(
tag_id: u8,
pointer: PointerValue<'ctx>,
) -> PointerValue<'ctx> {
// we only have 3 bits, so can encode only 0..7
debug_assert!(tag_id < 8);
// we only have 3 bits, so can encode only 0..7 (or on 32-bit targets, 2 bits to encode 0..3)
debug_assert!((tag_id as u32) < env.ptr_bytes);
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")
}
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>(
env: &Env<'a, 'ctx, 'env>,
pointer: PointerValue<'ctx>,
) -> IntValue<'ctx> {
let mask: u64 = match env.ptr_bytes {
8 => 0b0000_0111,
4 => 0b0000_0011,
_ => unreachable!(),
};
let (_, mask) = tag_pointer_tag_id_bits_and_mask(env.ptr_bytes);
let ptr_int = env.ptr_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> {
let ptr_int = env.ptr_int();
let tag_id_bits_mask = match env.ptr_bytes {
8 => 3,
4 => 2,
_ => unreachable!(),
};
let (tag_id_bits_mask, _) = tag_pointer_tag_id_bits_and_mask(env.ptr_bytes);
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
}
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>(
env: &Env<'a, 'ctx, 'env>,
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);
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 output_type = return_type.ptr_type(AddressSpace::Generic);
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(
@ -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 call_result = {
let last_block = builder.get_insert_block().unwrap();
let roc_wrapper_function = make_exception_catcher(env, roc_function, return_layout);
builder.position_at_end(entry);
builder.position_at_end(last_block);
call_roc_function(
env,
@ -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 c_function_type = match cc_return {
CCReturn::Void => env.context.void_type().fn_type(&argument_types, false),
CCReturn::Return => return_type.fn_type(&argument_types, false),
CCReturn::Void => env
.context
.void_type()
.fn_type(&function_arguments(env, &argument_types), false),
CCReturn::Return => return_type.fn_type(&function_arguments(env, &argument_types), false),
CCReturn::ByPointer => {
let output_type = return_type.ptr_type(AddressSpace::Generic);
argument_types.push(output_type.into());
env.context.void_type().fn_type(&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());
// 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.
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) {
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 => {
// println!( "{:?} will return void instead of {:?}", symbol, proc.ret_layout);
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) {
RocReturn::ByPointer if !pass_by_pointer => {
// 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();
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")
}
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_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(),
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
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])
}
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 => {
// Str.fromInt : Int -> Str
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]);
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 => {
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]);
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 => {
// 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_2 = load_symbol(scope, &args[2]);
match list_layout {
Layout::Builtin(Builtin::List(element_layout)) => list_swap(
env,
original_wrapper,
index_1.into_int_value(),
index_2.into_int_value(),
element_layout,
update_mode,
),
_ => unreachable!("Invalid layout {:?} in List.swap", list_layout),
}
let element_layout = list_element_layout!(list_layout);
list_swap(
env,
original_wrapper,
index_1.into_int_value(),
index_2.into_int_value(),
element_layout,
update_mode,
)
}
ListSublist => {
// 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 len = load_symbol(scope, &args[2]);
match list_layout {
Layout::Builtin(Builtin::List(element_layout)) => list_sublist(
env,
layout_ids,
original_wrapper,
start.into_int_value(),
len.into_int_value(),
element_layout,
),
_ => unreachable!("Invalid layout {:?} in List.sublist", list_layout),
}
let element_layout = list_element_layout!(list_layout);
list_sublist(
env,
layout_ids,
original_wrapper,
start.into_int_value(),
len.into_int_value(),
element_layout,
)
}
ListDropAt => {
// 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]);
match list_layout {
Layout::Builtin(Builtin::List(element_layout)) => list_drop_at(
env,
layout_ids,
original_wrapper,
count.into_int_value(),
element_layout,
),
_ => unreachable!("Invalid layout {:?} in List.dropAt", list_layout),
}
let element_layout = list_element_layout!(list_layout);
list_drop_at(
env,
layout_ids,
original_wrapper,
count.into_int_value(),
element_layout,
)
}
ListPrepend => {
// 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]);
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 => {
// 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);
build_int_unary_op(env, arg.into_int_value(), int_type, op)
}
Float(float_width) => {
build_float_unary_op(env, arg.into_float_value(), op, *float_width)
}
Float(float_width) => build_float_unary_op(
env,
layout,
arg.into_float_value(),
op,
*float_width,
),
_ => {
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)
}
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 => {
debug_assert_eq!(args.len(), 2);
let seed = load_symbol(scope, &args[0]);
@ -5871,64 +5943,44 @@ fn run_low_level<'a, 'ctx, 'env>(
debug_assert_eq!(args.len(), 2);
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 {
Layout::Builtin(Builtin::Dict(_, value_layout)) => {
dict_remove(env, layout_ids, dict, key, key_layout, value_layout)
}
_ => unreachable!("invalid dict layout"),
}
let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
dict_remove(env, layout_ids, dict, key, key_layout, value_layout)
}
DictContains => {
debug_assert_eq!(args.len(), 2);
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 {
Layout::Builtin(Builtin::Dict(_, value_layout)) => {
dict_contains(env, layout_ids, dict, key, key_layout, value_layout)
}
_ => unreachable!("invalid dict layout"),
}
let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
dict_contains(env, layout_ids, dict, key, key_layout, value_layout)
}
DictGetUnsafe => {
debug_assert_eq!(args.len(), 2);
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 {
Layout::Builtin(Builtin::Dict(_, value_layout)) => {
dict_get(env, layout_ids, dict, key, key_layout, value_layout)
}
_ => unreachable!("invalid dict layout"),
}
let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
dict_get(env, layout_ids, dict, key, key_layout, value_layout)
}
DictKeys => {
debug_assert_eq!(args.len(), 1);
let (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]);
match dict_layout {
Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => {
dict_keys(env, layout_ids, dict, key_layout, value_layout)
}
_ => unreachable!("invalid dict layout"),
}
let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
dict_keys(env, layout_ids, dict, key_layout, value_layout)
}
DictValues => {
debug_assert_eq!(args.len(), 1);
let (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]);
match dict_layout {
Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => {
dict_values(env, layout_ids, dict, key_layout, value_layout)
}
_ => unreachable!("invalid dict layout"),
}
let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
dict_values(env, layout_ids, dict, key_layout, value_layout)
}
DictUnion => {
debug_assert_eq!(args.len(), 2);
@ -5936,12 +5988,8 @@ fn run_low_level<'a, 'ctx, 'env>(
let (dict1, dict_layout) = load_symbol_and_layout(scope, &args[0]);
let (dict2, _) = load_symbol_and_layout(scope, &args[1]);
match dict_layout {
Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => {
dict_union(env, layout_ids, dict1, dict2, key_layout, value_layout)
}
_ => unreachable!("invalid dict layout"),
}
let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
dict_union(env, layout_ids, dict1, dict2, key_layout, value_layout)
}
DictDifference => {
debug_assert_eq!(args.len(), 2);
@ -5949,12 +5997,8 @@ fn run_low_level<'a, 'ctx, 'env>(
let (dict1, dict_layout) = load_symbol_and_layout(scope, &args[0]);
let (dict2, _) = load_symbol_and_layout(scope, &args[1]);
match dict_layout {
Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => {
dict_difference(env, layout_ids, dict1, dict2, key_layout, value_layout)
}
_ => unreachable!("invalid dict layout"),
}
let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
dict_difference(env, layout_ids, dict1, dict2, key_layout, value_layout)
}
DictIntersection => {
debug_assert_eq!(args.len(), 2);
@ -5962,24 +6006,16 @@ fn run_low_level<'a, 'ctx, 'env>(
let (dict1, dict_layout) = load_symbol_and_layout(scope, &args[0]);
let (dict2, _) = load_symbol_and_layout(scope, &args[1]);
match dict_layout {
Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => {
dict_intersection(env, layout_ids, dict1, dict2, key_layout, value_layout)
}
_ => unreachable!("invalid dict layout"),
}
let (key_layout, value_layout) = dict_key_value_layout!(dict_layout);
dict_intersection(env, layout_ids, dict1, dict2, key_layout, value_layout)
}
SetFromList => {
debug_assert_eq!(args.len(), 1);
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
match list_layout {
Layout::Builtin(Builtin::List(key_layout)) => {
set_from_list(env, layout_ids, list, key_layout)
}
_ => unreachable!("invalid dict layout"),
}
let key_layout = list_element_layout!(list_layout);
set_from_list(env, layout_ids, list, key_layout)
}
ExpectTrue => {
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")
}
RefCountGetPtr | RefCountInc | RefCountDec => {
unreachable!("LLVM backend does not use lowlevels for refcounting");
PtrCast | RefCountInc | RefCountDec => {
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>(
env: &Env<'a, 'ctx, 'env>,
scope: &mut Scope<'a, 'ctx>,
@ -6189,17 +6233,25 @@ fn build_foreign_symbol<'a, 'ctx, 'env>(
}
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 => {
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 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(
env.module,
@ -6220,14 +6272,14 @@ fn build_foreign_symbol<'a, 'ctx, 'env>(
let mut cc_arguments =
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 {
cc_arguments.push(param);
cc_arguments.push(param.into());
} else {
let as_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::*;
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 {
Int(int_width) => build_int_binop(
env,
@ -6503,7 +6544,14 @@ pub fn build_num_binop<'a, 'ctx, 'env>(
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 => {
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>(
env: &Env<'a, 'ctx, 'env>,
layout: &Layout<'a>,
arg: FloatValue<'ctx>,
op: LowLevel,
float_width: FloatWidth,
float_width: FloatWidth, // arg width
) -> BasicValueEnum<'ctx> {
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()]),
NumSqrtUnchecked => env.call_intrinsic(&LLVM_SQRT[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(
InstructionOpcode::FPToSI,
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 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;

View File

@ -346,7 +346,7 @@ fn build_hash_tag<'a, 'ctx, 'env>(
.set_current_debug_location(env.context, di_location);
let call = env
.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);

View File

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

View File

@ -432,12 +432,3 @@ pub fn str_equal<'a, 'ctx, 'env>(
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
.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);

View File

@ -203,22 +203,6 @@ pub fn block_of_memory_slices<'ctx>(
block_of_memory_help(context, union_size)
}
pub fn union_data_is_struct<'a, 'ctx, 'env>(
env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
layouts: &[Layout<'_>],
) -> StructType<'ctx> {
let data_type = basic_type_from_record(env, layouts);
union_data_is_struct_type(env.context, data_type.into_struct_type())
}
pub fn union_data_is_struct_type<'ctx>(
context: &'ctx Context,
struct_type: StructType<'ctx>,
) -> StructType<'ctx> {
let tag_id_type = context.i64_type();
context.struct_type(&[struct_type.into(), tag_id_type.into()], false)
}
pub fn block_of_memory<'ctx>(
context: &'ctx Context,
layout: &Layout<'_>,
@ -246,14 +230,18 @@ fn block_of_memory_help(context: &Context, union_size: u32) -> BasicTypeEnum<'_>
let num_i64 = 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();
if num_i8 == 0 {
// the object fits perfectly in some number of i64's
if num_i64 == 0 {
// 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)
context.struct_type(&[i64_array_type], false).into()
} 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();
context

View File

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

View File

@ -10,7 +10,7 @@ use bumpalo::collections::Vec;
use inkwell::basic_block::BasicBlock;
use inkwell::context::Context;
use inkwell::module::Linkage;
use inkwell::types::{AnyTypeEnum, BasicType, BasicTypeEnum};
use inkwell::types::{AnyTypeEnum, BasicMetadataTypeEnum, BasicType, BasicTypeEnum};
use inkwell::values::{
BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue,
};
@ -567,9 +567,11 @@ fn call_help<'a, 'ctx, 'env>(
let call = match call_mode {
CallMode::Inc(inc_amount) => {
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);
@ -1053,6 +1055,11 @@ pub fn build_header_help<'a, 'ctx, 'env>(
arguments: &[BasicTypeEnum<'ctx>],
) -> FunctionValue<'ctx> {
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 {
ArrayType(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_collections::all::{MutMap, MutSet};
use roc_module::low_level::LowLevel;
use roc_module::low_level::LowLevelWrapperType;
use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::gen_refcount::RefcountProcGenerator;
use roc_mono::code_gen_help::CodeGenHelp;
use roc_mono::ir::{Proc, ProcLayout};
use roc_mono::layout::LayoutIds;
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 FRAME_ALIGNMENT_BYTES: i32 = 16;
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 struct Env<'a> {
@ -62,7 +62,10 @@ pub fn build_module_help<'a>(
// and filter out procs we're going to inline
let mut fn_index: u32 = 0;
for ((sym, layout), proc) in procedures.into_iter() {
if LowLevel::from_inlined_wrapper(sym).is_some() {
if matches!(
LowLevelWrapperType::from_symbol(sym),
LowLevelWrapperType::CanBeReplacedBy(_)
) {
continue;
}
procs.push(proc);
@ -94,14 +97,14 @@ pub fn build_module_help<'a>(
proc_symbols,
linker_symbols,
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");
for proc in procs.iter() {
println!("{}", proc.to_pretty(200));
println!("{:#?}", proc);
// println!("{:#?}", proc);
}
}
@ -110,21 +113,21 @@ pub fn build_module_help<'a>(
backend.build_proc(proc);
}
// Generate IR for refcounting procs
let refcount_procs = backend.generate_refcount_procs();
// Generate specialized helpers for refcounting & equality
let helper_procs = backend.generate_helpers();
backend.register_symbol_debug_names();
if false {
println!("## refcount_procs");
for proc in refcount_procs.iter() {
if DEBUG_LOG_SETTINGS.helper_procs_ir {
println!("## helper_procs");
for proc in helper_procs.iter() {
println!("{}", proc.to_pretty(200));
println!("{:#?}", proc);
// println!("{:#?}", proc);
}
}
// Generate Wasm for refcounting procs
for proc in refcount_procs.iter() {
for proc in helper_procs.iter() {
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)
pub fn round_up_to_alignment(unaligned: i32, alignment_bytes: i32) -> i32 {
if alignment_bytes <= 1 {
return unaligned;
}
if alignment_bytes.count_ones() != 1 {
internal_error!(
"Cannot align to {} bytes. Not a power of 2.",
alignment_bytes
);
}
let mut aligned = unaligned;
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
#[macro_export]
macro_rules! round_up_to_alignment {
($unaligned: expr, $alignment_bytes: expr) => {
if $alignment_bytes <= 1 {
$unaligned
} else if $alignment_bytes.count_ones() != 1 {
panic!(
"Cannot align to {} bytes. Not a power of 2.",
$alignment_bytes
);
} else {
let mut aligned = $unaligned;
aligned += $alignment_bytes - 1; // if lower bits are non-zero, push it over the next boundary
aligned &= !$alignment_bytes + 1; // mask with a flag that has upper bits 1, lower bits 0
aligned
}
};
}
pub fn debug_panic<E: std::fmt::Debug>(error: E) {
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::storage::{Storage, StoredValue};
use crate::wasm_module::{CodeBuilder, ValueType::*};
use crate::wasm_module::{Align, CodeBuilder, ValueType::*};
#[derive(Debug)]
pub enum LowlevelBuildResult {
Done,
BuiltinCall(&'static str),
NotImplemented,
}
pub fn decode_low_level<'a>(
pub fn dispatch_low_level<'a>(
code_builder: &mut CodeBuilder<'a>,
storage: &mut Storage<'a>,
lowlevel: LowLevel,
args: &'a [Symbol],
args: &[Symbol],
ret_layout: &WasmLayout,
) -> LowlevelBuildResult {
use LowlevelBuildResult::*;
@ -26,8 +27,9 @@ pub fn decode_low_level<'a>(
|| internal_error!("Invalid return layout for {:?}: {:?}", lowlevel, ret_layout);
match lowlevel {
// Str
StrConcat => return BuiltinCall(bitcode::STR_CONCAT),
StrJoinWith => return NotImplemented, // needs Array
StrJoinWith => return BuiltinCall(bitcode::STR_JOIN_WITH),
StrIsEmpty => {
code_builder.i64_const(i64::MIN);
code_builder.i64_eq();
@ -35,24 +37,46 @@ pub fn decode_low_level<'a>(
StrStartsWith => return BuiltinCall(bitcode::STR_STARTS_WITH),
StrStartsWithCodePt => return BuiltinCall(bitcode::STR_STARTS_WITH_CODE_PT),
StrEndsWith => return BuiltinCall(bitcode::STR_ENDS_WITH),
StrSplit => return NotImplemented, // needs Array
StrCountGraphemes => return NotImplemented, // test needs Array
StrFromInt => return NotImplemented, // choose builtin based on storage size
StrFromUtf8 => return NotImplemented, // needs Array
StrTrimLeft => return BuiltinCall(bitcode::STR_TRIM_LEFT),
StrTrimRight => return BuiltinCall(bitcode::STR_TRIM_RIGHT),
StrFromUtf8Range => return NotImplemented, // needs Array
StrToUtf8 => return NotImplemented, // needs Array
StrRepeat => return BuiltinCall(bitcode::STR_REPEAT),
StrSplit => {
// Roughly we need to:
// 1. count segments
// 2. make a new pointer
// 3. split that pointer in place
// see: build_str.rs line 31
return NotImplemented;
}
StrCountGraphemes => return BuiltinCall(bitcode::STR_COUNT_GRAPEHEME_CLUSTERS),
StrToNum => return NotImplemented, // choose builtin based on storage size
StrFromInt => {
// This does not get exposed in user space. We switched to NumToStr instead.
// We can probably just leave this as NotImplemented. We may want remove this LowLevel.
// see: https://github.com/rtfeldman/roc/pull/2108
return NotImplemented;
}
StrFromFloat => {
// linker errors for __ashlti3, __fixunsdfti, __multi3, __udivti3, __umodti3
// https://gcc.gnu.org/onlinedocs/gccint/Integer-library-routines.html
// https://gcc.gnu.org/onlinedocs/gccint/Soft-float-library-routines.html
return NotImplemented;
}
StrFromUtf8 => return BuiltinCall(bitcode::STR_FROM_UTF8),
StrTrimLeft => return BuiltinCall(bitcode::STR_TRIM_LEFT),
StrTrimRight => return BuiltinCall(bitcode::STR_TRIM_RIGHT),
StrFromUtf8Range => return BuiltinCall(bitcode::STR_FROM_UTF8_RANGE), // refcounting errors
StrToUtf8 => return BuiltinCall(bitcode::STR_TO_UTF8), // refcounting errors
StrRepeat => return BuiltinCall(bitcode::STR_REPEAT),
StrTrim => return BuiltinCall(bitcode::STR_TRIM),
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
| ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk | ListWalkUntil
| ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith | ListSublist
@ -62,6 +86,7 @@ pub fn decode_low_level<'a>(
return NotImplemented;
}
// Num
NumAdd => match ret_layout {
WasmLayout::Primitive(value_type, _) => match value_type {
I32 => code_builder.i32_add(),
@ -80,8 +105,6 @@ pub fn decode_low_level<'a>(
WasmLayout::Primitive(value_type, size) => match value_type {
I32 => {
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);
}
I64 => code_builder.i64_add(),
@ -346,27 +369,56 @@ pub fn decode_low_level<'a>(
},
WasmLayout::StackMemory { .. } => return NotImplemented,
},
NumIsFinite => match ret_layout {
WasmLayout::Primitive(value_type, _) => match value_type {
I32 => code_builder.i32_const(1),
I64 => code_builder.i32_const(1),
F32 => {
code_builder.i32_reinterpret_f32();
code_builder.i32_const(0x7f800000);
code_builder.i32_and();
code_builder.i32_const(0x7f800000);
code_builder.i32_ne();
NumIsFinite => {
use StoredValue::*;
match storage.get(&args[0]) {
VirtualMachineStack { value_type, .. } | Local { value_type, .. } => {
match value_type {
I32 | I64 => code_builder.i32_const(1), // always true for integers
F32 => {
code_builder.i32_reinterpret_f32();
code_builder.i32_const(0x7f80_0000);
code_builder.i32_and();
code_builder.i32_const(0x7f80_0000);
code_builder.i32_ne();
}
F64 => {
code_builder.i64_reinterpret_f64();
code_builder.i64_const(0x7ff0_0000_0000_0000);
code_builder.i64_and();
code_builder.i64_const(0x7ff0_0000_0000_0000);
code_builder.i64_ne();
}
}
}
F64 => {
code_builder.i64_reinterpret_f64();
code_builder.i64_const(0x7ff0000000000000);
code_builder.i64_and();
code_builder.i64_const(0x7ff0000000000000);
code_builder.i64_ne();
StackMemory {
format, location, ..
} => {
let (local_id, offset) = location.local_and_offset(storage.stack_frame_pointer);
match format {
Int128 => code_builder.i32_const(1),
Float128 => {
code_builder.get_local(local_id);
code_builder.i64_load(Align::Bytes4, offset + 8);
code_builder.i64_const(0x7fff_0000_0000_0000);
code_builder.i64_and();
code_builder.i64_const(0x7fff_0000_0000_0000);
code_builder.i64_ne();
}
Decimal => {
code_builder.get_local(local_id);
code_builder.i64_load(Align::Bytes4, offset + 8);
code_builder.i64_const(0x7100_0000_0000_0000);
code_builder.i64_and();
code_builder.i64_const(0x7100_0000_0000_0000);
code_builder.i64_ne();
}
DataStructure => return NotImplemented,
}
}
},
WasmLayout::StackMemory { .. } => return NotImplemented,
},
}
}
NumAtan => {
let width = float_width_from_layout(ret_layout);
return BuiltinCall(&bitcode::NUM_ATAN[width]);
@ -467,37 +519,15 @@ pub fn decode_low_level<'a>(
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(),
Or => code_builder.i32_or(),
Not => code_builder.i32_eqz(),
Hash => return NotImplemented,
ExpectTrue => return NotImplemented,
RefCountGetPtr => {
code_builder.i32_const(4);
code_builder.i32_sub();
}
RefCountInc => return BuiltinCall(bitcode::UTILS_INCREF),
RefCountDec => return BuiltinCall(bitcode::UTILS_DECREF),
Eq | NotEq | Hash | PtrCast => {
internal_error!("{:?} should be handled in backend.rs", lowlevel)
}
}
Done
}

View File

@ -3,6 +3,7 @@ use bumpalo::Bump;
use roc_collections::all::MutMap;
use roc_module::symbol::Symbol;
use roc_mono::layout::Layout;
use roc_reporting::internal_error;
use crate::layout::{
@ -82,8 +83,10 @@ impl StoredValue {
/// including the VM stack, local variables, and linear memory
#[derive(Debug)]
pub struct Storage<'a> {
pub return_var: Option<LocalId>,
pub arg_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 stack_frame_pointer: Option<LocalId>,
pub stack_frame_size: i32,
@ -92,8 +95,10 @@ pub struct Storage<'a> {
impl<'a> Storage<'a> {
pub fn new(arena: &'a Bump) -> Self {
Storage {
return_var: None,
arg_types: Vec::with_capacity_in(8, arena),
local_types: Vec::with_capacity_in(32, arena),
symbol_layouts: MutMap::default(),
symbol_storage_map: MutMap::default(),
stack_frame_pointer: None,
stack_frame_size: 0,
@ -101,18 +106,26 @@ impl<'a> Storage<'a> {
}
pub fn clear(&mut self) {
self.return_var = None;
self.arg_types.clear();
self.local_types.clear();
self.symbol_layouts.clear();
self.symbol_storage_map.clear();
self.stack_frame_pointer = None;
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 {
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
///
/// 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.
pub fn allocate(
&mut self,
wasm_layout: &WasmLayout,
layout: Layout<'a>,
symbol: Symbol,
kind: StoredValueKind,
) -> StoredValue {
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 {
WasmLayout::Primitive(value_type, size) => match kind {
StoredValueKind::Parameter => {
self.arg_types.push(*value_type);
self.arg_types.push(value_type);
StoredValue::Local {
local_id: next_local_id,
value_type: *value_type,
size: *size,
value_type,
size,
}
}
_ => StoredValue::VirtualMachineStack {
vm_state: VmSymbolState::NotYetPushed,
value_type: *value_type,
size: *size,
value_type,
size,
},
},
@ -155,7 +170,7 @@ impl<'a> Storage<'a> {
} => {
let location = match kind {
StoredValueKind::Parameter => {
if *size > 0 {
if size > 0 {
self.arg_types.push(PTR_TYPE);
StackMemoryLocation::PointerArg(next_local_id)
} else {
@ -166,15 +181,15 @@ impl<'a> Storage<'a> {
}
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.local_types.push(PTR_TYPE);
}
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)
}
@ -184,9 +199,9 @@ impl<'a> Storage<'a> {
StoredValue::StackMemory {
location,
size: *size,
alignment_bytes: *alignment_bytes,
format: *format,
size,
alignment_bytes,
format,
}
}
};
@ -313,9 +328,11 @@ impl<'a> Storage<'a> {
code_builder.i64_load(align, offset);
} else if *size <= 12 && BUILTINS_ZIG_VERSION == ZigVersion::Zig9 {
code_builder.i64_load(align, offset);
code_builder.get_local(local_id);
code_builder.i32_load(align, offset + 8);
} else {
code_builder.i64_load(align, offset);
code_builder.get_local(local_id);
code_builder.i64_load(align, offset + 8);
}
}
@ -502,6 +519,10 @@ impl<'a> Storage<'a> {
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);
copy_memory(
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.
/// 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.
@ -655,15 +677,12 @@ impl<'a> Storage<'a> {
size,
} = storage
{
let local_id = self.get_next_local_id();
if vm_state != VmSymbolState::NotYetPushed {
code_builder.load_symbol(symbol, vm_state, local_id);
code_builder.set_local(local_id);
}
let next_local_id = self.get_next_local_id();
code_builder.store_symbol_to_local(symbol, vm_state, next_local_id);
self.local_types.push(value_type);
let new_storage = StoredValue::Local {
local_id,
local_id: next_local_id,
value_type,
size,
};

View File

@ -8,12 +8,13 @@ use roc_module::symbol::Symbol;
use super::linking::{IndexRelocType, OffsetRelocType, RelocationEntry};
use super::opcodes::{OpCode, OpCode::*};
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 {
($($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)]
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,
}
}
}
const BLOCK_NO_RESULT: u8 = 0x40;
/// A control block in our model of the VM
/// Child blocks cannot "see" values from their parent block
@ -67,25 +46,17 @@ struct VmBlock<'a> {
opcode: OpCode,
/// the stack of values for this block
value_stack: Vec<'a, Symbol>,
/// whether this block pushes a result value to its parent
has_result: bool,
}
impl std::fmt::Debug for VmBlock<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"{:?} {}",
self.opcode,
if self.has_result {
"Result"
} else {
"NoResult"
}
))
f.write_fmt(format_args!("{:?}", self.opcode))
}
}
/// 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)]
#[derive(Clone, Copy, Debug)]
pub enum Align {
@ -93,10 +64,6 @@ pub enum Align {
Bytes2 = 1,
Bytes4 = 2,
Bytes8 = 3,
Bytes16 = 4,
Bytes32 = 5,
Bytes64 = 6,
// ... we can add more if we need them ...
}
impl From<u32> for Align {
@ -105,11 +72,13 @@ impl From<u32> for Align {
1 => Align::Bytes1,
2 => Align::Bytes2,
4 => Align::Bytes4,
8 => Align::Bytes8,
16 => Align::Bytes16,
32 => Align::Bytes32,
64 => Align::Bytes64,
_ => internal_error!("{:?}-byte alignment not supported", x),
_ => {
if x.count_ones() == 1 {
Align::Bytes8 // Max value supported by any Wasm instruction
} else {
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 function_block = VmBlock {
opcode: BLOCK,
has_result: true,
value_stack: Vec::with_capacity_in(8, arena),
};
vm_block_stack.push(function_block);
@ -312,30 +280,12 @@ impl<'a> CodeBuilder<'a> {
_ => {
// Symbol is not on top of the stack.
// We should have saved it to a local, so go back and do that now.
// It should still be on the stack in the block where it was assigned. Remove it.
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, 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);
}
self.store_pushed_symbol_to_local(
symbol,
vm_state,
pushed_at,
next_local_id,
);
// Recover the value again at the current position
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
@ -435,7 +439,7 @@ impl<'a> CodeBuilder<'a> {
/// 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.
pub fn build_fn_header(
pub fn build_fn_header_and_footer(
&mut self,
local_types: &[ValueType],
frame_size: i32,
@ -445,9 +449,9 @@ impl<'a> CodeBuilder<'a> {
if frame_size != 0 {
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_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
fn inst_base(&mut self, opcode: OpCode, pops: usize, push: bool) {
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);
if push {
current_stack.push(Symbol::WASM_TMP);
@ -545,23 +558,19 @@ impl<'a> CodeBuilder<'a> {
}
/// 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.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
self.vm_block_stack.push(VmBlock {
opcode,
value_stack: Vec::with_capacity_in(8, self.arena),
has_result: block_type != BlockType::NoResult,
});
log_instruction!(
"{:10} {:?}\t{:?}",
format!("{:?}", opcode),
block_type,
&self.vm_block_stack
);
log_instruction!("{:10}\t{:?}", format!("{:?}", opcode), &self.vm_block_stack);
}
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!(nop, NOP, 0, false);
pub fn block(&mut self, ty: BlockType) {
self.inst_block(BLOCK, 0, ty);
pub fn block(&mut self) {
self.inst_block(BLOCK, 0);
}
pub fn loop_(&mut self, ty: BlockType) {
self.inst_block(LOOP, 0, ty);
pub fn loop_(&mut self) {
self.inst_block(LOOP, 0);
}
pub fn if_(&mut self, ty: BlockType) {
self.inst_block(IF, 1, ty);
pub fn if_(&mut self) {
self.inst_block(IF, 1);
}
pub fn else_(&mut self) {
// Reuse the 'then' block but clear its value stack
@ -630,14 +639,21 @@ impl<'a> CodeBuilder<'a> {
}
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();
if ended_block.has_result {
let result = ended_block.value_stack.last().unwrap();
self.current_stack_mut().push(*result)
for _ in 0..n_unused {
self.drop_();
}
self.inst_base(END, 0, false);
self.vm_block_stack.pop();
log_instruction!("END \t\t{:?}", &self.vm_block_stack);
}
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!(f32_reinterpret_i32, F32REINTERPRETI32, 1, true);
instruction_no_args!(f64_reinterpret_i64, F64REINTERPRETI64, 1, true);
/// Generate a debug assertion for an expected i32 value
pub fn _debug_assert_i32(&mut self, expected: i32) {
self.i32_const(expected);
self.i32_eq();
self.i32_eqz();
self.if_();
self.unreachable_(); // Tell Wasm runtime to throw an exception
self.end();
// It matches. Restore the original value to the VM stack and continue the program.
// We know it matched the expected value, so just use that!
self.i32_const(expected);
}
}

View File

@ -4,6 +4,6 @@ pub mod opcodes;
pub mod sections;
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 sections::{ConstExpr, Export, ExportType, Global, GlobalType, Signature, WasmModule};

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