Merge remote-tracking branch 'origin/trunk' into gen-dev/quicksort2

This commit is contained in:
Brendan Hansknecht 2022-02-27 00:26:04 -08:00
commit 7a608524ec
262 changed files with 11188 additions and 2721 deletions

View file

@ -1,90 +1,5 @@
# Building the Roc compiler from source
## Installing LLVM, Zig, valgrind, and Python
To build the compiler, you need these installed:
* [Zig](https://ziglang.org/), see below for version
* `libxkbcommon` - macOS seems to have it already; on Ubuntu or Debian you can get it with `apt-get install libxkbcommon-dev`
* On Debian/Ubuntu `sudo apt-get install pkg-config`
* LLVM, see below for version
To run the test suite (via `cargo test`), you additionally need to install:
* [`valgrind`](https://www.valgrind.org/) (needs special treatment to [install on macOS](https://stackoverflow.com/a/61359781)
Alternatively, you can use `cargo test --no-fail-fast` or `cargo test -p specific_tests` to skip over the valgrind failures & tests.
For debugging LLVM IR, we use [DebugIR](https://github.com/vaivaswatha/debugir). This dependency is only required to build with the `--debug` flag, and for normal developtment you should be fine without it.
### libcxb libraries
You may see an error like this during builds:
```
/usr/bin/ld: cannot find -lxcb-render
/usr/bin/ld: cannot find -lxcb-shape
/usr/bin/ld: cannot find -lxcb-xfixes
```
If so, you can fix it like so:
```
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev
```
### Zig
**version: 0.8.0**
For any OS, you can use [`zigup`](https://github.com/marler8997/zigup) to manage zig installations.
If you prefer a package manager, you can try the following:
- For MacOS, you can install with `brew install zig`
- For, Ubuntu, you can use Snap, you can install with `snap install zig --classic --beta`
- For other systems, checkout this [page](https://github.com/ziglang/zig/wiki/Install-Zig-from-a-Package-Manager)
If you want to install it manually, you can also download Zig directly [here](https://ziglang.org/download/). Just make sure you download the right version, the bleeding edge master build is the first download link on this page.
### LLVM
**version: 12.0.x**
For macOS, you can install LLVM 12 using `brew install llvm@12` and then adding
`$(brew --prefix llvm@12)/bin` to your `PATH`. You can confirm this worked by
running `llc --version` - it should mention "LLVM version 12.0.0" at the top.
You may also need to manually specify a prefix env var like so:
```
export LLVM_SYS_120_PREFIX=/usr/local/opt/llvm@12
```
For Ubuntu and Debian:
```
sudo apt -y install lsb-release software-properties-common gnupg
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
./llvm.sh 12
```
If you use this script, you'll need to add `clang` and `llvm-as` to your `PATH`.
By default, the script installs them as `clang-12` and `llvm-as-12`,
respectively. You can address this with symlinks like so:
```
sudo ln -s /usr/bin/clang-12 /usr/bin/clang
```
```
sudo ln -s /usr/bin/llvm-as-12 /usr/bin/llvm-as
````
There are also alternative installation options at http://releases.llvm.org/download.html
[Troubleshooting](#troubleshooting)
### Building
Use `cargo build` to build the whole project.
Use `cargo run help` to see all subcommands.
To use the `repl` subcommand, execute `cargo run repl`.
## Using Nix
### Install
@ -168,6 +83,90 @@ Check the [nixGL repo](https://github.com/guibou/nixGL) for other graphics confi
Create an issue if you run into problems not listed here.
That will help us improve this document for everyone who reads it in the future!
## Manual Install
To build the compiler, you need these installed:
* [Zig](https://ziglang.org/), see below for version
* `libxkbcommon` - macOS seems to have it already; on Ubuntu or Debian you can get it with `apt-get install libxkbcommon-dev`
* On Debian/Ubuntu `sudo apt-get install pkg-config`
* LLVM, see below for version
To run the test suite (via `cargo test`), you additionally need to install:
* [`valgrind`](https://www.valgrind.org/) (needs special treatment to [install on macOS](https://stackoverflow.com/a/61359781)
Alternatively, you can use `cargo test --no-fail-fast` or `cargo test -p specific_tests` to skip over the valgrind failures & tests.
For debugging LLVM IR, we use [DebugIR](https://github.com/vaivaswatha/debugir). This dependency is only required to build with the `--debug` flag, and for normal developtment you should be fine without it.
### libcxb libraries
You may see an error like this during builds:
```
/usr/bin/ld: cannot find -lxcb-render
/usr/bin/ld: cannot find -lxcb-shape
/usr/bin/ld: cannot find -lxcb-xfixes
```
If so, you can fix it like so:
```
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev
```
### Zig
**version: 0.8.0**
For any OS, you can use [`zigup`](https://github.com/marler8997/zigup) to manage zig installations.
If you prefer a package manager, you can try the following:
- For MacOS, you can install with `brew install zig`
- For, Ubuntu, you can use Snap, you can install with `snap install zig --classic --beta`
- For other systems, checkout this [page](https://github.com/ziglang/zig/wiki/Install-Zig-from-a-Package-Manager)
If you want to install it manually, you can also download Zig directly [here](https://ziglang.org/download/). Just make sure you download the right version, the bleeding edge master build is the first download link on this page.
### LLVM
**version: 12.0.x**
For macOS, you can install LLVM 12 using `brew install llvm@12` and then adding
`$(brew --prefix llvm@12)/bin` to your `PATH`. You can confirm this worked by
running `llc --version` - it should mention "LLVM version 12.0.0" at the top.
You may also need to manually specify a prefix env var like so:
```
export LLVM_SYS_120_PREFIX=/usr/local/opt/llvm@12
```
For Ubuntu and Debian:
```
sudo apt -y install lsb-release software-properties-common gnupg
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
./llvm.sh 12
```
If you use this script, you'll need to add `clang` and `llvm-as` to your `PATH`.
By default, the script installs them as `clang-12` and `llvm-as-12`,
respectively. You can address this with symlinks like so:
```
sudo ln -s /usr/bin/clang-12 /usr/bin/clang
```
```
sudo ln -s /usr/bin/llvm-as-12 /usr/bin/llvm-as
````
There are also alternative installation options at http://releases.llvm.org/download.html
[Troubleshooting](#troubleshooting)
### Building
Use `cargo build` to build the whole project.
Use `cargo run help` to see all subcommands.
To use the `repl` subcommand, execute `cargo run repl`.
### LLVM installation on Linux
For a current list of all dependency versions and their names in apt, see the Earthfile.

View file

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

61
Cargo.lock generated
View file

@ -1193,6 +1193,38 @@ version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
[[package]]
name = "dunce"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "453440c271cf5577fd2a40e4942540cb7d0d2f85e27c8d07dd0023c925a67541"
[[package]]
name = "dynasm"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b1801e630bd336d0bbbdbf814de6cc749c9a400c7e3d995e6adfd455d0c83c"
dependencies = [
"bitflags",
"byteorder",
"lazy_static",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "dynasmrt"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d428afc93ad288f6dffc1fa5f4a78201ad2eec33c5a522e51c181009eb09061"
dependencies = [
"byteorder",
"dynasm",
"memmap2 0.5.0",
]
[[package]]
name = "either"
version = "1.6.1"
@ -3194,6 +3226,8 @@ dependencies = [
"roc_repl_cli",
"roc_test_utils",
"strip-ansi-escapes",
"wasmer",
"wasmer-wasi",
]
[[package]]
@ -3288,6 +3322,7 @@ dependencies = [
name = "roc_builtins"
version = "0.1.0"
dependencies = [
"dunce",
"roc_collections",
"roc_module",
"roc_region",
@ -3304,6 +3339,7 @@ dependencies = [
"pretty_assertions",
"roc_builtins",
"roc_collections",
"roc_error_macros",
"roc_module",
"roc_parse",
"roc_problem",
@ -3381,6 +3417,7 @@ dependencies = [
"roc_builtins",
"roc_can",
"roc_collections",
"roc_error_macros",
"roc_module",
"roc_parse",
"roc_region",
@ -3572,6 +3609,7 @@ dependencies = [
"roc_can",
"roc_collections",
"roc_constrain",
"roc_error_macros",
"roc_module",
"roc_mono",
"roc_parse",
@ -3582,6 +3620,7 @@ dependencies = [
"roc_target",
"roc_types",
"roc_unify",
"strip-ansi-escapes",
"tempfile",
"ven_pretty",
]
@ -3705,6 +3744,7 @@ name = "roc_repl_wasm"
version = "0.1.0"
dependencies = [
"bumpalo",
"futures",
"js-sys",
"roc_builtins",
"roc_collections",
@ -3770,6 +3810,7 @@ name = "roc_std"
version = "0.1.0"
dependencies = [
"indoc",
"libc",
"pretty_assertions",
"quickcheck",
"quickcheck_macros",
@ -4746,6 +4787,7 @@ dependencies = [
"thiserror",
"wasmer-compiler",
"wasmer-compiler-cranelift",
"wasmer-compiler-singlepass",
"wasmer-derive",
"wasmer-engine",
"wasmer-engine-dylib",
@ -4794,6 +4836,25 @@ dependencies = [
"wasmer-vm",
]
[[package]]
name = "wasmer-compiler-singlepass"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9429b9f7708c582d855b1787f09c7029ff23fb692550d4a1cc351c8ea84c3014"
dependencies = [
"byteorder",
"dynasm",
"dynasmrt",
"lazy_static",
"loupe",
"more-asserts",
"rayon",
"smallvec",
"wasmer-compiler",
"wasmer-types",
"wasmer-vm",
]
[[package]]
name = "wasmer-derive"
version = "2.0.0"

77
FAQ.md
View file

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

View file

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

View file

@ -6,6 +6,10 @@ and more!
Enjoy!
## Getting started
Learn how to install roc on your machine [here](https://github.com/rtfeldman/roc#getting-started).
## Strings and Numbers
Lets start by getting acquainted with Rocs Read Eval Print Loop, or REPL for

View file

@ -1474,6 +1474,15 @@ pub fn constrain_pattern<'a>(
));
}
CharacterLiteral(_) => {
state.constraints.push(Constraint::Pattern(
region,
PatternCategory::Character,
num_unsigned32(env.pool),
expected,
));
}
RecordDestructure {
whole_var,
ext_var,
@ -1927,6 +1936,26 @@ fn _num_signed64(pool: &mut Pool) -> Type2 {
)
}
#[inline(always)]
fn num_unsigned32(pool: &mut Pool) -> Type2 {
let alias_content = Type2::TagUnion(
PoolVec::new(
std::iter::once((
TagName::Private(Symbol::NUM_UNSIGNED32),
PoolVec::empty(pool),
)),
pool,
),
pool.add(Type2::EmptyTagUnion),
);
Type2::Alias(
Symbol::NUM_UNSIGNED32,
PoolVec::empty(pool),
pool.add(alias_content),
)
}
#[inline(always)]
fn _num_integer(pool: &mut Pool, range: TypeId) -> Type2 {
let range_type = pool.get(range);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,6 +5,7 @@ use build::{BuildOutcome, BuiltFile};
use bumpalo::Bump;
use clap::{App, AppSettings, Arg, ArgMatches};
use roc_build::link::LinkType;
use roc_error_macros::user_error;
use roc_load::file::LoadingProblem;
use roc_mono::ir::OptLevel;
use std::env;
@ -31,6 +32,7 @@ pub const CMD_FORMAT: &str = "format";
pub const FLAG_DEBUG: &str = "debug";
pub const FLAG_DEV: &str = "dev";
pub const FLAG_OPTIMIZE: &str = "optimize";
pub const FLAG_OPT_SIZE: &str = "opt-size";
pub const FLAG_LIB: &str = "lib";
pub const FLAG_BACKEND: &str = "backend";
pub const FLAG_TIME: &str = "time";
@ -61,6 +63,12 @@ pub fn build_app<'a>() -> App<'a> {
.about("Optimize your compiled Roc program to run faster. (Optimization takes time to complete.)")
.required(false),
)
.arg(
Arg::new(FLAG_OPT_SIZE)
.long(FLAG_OPT_SIZE)
.about("Optimize your compiled Roc program to have a small binary size. (Optimization takes time to complete.)")
.required(false),
)
.arg(
Arg::new(FLAG_DEV)
.long(FLAG_DEV)
@ -166,12 +174,18 @@ pub fn build_app<'a>() -> App<'a> {
.requires(ROC_FILE)
.required(false),
)
.arg(
Arg::new(FLAG_DEV)
.long(FLAG_DEV)
.about("Make compilation as fast as possible. (Runtime performance may suffer)")
.required(false),
)
.arg(
Arg::new(FLAG_OPT_SIZE)
.long(FLAG_OPT_SIZE)
.about("Optimize your compiled Roc program to have a small binary size. (Optimization takes time to complete.)")
.required(false),
)
.arg(
Arg::new(FLAG_DEV)
.long(FLAG_DEV)
.about("Make compilation as fast as possible. (Runtime performance may suffer)")
.required(false),
)
.arg(
Arg::new(FLAG_DEBUG)
.long(FLAG_DEBUG)
@ -272,12 +286,14 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
let original_cwd = std::env::current_dir()?;
let opt_level = match (
matches.is_present(FLAG_OPTIMIZE),
matches.is_present(FLAG_OPT_SIZE),
matches.is_present(FLAG_DEV),
) {
(true, false) => OptLevel::Optimize,
(true, true) => panic!("development cannot be optimized!"),
(false, true) => OptLevel::Development,
(false, false) => OptLevel::Normal,
(true, false, false) => OptLevel::Optimize,
(false, true, false) => OptLevel::Size,
(false, false, true) => OptLevel::Development,
(false, false, false) => OptLevel::Normal,
_ => user_error!("build can be only one of `--dev`, `--optimize`, or `--opt-size`"),
};
let emit_debug_info = matches.is_present(FLAG_DEBUG);
let emit_timings = matches.is_present(FLAG_TIME);

View file

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

View file

@ -12,8 +12,9 @@ extern crate indoc;
#[cfg(test)]
mod cli_run {
use cli_utils::helpers::{
example_file, examples_dir, extract_valgrind_errors, fixture_file, known_bad_file, run_cmd,
run_roc, run_with_valgrind, ValgrindError, ValgrindErrorXWhat,
example_file, examples_dir, extract_valgrind_errors, fixture_file, fixtures_dir,
known_bad_file, run_cmd, run_roc, run_with_valgrind, Out, ValgrindError,
ValgrindErrorXWhat,
};
use roc_test_utils::assert_multiline_str_eq;
use serial_test::serial;
@ -80,6 +81,17 @@ mod cli_run {
}
}
fn build_example(file: &Path, flags: &[&str]) -> Out {
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags].concat());
if !compile_out.stderr.is_empty() {
panic!("roc build had stderr: {}", compile_out.stderr);
}
assert!(compile_out.status.success(), "bad status {:?}", compile_out);
compile_out
}
fn check_output_with_stdin(
file: &Path,
stdin: &[&str],
@ -96,12 +108,7 @@ mod cli_run {
all_flags.extend_from_slice(&["--valgrind"]);
}
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], &all_flags[..]].concat());
if !compile_out.stderr.is_empty() {
panic!("roc build had stderr: {}", compile_out.stderr);
}
assert!(compile_out.status.success(), "bad status {:?}", compile_out);
build_example(file, &all_flags[..]);
let out = if use_valgrind && ALLOW_VALGRIND {
let (valgrind_out, raw_xml) = if let Some(input_file) = input_file {
@ -238,6 +245,17 @@ mod cli_run {
return;
}
}
"hello-gui" => {
// Since this one requires opening a window, we do `roc build` on it but don't run it.
if cfg!(target_os = "linux") {
// The surgical linker can successfully link this on Linux, but the legacy linker errors!
build_example(&file_name, &["--optimize", "--roc-linker"]);
} else {
build_example(&file_name, &["--optimize"]);
}
return;
}
_ => {}
}
@ -354,6 +372,14 @@ mod cli_run {
expected_ending:"55\n",
use_valgrind: true,
},
gui:"gui" => Example {
filename: "Hello.roc",
executable_filename: "hello-gui",
stdin: &[],
input_file: None,
expected_ending: "",
use_valgrind: false,
},
quicksort:"quicksort" => Example {
filename: "Quicksort.roc",
executable_filename: "quicksort",
@ -884,6 +910,15 @@ mod cli_run {
fn format_check_reformatting_needed() {
check_format_check_as_expected(&fixture_file("format", "NotFormatted.roc"), false);
}
#[test]
fn format_check_folders() {
// This fails, because "NotFormatted.roc" is present in this folder
check_format_check_as_expected(&fixtures_dir("format"), false);
// This doesn't fail, since only "Formatted.roc" is present in this folder
check_format_check_as_expected(&fixtures_dir("format/formatted_directory"), true);
}
}
#[allow(dead_code)]

View file

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

14
cli_utils/Cargo.lock generated
View file

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

View file

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

View file

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

View file

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

View file

@ -24,7 +24,7 @@ roc_gen_llvm = { path = "../gen_llvm", optional = true }
roc_gen_wasm = { path = "../gen_wasm", optional = true }
roc_gen_dev = { path = "../gen_dev", default-features = false }
roc_reporting = { path = "../../reporting" }
roc_std = { path = "../../roc_std" }
roc_std = { path = "../../roc_std", default-features = false }
bumpalo = { version = "3.8.0", features = ["collections"] }
libloading = "0.7.1"
tempfile = "3.2.0"
@ -35,7 +35,6 @@ target-lexicon = "0.12.2"
serde_json = "1.0.69"
[features]
default = ["llvm", "target-aarch64", "target-x86_64", "target-wasm32"]
target-arm = []
target-aarch64 = ["roc_gen_dev/target-aarch64"]
target-x86 = []

View file

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

View file

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

View file

@ -114,6 +114,8 @@ pub fn target_machine(
pub fn convert_opt_level(level: OptLevel) -> OptimizationLevel {
match level {
OptLevel::Development | OptLevel::Normal => OptimizationLevel::None,
// Default is O2/Os. If we want Oz, we have to explicitly turn of loop vectorization as well.
OptLevel::Size => OptimizationLevel::Default,
OptLevel::Optimize => OptimizationLevel::Aggressive,
}
}

View file

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

View file

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

View file

@ -98,6 +98,14 @@ comptime {
num.exportDivCeil(T, ROC_BUILTINS ++ "." ++ NUM ++ ".div_ceil.");
}
inline for (INTEGERS) |FROM| {
inline for (INTEGERS) |TO| {
// We're exporting more than we need here, but that's okay.
num.exportToIntCheckingMax(FROM, TO, ROC_BUILTINS ++ "." ++ NUM ++ ".int_to_" ++ @typeName(TO) ++ "_checking_max.");
num.exportToIntCheckingMaxAndMin(FROM, TO, ROC_BUILTINS ++ "." ++ NUM ++ ".int_to_" ++ @typeName(TO) ++ "_checking_max_and_min.");
}
}
inline for (FLOATS) |T| {
num.exportAsin(T, ROC_BUILTINS ++ "." ++ NUM ++ ".asin.");
num.exportAcos(T, ROC_BUILTINS ++ "." ++ NUM ++ ".acos.");

View file

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

View file

@ -27,7 +27,8 @@ fn main() {
}
// "." is relative to where "build.rs" is
let build_script_dir_path = fs::canonicalize(Path::new(".")).unwrap();
// dunce can be removed once ziglang/zig#5109 is fixed
let build_script_dir_path = dunce::canonicalize(Path::new(".")).unwrap();
let bitcode_path = build_script_dir_path.join("bitcode");
// LLVM .bc FILES

View file

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

View file

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

View file

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

View file

@ -7,6 +7,7 @@ edition = "2018"
[dependencies]
roc_collections = { path = "../collections" }
roc_error_macros = { path = "../../error_macros" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_parse = { path = "../parse" }

View file

@ -3,10 +3,10 @@ use crate::scope::Scope;
use roc_collections::all::{ImMap, MutMap, MutSet, SendMap};
use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_parse::ast::{AliasHeader, AssignedField, Pattern, Tag, TypeAnnotation};
use roc_parse::ast::{AssignedField, Pattern, Tag, TypeAnnotation, TypeHeader};
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::{Alias, LambdaSet, Problem, RecordField, Type};
use roc_types::types::{Alias, AliasKind, LambdaSet, Problem, RecordField, Type};
#[derive(Clone, Debug, PartialEq)]
pub struct Annotation {
@ -130,13 +130,20 @@ fn make_apply_symbol(
// it was imported but it doesn't expose this ident.
env.problem(roc_problem::can::Problem::RuntimeError(problem));
Err(Type::Erroneous(Problem::UnrecognizedIdent((*ident).into())))
// A failed import should have already been reported through
// roc_can::env::Env::qualified_lookup's checks
Err(Type::Erroneous(Problem::SolvedTypeError))
}
}
}
}
pub fn find_alias_symbols(
/// Retrieves all symbols in an annotations that reference a type definition, that is either an
/// alias or an opaque type.
///
/// For example, in `[ A Age U8, B Str {} ]`, there are three type definition references - `Age`,
/// `U8`, and `Str`.
pub fn find_type_def_symbols(
module_id: ModuleId,
ident_ids: &mut IdentIds,
initial_annotation: &roc_parse::ast::TypeAnnotation,
@ -355,6 +362,7 @@ fn can_annotation_help(
type_arguments: vars,
lambda_set_variables,
actual: Box::new(actual),
kind: alias.kind,
}
}
None => Type::Apply(symbol, args, region),
@ -378,7 +386,7 @@ fn can_annotation_help(
loc_inner,
_spaces,
alias_header
@ AliasHeader {
@ TypeHeader {
name,
vars: loc_vars,
},
@ -488,7 +496,13 @@ fn can_annotation_help(
hidden_variables.remove(&loc_var.value.1);
}
scope.add_alias(symbol, region, lowercase_vars, alias_actual);
scope.add_alias(
symbol,
region,
lowercase_vars,
alias_actual,
AliasKind::Structural, // aliases in "as" are never opaque
);
let alias = scope.lookup_alias(symbol).unwrap();
local_aliases.insert(symbol, alias.clone());
@ -511,6 +525,7 @@ fn can_annotation_help(
type_arguments: vars,
lambda_set_variables: alias.lambda_set_variables.clone(),
actual: Box::new(alias.typ.clone()),
kind: alias.kind,
}
}
}

View file

@ -242,6 +242,26 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
NUM_MAX_U64=> num_max_u64,
NUM_MIN_I128=> num_min_i128,
NUM_MAX_I128=> num_max_i128,
NUM_TO_I8 => num_to_i8,
NUM_TO_I8_CHECKED => num_to_i8_checked,
NUM_TO_I16 => num_to_i16,
NUM_TO_I16_CHECKED => num_to_i16_checked,
NUM_TO_I32 => num_to_i32,
NUM_TO_I32_CHECKED => num_to_i32_checked,
NUM_TO_I64 => num_to_i64,
NUM_TO_I64_CHECKED => num_to_i64_checked,
NUM_TO_I128 => num_to_i128,
NUM_TO_I128_CHECKED => num_to_i128_checked,
NUM_TO_U8 => num_to_u8,
NUM_TO_U8_CHECKED => num_to_u8_checked,
NUM_TO_U16 => num_to_u16,
NUM_TO_U16_CHECKED => num_to_u16_checked,
NUM_TO_U32 => num_to_u32,
NUM_TO_U32_CHECKED => num_to_u32_checked,
NUM_TO_U64 => num_to_u64,
NUM_TO_U64_CHECKED => num_to_u64_checked,
NUM_TO_U128 => num_to_u128,
NUM_TO_U128_CHECKED => num_to_u128_checked,
NUM_TO_STR => num_to_str,
RESULT_MAP => result_map,
RESULT_MAP_ERR => result_map_err,
@ -390,6 +410,174 @@ fn lowlevel_5(symbol: Symbol, op: LowLevel, var_store: &mut VarStore) -> Def {
)
}
// Num.toI8 : Int * -> I8
fn num_to_i8(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to IntCast
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
// Num.toI16 : Int * -> I16
fn num_to_i16(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to IntCast
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
// Num.toI32 : Int * -> I32
fn num_to_i32(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to IntCast
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
// Num.toI64 : Int * -> I64
fn num_to_i64(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to IntCast
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
// Num.toI128 : Int * -> I128
fn num_to_i128(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to IntCast
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
// Num.toU8 : Int * -> U8
fn num_to_u8(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to IntCast
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
// Num.toU16 : Int * -> U16
fn num_to_u16(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to IntCast
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
// Num.toU32 : Int * -> U32
fn num_to_u32(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to IntCast
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
// Num.toU64 : Int * -> U64
fn num_to_u64(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to IntCast
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
// Num.toU128 : Int * -> U128
fn num_to_u128(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to IntCast
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
fn to_num_checked(symbol: Symbol, var_store: &mut VarStore, lowlevel: LowLevel) -> Def {
let bool_var = var_store.fresh();
let num_var_1 = var_store.fresh();
let num_var_2 = var_store.fresh();
let ret_var = var_store.fresh();
let record_var = var_store.fresh();
// let arg_2 = RunLowLevel NumToXXXChecked arg_1
// if arg_2.b then
// Err OutOfBounds
// else
// Ok arg_2.a
//
// "a" and "b" because the lowlevel return value looks like { converted_val: XXX, out_of_bounds: bool },
// and codegen will sort by alignment, so "a" will be the first key, etc.
let cont = If {
branch_var: ret_var,
cond_var: bool_var,
branches: vec![(
// if-condition
no_region(
// arg_2.b
Access {
record_var,
ext_var: var_store.fresh(),
field: "b".into(),
field_var: var_store.fresh(),
loc_expr: Box::new(no_region(Var(Symbol::ARG_2))),
},
),
// out of bounds!
no_region(tag(
"Err",
vec![tag("OutOfBounds", Vec::new(), var_store)],
var_store,
)),
)],
final_else: Box::new(
// all is well
no_region(
// Ok arg_2.a
tag(
"Ok",
vec![
// arg_2.a
Access {
record_var,
ext_var: var_store.fresh(),
field: "a".into(),
field_var: num_var_2,
loc_expr: Box::new(no_region(Var(Symbol::ARG_2))),
},
],
var_store,
),
),
),
};
// arg_2 = RunLowLevel NumToXXXChecked arg_1
let def = crate::def::Def {
loc_pattern: no_region(Pattern::Identifier(Symbol::ARG_2)),
loc_expr: no_region(RunLowLevel {
op: lowlevel,
args: vec![(num_var_1, Var(Symbol::ARG_1))],
ret_var: record_var,
}),
expr_var: record_var,
pattern_vars: SendMap::default(),
annotation: None,
};
let body = LetNonRec(Box::new(def), Box::new(no_region(cont)), ret_var);
defn(
symbol,
vec![(num_var_1, Symbol::ARG_1)],
var_store,
body,
ret_var,
)
}
macro_rules! num_to_checked {
($($fn:ident)*) => {$(
// Num.toXXXChecked : Int * -> Result XXX [ OutOfBounds ]*
fn $fn(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Use the generic `NumToIntChecked`; we'll figure out exactly what layout(s) we need
// during code generation after types are resolved.
to_num_checked(symbol, var_store, LowLevel::NumToIntChecked)
}
)*}
}
num_to_checked! {
num_to_i8_checked
num_to_i16_checked
num_to_i32_checked
num_to_i64_checked
num_to_i128_checked
num_to_u8_checked
num_to_u16_checked
num_to_u32_checked
num_to_u64_checked
num_to_u128_checked
}
// Num.toStr : Num a -> Str
fn num_to_str(symbol: Symbol, var_store: &mut VarStore) -> Def {
let num_var = var_store.fresh();

View file

@ -15,11 +15,12 @@ use roc_collections::all::{default_hasher, ImMap, ImSet, MutMap, MutSet, SendMap
use roc_module::ident::Lowercase;
use roc_module::symbol::Symbol;
use roc_parse::ast;
use roc_parse::ast::AliasHeader;
use roc_parse::ast::TypeHeader;
use roc_parse::pattern::PatternType;
use roc_problem::can::{CycleEntry, Problem, RuntimeError};
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::AliasKind;
use roc_types::types::{Alias, Type};
use std::collections::HashMap;
use std::fmt::Debug;
@ -73,17 +74,18 @@ enum PendingDef<'a> {
&'a Loc<ast::Expr<'a>>,
),
/// A type alias, e.g. `Ints : List Int`
/// A structural or opaque type alias, e.g. `Ints : List Int` or `Age := U32` respectively.
Alias {
name: Loc<Symbol>,
vars: Vec<Loc<Lowercase>>,
ann: &'a Loc<ast::TypeAnnotation<'a>>,
kind: AliasKind,
},
/// An invalid alias, that is ignored in the rest of the pipeline
/// e.g. a shadowed alias, or a definition like `MyAlias 1 : Int`
/// with an incorrect pattern
InvalidAlias,
InvalidAlias { kind: AliasKind },
}
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
@ -108,18 +110,20 @@ impl Declaration {
}
}
/// Returns a topologically sorted sequence of alias names
fn sort_aliases_before_introduction(mut alias_symbols: MutMap<Symbol, Vec<Symbol>>) -> Vec<Symbol> {
let defined_symbols: Vec<Symbol> = alias_symbols.keys().copied().collect();
/// Returns a topologically sorted sequence of alias/opaque names
fn sort_type_defs_before_introduction(
mut referenced_symbols: MutMap<Symbol, Vec<Symbol>>,
) -> Vec<Symbol> {
let defined_symbols: Vec<Symbol> = referenced_symbols.keys().copied().collect();
// find the strongly connected components and their relations
let sccs = {
// only retain symbols from the current alias_defs
for v in alias_symbols.iter_mut() {
// only retain symbols from the current set of defined symbols; the rest come from other modules
for v in referenced_symbols.iter_mut() {
v.1.retain(|x| defined_symbols.iter().any(|s| s == x));
}
let all_successors_with_self = |symbol: &Symbol| alias_symbols[symbol].iter().copied();
let all_successors_with_self = |symbol: &Symbol| referenced_symbols[symbol].iter().copied();
strongly_connected_components(&defined_symbols, all_successors_with_self)
};
@ -139,7 +143,7 @@ fn sort_aliases_before_introduction(mut alias_symbols: MutMap<Symbol, Vec<Symbol
for (index, group) in sccs.iter().enumerate() {
for s in group {
let reachable = &alias_symbols[s];
let reachable = &referenced_symbols[s];
for r in reachable {
let new_index = symbol_to_group_index[r];
@ -224,7 +228,10 @@ pub fn canonicalize_defs<'a>(
.map(|t| t.0),
)
}
PendingDef::Alias { .. } | PendingDef::InvalidAlias => {}
// Type definitions aren't value definitions, so we don't need to do
// anything for them here.
PendingDef::Alias { .. } | PendingDef::InvalidAlias { .. } => {}
}
}
// Record the ast::Expr for later. We'll do another pass through these
@ -245,25 +252,34 @@ pub fn canonicalize_defs<'a>(
let mut value_defs = Vec::new();
let mut alias_defs = MutMap::default();
let mut alias_symbols = MutMap::default();
let mut referenced_type_symbols = MutMap::default();
for pending_def in pending.into_iter() {
match pending_def {
PendingDef::Alias { name, vars, ann } => {
let symbols =
crate::annotation::find_alias_symbols(env.home, &mut env.ident_ids, &ann.value);
PendingDef::Alias {
name,
vars,
ann,
kind,
} => {
let referenced_symbols = crate::annotation::find_type_def_symbols(
env.home,
&mut env.ident_ids,
&ann.value,
);
alias_symbols.insert(name.value, symbols);
alias_defs.insert(name.value, (name, vars, ann));
referenced_type_symbols.insert(name.value, referenced_symbols);
alias_defs.insert(name.value, (name, vars, ann, kind));
}
other => value_defs.push(other),
}
}
let sorted = sort_aliases_before_introduction(alias_symbols);
let sorted = sort_type_defs_before_introduction(referenced_type_symbols);
for alias_name in sorted {
let (name, vars, ann) = alias_defs.remove(&alias_name).unwrap();
for type_name in sorted {
let (name, vars, ann, kind) = alias_defs.remove(&type_name).unwrap();
let symbol = name.value;
let can_ann = canonicalize_annotation(env, &mut scope, &ann.value, ann.region, var_store);
@ -271,7 +287,7 @@ pub fn canonicalize_defs<'a>(
// Record all the annotation's references in output.references.lookups
for symbol in can_ann.references {
output.references.lookups.insert(symbol);
output.references.referenced_aliases.insert(symbol);
output.references.referenced_type_defs.insert(symbol);
}
let mut can_vars: Vec<Loc<(Lowercase, Variable)>> = Vec::with_capacity(vars.len());
@ -282,7 +298,7 @@ pub fn canonicalize_defs<'a>(
.introduced_variables
.var_by_name(&loc_lowercase.value)
{
// This is a valid lowercase rigid var for the alias.
// This is a valid lowercase rigid var for the type def.
can_vars.push(Loc {
value: (loc_lowercase.value.clone(), *var),
region: loc_lowercase.region,
@ -291,7 +307,7 @@ pub fn canonicalize_defs<'a>(
is_phantom = true;
env.problems.push(Problem::PhantomTypeArgument {
alias: symbol,
typ: symbol,
variable_region: loc_lowercase.region,
variable_name: loc_lowercase.value.clone(),
});
@ -303,7 +319,13 @@ pub fn canonicalize_defs<'a>(
continue;
}
let alias = create_alias(symbol, name.region, can_vars.clone(), can_ann.typ.clone());
let alias = create_alias(
symbol,
name.region,
can_vars.clone(),
can_ann.typ.clone(),
kind,
);
aliases.insert(symbol, alias.clone());
}
@ -316,6 +338,7 @@ pub fn canonicalize_defs<'a>(
alias.region,
alias.type_variables.clone(),
alias.typ.clone(),
alias.kind,
);
}
@ -423,7 +446,7 @@ pub fn sort_can_defs(
let mut defined_symbols: Vec<Symbol> = Vec::new();
let mut defined_symbols_set: ImSet<Symbol> = ImSet::default();
for symbol in can_defs_by_symbol.keys().into_iter() {
for symbol in can_defs_by_symbol.keys() {
defined_symbols.push(*symbol);
defined_symbols_set.insert(*symbol);
}
@ -812,6 +835,15 @@ fn pattern_to_vars_by_symbol(
}
}
UnwrappedOpaque {
arguments, opaque, ..
} => {
for (var, nested) in arguments {
pattern_to_vars_by_symbol(vars_by_symbol, &nested.value, *var);
}
vars_by_symbol.insert(*opaque, expr_var);
}
RecordDestructure { destructs, .. } => {
for destruct in destructs {
vars_by_symbol.insert(destruct.value.symbol, destruct.value.var);
@ -822,9 +854,11 @@ fn pattern_to_vars_by_symbol(
| IntLiteral(..)
| FloatLiteral(..)
| StrLiteral(_)
| SingleQuote(_)
| Underscore
| MalformedPattern(_, _)
| UnsupportedPattern(_) => {}
| UnsupportedPattern(_)
| OpaqueNotInScope(..) => {}
}
}
@ -858,7 +892,7 @@ fn canonicalize_pending_def<'a>(
for symbol in ann.references {
output.references.lookups.insert(symbol);
output.references.referenced_aliases.insert(symbol);
output.references.referenced_type_defs.insert(symbol);
}
aliases.extend(ann.aliases.clone());
@ -958,8 +992,9 @@ fn canonicalize_pending_def<'a>(
}
Alias { .. } => unreachable!("Aliases are handled in a separate pass"),
InvalidAlias => {
// invalid aliases (shadowed, incorrect patterns) get ignored
InvalidAlias { .. } => {
// invalid aliases and opaques (shadowed, incorrect patterns) get ignored
}
TypedBody(loc_pattern, loc_can_pattern, loc_ann, loc_expr) => {
let ann =
@ -968,7 +1003,7 @@ fn canonicalize_pending_def<'a>(
// Record all the annotation's references in output.references.lookups
for symbol in ann.references {
output.references.lookups.insert(symbol);
output.references.referenced_aliases.insert(symbol);
output.references.referenced_type_defs.insert(symbol);
}
let typ = ann.typ;
@ -1442,10 +1477,19 @@ fn to_pending_def<'a>(
}
Alias {
header: AliasHeader { name, vars },
header: TypeHeader { name, vars },
ann,
..
}
| Opaque {
header: TypeHeader { name, vars },
typ: ann,
} => {
let kind = if matches!(def, Alias { .. }) {
AliasKind::Structural
} else {
AliasKind::Opaque
};
let region = Region::span_across(&name.region, &ann.region);
match scope.introduce(
@ -1470,27 +1514,33 @@ fn to_pending_def<'a>(
}
_ => {
// any other pattern in this position is a syntax error.
env.problems.push(Problem::InvalidAliasRigid {
let problem = Problem::InvalidAliasRigid {
alias_name: symbol,
region: loc_var.region,
});
};
env.problems.push(problem);
return Some((Output::default(), PendingDef::InvalidAlias));
return Some((
Output::default(),
PendingDef::InvalidAlias { kind },
));
}
}
}
Some((
Output::default(),
PendingDef::Alias {
name: Loc {
region: name.region,
value: symbol,
},
vars: can_rigids,
ann,
},
))
let name = Loc {
region: name.region,
value: symbol,
};
let pending_def = PendingDef::Alias {
name,
vars: can_rigids,
ann,
kind,
};
Some((Output::default(), pending_def))
}
Err((original_region, loc_shadowed_symbol, _new_symbol)) => {
@ -1499,7 +1549,7 @@ fn to_pending_def<'a>(
shadow: loc_shadowed_symbol,
});
Some((Output::default(), PendingDef::InvalidAlias))
Some((Output::default(), PendingDef::InvalidAlias { kind }))
}
}
}

View file

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

View file

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

View file

@ -73,6 +73,7 @@ pub enum Expr {
Int(Variable, Variable, Box<str>, IntValue, IntBound),
Float(Variable, Variable, Box<str>, f64, FloatBound),
Str(Box<str>),
SingleQuote(char),
List {
elem_var: Variable,
loc_elems: Vec<Loc<Expr>>,
@ -172,6 +173,11 @@ pub enum Expr {
arguments: Vec<(Variable, Loc<Expr>)>,
},
OpaqueRef {
name: Symbol,
arguments: Vec<(Variable, Loc<Expr>)>,
},
// Test
Expect(Box<Loc<Expr>>, Box<Loc<Expr>>),
@ -318,6 +324,28 @@ pub fn canonicalize_expr<'a>(
}
}
ast::Expr::Str(literal) => flatten_str_literal(env, var_store, scope, literal),
ast::Expr::SingleQuote(string) => {
let mut it = string.chars().peekable();
if let Some(char) = it.next() {
if it.peek().is_none() {
(Expr::SingleQuote(char), Output::default())
} else {
// multiple chars is found
let error = roc_problem::can::RuntimeError::MultipleCharsInSingleQuote(region);
let answer = Expr::RuntimeError(error);
(answer, Output::default())
}
} else {
// no characters found
let error = roc_problem::can::RuntimeError::EmptySingleQuote(region);
let answer = Expr::RuntimeError(error);
(answer, Output::default())
}
}
ast::Expr::List(loc_elems) => {
if loc_elems.is_empty() {
(
@ -419,6 +447,10 @@ pub fn canonicalize_expr<'a>(
name,
arguments: args,
},
OpaqueRef { name, .. } => OpaqueRef {
name,
arguments: args,
},
ZeroArgumentTag {
variant_var,
ext_var,
@ -545,7 +577,7 @@ pub fn canonicalize_expr<'a>(
output.union(new_output);
// filter out aliases
captured_symbols.retain(|s| !output.references.referenced_aliases.contains(s));
captured_symbols.retain(|s| !output.references.referenced_type_defs.contains(s));
// filter out functions that don't close over anything
captured_symbols.retain(|s| !output.non_closures.contains(s));
@ -696,6 +728,19 @@ pub fn canonicalize_expr<'a>(
Output::default(),
)
}
ast::Expr::OpaqueRef(opaque_ref) => match scope.lookup_opaque_ref(opaque_ref, region) {
Ok(name) => (
OpaqueRef {
name,
arguments: vec![],
},
Output::default(),
),
Err(runtime_error) => {
env.problem(Problem::RuntimeError(runtime_error.clone()));
(RuntimeError(runtime_error), Output::default())
}
},
ast::Expr::Expect(condition, continuation) => {
let mut output = Output::default();
@ -1245,6 +1290,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
| other @ Int(..)
| other @ Float(..)
| other @ Str { .. }
| other @ SingleQuote(_)
| other @ RuntimeError(_)
| other @ EmptyRecord
| other @ Accessor { .. }
@ -1473,6 +1519,20 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
);
}
OpaqueRef { name, arguments } => {
let arguments = arguments
.into_iter()
.map(|(var, loc_expr)| {
(
var,
loc_expr.map_owned(|expr| inline_calls(var_store, scope, expr)),
)
})
.collect();
OpaqueRef { name, arguments }
}
ZeroArgumentTag {
closure_name,
variant_var,

View file

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

View file

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

View file

@ -5,6 +5,7 @@ use crate::num::{
NumericBound, ParsedNumResult,
};
use crate::scope::Scope;
use roc_error_macros::todo_opaques;
use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::Symbol;
use roc_parse::ast::{self, StrLiteral, StrSegment};
@ -24,6 +25,11 @@ pub enum Pattern {
tag_name: TagName,
arguments: Vec<(Variable, Loc<Pattern>)>,
},
UnwrappedOpaque {
whole_var: Variable,
opaque: Symbol,
arguments: Vec<(Variable, Loc<Pattern>)>,
},
RecordDestructure {
whole_var: Variable,
ext_var: Variable,
@ -33,10 +39,12 @@ pub enum Pattern {
IntLiteral(Variable, Variable, Box<str>, IntValue, IntBound),
FloatLiteral(Variable, Variable, Box<str>, f64, FloatBound),
StrLiteral(Box<str>),
SingleQuote(char),
Underscore,
// Runtime Exceptions
Shadowed(Region, Loc<Ident>, Symbol),
OpaqueNotInScope(Loc<Ident>),
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
UnsupportedPattern(Region),
// parse error patterns
@ -78,6 +86,14 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
symbols_from_pattern_help(&nested.value, symbols);
}
}
UnwrappedOpaque {
opaque, arguments, ..
} => {
symbols.push(*opaque);
for (_, nested) in arguments {
symbols_from_pattern_help(&nested.value, symbols);
}
}
RecordDestructure { destructs, .. } => {
for destruct in destructs {
// when a record field has a pattern guard, only symbols in the guard are introduced
@ -93,9 +109,11 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
| IntLiteral(..)
| FloatLiteral(..)
| StrLiteral(_)
| SingleQuote(_)
| Underscore
| MalformedPattern(_, _)
| UnsupportedPattern(_) => {}
| UnsupportedPattern(_)
| OpaqueNotInScope(..) => {}
}
}
@ -153,17 +171,8 @@ pub fn canonicalize_pattern<'a>(
arguments: vec![],
}
}
OpaqueRef(..) => todo_opaques!(),
Apply(tag, patterns) => {
let tag_name = match tag.value {
GlobalTag(name) => TagName::Global(name.into()),
PrivateTag(name) => {
let ident_id = env.ident_ids.get_or_insert(&name.into());
TagName::Private(Symbol::new(env.home, ident_id))
}
_ => unreachable!("Other patterns cannot be applied"),
};
let mut can_patterns = Vec::with_capacity(patterns.len());
for loc_pattern in *patterns {
let (new_output, can_pattern) = canonicalize_pattern(
@ -180,11 +189,41 @@ pub fn canonicalize_pattern<'a>(
can_patterns.push((var_store.fresh(), can_pattern));
}
Pattern::AppliedTag {
whole_var: var_store.fresh(),
ext_var: var_store.fresh(),
tag_name,
arguments: can_patterns,
match tag.value {
GlobalTag(name) => {
let tag_name = TagName::Global(name.into());
Pattern::AppliedTag {
whole_var: var_store.fresh(),
ext_var: var_store.fresh(),
tag_name,
arguments: can_patterns,
}
}
PrivateTag(name) => {
let ident_id = env.ident_ids.get_or_insert(&name.into());
let tag_name = TagName::Private(Symbol::new(env.home, ident_id));
Pattern::AppliedTag {
whole_var: var_store.fresh(),
ext_var: var_store.fresh(),
tag_name,
arguments: can_patterns,
}
}
OpaqueRef(name) => match scope.lookup_opaque_ref(name, tag.region) {
Ok(opaque) => Pattern::UnwrappedOpaque {
whole_var: var_store.fresh(),
opaque,
arguments: can_patterns,
},
Err(runtime_error) => {
env.problem(Problem::RuntimeError(runtime_error));
Pattern::OpaqueNotInScope(Loc::at(tag.region, name.into()))
}
},
_ => unreachable!("Other patterns cannot be applied"),
}
}
@ -254,7 +293,7 @@ pub fn canonicalize_pattern<'a>(
}
Ok((int, bound)) => {
let sign_str = if is_negative { "-" } else { "" };
let int_str = format!("{}{}", sign_str, int.to_string()).into_boxed_str();
let int_str = format!("{}{}", sign_str, int).into_boxed_str();
let i = match int {
// Safety: this is fine because I128::MAX = |I128::MIN| - 1
IntValue::I128(n) if is_negative => IntValue::I128(-n),
@ -272,6 +311,23 @@ pub fn canonicalize_pattern<'a>(
ptype => unsupported_pattern(env, ptype, region),
},
SingleQuote(string) => {
let mut it = string.chars().peekable();
if let Some(char) = it.next() {
if it.peek().is_none() {
Pattern::SingleQuote(char)
} else {
// multiple chars is found
let problem = MalformedPatternProblem::MultipleCharsInSingleQuote;
malformed_pattern(env, problem, region)
}
} else {
// no characters found
let problem = MalformedPatternProblem::EmptySingleQuote;
malformed_pattern(env, problem, region)
}
}
SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) => {
return canonicalize_pattern(env, var_store, scope, pattern_type, sub_pattern, region)
}
@ -500,6 +556,16 @@ fn add_bindings_from_patterns(
add_bindings_from_patterns(&loc_arg.region, &loc_arg.value, answer);
}
}
UnwrappedOpaque {
arguments: loc_args,
opaque,
..
} => {
for (_, loc_arg) in loc_args {
add_bindings_from_patterns(&loc_arg.region, &loc_arg.value, answer);
}
answer.push((*opaque, *region));
}
RecordDestructure { destructs, .. } => {
for Loc {
region,
@ -513,9 +579,11 @@ fn add_bindings_from_patterns(
| IntLiteral(..)
| FloatLiteral(..)
| StrLiteral(_)
| SingleQuote(_)
| Underscore
| MalformedPattern(_, _)
| UnsupportedPattern(_) => (),
| UnsupportedPattern(_)
| OpaqueNotInScope(..) => (),
}
}

View file

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

View file

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

View file

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

View file

@ -7,6 +7,7 @@ edition = "2018"
[dependencies]
roc_collections = { path = "../collections" }
roc_error_macros = { path = "../../error_macros" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_parse = { path = "../parse" }

View file

@ -7,9 +7,9 @@ use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::Symbol;
use roc_region::all::Region;
use roc_types::subs::Variable;
use roc_types::types::Category;
use roc_types::types::Reason;
use roc_types::types::Type::{self, *};
use roc_types::types::{AliasKind, Category};
#[must_use]
pub fn add_numeric_bound_constr(
@ -162,6 +162,8 @@ fn builtin_alias(
type_arguments,
actual,
lambda_set_variables: vec![],
// TODO(opaques): revisit later
kind: AliasKind::Structural,
}
}
@ -191,6 +193,21 @@ pub fn num_floatingpoint(range: Type) -> Type {
)
}
#[inline(always)]
pub fn num_u32() -> Type {
builtin_alias(Symbol::NUM_U32, vec![], Box::new(num_int(num_unsigned32())))
}
#[inline(always)]
fn num_unsigned32() -> Type {
let alias_content = Type::TagUnion(
vec![(TagName::Private(Symbol::NUM_AT_UNSIGNED32), vec![])],
Box::new(Type::EmptyTagUnion),
);
builtin_alias(Symbol::NUM_UNSIGNED32, vec![], Box::new(alias_content))
}
#[inline(always)]
pub fn num_binary64() -> Type {
let alias_content = Type::TagUnion(

View file

@ -1,5 +1,5 @@
use crate::builtins::{
empty_list_type, float_literal, int_literal, list_type, num_literal, str_type,
empty_list_type, float_literal, int_literal, list_type, num_literal, num_u32, str_type,
};
use crate::pattern::{constrain_pattern, PatternState};
use roc_can::annotation::IntroducedVariables;
@ -12,6 +12,7 @@ use roc_can::expr::Expr::{self, *};
use roc_can::expr::{ClosureData, Field, WhenBranch};
use roc_can::pattern::Pattern;
use roc_collections::all::{ImMap, Index, MutSet, SendMap};
use roc_error_macros::todo_opaques;
use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::{Loc, Region};
@ -79,7 +80,6 @@ fn constrain_untyped_args(
loc_pattern.region,
pattern_expected,
&mut pattern_state,
true,
);
vars.push(*pattern_var);
@ -213,6 +213,7 @@ pub fn constrain_expr(
exists(vars, And(cons))
}
Str(_) => Eq(str_type(), expected, Category::Str, region),
SingleQuote(_) => Eq(num_u32(), expected, Category::Character, region),
List {
elem_var,
loc_elems,
@ -916,6 +917,8 @@ pub fn constrain_expr(
exists(vars, And(arg_cons))
}
OpaqueRef { .. } => todo_opaques!(),
RunLowLevel { args, ret_var, op } => {
// This is a modified version of what we do for function calls.
@ -1036,7 +1039,6 @@ fn constrain_when_branch(
loc_pattern.region,
pattern_expected.clone(),
&mut state,
true,
);
}
@ -1140,7 +1142,6 @@ fn constrain_def_pattern(env: &Env, loc_pattern: &Loc<Pattern>, expr_type: Type)
loc_pattern.region,
pattern_expected,
&mut state,
true,
);
state
@ -1261,7 +1262,6 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
loc_pattern.region,
pattern_expected,
&mut state,
false,
);
}
@ -1629,7 +1629,6 @@ pub fn rec_defs_help(
loc_pattern.region,
pattern_expected,
&mut state,
false,
);
}

View file

@ -5,6 +5,7 @@ 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_error_macros::todo_opaques;
use roc_module::ident::Lowercase;
use roc_module::symbol::Symbol;
use roc_region::all::{Loc, Region};
@ -55,9 +56,11 @@ fn headers_from_annotation_help(
Underscore
| MalformedPattern(_, _)
| UnsupportedPattern(_)
| OpaqueNotInScope(..)
| NumLiteral(..)
| IntLiteral(..)
| FloatLiteral(..)
| SingleQuote(_)
| StrLiteral(_) => true,
RecordDestructure { destructs, .. } => match annotation.value.shallow_dealias() {
@ -114,23 +117,8 @@ fn headers_from_annotation_help(
}
_ => false,
},
}
}
fn make_pattern_constraint(
region: Region,
category: PatternCategory,
actual: Type,
expected: PExpected<Type>,
presence_con: bool,
) -> Constraint {
if presence_con {
Constraint::Present(
actual,
PresenceConstraint::Pattern(region, category, expected),
)
} else {
Constraint::Pattern(region, category, actual, expected)
UnwrappedOpaque { .. } => todo_opaques!(),
}
}
@ -143,10 +131,9 @@ pub fn constrain_pattern(
region: Region,
expected: PExpected<Type>,
state: &mut PatternState,
destruct_position: bool,
) {
match pattern {
Underscore if destruct_position => {
Underscore => {
// This is an underscore in a position where we destruct a variable,
// like a when expression:
// when x is
@ -158,17 +145,15 @@ pub fn constrain_pattern(
PresenceConstraint::IsOpen,
));
}
Underscore | UnsupportedPattern(_) | MalformedPattern(_, _) => {
// Neither the _ pattern nor erroneous ones add any constraints.
UnsupportedPattern(_) | MalformedPattern(_, _) | OpaqueNotInScope(..) => {
// Erroneous patterns don't add any constraints.
}
Identifier(symbol) | Shadowed(_, _, symbol) => {
if destruct_position {
state.constraints.push(Constraint::Present(
expected.get_type_ref().clone(),
PresenceConstraint::IsOpen,
));
}
state.constraints.push(Constraint::Present(
expected.get_type_ref().clone(),
PresenceConstraint::IsOpen,
));
state.headers.insert(
*symbol,
Loc {
@ -268,6 +253,15 @@ pub fn constrain_pattern(
));
}
SingleQuote(_) => {
state.constraints.push(Constraint::Pattern(
region,
PatternCategory::Character,
builtins::num_u32(),
expected,
));
}
RecordDestructure {
whole_var,
ext_var,
@ -301,41 +295,36 @@ pub fn constrain_pattern(
let field_type = match typ {
DestructType::Guard(guard_var, loc_guard) => {
state.constraints.push(make_pattern_constraint(
region,
PatternCategory::PatternGuard,
state.constraints.push(Constraint::Present(
Type::Variable(*guard_var),
PExpected::ForReason(
PReason::PatternGuard,
pat_type.clone(),
loc_guard.region,
PresenceConstraint::Pattern(
region,
PatternCategory::PatternGuard,
PExpected::ForReason(
PReason::PatternGuard,
pat_type.clone(),
loc_guard.region,
),
),
destruct_position,
));
state.vars.push(*guard_var);
constrain_pattern(
env,
&loc_guard.value,
loc_guard.region,
expected,
state,
destruct_position,
);
constrain_pattern(env, &loc_guard.value, loc_guard.region, expected, state);
RecordField::Demanded(pat_type)
}
DestructType::Optional(expr_var, loc_expr) => {
state.constraints.push(make_pattern_constraint(
region,
PatternCategory::PatternDefault,
state.constraints.push(Constraint::Present(
Type::Variable(*expr_var),
PExpected::ForReason(
PReason::OptionalField,
pat_type.clone(),
loc_expr.region,
PresenceConstraint::Pattern(
region,
PatternCategory::PatternDefault,
PExpected::ForReason(
PReason::OptionalField,
pat_type.clone(),
loc_expr.region,
),
),
destruct_position,
));
state.vars.push(*expr_var);
@ -372,12 +361,9 @@ pub fn constrain_pattern(
region,
);
let record_con = make_pattern_constraint(
region,
PatternCategory::Record,
let record_con = Constraint::Present(
Type::Variable(*whole_var),
expected,
destruct_position,
PresenceConstraint::Pattern(region, PatternCategory::Record, expected),
);
state.constraints.push(whole_con);
@ -404,39 +390,21 @@ pub fn constrain_pattern(
pattern_type,
region,
);
constrain_pattern(
env,
&loc_pattern.value,
loc_pattern.region,
expected,
state,
destruct_position,
);
constrain_pattern(env, &loc_pattern.value, loc_pattern.region, expected, state);
}
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 whole_con = Constraint::Present(
expected.clone().get_type(),
PresenceConstraint::IncludesTag(tag_name.clone(), argument_types.clone()),
);
let tag_con = make_pattern_constraint(
region,
PatternCategory::Ctor(tag_name.clone()),
let tag_con = Constraint::Present(
Type::Variable(*whole_var),
expected,
destruct_position,
PresenceConstraint::Pattern(
region,
PatternCategory::Ctor(tag_name.clone()),
expected,
),
);
state.vars.push(*whole_var);
@ -444,5 +412,7 @@ pub fn constrain_pattern(
state.constraints.push(whole_con);
state.constraints.push(tag_con);
}
UnwrappedOpaque { .. } => todo_opaques!(),
}
}

View file

@ -3,7 +3,7 @@ use crate::{
spaces::{fmt_comments_only, fmt_spaces, NewlineAt, INDENT},
Buf,
};
use roc_parse::ast::{AliasHeader, AssignedField, Collection, Expr, Tag, TypeAnnotation};
use roc_parse::ast::{AssignedField, Collection, Expr, Tag, TypeAnnotation, TypeHeader};
use roc_parse::ident::UppercaseIdent;
use roc_region::all::Loc;
@ -276,7 +276,7 @@ impl<'a> Formattable for TypeAnnotation<'a> {
}
}
As(lhs, _spaces, AliasHeader { name, vars }) => {
As(lhs, _spaces, TypeHeader { name, vars }) => {
// TODO use _spaces?
lhs.value
.format_with_options(buf, Parens::InFunctionType, Newlines::No, indent);

View file

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

View file

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

View file

@ -30,6 +30,7 @@ impl<'a> Formattable for Expr<'a> {
Float(..)
| Num(..)
| NonBase10Int { .. }
| SingleQuote(_)
| Access(_, _)
| AccessorFunction(_)
| Var { .. }
@ -37,7 +38,8 @@ impl<'a> Formattable for Expr<'a> {
| MalformedIdent(_, _)
| MalformedClosure
| GlobalTag(_)
| PrivateTag(_) => false,
| PrivateTag(_)
| OpaqueRef(_) => false,
// These expressions always have newlines
Defs(_, _) | When(_, _) => true,
@ -204,10 +206,15 @@ impl<'a> Formattable for Expr<'a> {
buf.indent(indent);
buf.push_str(string);
}
GlobalTag(string) | PrivateTag(string) => {
GlobalTag(string) | PrivateTag(string) | OpaqueRef(string) => {
buf.indent(indent);
buf.push_str(string)
}
SingleQuote(string) => {
buf.push('\'');
buf.push_str(string);
buf.push('\'');
}
&NonBase10Int {
base,
string,
@ -347,7 +354,8 @@ fn push_op(buf: &mut Buf, op: BinOp) {
called_via::BinOp::Or => buf.push_str("||"),
called_via::BinOp::Pizza => buf.push_str("|>"),
called_via::BinOp::Assignment => unreachable!(),
called_via::BinOp::HasType => unreachable!(),
called_via::BinOp::IsAliasType => unreachable!(),
called_via::BinOp::IsOpaqueType => unreachable!(),
called_via::BinOp::Backpassing => unreachable!(),
}
}
@ -1067,7 +1075,11 @@ fn sub_expr_requests_parens(expr: &Expr<'_>) -> bool {
| BinOp::GreaterThanOrEq
| BinOp::And
| BinOp::Or => true,
BinOp::Pizza | BinOp::Assignment | BinOp::HasType | BinOp::Backpassing => false,
BinOp::Pizza
| BinOp::Assignment
| BinOp::IsAliasType
| BinOp::IsOpaqueType
| BinOp::Backpassing => false,
})
}
Expr::If(_, _) => true,

View file

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

View file

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

View file

@ -641,7 +641,7 @@ impl<
}
let base_offset = self.claim_stack_area(sym, struct_size);
if let Layout::Struct(field_layouts) = layout {
if let Layout::Struct { field_layouts, .. } = layout {
let mut current_offset = base_offset;
for (field, field_layout) in fields.iter().zip(field_layouts.iter()) {
self.copy_symbol_to_stack_offset(buf, current_offset, field, field_layout);

View file

@ -13,7 +13,7 @@ roc_builtins = { path = "../builtins" }
roc_error_macros = { path = "../../error_macros" }
roc_mono = { path = "../mono" }
roc_target = { path = "../roc_target" }
roc_std = { path = "../../roc_std" }
roc_std = { path = "../../roc_std", default-features = false }
morphic_lib = { path = "../../vendor/morphic_lib" }
bumpalo = { version = "3.8.0", features = ["collections"] }
inkwell = { path = "../../vendor/inkwell" }

View file

@ -1,7 +1,8 @@
/// Helpers for interacting with the zig that generates bitcode
use crate::debug_info_init;
use crate::llvm::build::{
load_roc_value, struct_from_fields, Env, C_CALL_CONV, FAST_CALL_CONV, TAG_DATA_INDEX,
complex_bitcast_check_size, load_roc_value, struct_from_fields, to_cc_return, CCReturn, Env,
C_CALL_CONV, FAST_CALL_CONV, TAG_DATA_INDEX,
};
use crate::llvm::convert::basic_type_from_layout;
use crate::llvm::refcounting::{
@ -11,9 +12,12 @@ use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::types::{BasicType, BasicTypeEnum};
use inkwell::values::{BasicValue, BasicValueEnum, CallSiteValue, FunctionValue, InstructionValue};
use inkwell::AddressSpace;
use roc_error_macros::internal_error;
use roc_module::symbol::Symbol;
use roc_mono::layout::{LambdaSet, Layout, LayoutIds, UnionLayout};
use std::convert::TryInto;
pub fn call_bitcode_fn<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
args: &[BasicValueEnum<'ctx>],
@ -92,6 +96,63 @@ fn call_bitcode_fn_help<'a, 'ctx, 'env>(
call
}
pub fn call_bitcode_fn_fixing_for_convention<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
args: &[BasicValueEnum<'ctx>],
return_layout: &Layout<'_>,
fn_name: &str,
) -> BasicValueEnum<'ctx> {
// Calling zig bitcode, so we must follow C calling conventions.
let cc_return = to_cc_return(env, return_layout);
match cc_return {
CCReturn::Return => {
// We'll get a return value
call_bitcode_fn(env, args, fn_name)
}
CCReturn::ByPointer => {
// We need to pass the return value by pointer.
let roc_return_type = basic_type_from_layout(env, return_layout);
let cc_ptr_return_type = env
.module
.get_function(fn_name)
.unwrap()
.get_type()
.get_param_types()[0]
.into_pointer_type();
let cc_return_type: BasicTypeEnum<'ctx> = cc_ptr_return_type
.get_element_type()
.try_into()
.expect("Zig bitcode return type is not a basic type!");
let cc_return_value_ptr = env.builder.build_alloca(cc_return_type, "return_value");
let fixed_args: Vec<BasicValueEnum<'ctx>> = [cc_return_value_ptr.into()]
.iter()
.chain(args)
.copied()
.collect();
call_void_bitcode_fn(env, &fixed_args, fn_name);
let cc_return_value = env.builder.build_load(cc_return_value_ptr, "read_result");
if roc_return_type.size_of() == cc_return_type.size_of() {
cc_return_value
} else {
// We need to convert the C-callconv return type, which may be larger than the Roc
// return type, into the Roc return type.
complex_bitcast_check_size(
env,
cc_return_value,
roc_return_type,
"c_value_to_roc_value",
)
}
}
CCReturn::Void => {
internal_error!("Tried to call valued bitcode function, but it has no return type")
}
}
}
const ARGUMENT_SYMBOLS: [Symbol; 8] = [
Symbol::ARG_1,
Symbol::ARG_2,
@ -313,7 +374,9 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
}
match closure_data_layout.runtime_representation() {
Layout::Struct(&[]) => {
Layout::Struct {
field_layouts: &[], ..
} => {
// nothing to add
}
other => {
@ -633,7 +696,9 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>(
let default = [value1.into(), value2.into()];
let arguments_cast = match closure_data_layout.runtime_representation() {
Layout::Struct(&[]) => {
Layout::Struct {
field_layouts: &[], ..
} => {
// nothing to add
&default
}

View file

@ -1,7 +1,9 @@
use std::convert::TryFrom;
use std::path::Path;
use crate::llvm::bitcode::{call_bitcode_fn, call_void_bitcode_fn};
use crate::llvm::bitcode::{
call_bitcode_fn, call_bitcode_fn_fixing_for_convention, call_void_bitcode_fn,
};
use crate::llvm::build_dict::{
self, dict_contains, dict_difference, dict_empty, dict_get, dict_insert, dict_intersection,
dict_keys, dict_len, dict_remove, dict_union, dict_values, dict_walk, set_from_list,
@ -53,7 +55,7 @@ use morphic_lib::{
CalleeSpecVar, FuncName, FuncSpec, FuncSpecSolutions, ModSolutions, UpdateMode, UpdateModeVar,
};
use roc_builtins::bitcode::{self, FloatWidth, IntWidth, IntrinsicName};
use roc_builtins::{float_intrinsic, int_intrinsic};
use roc_builtins::{float_intrinsic, llvm_int_intrinsic};
use roc_collections::all::{ImMap, MutMap, MutSet};
use roc_error_macros::internal_error;
use roc_module::low_level::LowLevel;
@ -609,14 +611,14 @@ static LLVM_SETJMP: &str = "llvm.eh.sjlj.setjmp";
pub static LLVM_LONGJMP: &str = "llvm.eh.sjlj.longjmp";
const LLVM_ADD_WITH_OVERFLOW: IntrinsicName =
int_intrinsic!("llvm.sadd.with.overflow", "llvm.uadd.with.overflow");
llvm_int_intrinsic!("llvm.sadd.with.overflow", "llvm.uadd.with.overflow");
const LLVM_SUB_WITH_OVERFLOW: IntrinsicName =
int_intrinsic!("llvm.ssub.with.overflow", "llvm.usub.with.overflow");
llvm_int_intrinsic!("llvm.ssub.with.overflow", "llvm.usub.with.overflow");
const LLVM_MUL_WITH_OVERFLOW: IntrinsicName =
int_intrinsic!("llvm.smul.with.overflow", "llvm.umul.with.overflow");
llvm_int_intrinsic!("llvm.smul.with.overflow", "llvm.umul.with.overflow");
const LLVM_ADD_SATURATED: IntrinsicName = int_intrinsic!("llvm.sadd.sat", "llvm.uadd.sat");
const LLVM_SUB_SATURATED: IntrinsicName = int_intrinsic!("llvm.ssub.sat", "llvm.usub.sat");
const LLVM_ADD_SATURATED: IntrinsicName = llvm_int_intrinsic!("llvm.sadd.sat", "llvm.uadd.sat");
const LLVM_SUB_SATURATED: IntrinsicName = llvm_int_intrinsic!("llvm.ssub.sat", "llvm.usub.sat");
fn add_intrinsic<'ctx>(
module: &Module<'ctx>,
@ -655,35 +657,43 @@ pub fn construct_optimization_passes<'a>(
OptLevel::Development | OptLevel::Normal => {
pmb.set_optimization_level(OptimizationLevel::None);
}
OptLevel::Size => {
pmb.set_optimization_level(OptimizationLevel::Default);
// TODO: For some usecase, like embedded, it is useful to expose this and tune it.
pmb.set_inliner_with_threshold(50);
}
OptLevel::Optimize => {
pmb.set_optimization_level(OptimizationLevel::Aggressive);
// this threshold seems to do what we want
pmb.set_inliner_with_threshold(275);
// TODO figure out which of these actually help
// function passes
fpm.add_cfg_simplification_pass();
mpm.add_cfg_simplification_pass();
fpm.add_jump_threading_pass();
mpm.add_jump_threading_pass();
fpm.add_memcpy_optimize_pass(); // this one is very important
fpm.add_licm_pass();
// turn invoke into call
mpm.add_prune_eh_pass();
// remove unused global values (often the `_wrapper` can be removed)
mpm.add_global_dce_pass();
mpm.add_function_inlining_pass();
}
}
// Add optimization passes for Size and Optimize.
if matches!(opt_level, OptLevel::Size | OptLevel::Optimize) {
// TODO figure out which of these actually help
// function passes
fpm.add_cfg_simplification_pass();
mpm.add_cfg_simplification_pass();
fpm.add_jump_threading_pass();
mpm.add_jump_threading_pass();
fpm.add_memcpy_optimize_pass(); // this one is very important
fpm.add_licm_pass();
// turn invoke into call
mpm.add_prune_eh_pass();
// remove unused global values (often the `_wrapper` can be removed)
mpm.add_global_dce_pass();
mpm.add_function_inlining_pass();
}
pmb.populate_module_pass_manager(&mpm);
pmb.populate_function_pass_manager(&fpm);
@ -712,8 +722,7 @@ fn promote_to_main_function<'a, 'ctx, 'env>(
);
// NOTE fake layout; it is only used for debug prints
let roc_main_fn =
function_value_by_func_spec(env, *func_spec, symbol, &[], &Layout::Struct(&[]));
let roc_main_fn = function_value_by_func_spec(env, *func_spec, symbol, &[], &Layout::UNIT);
let main_fn_name = "$Test.main";
@ -1186,8 +1195,8 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
// extract field from a record
match (value, layout) {
(StructValue(argument), Layout::Struct(fields)) => {
debug_assert!(!fields.is_empty());
(StructValue(argument), Layout::Struct { field_layouts, .. }) => {
debug_assert!(!field_layouts.is_empty());
let field_value = env
.builder
@ -1199,14 +1208,14 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
)
.unwrap();
let field_layout = fields[*index as usize];
let field_layout = field_layouts[*index as usize];
use_roc_value(env, field_layout, field_value, "struct_field_tag")
}
(
PointerValue(argument),
Layout::Union(UnionLayout::NonNullableUnwrapped(fields)),
) => {
let struct_layout = Layout::Struct(fields);
let struct_layout = Layout::struct_no_name_order(fields);
let struct_type = basic_type_from_layout(env, &struct_layout);
let cast_argument = env
@ -1290,7 +1299,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
)
}
UnionLayout::NonNullableUnwrapped(field_layouts) => {
let struct_layout = Layout::Struct(field_layouts);
let struct_layout = Layout::struct_no_name_order(field_layouts);
let struct_type = basic_type_from_layout(env, &struct_layout);
@ -1339,7 +1348,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
debug_assert_ne!(*tag_id != 0, *nullable_id);
let field_layouts = other_fields;
let struct_layout = Layout::Struct(field_layouts);
let struct_layout = Layout::struct_no_name_order(field_layouts);
let struct_type = basic_type_from_layout(env, &struct_layout);
@ -2022,7 +2031,7 @@ fn lookup_at_index_ptr2<'a, 'ctx, 'env>(
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let struct_layout = Layout::Struct(field_layouts);
let struct_layout = Layout::struct_no_name_order(field_layouts);
let struct_type = basic_type_from_layout(env, &struct_layout);
let wrapper_type = env
@ -2921,7 +2930,7 @@ pub fn complex_bitcast<'ctx>(
/// Check the size of the input and output types. Pretending we have more bytes at a pointer than
/// we actually do can lead to faulty optimizations and weird segfaults/crashes
fn complex_bitcast_check_size<'a, 'ctx, 'env>(
pub fn complex_bitcast_check_size<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
from_value: BasicValueEnum<'ctx>,
to_type: BasicTypeEnum<'ctx>,
@ -3520,7 +3529,7 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>(
call_roc_function(
env,
roc_wrapper_function,
&Layout::Struct(&[Layout::u64(), return_layout]),
&Layout::struct_no_name_order(&[Layout::u64(), return_layout]),
arguments_for_call,
)
};
@ -3610,7 +3619,12 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
_ => (&params[..], &param_types[..]),
};
debug_assert_eq!(params.len(), param_types.len());
debug_assert!(
params.len() == param_types.len(),
"when exposing a function to the host, params.len() was {}, but param_types.len() was {}",
params.len(),
param_types.len()
);
let it = params.iter().zip(param_types).map(|(arg, fastcc_type)| {
let arg_type = arg.get_type();
@ -3901,7 +3915,7 @@ fn roc_result_layout<'a>(
) -> Layout<'a> {
let elements = [Layout::u64(), Layout::usize(target_info), return_layout];
Layout::Struct(arena.alloc(elements))
Layout::struct_no_name_order(arena.alloc(elements))
}
fn roc_result_type<'a, 'ctx, 'env>(
@ -4073,7 +4087,13 @@ pub fn build_procedures_return_main<'a, 'ctx, 'env>(
procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>,
entry_point: EntryPoint<'a>,
) -> (&'static str, FunctionValue<'ctx>) {
let mod_solutions = build_procedures_help(env, opt_level, procedures, entry_point, None);
let mod_solutions = build_procedures_help(
env,
opt_level,
procedures,
entry_point,
Some(Path::new("/tmp/test.ll")),
);
promote_to_main_function(env, mod_solutions, entry_point.symbol, entry_point.layout)
}
@ -5355,7 +5375,7 @@ fn run_low_level<'a, 'ctx, 'env>(
let (string, _string_layout) = load_symbol_and_layout(scope, &args[0]);
let number_layout = match layout {
Layout::Struct(fields) => fields[0], // TODO: why is it sometimes a struct?
Layout::Struct { field_layouts, .. } => field_layouts[0], // TODO: why is it sometimes a struct?
_ => unreachable!(),
};
@ -5680,7 +5700,8 @@ fn run_low_level<'a, 'ctx, 'env>(
}
}
NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumLogUnchecked | NumSin | NumCos
| NumCeiling | NumFloor | NumToFloat | NumIsFinite | NumAtan | NumAcos | NumAsin => {
| NumCeiling | NumFloor | NumToFloat | NumIsFinite | NumAtan | NumAcos | NumAsin
| NumToIntChecked => {
debug_assert_eq!(args.len(), 1);
let (arg, arg_layout) = load_symbol_and_layout(scope, &args[0]);
@ -5692,7 +5713,14 @@ fn run_low_level<'a, 'ctx, 'env>(
match arg_builtin {
Int(int_width) => {
let int_type = convert::int_type_from_int_width(env, *int_width);
build_int_unary_op(env, arg.into_int_value(), int_type, op, layout)
build_int_unary_op(
env,
arg.into_int_value(),
*int_width,
int_type,
op,
layout,
)
}
Float(float_width) => build_float_unary_op(
env,
@ -6186,7 +6214,7 @@ impl RocReturn {
}
#[derive(Debug)]
enum CCReturn {
pub enum CCReturn {
/// Return as normal
Return,
/// require an extra argument, a pointer
@ -6228,7 +6256,7 @@ impl CCReturn {
}
/// According to the C ABI, how should we return a value with the given layout?
fn to_cc_return<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>) -> CCReturn {
pub fn to_cc_return<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>) -> CCReturn {
let return_size = layout.stack_size(env.target_info);
let pass_result_by_pointer = return_size > 2 * env.target_info.ptr_width() as u32;
@ -6922,7 +6950,8 @@ fn int_type_signed_min(int_type: IntType) -> IntValue {
fn build_int_unary_op<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
arg: IntValue<'ctx>,
int_type: IntType<'ctx>,
arg_width: IntWidth,
arg_int_type: IntType<'ctx>,
op: LowLevel,
return_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
@ -6933,11 +6962,11 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
match op {
NumNeg => {
// integer abs overflows when applied to the minimum value of a signed type
int_neg_raise_on_overflow(env, arg, int_type)
int_neg_raise_on_overflow(env, arg, arg_int_type)
}
NumAbs => {
// integer abs overflows when applied to the minimum value of a signed type
int_abs_raise_on_overflow(env, arg, int_type)
int_abs_raise_on_overflow(env, arg, arg_int_type)
}
NumToFloat => {
// This is an Int, so we need to convert it.
@ -6956,6 +6985,75 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
"i64_to_f64",
)
}
NumToIntChecked => {
// return_layout : Result N [ OutOfBounds ]* ~ { result: N, out_of_bounds: bool }
let target_int_width = match return_layout {
Layout::Struct { field_layouts, .. } if field_layouts.len() == 2 => {
debug_assert!(matches!(field_layouts[1], Layout::Builtin(Builtin::Bool)));
match field_layouts[0] {
Layout::Builtin(Builtin::Int(iw)) => iw,
layout => internal_error!(
"There can only be an int layout here, found {:?}!",
layout
),
}
}
layout => internal_error!(
"There can only be a result layout here, found {:?}!",
layout
),
};
let arg_always_fits_in_target = (arg_width.stack_size() < target_int_width.stack_size()
&& (
// If the arg is unsigned, it will always fit in either a signed or unsigned
// int of a larger width.
!arg_width.is_signed()
||
// Otherwise if the arg is signed, it will always fit in a signed int of a
// larger width.
(target_int_width.is_signed() )
) )
|| // Or if the two types are the same, they trivially fit.
arg_width == target_int_width;
if arg_always_fits_in_target {
// This is guaranteed to succeed so we can just make it an int cast and let LLVM
// optimize it away.
let target_int_type = convert::int_type_from_int_width(env, target_int_width);
let target_int_val: BasicValueEnum<'ctx> = env
.builder
.build_int_cast(arg, target_int_type, "int_cast")
.into();
let return_type =
convert::basic_type_from_layout(env, return_layout).into_struct_type();
let r = return_type.const_zero();
let r = bd
.build_insert_value(r, target_int_val, 0, "converted_int")
.unwrap();
let r = bd
.build_insert_value(r, env.context.bool_type().const_zero(), 1, "out_of_bounds")
.unwrap();
r.into_struct_value().into()
} else {
let bitcode_fn = if !arg_width.is_signed() {
// We are trying to convert from unsigned to signed/unsigned of same or lesser width, e.g.
// u16 -> i16, u16 -> i8, or u16 -> u8. We only need to check that the argument
// value fits in the MAX target type value.
&bitcode::NUM_INT_TO_INT_CHECKING_MAX[target_int_width][arg_width]
} else {
// We are trying to convert from signed to signed/unsigned of same or lesser width, e.g.
// i16 -> u16, i16 -> i8, or i16 -> u8. We need to check that the argument value fits in
// the MAX and MIN target type.
&bitcode::NUM_INT_TO_INT_CHECKING_MAX_AND_MIN[target_int_width][arg_width]
};
call_bitcode_fn_fixing_for_convention(env, &[arg.into()], return_layout, bitcode_fn)
}
}
_ => {
unreachable!("Unrecognized int unary operation: {:?}", op);
}

View file

@ -735,8 +735,7 @@ pub fn set_from_list<'a, 'ctx, 'env>(
let result_alloca = builder.build_alloca(zig_dict_type(env), "result_alloca");
let alignment =
Alignment::from_key_value_layout(key_layout, &Layout::Struct(&[]), env.target_info);
let alignment = Alignment::from_key_value_layout(key_layout, &Layout::UNIT, env.target_info);
let alignment_iv = alignment.as_int_value(env.context);
let hash_fn = build_hash_wrapper(env, layout_ids, key_layout);

View file

@ -50,10 +50,10 @@ fn build_hash_layout<'a, 'ctx, 'env>(
hash_builtin(env, layout_ids, seed, val, layout, builtin, when_recursive)
}
Layout::Struct(fields) => build_hash_struct(
Layout::Struct { field_layouts, .. } => build_hash_struct(
env,
layout_ids,
fields,
field_layouts,
when_recursive,
seed,
val.into_struct_value(),
@ -166,7 +166,7 @@ fn build_hash_struct<'a, 'ctx, 'env>(
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let struct_layout = Layout::Struct(field_layouts);
let struct_layout = Layout::struct_no_name_order(field_layouts);
let symbol = Symbol::GENERIC_HASH;
let fn_name = layout_ids
@ -248,7 +248,7 @@ fn hash_struct<'a, 'ctx, 'env>(
) -> IntValue<'ctx> {
let ptr_bytes = env.target_info;
let layout = Layout::Struct(field_layouts);
let layout = Layout::struct_no_name_order(field_layouts);
// Optimization: if the bit representation of equal values is the same
// just hash the bits. Caveat here is tags: e.g. `Nothing` in `Just a`
@ -818,7 +818,7 @@ fn hash_ptr_to_struct<'a, 'ctx, 'env>(
.build_struct_gep(wrapper_ptr, TAG_DATA_INDEX, "get_tag_data")
.unwrap();
let struct_layout = Layout::Struct(field_layouts);
let struct_layout = Layout::struct_no_name_order(field_layouts);
let struct_type = basic_type_from_layout(env, &struct_layout);
let struct_ptr = env
.builder

View file

@ -161,10 +161,10 @@ fn build_eq<'a, 'ctx, 'env>(
build_eq_builtin(env, layout_ids, lhs_val, rhs_val, builtin, when_recursive)
}
Layout::Struct(fields) => build_struct_eq(
Layout::Struct { field_layouts, .. } => build_struct_eq(
env,
layout_ids,
fields,
field_layouts,
when_recursive,
lhs_val.into_struct_value(),
rhs_val.into_struct_value(),
@ -330,11 +330,11 @@ fn build_neq<'a, 'ctx, 'env>(
build_neq_builtin(env, layout_ids, lhs_val, rhs_val, builtin, when_recursive)
}
Layout::Struct(fields) => {
Layout::Struct { field_layouts, .. } => {
let is_equal = build_struct_eq(
env,
layout_ids,
fields,
field_layouts,
when_recursive,
lhs_val.into_struct_value(),
rhs_val.into_struct_value(),
@ -587,7 +587,7 @@ fn build_struct_eq<'a, 'ctx, 'env>(
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let struct_layout = Layout::Struct(field_layouts);
let struct_layout = Layout::struct_no_name_order(field_layouts);
let symbol = Symbol::GENERIC_EQ;
let fn_name = layout_ids
@ -1208,7 +1208,7 @@ fn eq_ptr_to_struct<'a, 'ctx, 'env>(
tag1: PointerValue<'ctx>,
tag2: PointerValue<'ctx>,
) -> IntValue<'ctx> {
let struct_layout = Layout::Struct(field_layouts);
let struct_layout = Layout::struct_no_name_order(field_layouts);
let wrapper_type = basic_type_from_layout(env, &struct_layout);
debug_assert!(wrapper_type.is_struct_type());

View file

@ -28,7 +28,10 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>(
use Layout::*;
match layout {
Struct(sorted_fields) => basic_type_from_record(env, sorted_fields),
Struct {
field_layouts: sorted_fields,
..
} => basic_type_from_record(env, sorted_fields),
LambdaSet(lambda_set) => basic_type_from_layout(env, &lambda_set.runtime_representation()),
Union(union_layout) => {
use UnionLayout::*;
@ -86,7 +89,10 @@ pub fn basic_type_from_layout_1<'a, 'ctx, 'env>(
use Layout::*;
match layout {
Struct(sorted_fields) => basic_type_from_record(env, sorted_fields),
Struct {
field_layouts: sorted_fields,
..
} => basic_type_from_record(env, sorted_fields),
LambdaSet(lambda_set) => {
basic_type_from_layout_1(env, &lambda_set.runtime_representation())
}

View file

@ -280,7 +280,7 @@ fn modify_refcount_struct<'a, 'ctx, 'env>(
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let layout = Layout::Struct(layouts);
let layout = Layout::struct_no_name_order(layouts);
let (_, fn_name) = function_name_from_mode(
layout_ids,
@ -440,7 +440,7 @@ fn modify_refcount_builtin<'a, 'ctx, 'env>(
}
Set(element_layout) => {
let key_layout = element_layout;
let value_layout = &Layout::Struct(&[]);
let value_layout = &Layout::UNIT;
let function = modify_refcount_dict(
env,
@ -619,8 +619,9 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>(
}
}
Struct(layouts) => {
let function = modify_refcount_struct(env, layout_ids, layouts, mode, when_recursive);
Struct { field_layouts, .. } => {
let function =
modify_refcount_struct(env, layout_ids, field_layouts, mode, when_recursive);
Some(function)
}
@ -1312,7 +1313,8 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
env.builder.position_at_end(block);
let wrapper_type = basic_type_from_layout(env, &Layout::Struct(field_layouts));
let wrapper_type =
basic_type_from_layout(env, &Layout::struct_no_name_order(field_layouts));
// cast the opaque pointer to a pointer of the correct shape
let struct_ptr = env
@ -1720,7 +1722,8 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
let block = env.context.append_basic_block(parent, "tag_id_modify");
env.builder.position_at_end(block);
let wrapper_type = basic_type_from_layout(env, &Layout::Struct(field_layouts));
let wrapper_type =
basic_type_from_layout(env, &Layout::struct_no_name_order(field_layouts));
debug_assert!(wrapper_type.is_struct_type());
let opaque_tag_data_ptr = env

View file

@ -12,5 +12,5 @@ roc_collections = { path = "../collections" }
roc_module = { path = "../module" }
roc_mono = { path = "../mono" }
roc_target = { path = "../roc_target" }
roc_std = { path = "../../roc_std" }
roc_std = { path = "../../roc_std", default-features = false }
roc_error_macros = { path = "../../error_macros" }

View file

@ -44,7 +44,7 @@ pub struct WasmBackend<'a> {
next_constant_addr: u32,
fn_index_offset: u32,
called_preload_fns: Vec<'a, u32>,
proc_symbols: Vec<'a, (Symbol, u32)>,
proc_lookup: Vec<'a, (Symbol, ProcLayout<'a>, u32)>,
helper_proc_gen: CodeGenHelp<'a>,
// Function-level data
@ -62,7 +62,7 @@ impl<'a> WasmBackend<'a> {
env: &'a Env<'a>,
interns: &'a mut Interns,
layout_ids: LayoutIds<'a>,
proc_symbols: Vec<'a, (Symbol, u32)>,
proc_lookup: Vec<'a, (Symbol, ProcLayout<'a>, u32)>,
mut module: WasmModule<'a>,
fn_index_offset: u32,
helper_proc_gen: CodeGenHelp<'a>,
@ -89,7 +89,7 @@ impl<'a> WasmBackend<'a> {
next_constant_addr: CONST_SEGMENT_BASE_ADDR,
fn_index_offset,
called_preload_fns: Vec::with_capacity_in(2, env.arena),
proc_symbols,
proc_lookup,
helper_proc_gen,
// Function-level data
@ -106,7 +106,7 @@ impl<'a> WasmBackend<'a> {
fn register_helper_proc(&mut self, new_proc_info: (Symbol, ProcLayout<'a>)) {
let (new_proc_sym, new_proc_layout) = new_proc_info;
let wasm_fn_index = self.proc_symbols.len() as u32;
let wasm_fn_index = self.proc_lookup.len() as u32;
let linker_sym_index = self.module.linking.symbol_table.len() as u32;
let name = self
@ -114,7 +114,8 @@ impl<'a> WasmBackend<'a> {
.get_toplevel(new_proc_sym, &new_proc_layout)
.to_symbol_string(new_proc_sym, self.interns);
self.proc_symbols.push((new_proc_sym, linker_sym_index));
self.proc_lookup
.push((new_proc_sym, new_proc_layout, linker_sym_index));
let linker_symbol = SymInfo::Function(WasmObjectSymbol::Defined {
flags: 0,
index: wasm_fn_index,
@ -742,8 +743,24 @@ impl<'a> WasmBackend<'a> {
ret_storage: &StoredValue,
) {
match call_type {
CallType::ByName { name: func_sym, .. } => {
self.expr_call_by_name(*func_sym, arguments, ret_sym, ret_layout, ret_storage)
CallType::ByName {
name: func_sym,
arg_layouts,
ret_layout: result,
..
} => {
let proc_layout = ProcLayout {
arguments: arg_layouts,
result: **result,
};
self.expr_call_by_name(
*func_sym,
&proc_layout,
arguments,
ret_sym,
ret_layout,
ret_storage,
)
}
CallType::LowLevel { op: lowlevel, .. } => {
self.expr_call_low_level(*lowlevel, arguments, ret_sym, ret_layout, ret_storage)
@ -756,6 +773,7 @@ impl<'a> WasmBackend<'a> {
fn expr_call_by_name(
&mut self,
func_sym: Symbol,
proc_layout: &ProcLayout<'a>,
arguments: &'a [Symbol],
ret_sym: Symbol,
ret_layout: &Layout<'a>,
@ -779,9 +797,10 @@ impl<'a> WasmBackend<'a> {
CallConv::C,
);
for (roc_proc_index, (ir_sym, linker_sym_index)) in self.proc_symbols.iter().enumerate() {
let wasm_fn_index = self.fn_index_offset + roc_proc_index as u32;
if *ir_sym == func_sym {
let iter = self.proc_lookup.iter().enumerate();
for (roc_proc_index, (ir_sym, pl, linker_sym_index)) in iter {
if *ir_sym == func_sym && pl == proc_layout {
let wasm_fn_index = self.fn_index_offset + roc_proc_index as u32;
let num_wasm_args = param_types.len();
let has_return_val = ret_type.is_some();
self.code_builder.call(
@ -795,9 +814,10 @@ impl<'a> WasmBackend<'a> {
}
internal_error!(
"Could not find procedure {:?}\nKnown procedures: {:?}",
"Could not find procedure {:?} with proc_layout {:?}\nKnown procedures: {:#?}",
func_sym,
self.proc_symbols
proc_layout,
self.proc_lookup
);
}
@ -884,7 +904,7 @@ impl<'a> WasmBackend<'a> {
storage: &StoredValue,
fields: &'a [Symbol],
) {
if matches!(layout, Layout::Struct(_)) {
if matches!(layout, Layout::Struct { .. }) {
match storage {
StoredValue::StackMemory { location, size, .. } => {
if *size > 0 {

View file

@ -88,7 +88,7 @@ impl WasmLayout {
},
Layout::Builtin(Str | Dict(_, _) | Set(_) | List(_))
| Layout::Struct(_)
| Layout::Struct { .. }
| Layout::LambdaSet(_)
| Layout::Union(NonRecursive(_)) => Self::StackMemory {
size,

View file

@ -73,7 +73,7 @@ pub fn build_module_without_wrapper<'a>(
) -> (WasmModule<'a>, Vec<'a, u32>, u32) {
let mut layout_ids = LayoutIds::default();
let mut procs = Vec::with_capacity_in(procedures.len(), env.arena);
let mut proc_symbols = Vec::with_capacity_in(procedures.len() * 2, env.arena);
let mut proc_lookup = Vec::with_capacity_in(procedures.len() * 2, env.arena);
let mut linker_symbols = Vec::with_capacity_in(procedures.len() * 2, env.arena);
let mut exports = Vec::with_capacity_in(4, env.arena);
let mut maybe_main_fn_index = None;
@ -81,7 +81,7 @@ pub fn build_module_without_wrapper<'a>(
// Collect the symbols & names for the procedures,
// and filter out procs we're going to inline
let mut fn_index: u32 = 0;
for ((sym, layout), proc) in procedures.into_iter() {
for ((sym, proc_layout), proc) in procedures.into_iter() {
if matches!(
LowLevelWrapperType::from_symbol(sym),
LowLevelWrapperType::CanBeReplacedBy(_)
@ -91,7 +91,7 @@ pub fn build_module_without_wrapper<'a>(
procs.push(proc);
let fn_name = layout_ids
.get_toplevel(sym, &layout)
.get_toplevel(sym, &proc_layout)
.to_symbol_string(sym, interns);
if env.exposed_to_host.contains(&sym) {
@ -104,7 +104,10 @@ pub fn build_module_without_wrapper<'a>(
}
let linker_sym = SymInfo::for_function(fn_index, fn_name);
proc_symbols.push((sym, linker_symbols.len() as u32));
let linker_sym_index = linker_symbols.len() as u32;
// linker_sym_index is redundant for these procs from user code, but needed for generated helpers!
proc_lookup.push((sym, proc_layout, linker_sym_index));
linker_symbols.push(linker_sym);
fn_index += 1;
@ -121,7 +124,7 @@ pub fn build_module_without_wrapper<'a>(
env,
interns,
layout_ids,
proc_symbols,
proc_lookup,
initial_module,
fn_index_offset,
CodeGenHelp::new(env.arena, TargetInfo::default_wasm32(), env.module_id),

View file

@ -212,7 +212,7 @@ impl<'a> LowLevelCall<'a> {
}
StrToNum => {
let number_layout = match self.ret_layout {
Layout::Struct(fields) => fields[0],
Layout::Struct { field_layouts, .. } => field_layouts[0],
_ => {
internal_error!("Unexpected mono layout {:?} for StrToNum", self.ret_layout)
}
@ -655,6 +655,9 @@ impl<'a> LowLevelCall<'a> {
_ => todo!("{:?}: {:?} -> {:?}", self.lowlevel, arg_type, ret_type),
}
}
NumToIntChecked => {
todo!()
}
And => {
self.load_args(backend);
backend.code_builder.i32_and();
@ -708,7 +711,7 @@ impl<'a> LowLevelCall<'a> {
// Empty record is always equal to empty record.
// There are no runtime arguments to check, so just emit true or false.
Layout::Struct(fields) if fields.is_empty() => {
Layout::Struct { field_layouts, .. } if field_layouts.is_empty() => {
backend.code_builder.i32_const(!invert_result as i32);
}
@ -719,7 +722,7 @@ impl<'a> LowLevelCall<'a> {
}
Layout::Builtin(Builtin::Dict(_, _) | Builtin::Set(_) | Builtin::List(_))
| Layout::Struct(_)
| Layout::Struct { .. }
| Layout::Union(_)
| Layout::LambdaSet(_) => {
// Don't want Zig calling convention here, we're calling internal Roc functions

View file

@ -7,6 +7,7 @@ The user needs to analyse the Wasm module's memory to decode the result.
use bumpalo::{collections::Vec, Bump};
use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_mono::layout::{Builtin, Layout};
use roc_std::ReferenceCount;
use roc_target::TargetInfo;
use crate::wasm32_sized::Wasm32Sized;
@ -175,7 +176,7 @@ wasm_result_stack_memory!(i128);
wasm_result_stack_memory!(RocDec);
wasm_result_stack_memory!(RocStr);
impl<T: Wasm32Result> Wasm32Result for RocList<T> {
impl<T: Wasm32Result + ReferenceCount> Wasm32Result for RocList<T> {
fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) {
build_wrapper_body_stack_memory(code_builder, main_function_index, 12)
}

View file

@ -1,4 +1,4 @@
use roc_std::{RocDec, RocList, RocOrder, RocStr};
use roc_std::{ReferenceCount, RocDec, RocList, RocOrder, RocStr};
pub trait Wasm32Sized: Sized {
const SIZE_OF_WASM: usize;
@ -35,7 +35,7 @@ impl Wasm32Sized for RocStr {
const ALIGN_OF_WASM: usize = 4;
}
impl<T: Wasm32Sized> Wasm32Sized for RocList<T> {
impl<T: Wasm32Sized + ReferenceCount> Wasm32Sized for RocList<T> {
const SIZE_OF_WASM: usize = 8;
const ALIGN_OF_WASM: usize = 4;
}

View file

@ -3,7 +3,8 @@
use core::cmp::Ordering;
use core::convert::From;
use core::{fmt, mem, ptr, slice};
use std::alloc::{GlobalAlloc, Layout, System};
use std::alloc::{alloc, dealloc, Layout};
use std::os::raw::c_char;
/// A string which can store identifiers using the small string optimization.
/// It relies on the invariant that it cannot store null characters to store
@ -66,19 +67,7 @@ impl IdentStr {
}
pub fn get(&self, index: usize) -> Option<&u8> {
if index < self.len() {
Some(unsafe {
let raw = if self.is_small_str() {
self.get_small_str_ptr().add(index)
} else {
self.elements.add(index)
};
&*raw
})
} else {
None
}
self.as_bytes().get(index)
}
pub fn get_bytes(&self) -> *const u8 {
@ -93,59 +82,38 @@ impl IdentStr {
(self as *const IdentStr).cast()
}
fn from_slice(slice: &[u8]) -> Self {
fn from_str(str: &str) -> Self {
let slice = str.as_bytes();
let len = slice.len();
match len.cmp(&mem::size_of::<Self>()) {
Ordering::Less => {
// This fits in a small string, but needs its length recorded
let mut answer_bytes: [u8; mem::size_of::<Self>()] = unsafe {
mem::transmute::<Self, [u8; mem::size_of::<Self>()]>(Self::default())
};
let mut bytes = [0; mem::size_of::<Self>()];
// Copy the bytes from the slice into the answer
let dest_slice =
unsafe { slice::from_raw_parts_mut(&mut answer_bytes as *mut u8, len) };
dest_slice.copy_from_slice(slice);
let mut answer: Self =
unsafe { mem::transmute::<[u8; mem::size_of::<Self>()], Self>(answer_bytes) };
// Copy the bytes from the slice into bytes.
bytes[..len].copy_from_slice(slice);
// Write length and small string bit to last byte of length.
{
let mut bytes = answer.length.to_ne_bytes();
bytes[mem::size_of::<usize>() * 2 - 1] = u8::MAX - len as u8;
bytes[mem::size_of::<usize>() - 1] = u8::MAX - len as u8;
answer.length = usize::from_ne_bytes(bytes);
}
answer
unsafe { mem::transmute::<[u8; mem::size_of::<Self>()], Self>(bytes) }
}
Ordering::Equal => {
// This fits in a small string, and is exactly long enough to
// take up the entire available struct
let mut answer_bytes: [u8; mem::size_of::<Self>()] = unsafe {
mem::transmute::<Self, [u8; mem::size_of::<Self>()]>(Self::default())
};
let mut bytes = [0; mem::size_of::<Self>()];
// Copy the bytes from the slice into the answer
let dest_slice = unsafe {
slice::from_raw_parts_mut(&mut answer_bytes as *mut u8, mem::size_of::<Self>())
};
bytes.copy_from_slice(slice);
dest_slice.copy_from_slice(slice);
unsafe { mem::transmute::<[u8; mem::size_of::<Self>()], Self>(answer_bytes) }
unsafe { mem::transmute::<[u8; mem::size_of::<Self>()], Self>(bytes) }
}
Ordering::Greater => {
// This needs a big string
let align = mem::align_of::<u8>();
let elements = unsafe {
let align = mem::align_of::<u8>();
let layout = Layout::from_size_align_unchecked(len, align);
System.alloc(layout)
alloc(layout)
};
// Turn the new elements into a slice, and copy the existing
@ -167,7 +135,9 @@ impl IdentStr {
pub fn as_slice(&self) -> &[u8] {
use core::slice::from_raw_parts;
if self.is_small_str() {
if self.is_empty() {
&[]
} else if self.is_small_str() {
unsafe { from_raw_parts(self.get_small_str_ptr(), self.len()) }
} else {
unsafe { from_raw_parts(self.elements, self.length) }
@ -186,15 +156,12 @@ impl IdentStr {
/// # Safety
/// This assumes the given buffer has enough space, so make sure you only
/// pass in a pointer to an allocation that's at least as long as this Str!
pub unsafe fn write_c_str(&self, buf: *mut char) {
if self.is_small_str() {
ptr::copy_nonoverlapping(self.get_small_str_ptr(), buf as *mut u8, self.len());
} else {
ptr::copy_nonoverlapping(self.elements, buf as *mut u8, self.len());
}
pub unsafe fn write_c_str(&self, buf: *mut c_char) {
let bytes = self.as_bytes();
ptr::copy_nonoverlapping(bytes.as_ptr().cast(), buf, bytes.len());
// null-terminate
*(buf.add(self.len())) = '\0';
*buf.add(self.len()) = 0;
}
}
@ -217,13 +184,13 @@ impl std::ops::Deref for IdentStr {
impl From<&str> for IdentStr {
fn from(str: &str) -> Self {
Self::from_slice(str.as_bytes())
Self::from_str(str)
}
}
impl From<String> for IdentStr {
fn from(str: String) -> Self {
Self::from_slice(str.as_bytes())
Self::from_str(&str)
}
}
@ -279,44 +246,17 @@ impl std::hash::Hash for IdentStr {
impl Clone for IdentStr {
fn clone(&self) -> Self {
if self.is_small_str() || self.is_empty() {
Self {
elements: self.elements,
length: self.length,
}
} else {
let capacity_size = core::mem::size_of::<usize>();
let copy_length = self.length + capacity_size;
let elements = unsafe {
let align = mem::align_of::<u8>();
let layout = Layout::from_size_align_unchecked(copy_length, align);
let raw_ptr = System.alloc(layout);
let dest_slice = slice::from_raw_parts_mut(raw_ptr, copy_length);
let src_ptr = self.elements as *mut u8;
let src_slice = slice::from_raw_parts(src_ptr, copy_length);
dest_slice.copy_from_slice(src_slice);
raw_ptr as *mut u8
};
Self {
elements,
length: self.length,
}
}
Self::from_str(self.as_str())
}
}
impl Drop for IdentStr {
fn drop(&mut self) {
if !self.is_empty() && !self.is_small_str() {
let align = mem::align_of::<u8>();
unsafe {
let align = mem::align_of::<u8>();
let layout = Layout::from_size_align_unchecked(self.length, align);
System.dealloc(self.elements as *mut _, layout);
dealloc(self.elements as *mut _, layout);
}
}
}

View file

@ -7,6 +7,7 @@ edition = "2018"
[dependencies]
roc_collections = { path = "../collections" }
roc_error_macros = { path = "../../error_macros" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_types = { path = "../types" }
@ -23,7 +24,7 @@ roc_reporting = { path = "../../reporting" }
morphic_lib = { path = "../../vendor/morphic_lib" }
ven_pretty = { path = "../../vendor/pretty" }
bumpalo = { version = "3.8.0", features = ["collections"] }
parking_lot = { version = "0.11.2" }
parking_lot = "0.11.2"
crossbeam = "0.8.1"
num_cpus = "1.13.0"
@ -32,3 +33,4 @@ tempfile = "3.2.0"
pretty_assertions = "1.0.0"
maplit = "1.0.2"
indoc = "1.0.3"
strip-ansi-escapes = "0.1.1"

View file

@ -7,7 +7,7 @@ use roc_can::scope::Scope;
use roc_module::ident::ModuleName;
use roc_module::symbol::IdentIds;
use roc_parse::ast::CommentOrNewline;
use roc_parse::ast::{self, AliasHeader};
use roc_parse::ast::{self, TypeHeader};
use roc_parse::ast::{AssignedField, Def};
use roc_region::all::Loc;
@ -206,7 +206,7 @@ fn generate_entry_doc<'a>(
},
Def::Alias {
header: AliasHeader { name, vars },
header: TypeHeader { name, vars },
ann,
} => {
let mut type_vars = Vec::new();
@ -228,6 +228,29 @@ fn generate_entry_doc<'a>(
(acc, None)
}
Def::Opaque {
header: TypeHeader { name, vars },
..
} => {
let mut type_vars = Vec::new();
for var in vars.iter() {
if let Pattern::Identifier(ident_name) = var.value {
type_vars.push(ident_name.to_string());
}
}
let doc_def = DocDef {
name: name.value.to_string(),
type_annotation: TypeAnnotation::NoTypeAnn,
type_vars,
docs: before_comments_or_new_lines.and_then(comments_or_new_lines_to_docs),
};
acc.push(DocEntry::DocDef(doc_def));
(acc, None)
}
Def::Body(_, _) => (acc, None),
Def::Expect(c) => todo!("documentation for tests {:?}", c),

View file

@ -2101,7 +2101,7 @@ fn finish_specialization(
EntryPoint {
layout: roc_mono::ir::ProcLayout {
arguments: &[],
result: Layout::Struct(&[]),
result: Layout::struct_no_name_order(&[]),
},
symbol,
}

View file

@ -23,14 +23,45 @@ mod test_load {
use roc_load::file::LoadedModule;
use roc_module::ident::ModuleName;
use roc_module::symbol::{Interns, ModuleId};
use roc_problem::can::Problem;
use roc_region::all::LineInfo;
use roc_reporting::report::can_problem;
use roc_reporting::report::RocDocAllocator;
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
use roc_types::subs::Subs;
use std::collections::HashMap;
use std::path::PathBuf;
const TARGET_INFO: roc_target::TargetInfo = roc_target::TargetInfo::default_x86_64();
// HELPERS
fn format_can_problems(
problems: Vec<Problem>,
home: ModuleId,
interns: &Interns,
filename: PathBuf,
src: &str,
) -> String {
use ven_pretty::DocAllocator;
let src_lines: Vec<&str> = src.split('\n').collect();
let lines = LineInfo::new(src);
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
let reports = problems
.into_iter()
.map(|problem| can_problem(&alloc, &lines, filename.clone(), problem).pretty(&alloc));
let mut buf = String::new();
alloc
.stack(reports)
.append(alloc.line())
.1
.render_raw(70, &mut roc_reporting::report::CiWrite::new(&mut buf))
.unwrap();
buf
}
fn multiple_modules(files: Vec<(&str, &str)>) -> Result<LoadedModule, String> {
use roc_load::file::LoadingProblem;
@ -43,11 +74,19 @@ mod test_load {
Ok(Err(loading_problem)) => Err(format!("{:?}", loading_problem)),
Ok(Ok(mut loaded_module)) => {
let home = loaded_module.module_id;
let (filepath, src) = loaded_module.sources.get(&home).unwrap();
let can_problems = loaded_module.can_problems.remove(&home).unwrap_or_default();
if !can_problems.is_empty() {
return Err(format_can_problems(
can_problems,
home,
&loaded_module.interns,
filepath.clone(),
src,
));
}
assert_eq!(
loaded_module.can_problems.remove(&home).unwrap_or_default(),
Vec::new()
);
assert_eq!(
loaded_module
.type_problems
@ -67,7 +106,6 @@ mod test_load {
) -> Result<Result<LoadedModule, roc_load::file::LoadingProblem<'a>>, std::io::Error> {
use std::fs::{self, File};
use std::io::Write;
use std::path::PathBuf;
use tempfile::tempdir;
let stdlib = roc_builtins::std::standard_stdlib();
@ -682,4 +720,81 @@ mod test_load {
assert!(multiple_modules(modules).is_ok());
}
#[test]
fn opaque_wrapped_unwrapped_outside_defining_module() {
let modules = vec![
(
"Age",
indoc!(
r#"
interface Age exposes [ Age ] imports []
Age := U32
"#
),
),
(
"Main",
indoc!(
r#"
interface Main exposes [ twenty, readAge ] imports [ Age.{ Age } ]
twenty = $Age 20
readAge = \$Age n -> n
"#
),
),
];
let err = multiple_modules(modules).unwrap_err();
let err = strip_ansi_escapes::strip(err).unwrap();
let err = String::from_utf8(err).unwrap();
assert_eq!(
err,
indoc!(
r#"
OPAQUE DECLARED OUTSIDE SCOPE
The unwrapped opaque type Age referenced here:
3 twenty = $Age 20
^^^^
is imported from another module:
1 interface Main exposes [ twenty, readAge ] imports [ Age.{ Age } ]
^^^^^^^^^^^
Note: Opaque types can only be wrapped and unwrapped in the module they are defined in!
OPAQUE DECLARED OUTSIDE SCOPE
The unwrapped opaque type Age referenced here:
5 readAge = \$Age n -> n
^^^^
is imported from another module:
1 interface Main exposes [ twenty, readAge ] imports [ Age.{ Age } ]
^^^^^^^^^^^
Note: Opaque types can only be wrapped and unwrapped in the module they are defined in!
UNUSED IMPORT
Nothing from Age is used in this module.
1 interface Main exposes [ twenty, readAge ] imports [ Age.{ Age } ]
^^^^^^^^^^^
Since Age isn't used, you don't need to import it.
"#
),
"\n{}",
err
);
}
}

View file

@ -47,7 +47,8 @@ pub enum BinOp {
Or,
Pizza,
Assignment,
HasType,
IsAliasType,
IsOpaqueType,
Backpassing,
// lowest precedence
}
@ -59,7 +60,7 @@ impl BinOp {
Caret | Star | Slash | Percent | Plus | Minus | LessThan | GreaterThan => 1,
DoubleSlash | DoublePercent | Equals | NotEquals | LessThanOrEq | GreaterThanOrEq
| And | Or | Pizza => 2,
Assignment | HasType | Backpassing => unreachable!(),
Assignment | IsAliasType | IsOpaqueType | Backpassing => unreachable!(),
}
}
}
@ -103,7 +104,7 @@ impl BinOp {
Equals | NotEquals | LessThan | GreaterThan | LessThanOrEq | GreaterThanOrEq => {
NonAssociative
}
Assignment | HasType | Backpassing => unreachable!(),
Assignment | IsAliasType | IsOpaqueType | Backpassing => unreachable!(),
}
}
@ -116,7 +117,7 @@ impl BinOp {
And => 3,
Or => 2,
Pizza => 1,
Assignment | HasType | Backpassing => unreachable!(),
Assignment | IsAliasType | IsOpaqueType | Backpassing => unreachable!(),
}
}
}
@ -154,7 +155,8 @@ impl std::fmt::Display for BinOp {
Or => "||",
Pizza => "|>",
Assignment => "=",
HasType => ":",
IsAliasType => ":",
IsOpaqueType => ":=",
Backpassing => "<-",
};

View file

@ -111,6 +111,7 @@ pub enum LowLevel {
NumShiftRightBy,
NumShiftRightZfBy,
NumIntCast,
NumToIntChecked,
NumToStr,
Eq,
NotEq,

View file

@ -1010,6 +1010,26 @@ define_builtins! {
122 NUM_MAX_U64: "maxU64"
123 NUM_MIN_I128: "minI128"
124 NUM_MAX_I128: "maxI128"
125 NUM_TO_I8: "toI8"
126 NUM_TO_I8_CHECKED: "toI8Checked"
127 NUM_TO_I16: "toI16"
128 NUM_TO_I16_CHECKED: "toI16Checked"
129 NUM_TO_I32: "toI32"
130 NUM_TO_I32_CHECKED: "toI32Checked"
131 NUM_TO_I64: "toI64"
132 NUM_TO_I64_CHECKED: "toI64Checked"
133 NUM_TO_I128: "toI128"
134 NUM_TO_I128_CHECKED: "toI128Checked"
135 NUM_TO_U8: "toU8"
136 NUM_TO_U8_CHECKED: "toU8Checked"
137 NUM_TO_U16: "toU16"
138 NUM_TO_U16_CHECKED: "toU16Checked"
139 NUM_TO_U32: "toU32"
140 NUM_TO_U32_CHECKED: "toU32Checked"
141 NUM_TO_U64: "toU64"
142 NUM_TO_U64_CHECKED: "toU64Checked"
143 NUM_TO_U128: "toU128"
144 NUM_TO_U128_CHECKED: "toU128Checked"
}
2 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias

View file

@ -13,7 +13,7 @@ roc_types = { path = "../types" }
roc_can = { path = "../can" }
roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" }
roc_std = { path = "../../roc_std" }
roc_std = { path = "../../roc_std", default-features = false }
roc_problem = { path = "../problem" }
roc_builtins = { path = "../builtins" }
roc_target = { path = "../roc_target" }

View file

@ -245,7 +245,7 @@ where
match opt_level {
OptLevel::Development | OptLevel::Normal => morphic_lib::solve_trivial(program),
OptLevel::Optimize => morphic_lib::solve(program),
OptLevel::Optimize | OptLevel::Size => morphic_lib::solve(program),
}
}
@ -308,7 +308,7 @@ fn build_entry_point(
let block = builder.add_block();
let type_id = layout_spec(&mut builder, &Layout::Struct(layouts))?;
let type_id = layout_spec(&mut builder, &Layout::struct_no_name_order(layouts))?;
let argument = builder.add_unknown_with(block, &[], type_id)?;
@ -349,7 +349,10 @@ fn proc_spec<'a>(proc: &Proc<'a>) -> Result<(FuncDef, MutSet<UnionLayout<'a>>)>
let value_id = stmt_spec(&mut builder, &mut env, block, &proc.ret_layout, &proc.body)?;
let root = BlockExpr(block, value_id);
let arg_type_id = layout_spec(&mut builder, &Layout::Struct(&argument_layouts))?;
let arg_type_id = layout_spec(
&mut builder,
&Layout::struct_no_name_order(&argument_layouts),
)?;
let ret_type_id = layout_spec(&mut builder, &proc.ret_layout)?;
let spec = builder.build(arg_type_id, ret_type_id, root)?;
@ -1135,7 +1138,7 @@ fn call_spec(
// ListFindUnsafe returns { value: v, found: Bool=Int1 }
let output_layouts = vec![argument_layouts[0], Layout::Builtin(Builtin::Bool)];
let output_layout = Layout::Struct(&output_layouts);
let output_layout = Layout::struct_no_name_order(&output_layouts);
let output_type = layout_spec(builder, &output_layout)?;
let loop_body = |builder: &mut FuncDefBuilder, block, output| {
@ -1672,7 +1675,9 @@ fn layout_spec_help(
match layout {
Builtin(builtin) => builtin_spec(builder, builtin, when_recursive),
Struct(fields) => build_recursive_tuple_type(builder, fields, when_recursive),
Struct { field_layouts, .. } => {
build_recursive_tuple_type(builder, field_layouts, when_recursive)
}
LambdaSet(lambda_set) => layout_spec_help(
builder,
&lambda_set.runtime_representation(),

View file

@ -984,7 +984,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
NumToStr | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked
| NumRound | NumCeiling | NumFloor | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos
| NumAsin | NumIntCast => arena.alloc_slice_copy(&[irrelevant]),
| NumAsin | NumIntCast | NumToIntChecked => arena.alloc_slice_copy(&[irrelevant]),
NumBytesToU16 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
NumBytesToU32 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[owned, borrowed]),

View file

@ -32,7 +32,7 @@ pub fn eq_generic<'a>(
}
Layout::Builtin(Builtin::Dict(_, _) | Builtin::Set(_)) => eq_todo(),
Layout::Builtin(Builtin::List(elem_layout)) => eq_list(root, ident_ids, ctx, elem_layout),
Layout::Struct(field_layouts) => eq_struct(root, ident_ids, ctx, field_layouts),
Layout::Struct { field_layouts, .. } => eq_struct(root, ident_ids, ctx, field_layouts),
Layout::Union(union_layout) => eq_tag_union(root, ident_ids, ctx, union_layout),
Layout::LambdaSet(_) => unreachable!("`==` is not defined on functions"),
Layout::RecursivePointer => {

View file

@ -15,7 +15,7 @@ mod equality;
mod refcount;
const LAYOUT_BOOL: Layout = Layout::Builtin(Builtin::Bool);
const LAYOUT_UNIT: Layout = Layout::Struct(&[]);
const LAYOUT_UNIT: Layout = Layout::UNIT;
const ARG_1: Symbol = Symbol::ARG_1;
const ARG_2: Symbol = Symbol::ARG_2;
@ -194,10 +194,11 @@ impl<'a> CodeGenHelp<'a> {
let proc_name = self.find_or_create_proc(ident_ids, ctx, layout);
let (ret_layout, arg_layouts): (&'a Layout<'a>, &'a [Layout<'a>]) = {
let arg = self.replace_rec_ptr(ctx, layout);
match ctx.op {
Dec | DecRef(_) => (&LAYOUT_UNIT, self.arena.alloc([layout])),
Inc => (&LAYOUT_UNIT, self.arena.alloc([layout, self.layout_isize])),
Eq => (&LAYOUT_BOOL, self.arena.alloc([layout, layout])),
Dec | DecRef(_) => (&LAYOUT_UNIT, self.arena.alloc([arg])),
Inc => (&LAYOUT_UNIT, self.arena.alloc([arg, self.layout_isize])),
Eq => (&LAYOUT_BOOL, self.arena.alloc([arg, arg])),
}
};
@ -354,9 +355,15 @@ impl<'a> CodeGenHelp<'a> {
Layout::Builtin(_) => layout,
Layout::Struct(fields) => {
let new_fields_iter = fields.iter().map(|f| self.replace_rec_ptr(ctx, *f));
Layout::Struct(self.arena.alloc_slice_fill_iter(new_fields_iter))
Layout::Struct {
field_layouts,
field_order_hash,
} => {
let new_fields_iter = field_layouts.iter().map(|f| self.replace_rec_ptr(ctx, *f));
Layout::Struct {
field_layouts: self.arena.alloc_slice_fill_iter(new_fields_iter),
field_order_hash,
}
}
Layout::Union(UnionLayout::NonRecursive(tags)) => {
@ -462,7 +469,7 @@ fn layout_needs_helper_proc(layout: &Layout, op: HelperOp) -> bool {
Layout::Builtin(Builtin::Dict(_, _) | Builtin::Set(_) | Builtin::List(_)) => true,
Layout::Struct(fields) => !fields.is_empty(),
Layout::Struct { field_layouts, .. } => !field_layouts.is_empty(),
Layout::Union(UnionLayout::NonRecursive(tags)) => !tags.is_empty(),

View file

@ -12,7 +12,7 @@ use crate::layout::{Builtin, Layout, TagIdIntType, UnionLayout};
use super::{CodeGenHelp, Context, HelperOp};
const LAYOUT_BOOL: Layout = Layout::Builtin(Builtin::Bool);
const LAYOUT_UNIT: Layout = Layout::Struct(&[]);
const LAYOUT_UNIT: Layout = Layout::UNIT;
const LAYOUT_PTR: Layout = Layout::RecursivePointer;
const LAYOUT_U32: Layout = Layout::Builtin(Builtin::Int(IntWidth::U32));
@ -69,7 +69,7 @@ pub fn refcount_stmt<'a>(
}
// Struct and non-recursive Unions are stack-only, so DecRef is a no-op
Layout::Struct(_) => following,
Layout::Struct { .. } => following,
Layout::Union(UnionLayout::NonRecursive(_)) => following,
// Inline the refcounting code instead of making a function. Don't iterate fields,
@ -111,7 +111,7 @@ pub fn refcount_generic<'a>(
refcount_list(root, ident_ids, ctx, &layout, elem_layout, structure)
}
Layout::Builtin(Builtin::Dict(_, _) | Builtin::Set(_)) => rc_todo(),
Layout::Struct(field_layouts) => {
Layout::Struct { field_layouts, .. } => {
refcount_struct(root, ident_ids, ctx, field_layouts, structure)
}
Layout::Union(union_layout) => {
@ -135,7 +135,7 @@ pub fn is_rc_implemented_yet(layout: &Layout) -> bool {
Layout::Builtin(Builtin::Dict(..) | Builtin::Set(_)) => false,
Layout::Builtin(Builtin::List(elem_layout)) => is_rc_implemented_yet(elem_layout),
Layout::Builtin(_) => true,
Layout::Struct(fields) => fields.iter().all(is_rc_implemented_yet),
Layout::Struct { field_layouts, .. } => field_layouts.iter().all(is_rc_implemented_yet),
Layout::Union(union_layout) => match union_layout {
NonRecursive(tags) => tags
.iter()

View file

@ -747,7 +747,11 @@ fn to_relevant_branch_help<'a>(
// the test matches the constructor of this pattern
match layout {
UnionLayout::NonRecursive([[Layout::Struct([_])]]) => {
UnionLayout::NonRecursive(
[[Layout::Struct {
field_layouts: [_], ..
}]],
) => {
// a one-element record equivalent
// Theory: Unbox doesn't have any value for us
debug_assert_eq!(arguments.len(), 1);
@ -1235,7 +1239,7 @@ fn path_to_expr_help<'a>(
layout = inner_layout;
}
Layout::Struct(field_layouts) => {
Layout::Struct { field_layouts, .. } => {
debug_assert!(field_layouts.len() > 1);
let inner_expr = Expr::StructAtIndex {

View file

@ -10,6 +10,7 @@ use bumpalo::Bump;
use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_can::expr::{ClosureData, IntValue};
use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap};
use roc_error_macros::todo_opaques;
use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
use roc_module::low_level::LowLevel;
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
@ -89,6 +90,7 @@ macro_rules! return_on_layout_error_help {
pub enum OptLevel {
Development,
Normal,
Size,
Optimize,
}
@ -1125,7 +1127,7 @@ impl<'a> Param<'a> {
pub const EMPTY: Self = Param {
symbol: Symbol::EMPTY_PARAM,
borrow: false,
layout: Layout::Struct(&[]),
layout: Layout::UNIT,
};
}
@ -1725,11 +1727,11 @@ impl<'a> Stmt<'a> {
use Stmt::*;
match self {
Let(symbol, expr, _layout, cont) => alloc
Let(symbol, expr, layout, cont) => alloc
.text("let ")
.append(symbol_to_doc(alloc, *symbol))
.append(" : ")
.append(alloc.text(format!("{:?}", _layout)))
.append(layout.to_doc(alloc, Parens::NotNeeded))
.append(" = ")
.append(expr.to_doc(alloc))
.append(";")
@ -2012,6 +2014,13 @@ fn pattern_to_when<'a>(
(env.unique_symbol(), Loc::at_zero(RuntimeError(error)))
}
OpaqueNotInScope(loc_ident) => {
// create the runtime error here, instead of delegating to When.
// TODO(opaques) should be `RuntimeError::OpaqueNotDefined`
let error = roc_problem::can::RuntimeError::UnsupportedPattern(loc_ident.region);
(env.unique_symbol(), Loc::at_zero(RuntimeError(error)))
}
AppliedTag { .. } | RecordDestructure { .. } => {
let symbol = env.unique_symbol();
@ -2030,7 +2039,12 @@ fn pattern_to_when<'a>(
(symbol, Loc::at_zero(wrapped_body))
}
IntLiteral(..) | NumLiteral(..) | FloatLiteral(..) | StrLiteral(_) => {
UnwrappedOpaque { .. } => todo_opaques!(),
IntLiteral(..)
| NumLiteral(..)
| FloatLiteral(..)
| StrLiteral(..)
| roc_can::pattern::Pattern::SingleQuote(..) => {
// These patters are refutable, and thus should never occur outside a `when` expression
// They should have been replaced with `UnsupportedPattern` during canonicalization
unreachable!("refutable pattern {:?} where irrefutable pattern is expected. This should never happen!", pattern.value)
@ -2436,7 +2450,7 @@ fn specialize_external<'a>(
let closure_data_layout = match opt_closure_layout {
Some(lambda_set) => Layout::LambdaSet(lambda_set),
None => Layout::Struct(&[]),
None => Layout::UNIT,
};
// I'm not sure how to handle the closure case, does it ever occur?
@ -3136,6 +3150,13 @@ pub fn with_hole<'a>(
hole,
),
SingleQuote(character) => Stmt::Let(
assigned,
Expr::Literal(Literal::Int(character as _)),
Layout::int_width(IntWidth::I32),
hole,
),
Num(var, num_str, num, _bound) => {
// first figure out what kind of number this is
match num_argument_to_int_or_float(env.subs, env.target_info, var, false) {
@ -3412,6 +3433,8 @@ pub fn with_hole<'a>(
}
}
OpaqueRef { .. } => todo_opaques!(),
Record {
record_var,
mut fields,
@ -3430,6 +3453,7 @@ pub fn with_hole<'a>(
let mut field_symbols = Vec::with_capacity_in(fields.len(), env.arena);
let mut can_fields = Vec::with_capacity_in(fields.len(), env.arena);
#[allow(clippy::enum_variant_names)]
enum Field {
// TODO: rename this since it can handle unspecialized expressions now too
Function(Symbol, Variable),
@ -3984,7 +4008,7 @@ pub fn with_hole<'a>(
.unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err));
let field_layouts = match &record_layout {
Layout::Struct(layouts) => *layouts,
Layout::Struct { field_layouts, .. } => *field_layouts,
other => arena.alloc([*other]),
};
@ -4700,7 +4724,7 @@ fn construct_closure_data<'a>(
Vec::from_iter_in(combined.iter().map(|(_, b)| **b), env.arena).into_bump_slice();
debug_assert_eq!(
Layout::Struct(field_layouts),
Layout::struct_no_name_order(field_layouts),
lambda_set.runtime_representation()
);
@ -4784,9 +4808,7 @@ fn convert_tag_union<'a>(
"The `[]` type has no constructors, source var {:?}",
variant_var
),
Unit | UnitWithArguments => {
Stmt::Let(assigned, Expr::Struct(&[]), Layout::Struct(&[]), hole)
}
Unit | UnitWithArguments => Stmt::Let(assigned, Expr::Struct(&[]), Layout::UNIT, hole),
BoolUnion { ttrue, .. } => Stmt::Let(
assigned,
Expr::Literal(Literal::Bool(tag_name == ttrue)),
@ -5095,7 +5117,7 @@ fn sorted_field_symbols<'a>(
// Note it does not catch the use of `[]` currently.
use roc_can::expr::Expr;
arg.value = Expr::RuntimeError(RuntimeError::VoidValue);
Layout::Struct(&[])
Layout::UNIT
}
Err(LayoutProblem::Erroneous) => {
// something went very wrong
@ -5190,7 +5212,10 @@ fn register_capturing_closure<'a>(
Content::Structure(FlatType::Func(_, closure_var, _)) => {
match LambdaSet::from_var(env.arena, env.subs, closure_var, env.target_info) {
Ok(lambda_set) => {
if let Layout::Struct(&[]) = lambda_set.runtime_representation() {
if let Layout::Struct {
field_layouts: &[], ..
} = lambda_set.runtime_representation()
{
CapturedSymbols::None
} else {
let mut temp = Vec::from_iter_in(captured_symbols, env.arena);
@ -6254,7 +6279,7 @@ fn store_pattern_help<'a>(
let mut fields = Vec::with_capacity_in(arguments.len(), env.arena);
fields.extend(arguments.iter().map(|x| x.1));
let layout = Layout::Struct(fields.into_bump_slice());
let layout = Layout::struct_no_name_order(fields.into_bump_slice());
return store_newtype_pattern(
env,
@ -6675,7 +6700,7 @@ fn force_thunk<'a>(
}
fn let_empty_struct<'a>(assigned: Symbol, hole: &'a Stmt<'a>) -> Stmt<'a> {
Stmt::Let(assigned, Expr::Struct(&[]), Layout::Struct(&[]), hole)
Stmt::Let(assigned, Expr::Struct(&[]), Layout::UNIT, hole)
}
/// If the symbol is a function, make sure it is properly specialized
@ -7211,7 +7236,8 @@ fn call_by_name_help<'a>(
} else {
debug_assert!(
!field_symbols.is_empty(),
"should be in the list of imported_module_thunks"
"{} should be in the list of imported_module_thunks",
proc_name
);
debug_assert_eq!(
@ -7730,6 +7756,7 @@ fn from_can_pattern_help<'a>(
}
}
StrLiteral(v) => Ok(Pattern::StrLiteral(v.clone())),
SingleQuote(c) => Ok(Pattern::IntLiteral(*c as _, IntWidth::I32)),
Shadowed(region, ident, _new_symbol) => Err(RuntimeError::Shadowing {
original_region: *region,
shadow: ident.clone(),
@ -7739,6 +7766,10 @@ fn from_can_pattern_help<'a>(
// TODO preserve malformed problem information here?
Err(RuntimeError::UnsupportedPattern(*region))
}
OpaqueNotInScope(loc_ident) => {
// TODO(opaques) should be `RuntimeError::OpaqueNotDefined`
Err(RuntimeError::UnsupportedPattern(loc_ident.region))
}
NumLiteral(var, num_str, num, _bound) => {
match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) {
IntOrFloat::Int(precision) => Ok(match num {
@ -8187,6 +8218,8 @@ fn from_can_pattern_help<'a>(
Ok(result)
}
UnwrappedOpaque { .. } => todo_opaques!(),
RecordDestructure {
whole_var,
destructs,
@ -8456,7 +8489,7 @@ where
env.arena.alloc(result),
)
}
Layout::Struct(_) => match lambda_set.set.get(0) {
Layout::Struct { .. } => match lambda_set.set.get(0) {
Some((function_symbol, _)) => {
let call_spec_id = env.next_call_specialization_id();
let update_mode = env.next_update_mode_id();
@ -8629,7 +8662,10 @@ fn match_on_lambda_set<'a>(
env.arena.alloc(result),
)
}
Layout::Struct(fields) => {
Layout::Struct {
field_layouts,
field_order_hash,
} => {
let function_symbol = lambda_set.set[0].0;
union_lambda_set_branch_help(
@ -8637,7 +8673,10 @@ fn match_on_lambda_set<'a>(
function_symbol,
lambda_set,
closure_data_symbol,
Layout::Struct(fields),
Layout::Struct {
field_layouts,
field_order_hash,
},
argument_symbols,
argument_layouts,
return_layout,
@ -8796,7 +8835,9 @@ fn union_lambda_set_branch_help<'a>(
hole: &'a Stmt<'a>,
) -> Stmt<'a> {
let (argument_layouts, argument_symbols) = match closure_data_layout {
Layout::Struct(&[])
Layout::Struct {
field_layouts: &[], ..
}
| Layout::Builtin(Builtin::Bool)
| Layout::Builtin(Builtin::Int(IntWidth::U8)) => {
(argument_layouts_slice, argument_symbols_slice)
@ -8923,7 +8964,9 @@ fn enum_lambda_set_branch<'a>(
let assigned = result_symbol;
let (argument_layouts, argument_symbols) = match closure_data_layout {
Layout::Struct(&[])
Layout::Struct {
field_layouts: &[], ..
}
| Layout::Builtin(Builtin::Bool)
| Layout::Builtin(Builtin::Int(IntWidth::U8)) => {
(argument_layouts_slice, argument_symbols_slice)

View file

@ -11,8 +11,9 @@ use roc_types::subs::{
Content, FlatType, RecordFields, Subs, UnionTags, UnsortedUnionTags, Variable,
};
use roc_types::types::{gather_fields_unsorted_iter, RecordField};
use std::collections::hash_map::Entry;
use std::collections::hash_map::{DefaultHasher, Entry};
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use ven_pretty::{DocAllocator, DocBuilder};
// if your changes cause this number to go down, great!
@ -201,14 +202,44 @@ impl<'a> RawFunctionLayout<'a> {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct FieldOrderHash(u64);
impl FieldOrderHash {
// NB: This should really be a proper "zero" hash via `DefaultHasher::new().finish()`, but Rust
// stdlib hashers are not (yet) compile-time-computable.
const ZERO_FIELD_HASH: Self = Self(0);
const IRRELEVANT_NON_ZERO_FIELD_HASH: Self = Self(1);
pub fn from_ordered_fields(fields: &[&Lowercase]) -> Self {
if fields.is_empty() {
// HACK: we must make sure this is always equivalent to a `ZERO_FIELD_HASH`.
return Self::ZERO_FIELD_HASH;
}
let mut hasher = DefaultHasher::new();
fields.iter().for_each(|field| field.hash(&mut hasher));
Self(hasher.finish())
}
}
/// Types for code gen must be monomorphic. No type variables allowed!
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Layout<'a> {
Builtin(Builtin<'a>),
/// A layout that is empty (turns into the empty struct in LLVM IR
/// but for our purposes, not zero-sized, so it does not get dropped from data structures
/// this is important for closures that capture zero-sized values
Struct(&'a [Layout<'a>]),
Struct {
/// Two different struct types can have the same layout, for example
/// { a: U8, b: I64 }
/// { a: I64, b: U8 }
/// both have the layout {I64, U8}. Not distinguishing the order of record fields can cause
/// us problems during monomorphization when we specialize the same type in different ways,
/// so keep a hash of the record order for disambiguation. This still of course may result
/// in collisions, but it's unlikely.
///
/// See also https://github.com/rtfeldman/roc/issues/2535.
field_order_hash: FieldOrderHash,
field_layouts: &'a [Layout<'a>],
},
Union(UnionLayout<'a>),
LambdaSet(LambdaSet<'a>),
RecursivePointer,
@ -417,7 +448,9 @@ impl<'a> UnionLayout<'a> {
fn tags_alignment_bytes(tags: &[&[Layout]], target_info: TargetInfo) -> u32 {
tags.iter()
.map(|fields| Layout::Struct(fields).alignment_bytes(target_info))
.map(|field_layouts| {
Layout::struct_no_name_order(field_layouts).alignment_bytes(target_info)
})
.max()
.unwrap_or(0)
}
@ -426,14 +459,14 @@ impl<'a> UnionLayout<'a> {
let allocation = match self {
UnionLayout::NonRecursive(_) => unreachable!("not heap-allocated"),
UnionLayout::Recursive(tags) => Self::tags_alignment_bytes(tags, target_info),
UnionLayout::NonNullableUnwrapped(fields) => {
Layout::Struct(fields).alignment_bytes(target_info)
UnionLayout::NonNullableUnwrapped(field_layouts) => {
Layout::struct_no_name_order(field_layouts).alignment_bytes(target_info)
}
UnionLayout::NullableWrapped { other_tags, .. } => {
Self::tags_alignment_bytes(other_tags, target_info)
}
UnionLayout::NullableUnwrapped { other_fields, .. } => {
Layout::Struct(other_fields).alignment_bytes(target_info)
Layout::struct_no_name_order(other_fields).alignment_bytes(target_info)
}
};
@ -495,12 +528,12 @@ impl<'a> UnionLayout<'a> {
let mut alignment_bytes = 0;
for field_layouts in variant_field_layouts {
let mut data = Layout::Struct(field_layouts);
let mut data = Layout::struct_no_name_order(field_layouts);
let fields_and_id;
if let Some(id_layout) = id_data_layout {
fields_and_id = [data, id_layout];
data = Layout::Struct(&fields_and_id);
data = Layout::struct_no_name_order(&fields_and_id);
}
let (variant_size, variant_alignment) = data.stack_size_and_alignment(target_info);
@ -590,7 +623,10 @@ impl<'a> LambdaSet<'a> {
}
pub fn is_represented(&self) -> Option<Layout<'a>> {
if let Layout::Struct(&[]) = self.representation {
if let Layout::Struct {
field_layouts: &[], ..
} = self.representation
{
None
} else {
Some(*self.representation)
@ -648,7 +684,7 @@ impl<'a> LambdaSet<'a> {
} => todo!("recursive closures"),
}
}
Layout::Struct(_) => {
Layout::Struct { .. } => {
// get the fields from the set, where they are sorted in alphabetic order
// (and not yet sorted by their alignment)
let (_, fields) = self
@ -673,7 +709,9 @@ impl<'a> LambdaSet<'a> {
argument_layouts
} else {
match self.representation {
Layout::Struct(&[]) => {
Layout::Struct {
field_layouts: &[], ..
} => {
// this function does not have anything in its closure, and the lambda set is a
// singleton, so we pass no extra argument
argument_layouts
@ -769,7 +807,7 @@ impl<'a> LambdaSet<'a> {
}
Newtype {
arguments: layouts, ..
} => Layout::Struct(layouts.into_bump_slice()),
} => Layout::struct_no_name_order(layouts.into_bump_slice()),
Wrapped(variant) => {
use WrappedVariant::*;
@ -865,7 +903,10 @@ pub const fn round_up_to_alignment(width: u32, alignment: u32) -> u32 {
impl<'a> Layout<'a> {
pub const VOID: Self = Layout::Union(UnionLayout::NonRecursive(&[]));
pub const UNIT: Self = Layout::Struct(&[]);
pub const UNIT: Self = Layout::Struct {
field_layouts: &[],
field_order_hash: FieldOrderHash::ZERO_FIELD_HASH,
};
fn new_help<'b>(
env: &mut Env<'a, 'b>,
@ -926,7 +967,7 @@ impl<'a> Layout<'a> {
match self {
Builtin(builtin) => builtin.safe_to_memcpy(),
Struct(fields) => fields
Struct { field_layouts, .. } => field_layouts
.iter()
.all(|field_layout| field_layout.safe_to_memcpy()),
Union(variant) => {
@ -990,10 +1031,10 @@ impl<'a> Layout<'a> {
match self {
Builtin(builtin) => builtin.stack_size(target_info),
Struct(fields) => {
Struct { field_layouts, .. } => {
let mut sum = 0;
for field_layout in *fields {
for field_layout in *field_layouts {
sum += field_layout.stack_size(target_info);
}
@ -1020,7 +1061,7 @@ impl<'a> Layout<'a> {
pub fn alignment_bytes(&self, target_info: TargetInfo) -> u32 {
match self {
Layout::Struct(fields) => fields
Layout::Struct { field_layouts, .. } => field_layouts
.iter()
.map(|x| x.alignment_bytes(target_info))
.max()
@ -1069,7 +1110,7 @@ impl<'a> Layout<'a> {
pub fn allocation_alignment_bytes(&self, target_info: TargetInfo) -> u32 {
match self {
Layout::Builtin(builtin) => builtin.allocation_alignment_bytes(target_info),
Layout::Struct(_) => unreachable!("not heap-allocated"),
Layout::Struct { .. } => unreachable!("not heap-allocated"),
Layout::Union(union_layout) => union_layout.allocation_alignment_bytes(target_info),
Layout::LambdaSet(lambda_set) => lambda_set
.runtime_representation()
@ -1103,7 +1144,7 @@ impl<'a> Layout<'a> {
match self {
Builtin(builtin) => builtin.is_refcounted(),
Struct(fields) => fields.iter().any(|f| f.contains_refcounted()),
Struct { field_layouts, .. } => field_layouts.iter().any(|f| f.contains_refcounted()),
Union(variant) => {
use UnionLayout::*;
@ -1134,8 +1175,8 @@ impl<'a> Layout<'a> {
match self {
Builtin(builtin) => builtin.to_doc(alloc, parens),
Struct(fields) => {
let fields_doc = fields.iter().map(|x| x.to_doc(alloc, parens));
Struct { field_layouts, .. } => {
let fields_doc = field_layouts.iter().map(|x| x.to_doc(alloc, parens));
alloc
.text("{")
@ -1147,6 +1188,18 @@ impl<'a> Layout<'a> {
RecursivePointer => alloc.text("*self"),
}
}
/// Used to build a `Layout::Struct` where the field name order is irrelevant.
pub fn struct_no_name_order(field_layouts: &'a [Layout]) -> Self {
if field_layouts.is_empty() {
Self::UNIT
} else {
Self::Struct {
field_layouts,
field_order_hash: FieldOrderHash::IRRELEVANT_NON_ZERO_FIELD_HASH,
}
}
}
}
/// Avoid recomputing Layout from Variable multiple times.
@ -1590,6 +1643,11 @@ fn layout_from_flat_type<'a>(
size2.cmp(&size1).then(label1.cmp(label2))
});
let ordered_field_names =
Vec::from_iter_in(pairs.iter().map(|(label, _)| *label), arena);
let field_order_hash =
FieldOrderHash::from_ordered_fields(ordered_field_names.as_slice());
let mut layouts = Vec::from_iter_in(pairs.into_iter().map(|t| t.1), arena);
if layouts.len() == 1 {
@ -1597,7 +1655,10 @@ fn layout_from_flat_type<'a>(
// unwrap it.
Ok(layouts.pop().unwrap())
} else {
Ok(Layout::Struct(layouts.into_bump_slice()))
Ok(Layout::Struct {
field_order_hash,
field_layouts: layouts.into_bump_slice(),
})
}
}
TagUnion(tags, ext_var) => {
@ -2430,7 +2491,7 @@ fn layout_from_tag_union<'a>(
let answer1 = if field_layouts.len() == 1 {
field_layouts[0]
} else {
Layout::Struct(field_layouts.into_bump_slice())
Layout::struct_no_name_order(field_layouts.into_bump_slice())
};
answer1

View file

@ -16,7 +16,7 @@ bumpalo = { version = "3.8.0", features = ["collections"] }
encode_unicode = "0.3.6"
[dev-dependencies]
criterion = { version = "0.3", features = ["html_reports"] }
criterion = { version = "0.3.5", features = ["html_reports"] }
pretty_assertions = "1.0.0"
indoc = "1.0.3"
quickcheck = "1.0.3"

View file

@ -152,6 +152,8 @@ pub enum Expr<'a> {
Access(&'a Expr<'a>, &'a str),
/// e.g. `.foo`
AccessorFunction(&'a str),
/// eg 'b'
SingleQuote(&'a str),
// Collection Literals
List(Collection<'a, &'a Loc<Expr<'a>>>),
@ -175,6 +177,10 @@ pub enum Expr<'a> {
GlobalTag(&'a str),
PrivateTag(&'a str),
// Reference to an opaque type, e.g. $Opaq
// TODO(opaques): $->@ in the above comment
OpaqueRef(&'a str),
// Pattern Matching
Closure(&'a [Loc<Pattern<'a>>], &'a Loc<Expr<'a>>),
/// Multiple defs in a row
@ -227,12 +233,12 @@ pub struct PrecedenceConflict<'a> {
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct AliasHeader<'a> {
pub struct TypeHeader<'a> {
pub name: Loc<&'a str>,
pub vars: &'a [Loc<Pattern<'a>>],
}
impl<'a> AliasHeader<'a> {
impl<'a> TypeHeader<'a> {
pub fn region(&self) -> Region {
Region::across_all(
[self.name.region]
@ -253,10 +259,16 @@ pub enum Def<'a> {
///
/// Foo : Bar Baz
Alias {
header: AliasHeader<'a>,
header: TypeHeader<'a>,
ann: Loc<TypeAnnotation<'a>>,
},
/// An opaque type, wrapping its inner type. E.g. Age := U64.
Opaque {
header: TypeHeader<'a>,
typ: Loc<TypeAnnotation<'a>>,
},
// TODO in canonicalization, check to see if there are any newlines after the
// annotation; if not, and if it's followed by a Body, then the annotation
// applies to that expr! (TODO: verify that the pattern for both annotation and body match.)
@ -307,7 +319,7 @@ pub enum TypeAnnotation<'a> {
As(
&'a Loc<TypeAnnotation<'a>>,
&'a [CommentOrNewline<'a>],
AliasHeader<'a>,
TypeHeader<'a>,
),
Record {
@ -424,6 +436,9 @@ pub enum Pattern<'a> {
GlobalTag(&'a str),
PrivateTag(&'a str),
OpaqueRef(&'a str),
Apply(&'a Loc<Pattern<'a>>, &'a [Loc<Pattern<'a>>]),
/// This is Located<Pattern> rather than Located<str> so we can record comments
@ -449,6 +464,7 @@ pub enum Pattern<'a> {
FloatLiteral(&'a str),
StrLiteral(StrLiteral<'a>),
Underscore(&'a str),
SingleQuote(&'a str),
// Space
SpaceBefore(&'a Pattern<'a>, &'a [CommentOrNewline<'a>]),
@ -476,6 +492,7 @@ impl<'a> Pattern<'a> {
match ident {
Ident::GlobalTag(string) => Pattern::GlobalTag(string),
Ident::PrivateTag(string) => Pattern::PrivateTag(string),
Ident::OpaqueRef(string) => Pattern::OpaqueRef(string),
Ident::Access { module_name, parts } => {
if parts.len() == 1 {
// This is valid iff there is no module.

View file

@ -1,6 +1,6 @@
use crate::ast::{
AliasHeader, AssignedField, Collection, CommentOrNewline, Def, Expr, ExtractSpaces, Pattern,
Spaceable, TypeAnnotation,
AssignedField, Collection, CommentOrNewline, Def, Expr, ExtractSpaces, Pattern, Spaceable,
TypeAnnotation, TypeHeader,
};
use crate::blankspace::{space0_after_e, space0_around_ee, space0_before_e, space0_e};
use crate::ident::{lowercase_ident, parse_ident, Ident};
@ -195,6 +195,7 @@ fn parse_loc_term_or_underscore<'a>(
one_of!(
loc_expr_in_parens_etc_help(min_indent),
loc!(specialize(EExpr::Str, string_literal_help())),
loc!(specialize(EExpr::SingleQuote, single_quote_literal_help())),
loc!(specialize(EExpr::Number, positive_number_literal_help())),
loc!(specialize(EExpr::Lambda, closure_help(min_indent, options))),
loc!(underscore_expression()),
@ -217,6 +218,7 @@ fn parse_loc_term<'a>(
one_of!(
loc_expr_in_parens_etc_help(min_indent),
loc!(specialize(EExpr::Str, string_literal_help())),
loc!(specialize(EExpr::SingleQuote, single_quote_literal_help())),
loc!(specialize(EExpr::Number, positive_number_literal_help())),
loc!(specialize(EExpr::Lambda, closure_help(min_indent, options))),
loc!(record_literal_help(min_indent)),
@ -417,16 +419,27 @@ impl<'a> ExprState<'a> {
}
}
fn validate_has_type(
fn validate_is_type_def(
mut self,
arena: &'a Bump,
loc_op: Loc<BinOp>,
kind: TypeKind,
) -> Result<(Loc<Expr<'a>>, Vec<'a, &'a Loc<Expr<'a>>>), EExpr<'a>> {
debug_assert_eq!(loc_op.value, BinOp::HasType);
debug_assert_eq!(
loc_op.value,
match kind {
TypeKind::Alias => BinOp::IsAliasType,
TypeKind::Opaque => BinOp::IsOpaqueType,
}
);
if !self.operators.is_empty() {
// this `:` likely occurred inline; treat it as an invalid operator
let fail = EExpr::BadOperator(":", loc_op.region.start());
// this `:`/`:=` likely occurred inline; treat it as an invalid operator
let op = match kind {
TypeKind::Alias => ":",
TypeKind::Opaque => ":=",
};
let fail = EExpr::BadOperator(op, loc_op.region.start());
Err(fail)
} else {
@ -671,6 +684,7 @@ fn append_annotation_definition<'a>(
spaces: &'a [CommentOrNewline<'a>],
loc_pattern: Loc<Pattern<'a>>,
loc_ann: Loc<TypeAnnotation<'a>>,
kind: TypeKind,
) {
let region = Region::span_across(&loc_pattern.region, &loc_ann.region);
@ -682,7 +696,7 @@ fn append_annotation_definition<'a>(
..
},
alias_arguments,
) => append_alias_definition(
) => append_type_definition(
arena,
defs,
region,
@ -690,8 +704,9 @@ fn append_annotation_definition<'a>(
Loc::at(loc_pattern.region, name),
alias_arguments,
loc_ann,
kind,
),
Pattern::GlobalTag(name) => append_alias_definition(
Pattern::GlobalTag(name) => append_type_definition(
arena,
defs,
region,
@ -699,6 +714,7 @@ fn append_annotation_definition<'a>(
Loc::at(loc_pattern.region, name),
&[],
loc_ann,
kind,
),
_ => {
let mut loc_def = Loc::at(region, Def::Annotation(loc_pattern, loc_ann));
@ -736,7 +752,8 @@ fn append_expect_definition<'a>(
defs.push(arena.alloc(loc_def));
}
fn append_alias_definition<'a>(
#[allow(clippy::too_many_arguments)]
fn append_type_definition<'a>(
arena: &'a Bump,
defs: &mut Vec<'a, &'a Loc<Def<'a>>>,
region: Region,
@ -744,13 +761,21 @@ fn append_alias_definition<'a>(
name: Loc<&'a str>,
pattern_arguments: &'a [Loc<Pattern<'a>>],
loc_ann: Loc<TypeAnnotation<'a>>,
kind: TypeKind,
) {
let def = Def::Alias {
header: AliasHeader {
name,
vars: pattern_arguments,
let header = TypeHeader {
name,
vars: pattern_arguments,
};
let def = match kind {
TypeKind::Alias => Def::Alias {
header,
ann: loc_ann,
},
TypeKind::Opaque => Def::Opaque {
header,
typ: loc_ann,
},
ann: loc_ann,
};
let mut loc_def = Loc::at(region, def);
@ -853,7 +878,7 @@ fn parse_defs_end<'a>(
parse_defs_end(options, column, def_state, arena, state)
}
Ok((_, BinOp::HasType, state)) => {
Ok((_, op @ (BinOp::IsAliasType | BinOp::IsOpaqueType), state)) => {
let (_, ann_type, state) = specialize(
EExpr::Type,
space0_before_e(
@ -870,6 +895,11 @@ fn parse_defs_end<'a>(
def_state.spaces_after,
loc_pattern,
ann_type,
match op {
BinOp::IsAliasType => TypeKind::Alias,
BinOp::IsOpaqueType => TypeKind::Opaque,
_ => unreachable!(),
},
);
parse_defs_end(options, column, def_state, arena, state)
@ -919,6 +949,128 @@ fn parse_defs_expr<'a>(
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
enum TypeKind {
Alias,
Opaque,
}
#[allow(clippy::too_many_arguments)]
fn finish_parsing_alias_or_opaque<'a>(
min_indent: u32,
options: ExprParseOptions,
start_column: u32,
expr_state: ExprState<'a>,
loc_op: Loc<BinOp>,
arena: &'a Bump,
state: State<'a>,
spaces_after_operator: &'a [CommentOrNewline<'a>],
kind: TypeKind,
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
let expr_region = expr_state.expr.region;
let indented_more = start_column + 1;
let (expr, arguments) = expr_state
.validate_is_type_def(arena, loc_op, kind)
.map_err(|fail| (MadeProgress, fail, state.clone()))?;
let (loc_def, state) = match &expr.value {
Expr::GlobalTag(name) => {
let mut type_arguments = Vec::with_capacity_in(arguments.len(), arena);
for argument in arguments {
match expr_to_pattern_help(arena, &argument.value) {
Ok(good) => {
type_arguments.push(Loc::at(argument.region, good));
}
Err(_) => panic!(),
}
}
let (_, ann_type, state) = specialize(
EExpr::Type,
space0_before_e(
type_annotation::located_help(indented_more, true),
min_indent,
EType::TIndentStart,
),
)
.parse(arena, state)?;
let def_region = Region::span_across(&expr.region, &ann_type.region);
let header = TypeHeader {
name: Loc::at(expr.region, name),
vars: type_arguments.into_bump_slice(),
};
let type_def = match kind {
TypeKind::Alias => Def::Alias {
header,
ann: ann_type,
},
TypeKind::Opaque => Def::Opaque {
header,
typ: ann_type,
},
};
(&*arena.alloc(Loc::at(def_region, type_def)), state)
}
_ => {
let call = to_call(arena, arguments, expr);
match expr_to_pattern_help(arena, &call.value) {
Ok(good) => {
let parser = specialize(
EExpr::Type,
space0_before_e(
type_annotation::located_help(indented_more, false),
min_indent,
EType::TIndentStart,
),
);
match parser.parse(arena, state) {
Err((_, fail, state)) => return Err((MadeProgress, fail, state)),
Ok((_, mut ann_type, state)) => {
// put the spaces from after the operator in front of the call
if !spaces_after_operator.is_empty() {
ann_type = arena
.alloc(ann_type.value)
.with_spaces_before(spaces_after_operator, ann_type.region);
}
let def_region = Region::span_across(&call.region, &ann_type.region);
let alias = Def::Annotation(Loc::at(expr_region, good), ann_type);
(&*arena.alloc(Loc::at(def_region, alias)), state)
}
}
}
Err(_) => {
// this `:`/`:=` likely occurred inline; treat it as an invalid operator
let op = match kind {
TypeKind::Alias => ":",
TypeKind::Opaque => ":=",
};
let fail = EExpr::BadOperator(op, loc_op.region.start());
return Err((MadeProgress, fail, state));
}
}
}
};
let def_state = DefState {
defs: bumpalo::vec![in arena; loc_def],
spaces_after: &[],
};
parse_defs_expr(options, start_column, def_state, arena, state)
}
fn parse_expr_operator<'a>(
min_indent: u32,
options: ExprParseOptions,
@ -1059,102 +1211,21 @@ fn parse_expr_operator<'a>(
Ok((MadeProgress, ret, state))
}
BinOp::HasType => {
let expr_region = expr_state.expr.region;
let indented_more = start_column + 1;
let (expr, arguments) = expr_state
.validate_has_type(arena, loc_op)
.map_err(|fail| (MadeProgress, fail, state.clone()))?;
let (loc_def, state) = match &expr.value {
Expr::GlobalTag(name) => {
let mut type_arguments = Vec::with_capacity_in(arguments.len(), arena);
for argument in arguments {
match expr_to_pattern_help(arena, &argument.value) {
Ok(good) => {
type_arguments.push(Loc::at(argument.region, good));
}
Err(_) => panic!(),
}
}
let (_, ann_type, state) = specialize(
EExpr::Type,
space0_before_e(
type_annotation::located_help(indented_more, true),
min_indent,
EType::TIndentStart,
),
)
.parse(arena, state)?;
let alias_region = Region::span_across(&expr.region, &ann_type.region);
let alias = Def::Alias {
header: AliasHeader {
name: Loc::at(expr.region, name),
vars: type_arguments.into_bump_slice(),
},
ann: ann_type,
};
(&*arena.alloc(Loc::at(alias_region, alias)), state)
}
_ => {
let call = to_call(arena, arguments, expr);
match expr_to_pattern_help(arena, &call.value) {
Ok(good) => {
let parser = specialize(
EExpr::Type,
space0_before_e(
type_annotation::located_help(indented_more, false),
min_indent,
EType::TIndentStart,
),
);
match parser.parse(arena, state) {
Err((_, fail, state)) => return Err((MadeProgress, fail, state)),
Ok((_, mut ann_type, state)) => {
// put the spaces from after the operator in front of the call
if !spaces_after_operator.is_empty() {
ann_type = arena.alloc(ann_type.value).with_spaces_before(
spaces_after_operator,
ann_type.region,
);
}
let alias_region =
Region::span_across(&call.region, &ann_type.region);
let alias =
Def::Annotation(Loc::at(expr_region, good), ann_type);
(&*arena.alloc(Loc::at(alias_region, alias)), state)
}
}
}
Err(_) => {
// this `:` likely occurred inline; treat it as an invalid operator
let fail = EExpr::BadOperator(":", loc_op.region.start());
return Err((MadeProgress, fail, state));
}
}
}
};
let def_state = DefState {
defs: bumpalo::vec![in arena; loc_def],
spaces_after: &[],
};
parse_defs_expr(options, start_column, def_state, arena, state)
}
BinOp::IsAliasType | BinOp::IsOpaqueType => finish_parsing_alias_or_opaque(
min_indent,
options,
start_column,
expr_state,
loc_op,
arena,
state,
spaces_after_operator,
match op {
BinOp::IsAliasType => TypeKind::Alias,
BinOp::IsOpaqueType => TypeKind::Opaque,
_ => unreachable!(),
},
),
_ => match loc_possibly_negative_or_negated_term(min_indent, options).parse(arena, state) {
Err((MadeProgress, f, s)) => Err((MadeProgress, f, s)),
Ok((_, mut new_expr, state)) => {
@ -1396,6 +1467,7 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
Expr::Underscore(opt_name) => Ok(Pattern::Underscore(opt_name)),
Expr::GlobalTag(value) => Ok(Pattern::GlobalTag(value)),
Expr::PrivateTag(value) => Ok(Pattern::PrivateTag(value)),
Expr::OpaqueRef(value) => Ok(Pattern::OpaqueRef(value)),
Expr::Apply(loc_val, loc_args, _) => {
let region = loc_val.region;
let value = expr_to_pattern_help(arena, &loc_val.value)?;
@ -1464,6 +1536,7 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
| Expr::UnaryOp(_, _) => Err(()),
Expr::Str(string) => Ok(Pattern::StrLiteral(*string)),
Expr::SingleQuote(string) => Ok(Pattern::SingleQuote(*string)),
Expr::MalformedIdent(string, _problem) => Ok(Pattern::Malformed(string)),
}
}
@ -2067,6 +2140,7 @@ fn ident_to_expr<'a>(arena: &'a Bump, src: Ident<'a>) -> Expr<'a> {
match src {
Ident::GlobalTag(string) => Expr::GlobalTag(string),
Ident::PrivateTag(string) => Expr::PrivateTag(string),
Ident::OpaqueRef(string) => Expr::OpaqueRef(string),
Ident::Access { module_name, parts } => {
let mut iter = parts.iter();
@ -2281,6 +2355,13 @@ fn string_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EString<'a>> {
map!(crate::string_literal::parse(), Expr::Str)
}
fn single_quote_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EString<'a>> {
map!(
crate::string_literal::parse_single_quote(),
Expr::SingleQuote
)
}
fn positive_number_literal_help<'a>() -> impl Parser<'a, Expr<'a>, ENumber> {
map!(
crate::number_literal::positive_number_literal(),
@ -2372,7 +2453,8 @@ where
Err((NoProgress, to_error(".", state.pos()), state))
}
"=" => good!(BinOp::Assignment, 1),
":" => good!(BinOp::HasType, 1),
":=" => good!(BinOp::IsOpaqueType, 2),
":" => good!(BinOp::IsAliasType, 1),
"|>" => good!(BinOp::Pizza, 2),
"==" => good!(BinOp::Equals, 2),
"!=" => good!(BinOp::NotEquals, 2),

View file

@ -38,6 +38,9 @@ pub enum Ident<'a> {
GlobalTag(&'a str),
/// @Foo or @Bar
PrivateTag(&'a str),
/// $Foo or $Bar
// TODO(opaques): $->@ in the above comment
OpaqueRef(&'a str),
/// foo or foo.bar or Foo.Bar.baz.qux
Access {
module_name: &'a str,
@ -54,7 +57,7 @@ impl<'a> Ident<'a> {
use self::Ident::*;
match self {
GlobalTag(string) | PrivateTag(string) => string.len(),
GlobalTag(string) | PrivateTag(string) | OpaqueRef(string) => string.len(),
Access { module_name, parts } => {
let mut len = if module_name.is_empty() {
0
@ -100,7 +103,11 @@ pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str, ()> {
pub fn tag_name<'a>() -> impl Parser<'a, &'a str, ()> {
move |arena, state: State<'a>| {
if state.bytes().starts_with(b"@") {
match chomp_private_tag(state.bytes(), state.pos()) {
match chomp_private_tag_or_opaque(
/* private tag */ true,
state.bytes(),
state.pos(),
) {
Err(BadIdent::Start(_)) => Err((NoProgress, (), state)),
Err(_) => Err((MadeProgress, (), state)),
Ok(ident) => {
@ -236,6 +243,7 @@ pub enum BadIdent {
WeirdDotQualified(Position),
StrayDot(Position),
BadPrivateTag(Position),
BadOpaqueRef(Position),
}
fn chomp_lowercase_part(buffer: &[u8]) -> Result<&str, Progress> {
@ -304,23 +312,33 @@ fn chomp_accessor(buffer: &[u8], pos: Position) -> Result<&str, BadIdent> {
}
/// a `@Token` private tag
fn chomp_private_tag(buffer: &[u8], pos: Position) -> Result<&str, BadIdent> {
fn chomp_private_tag_or_opaque(
private_tag: bool, // If false, opaque
buffer: &[u8],
pos: Position,
) -> Result<&str, BadIdent> {
// assumes the leading `@` has NOT been chomped already
debug_assert_eq!(buffer.get(0), Some(&b'@'));
debug_assert_eq!(buffer.get(0), Some(if private_tag { &b'@' } else { &b'$' }));
use encode_unicode::CharExt;
let bad_ident = if private_tag {
BadIdent::BadPrivateTag
} else {
BadIdent::BadOpaqueRef
};
match chomp_uppercase_part(&buffer[1..]) {
Ok(name) => {
let width = 1 + name.len();
if let Ok(('.', _)) = char::from_utf8_slice_start(&buffer[width..]) {
Err(BadIdent::BadPrivateTag(pos.bump_column(width as u32)))
Err(bad_ident(pos.bump_column(width as u32)))
} else {
let value = unsafe { std::str::from_utf8_unchecked(&buffer[..width]) };
Ok(value)
}
}
Err(_) => Err(BadIdent::BadPrivateTag(pos.bump_column(1))),
Err(_) => Err(bad_ident(pos.bump_column(1))),
}
}
@ -344,11 +362,17 @@ fn chomp_identifier_chain<'a>(
}
Err(fail) => return Err((1, fail)),
},
'@' => match chomp_private_tag(buffer, pos) {
c @ ('@' | '$') => match chomp_private_tag_or_opaque(c == '@', buffer, pos) {
Ok(tagname) => {
let bytes_parsed = tagname.len();
return Ok((bytes_parsed as u32, Ident::PrivateTag(tagname)));
let ident = if c == '@' {
Ident::PrivateTag
} else {
Ident::OpaqueRef
};
return Ok((bytes_parsed as u32, ident(tagname)));
}
Err(fail) => return Err((1, fail)),
},

View file

@ -355,6 +355,7 @@ pub enum EExpr<'a> {
InParens(EInParens<'a>, Position),
Record(ERecord<'a>, Position),
Str(EString<'a>, Position),
SingleQuote(EString<'a>, Position),
Number(ENumber, Position),
List(EList<'a>, Position),

View file

@ -61,6 +61,7 @@ pub fn loc_pattern_help<'a>(min_indent: u32) -> impl Parser<'a, Loc<Pattern<'a>>
)),
loc!(number_pattern_help()),
loc!(string_pattern_help()),
loc!(single_quote_pattern_help()),
)
}
@ -108,6 +109,7 @@ fn loc_parse_tag_pattern_arg<'a>(
crate::pattern::record_pattern_help(min_indent)
)),
loc!(string_pattern_help()),
loc!(single_quote_pattern_help()),
loc!(number_pattern_help())
)
.parse(arena, state)
@ -159,6 +161,16 @@ fn string_pattern_help<'a>() -> impl Parser<'a, Pattern<'a>, EPattern<'a>> {
)
}
fn single_quote_pattern_help<'a>() -> impl Parser<'a, Pattern<'a>, EPattern<'a>> {
specialize(
|_, pos| EPattern::Start(pos),
map!(
crate::string_literal::parse_single_quote(),
Pattern::SingleQuote
),
)
}
fn loc_ident_pattern_help<'a>(
min_indent: u32,
can_have_arguments: bool,
@ -197,31 +209,35 @@ fn loc_ident_pattern_help<'a>(
Ok((MadeProgress, loc_tag, state))
}
}
Ident::PrivateTag(tag) => {
let loc_tag = Loc {
Ident::PrivateTag(name) | Ident::OpaqueRef(name) => {
let loc_pat = Loc {
region: loc_ident.region,
value: Pattern::PrivateTag(tag),
value: if matches!(loc_ident.value, Ident::PrivateTag(..)) {
Pattern::PrivateTag(name)
} else {
Pattern::OpaqueRef(name)
},
};
// Make sure `Foo Bar 1` is parsed as `Foo (Bar) 1`, and not `Foo (Bar 1)`
// Make sure `@Foo Bar 1` is parsed as `@Foo (Bar) 1`, and not `@Foo (Bar 1)`
if can_have_arguments {
let (_, loc_args, state) =
loc_tag_pattern_args_help(min_indent).parse(arena, state)?;
if loc_args.is_empty() {
Ok((MadeProgress, loc_tag, state))
Ok((MadeProgress, loc_pat, state))
} else {
let region = Region::across_all(
std::iter::once(&loc_ident.region)
.chain(loc_args.iter().map(|loc_arg| &loc_arg.region)),
);
let value =
Pattern::Apply(&*arena.alloc(loc_tag), loc_args.into_bump_slice());
Pattern::Apply(&*arena.alloc(loc_pat), loc_args.into_bump_slice());
Ok((MadeProgress, Loc { region, value }, state))
}
} else {
Ok((MadeProgress, loc_tag, state))
Ok((MadeProgress, loc_pat, state))
}
}
Ident::Access { module_name, parts } => {

View file

@ -35,6 +35,100 @@ macro_rules! advance_state {
};
}
pub fn parse_single_quote<'a>() -> impl Parser<'a, &'a str, EString<'a>> {
move |arena: &'a Bump, mut state: State<'a>| {
if state.bytes().starts_with(b"\'") {
// we will be parsing a single-quote-string
} else {
return Err((NoProgress, EString::Open(state.pos()), state));
}
// early return did not hit, just advance one byte
state = advance_state!(state, 1)?;
// Handle back slaches in byte literal
// - starts with a backslash and used as an escape character. ex: '\n', '\t'
// - single quote floating (un closed single quote) should be an error
match state.bytes().first() {
Some(b'\\') => {
state = advance_state!(state, 1)?;
match state.bytes().first() {
Some(&ch) => {
state = advance_state!(state, 1)?;
if (ch == b'n' || ch == b'r' || ch == b't' || ch == b'\'' || ch == b'\\')
&& (state.bytes().first() == Some(&b'\''))
{
state = advance_state!(state, 1)?;
let test = match ch {
b'n' => '\n',
b't' => '\t',
b'r' => '\r',
// since we checked the current char between the single quotes we
// know they are valid UTF-8, allowing us to use 'from_u32_unchecked'
_ => unsafe { char::from_u32_unchecked(ch as u32) },
};
return Ok((MadeProgress, &*arena.alloc_str(&test.to_string()), state));
}
// invalid error, backslah escaping something we do not recognize
return Err((NoProgress, EString::CodePtEnd(state.pos()), state));
}
None => {
// no close quote found
return Err((NoProgress, EString::CodePtEnd(state.pos()), state));
}
}
}
Some(_) => {
// do nothing for other characters, handled below
}
None => return Err((NoProgress, EString::CodePtEnd(state.pos()), state)),
}
let mut bytes = state.bytes().iter();
let mut end_index = 1;
// Copy paste problem in mono
loop {
match bytes.next() {
Some(b'\'') => {
break;
}
Some(_) => end_index += 1,
None => {
return Err((NoProgress, EString::Open(state.pos()), state));
}
}
}
if end_index == 1 {
// no progress was made
// this case is a double single quote, ex: ''
// not supporting empty single quotes
return Err((NoProgress, EString::Open(state.pos()), state));
}
if end_index > (std::mem::size_of::<u32>() + 1) {
// bad case: too big to fit into u32
return Err((NoProgress, EString::Open(state.pos()), state));
}
// happy case -> we have some bytes that will fit into a u32
// ending up w/ a slice of bytes that we want to convert into an integer
let raw_bytes = &state.bytes()[0..end_index - 1];
state = advance_state!(state, end_index)?;
match std::str::from_utf8(raw_bytes) {
Ok(string) => Ok((MadeProgress, string, state)),
Err(_) => {
// invalid UTF-8
return Err((NoProgress, EString::CodePtEnd(state.pos()), state));
}
}
}
}
pub fn parse<'a>() -> impl Parser<'a, StrLiteral<'a>, EString<'a>> {
use StrLiteral::*;

View file

@ -1,4 +1,4 @@
use crate::ast::{AliasHeader, AssignedField, Pattern, Tag, TypeAnnotation};
use crate::ast::{AssignedField, Pattern, Tag, TypeAnnotation, TypeHeader};
use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
use crate::keyword;
use crate::parser::{
@ -49,7 +49,7 @@ fn tag_union_type<'a>(min_indent: u32) -> impl Parser<'a, TypeAnnotation<'a>, ET
fn check_type_alias(
p: Progress,
annot: Loc<TypeAnnotation>,
) -> impl Parser<AliasHeader, ETypeInlineAlias> {
) -> impl Parser<TypeHeader, ETypeInlineAlias> {
move |arena, state| match annot.value {
TypeAnnotation::Apply("", tag_name, vars) => {
let mut var_names = Vec::new_in(arena);
@ -70,7 +70,7 @@ fn check_type_alias(
let name_region =
Region::between(name_start, name_start.bump_column(tag_name.len() as u32));
let header = AliasHeader {
let header = TypeHeader {
name: Loc::at(name_region, tag_name),
vars: var_names.into_bump_slice(),
};
@ -84,7 +84,7 @@ fn check_type_alias(
}
}
fn parse_type_alias_after_as<'a>(min_indent: u32) -> impl Parser<'a, AliasHeader<'a>, EType<'a>> {
fn parse_type_alias_after_as<'a>(min_indent: u32) -> impl Parser<'a, TypeHeader<'a>, EType<'a>> {
move |arena, state| {
space0_before_e(term(min_indent), min_indent, EType::TAsIndentStart)
.parse(arena, state)
@ -127,7 +127,7 @@ fn term<'a>(min_indent: u32) -> impl Parser<'a, Loc<TypeAnnotation<'a>>, EType<'
]
),
|arena: &'a Bump,
(loc_ann, opt_as): (Loc<TypeAnnotation<'a>>, Option<(&'a [_], AliasHeader<'a>)>)| {
(loc_ann, opt_as): (Loc<TypeAnnotation<'a>>, Option<(&'a [_], TypeHeader<'a>)>)| {
match opt_as {
Some((spaces, alias)) => {
let alias_vars_region =

View file

@ -0,0 +1,3 @@
OpaqueRef(
"$Age",
)

View file

@ -0,0 +1 @@
$Age

View file

@ -0,0 +1,16 @@
Apply(
@0-4 OpaqueRef(
"$Age",
),
[
@5-6 Var {
module_name: "",
ident: "m",
},
@7-8 Var {
module_name: "",
ident: "n",
},
],
Space,
)

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