diff --git a/BUILDING_FROM_SOURCE.md b/BUILDING_FROM_SOURCE.md index 7bae697da1..d66b8e0648 100644 --- a/BUILDING_FROM_SOURCE.md +++ b/BUILDING_FROM_SOURCE.md @@ -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. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 748a9edd08..4c42d01e15 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 ``` diff --git a/Cargo.lock b/Cargo.lock index 1414f20e6f..bbaa9d4ce2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/FAQ.md b/FAQ.md index a80cc87ccc..24a02d9442 100644 --- a/FAQ.md +++ b/FAQ.md @@ -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! diff --git a/LEGAL_DETAILS b/LEGAL_DETAILS index 3cb8fd3034..588ad49184 100644 --- a/LEGAL_DETAILS +++ b/LEGAL_DETAILS @@ -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. \ No newline at end of file diff --git a/TUTORIAL.md b/TUTORIAL.md index a5e4aa97a5..5049d19f82 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -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 Let’s start by getting acquainted with Roc’s Read Eval Print Loop, or REPL for diff --git a/ast/src/constrain.rs b/ast/src/constrain.rs index f26eddd841..6c5af07eaf 100644 --- a/ast/src/constrain.rs +++ b/ast/src/constrain.rs @@ -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); diff --git a/ast/src/lang/core/def/def.rs b/ast/src/lang/core/def/def.rs index aeea6e6a73..e78658457b 100644 --- a/ast/src/lang/core/def/def.rs +++ b/ast/src/lang/core/def/def.rs @@ -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(), }); diff --git a/ast/src/lang/core/pattern.rs b/ast/src/lang/core/pattern.rs index 3bbefb7d83..6687acd197 100644 --- a/ast/src/lang/core/pattern.rs +++ b/ast/src/lang/core/pattern.rs @@ -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 { | 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 { .. } diff --git a/ast/src/lang/core/types.rs b/ast/src/lang/core/types.rs index d620af015f..4702515554 100644 --- a/ast/src/lang/core/types.rs +++ b/ast/src/lang/core/types.rs @@ -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, }, diff --git a/ast/src/lang/env.rs b/ast/src/lang/env.rs index 3f9e17e3d5..1b04f417d2 100644 --- a/ast/src/lang/env.rs +++ b/ast/src/lang/env.rs @@ -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, }), } } diff --git a/cli/Cargo.toml b/cli/Cargo.toml index dfffe25ee0..4b5d13d1c1 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -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 diff --git a/cli/src/format.rs b/cli/src/format.rs index f5a32c9caa..ef2e853867 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -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) -> std::vec::Vec { + 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, 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), } } } diff --git a/cli/src/lib.rs b/cli/src/lib.rs index e3027927ea..b790552538 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -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 { 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); diff --git a/cli/src/main.rs b/cli/src/main.rs index a90ca61554..f07ecdd943 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -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 diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 45a32eecd9..9e7aee0d8b 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -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)] diff --git a/cli/tests/fixtures/format/formatted_directory/Formatted.roc b/cli/tests/fixtures/format/formatted_directory/Formatted.roc new file mode 100644 index 0000000000..b62c494e66 --- /dev/null +++ b/cli/tests/fixtures/format/formatted_directory/Formatted.roc @@ -0,0 +1,6 @@ +app "formatted" + packages { pf: "platform" } imports [] + provides [ main ] to pf + +main : Str +main = Dep1.value1 {} diff --git a/cli_utils/Cargo.lock b/cli_utils/Cargo.lock index c25e9072c9..c1fa0f03f4 100644 --- a/cli_utils/Cargo.lock +++ b/cli_utils/Cargo.lock @@ -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]] diff --git a/code_markup/src/lib.rs b/code_markup/src/lib.rs index 9e5d220855..1b140a9678 100644 --- a/code_markup/src/lib.rs +++ b/code_markup/src/lib.rs @@ -3,3 +3,4 @@ pub mod markup; pub mod markup_error; pub mod slow_pool; pub mod syntax_highlight; +pub mod underline_style; diff --git a/code_markup/src/markup/attribute.rs b/code_markup/src/markup/attribute.rs index 90b4801f18..58f57bc206 100644 --- a/code_markup/src/markup/attribute.rs +++ b/code_markup/src/markup/attribute.rs @@ -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)] diff --git a/code_markup/src/underline_style.rs b/code_markup/src/underline_style.rs new file mode 100644 index 0000000000..4eb81ee73c --- /dev/null +++ b/code_markup/src/underline_style.rs @@ -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 { + 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 +} diff --git a/compiler/build/Cargo.toml b/compiler/build/Cargo.toml index ab61cf2637..54872936b4 100644 --- a/compiler/build/Cargo.toml +++ b/compiler/build/Cargo.toml @@ -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 = [] diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index b3d5e52062..f569cae620 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -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(segments: [&str; N]) -> Option { } } +/// 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 { + 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::>() + .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"); -} diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index 933e8f9e7a..8db3cbbb86 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -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, diff --git a/compiler/build/src/target.rs b/compiler/build/src/target.rs index 6064e58428..a49aff892b 100644 --- a/compiler/build/src/target.rs +++ b/compiler/build/src/target.rs @@ -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, } } diff --git a/compiler/builtins/Cargo.toml b/compiler/builtins/Cargo.toml index 91879456d7..579cae97ed 100644 --- a/compiler/builtins/Cargo.toml +++ b/compiler/builtins/Cargo.toml @@ -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" diff --git a/compiler/builtins/bitcode/README.md b/compiler/builtins/bitcode/README.md index e7a03e341f..604f3a53d0 100644 --- a/compiler/builtins/bitcode/README.md +++ b/compiler/builtins/bitcode/README.md @@ -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 diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index ce1cc10757..c047588d92 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -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."); diff --git a/compiler/builtins/bitcode/src/num.zig b/compiler/builtins/bitcode/src/num.zig index 6951da6962..41d93df976 100644 --- a/compiler/builtins/bitcode/src/num.zig +++ b/compiler/builtins/bitcode/src/num.zig @@ -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 }); } diff --git a/compiler/builtins/build.rs b/compiler/builtins/build.rs index 5e474a55dd..b117f888f7 100644 --- a/compiler/builtins/build.rs +++ b/compiler/builtins/build.rs @@ -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 diff --git a/compiler/builtins/docs/Num.roc b/compiler/builtins/docs/Num.roc index c4672f0c7f..286d256af7 100644 --- a/compiler/builtins/docs/Num.roc +++ b/compiler/builtins/docs/Num.roc @@ -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 diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index c52c30cf09..1345156d0f 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -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 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 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"); diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 8a4ee50b2a..97394b969b 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -445,6 +445,156 @@ pub fn types() -> MutMap { // 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, diff --git a/compiler/can/Cargo.toml b/compiler/can/Cargo.toml index 2e8bd639dd..0a83a9fc0c 100644 --- a/compiler/can/Cargo.toml +++ b/compiler/can/Cargo.toml @@ -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" } diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index 5cd1b22164..bcbb7aceed 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -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, } } } diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 3c21b53b9a..a8228c6ecb 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -242,6 +242,26 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option 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(); diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 7e03f8e3b9..fbb14fa846 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -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>, ), - /// 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, vars: Vec>, ann: &'a Loc>, + 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>) -> Vec { - let defined_symbols: Vec = alias_symbols.keys().copied().collect(); +/// Returns a topologically sorted sequence of alias/opaque names +fn sort_type_defs_before_introduction( + mut referenced_symbols: MutMap>, +) -> Vec { + let defined_symbols: Vec = 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( .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> = 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 = Vec::new(); let mut defined_symbols_set: ImSet = 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 })) } } } diff --git a/compiler/can/src/effect_module.rs b/compiler/can/src/effect_module.rs index 3735c85239..6c04645b14 100644 --- a/compiler/can/src/effect_module.rs +++ b/compiler/can/src/effect_module.rs @@ -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, } } diff --git a/compiler/can/src/env.rs b/compiler/can/src/env.rs index 46f627d7d9..e1216c42eb 100644 --- a/compiler/can/src/env.rs +++ b/compiler/can/src/env.rs @@ -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, }), } } diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 86579c248f..0912ea6988 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -73,6 +73,7 @@ pub enum Expr { Int(Variable, Variable, Box, IntValue, IntBound), Float(Variable, Variable, Box, f64, FloatBound), Str(Box), + SingleQuote(char), List { elem_var: Variable, loc_elems: Vec>, @@ -172,6 +173,11 @@ pub enum Expr { arguments: Vec<(Variable, Loc)>, }, + OpaqueRef { + name: Symbol, + arguments: Vec<(Variable, Loc)>, + }, + // Test Expect(Box>, Box>), @@ -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, diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index 8deb5a9671..df23b4ef42 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -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); } diff --git a/compiler/can/src/operator.rs b/compiler/can/src/operator.rs index 04255f9175..0c8d00177d 100644 --- a/compiler/can/src/operator.rs +++ b/compiler/can/src/operator.rs @@ -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>) -> &'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>) -> &'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>) -> &'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>) -> &'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"), } } diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index 41aa49ca9a..a7187bb764 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -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)>, }, + UnwrappedOpaque { + whole_var: Variable, + opaque: Symbol, + arguments: Vec<(Variable, Loc)>, + }, RecordDestructure { whole_var: Variable, ext_var: Variable, @@ -33,10 +39,12 @@ pub enum Pattern { IntLiteral(Variable, Variable, Box, IntValue, IntBound), FloatLiteral(Variable, Variable, Box, f64, FloatBound), StrLiteral(Box), + SingleQuote(char), Underscore, // Runtime Exceptions Shadowed(Region, Loc, Symbol), + OpaqueNotInScope(Loc), // 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) { 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) { | 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(..) => (), } } diff --git a/compiler/can/src/procedure.rs b/compiler/can/src/procedure.rs index 22f6f506e6..0794b86b3e 100644 --- a/compiler/can/src/procedure.rs +++ b/compiler/can/src/procedure.rs @@ -46,7 +46,8 @@ impl Procedure { pub struct References { pub bound_symbols: ImSet, pub lookups: ImSet, - pub referenced_aliases: ImSet, + /// Aliases or opaque types referenced + pub referenced_type_defs: ImSet, pub calls: ImSet, } @@ -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 { diff --git a/compiler/can/src/scope.rs b/compiler/can/src/scope.rs index 56dc837441..b3db4c9e5e 100644 --- a/compiler/can/src/scope.rs +++ b/compiler/can/src/scope.rs @@ -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 { + 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, + ) -> 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>, 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>, 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, } } diff --git a/compiler/can/tests/test_can.rs b/compiler/can/tests/test_can.rs index 3489b0677f..a3ee85d6db 100644 --- a/compiler/can/tests/test_can.rs +++ b/compiler/can/tests/test_can.rs @@ -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. diff --git a/compiler/constrain/Cargo.toml b/compiler/constrain/Cargo.toml index d45ebae952..82e7426c0d 100644 --- a/compiler/constrain/Cargo.toml +++ b/compiler/constrain/Cargo.toml @@ -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" } diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index b5ac1cf14f..cf1a907845 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -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( diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 5d7ec4e22d..dd93c7d535 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -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, 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, ); } diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index bf77a86f36..7308b261a0 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -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, - 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, 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!(), } } diff --git a/compiler/fmt/src/annotation.rs b/compiler/fmt/src/annotation.rs index 98c15b292a..4380af17e2 100644 --- a/compiler/fmt/src/annotation.rs +++ b/compiler/fmt/src/annotation.rs @@ -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); diff --git a/compiler/fmt/src/collection.rs b/compiler/fmt/src/collection.rs index 5765f0f4d1..9e54b327cf 100644 --- a/compiler/fmt/src/collection.rs +++ b/compiler/fmt/src/collection.rs @@ -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); } diff --git a/compiler/fmt/src/def.rs b/compiler/fmt/src/def.rs index 8ea8ed9639..935e6c6e35 100644 --- a/compiler/fmt/src/def.rs +++ b/compiler/fmt/src/def.rs @@ -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) diff --git a/compiler/fmt/src/expr.rs b/compiler/fmt/src/expr.rs index 8487c19761..72a41531ff 100644 --- a/compiler/fmt/src/expr.rs +++ b/compiler/fmt/src/expr.rs @@ -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, diff --git a/compiler/fmt/src/pattern.rs b/compiler/fmt/src/pattern.rs index e09f9e19d7..c903ad9596 100644 --- a/compiler/fmt/src/pattern.rs +++ b/compiler/fmt/src/pattern.rs @@ -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('_'); diff --git a/compiler/gen_dev/Cargo.toml b/compiler/gen_dev/Cargo.toml index ca81c78e67..0b5a08fe0d 100644 --- a/compiler/gen_dev/Cargo.toml +++ b/compiler/gen_dev/Cargo.toml @@ -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] diff --git a/compiler/gen_dev/src/generic64/storage.rs b/compiler/gen_dev/src/generic64/storage.rs index ece5ea3d1c..3f2b18866a 100644 --- a/compiler/gen_dev/src/generic64/storage.rs +++ b/compiler/gen_dev/src/generic64/storage.rs @@ -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); diff --git a/compiler/gen_llvm/Cargo.toml b/compiler/gen_llvm/Cargo.toml index e87ed1c434..96dfaaf58e 100644 --- a/compiler/gen_llvm/Cargo.toml +++ b/compiler/gen_llvm/Cargo.toml @@ -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" } diff --git a/compiler/gen_llvm/src/llvm/bitcode.rs b/compiler/gen_llvm/src/llvm/bitcode.rs index 0ba1130580..d80d626036 100644 --- a/compiler/gen_llvm/src/llvm/bitcode.rs +++ b/compiler/gen_llvm/src/llvm/bitcode.rs @@ -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> = [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 } diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index e1a1a981e4..26f4816b84 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -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>( _ => (¶ms[..], ¶m_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); } diff --git a/compiler/gen_llvm/src/llvm/build_dict.rs b/compiler/gen_llvm/src/llvm/build_dict.rs index 37b81e9129..cbecc59b9a 100644 --- a/compiler/gen_llvm/src/llvm/build_dict.rs +++ b/compiler/gen_llvm/src/llvm/build_dict.rs @@ -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); diff --git a/compiler/gen_llvm/src/llvm/build_hash.rs b/compiler/gen_llvm/src/llvm/build_hash.rs index 0db5348d1d..aa89583c77 100644 --- a/compiler/gen_llvm/src/llvm/build_hash.rs +++ b/compiler/gen_llvm/src/llvm/build_hash.rs @@ -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 diff --git a/compiler/gen_llvm/src/llvm/compare.rs b/compiler/gen_llvm/src/llvm/compare.rs index f5d596aae1..9f8a0928a6 100644 --- a/compiler/gen_llvm/src/llvm/compare.rs +++ b/compiler/gen_llvm/src/llvm/compare.rs @@ -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()); diff --git a/compiler/gen_llvm/src/llvm/convert.rs b/compiler/gen_llvm/src/llvm/convert.rs index c7d3142d40..8d516e539e 100644 --- a/compiler/gen_llvm/src/llvm/convert.rs +++ b/compiler/gen_llvm/src/llvm/convert.rs @@ -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()) } diff --git a/compiler/gen_llvm/src/llvm/refcounting.rs b/compiler/gen_llvm/src/llvm/refcounting.rs index 2962361c29..da820087bf 100644 --- a/compiler/gen_llvm/src/llvm/refcounting.rs +++ b/compiler/gen_llvm/src/llvm/refcounting.rs @@ -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 diff --git a/compiler/gen_wasm/Cargo.toml b/compiler/gen_wasm/Cargo.toml index b2223ee48a..ccdb6b2768 100644 --- a/compiler/gen_wasm/Cargo.toml +++ b/compiler/gen_wasm/Cargo.toml @@ -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" } diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 716758fc56..4544e46111 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -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 { diff --git a/compiler/gen_wasm/src/layout.rs b/compiler/gen_wasm/src/layout.rs index b7ad18b2ef..1b70e0757a 100644 --- a/compiler/gen_wasm/src/layout.rs +++ b/compiler/gen_wasm/src/layout.rs @@ -88,7 +88,7 @@ impl WasmLayout { }, Layout::Builtin(Str | Dict(_, _) | Set(_) | List(_)) - | Layout::Struct(_) + | Layout::Struct { .. } | Layout::LambdaSet(_) | Layout::Union(NonRecursive(_)) => Self::StackMemory { size, diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 7030419e49..679f97b8a7 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -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), diff --git a/compiler/gen_wasm/src/low_level.rs b/compiler/gen_wasm/src/low_level.rs index eae0a0cfb1..a5875e1813 100644 --- a/compiler/gen_wasm/src/low_level.rs +++ b/compiler/gen_wasm/src/low_level.rs @@ -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 diff --git a/compiler/gen_wasm/src/wasm32_result.rs b/compiler/gen_wasm/src/wasm32_result.rs index ce21c83683..dce56c1cdf 100644 --- a/compiler/gen_wasm/src/wasm32_result.rs +++ b/compiler/gen_wasm/src/wasm32_result.rs @@ -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 Wasm32Result for RocList { +impl Wasm32Result for RocList { fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { build_wrapper_body_stack_memory(code_builder, main_function_index, 12) } diff --git a/compiler/gen_wasm/src/wasm32_sized.rs b/compiler/gen_wasm/src/wasm32_sized.rs index 2eea997b6d..f59b7fffd4 100644 --- a/compiler/gen_wasm/src/wasm32_sized.rs +++ b/compiler/gen_wasm/src/wasm32_sized.rs @@ -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 Wasm32Sized for RocList { +impl Wasm32Sized for RocList { const SIZE_OF_WASM: usize = 8; const ALIGN_OF_WASM: usize = 4; } diff --git a/compiler/ident/src/lib.rs b/compiler/ident/src/lib.rs index e152271bc4..d611241926 100644 --- a/compiler/ident/src/lib.rs +++ b/compiler/ident/src/lib.rs @@ -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::()) { Ordering::Less => { - // This fits in a small string, but needs its length recorded - let mut answer_bytes: [u8; mem::size_of::()] = unsafe { - mem::transmute::()]>(Self::default()) - }; + let mut bytes = [0; mem::size_of::()]; - // 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>(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::() * 2 - 1] = u8::MAX - len as u8; - bytes[mem::size_of::() - 1] = u8::MAX - len as u8; - - answer.length = usize::from_ne_bytes(bytes); - } - - answer + unsafe { mem::transmute::<[u8; mem::size_of::()], 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::()] = unsafe { - mem::transmute::()]>(Self::default()) - }; + let mut bytes = [0; mem::size_of::()]; // 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::()) - }; + bytes.copy_from_slice(slice); - dest_slice.copy_from_slice(slice); - - unsafe { mem::transmute::<[u8; mem::size_of::()], Self>(answer_bytes) } + unsafe { mem::transmute::<[u8; mem::size_of::()], Self>(bytes) } } Ordering::Greater => { // This needs a big string + let align = mem::align_of::(); let elements = unsafe { - let align = mem::align_of::(); 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 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::(); - let copy_length = self.length + capacity_size; - let elements = unsafe { - let align = mem::align_of::(); - 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::(); unsafe { - let align = mem::align_of::(); let layout = Layout::from_size_align_unchecked(self.length, align); - - System.dealloc(self.elements as *mut _, layout); + dealloc(self.elements as *mut _, layout); } } } diff --git a/compiler/load/Cargo.toml b/compiler/load/Cargo.toml index 57f4ffd530..3945650771 100644 --- a/compiler/load/Cargo.toml +++ b/compiler/load/Cargo.toml @@ -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" diff --git a/compiler/load/src/docs.rs b/compiler/load/src/docs.rs index 82c351681e..bd8e526344 100644 --- a/compiler/load/src/docs.rs +++ b/compiler/load/src/docs.rs @@ -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), diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 0a119ecb23..0cfa433b12 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -2101,7 +2101,7 @@ fn finish_specialization( EntryPoint { layout: roc_mono::ir::ProcLayout { arguments: &[], - result: Layout::Struct(&[]), + result: Layout::struct_no_name_order(&[]), }, symbol, } diff --git a/compiler/load/tests/test_load.rs b/compiler/load/tests/test_load.rs index dc1afddcc0..894954d4dc 100644 --- a/compiler/load/tests/test_load.rs +++ b/compiler/load/tests/test_load.rs @@ -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, + 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 { 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>, 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 + ); + } } diff --git a/compiler/module/src/called_via.rs b/compiler/module/src/called_via.rs index 62d1ecbe3f..c83245d3e9 100644 --- a/compiler/module/src/called_via.rs +++ b/compiler/module/src/called_via.rs @@ -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 => "<-", }; diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index 57f05e194d..be5a8394d3 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -111,6 +111,7 @@ pub enum LowLevel { NumShiftRightBy, NumShiftRightZfBy, NumIntCast, + NumToIntChecked, NumToStr, Eq, NotEq, diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 6d9c7461cd..82778fa6d0 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -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 diff --git a/compiler/mono/Cargo.toml b/compiler/mono/Cargo.toml index 5bdf3723fa..dd07c5f107 100644 --- a/compiler/mono/Cargo.toml +++ b/compiler/mono/Cargo.toml @@ -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" } diff --git a/compiler/mono/src/alias_analysis.rs b/compiler/mono/src/alias_analysis.rs index f42b554628..f25e3e3b08 100644 --- a/compiler/mono/src/alias_analysis.rs +++ b/compiler/mono/src/alias_analysis.rs @@ -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>)> 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(), diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index 1036d55279..3f6a0bbc4e 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -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]), diff --git a/compiler/mono/src/code_gen_help/equality.rs b/compiler/mono/src/code_gen_help/equality.rs index d58d095274..cf4fbcff17 100644 --- a/compiler/mono/src/code_gen_help/equality.rs +++ b/compiler/mono/src/code_gen_help/equality.rs @@ -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 => { diff --git a/compiler/mono/src/code_gen_help/mod.rs b/compiler/mono/src/code_gen_help/mod.rs index e74e4058ed..8d5a239520 100644 --- a/compiler/mono/src/code_gen_help/mod.rs +++ b/compiler/mono/src/code_gen_help/mod.rs @@ -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(), diff --git a/compiler/mono/src/code_gen_help/refcount.rs b/compiler/mono/src/code_gen_help/refcount.rs index 272e503d18..60e15b9f73 100644 --- a/compiler/mono/src/code_gen_help/refcount.rs +++ b/compiler/mono/src/code_gen_help/refcount.rs @@ -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() diff --git a/compiler/mono/src/decision_tree.rs b/compiler/mono/src/decision_tree.rs index b37391d9cd..39c28f49d8 100644 --- a/compiler/mono/src/decision_tree.rs +++ b/compiler/mono/src/decision_tree.rs @@ -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 { diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 0e0c39019a..801a7c0c51 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -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) diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index b04993c9d6..21661dae6b 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -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> { - 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 diff --git a/compiler/parse/Cargo.toml b/compiler/parse/Cargo.toml index b1a4e86200..33954d8d95 100644 --- a/compiler/parse/Cargo.toml +++ b/compiler/parse/Cargo.toml @@ -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" diff --git a/compiler/parse/src/ast.rs b/compiler/parse/src/ast.rs index 8545d49f3d..6ca0c1cf5f 100644 --- a/compiler/parse/src/ast.rs +++ b/compiler/parse/src/ast.rs @@ -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>>), @@ -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>], &'a Loc>), /// 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>], } -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>, }, + /// An opaque type, wrapping its inner type. E.g. Age := U64. + Opaque { + header: TypeHeader<'a>, + typ: Loc>, + }, + // 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>, &'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>, &'a [Loc>]), /// This is Located rather than Located 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. diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index 21c8cc1277..4de9f94875 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -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, + kind: TypeKind, ) -> Result<(Loc>, Vec<'a, &'a Loc>>), 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>, loc_ann: Loc>, + 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>>, region: Region, @@ -744,13 +761,21 @@ fn append_alias_definition<'a>( name: Loc<&'a str>, pattern_arguments: &'a [Loc>], loc_ann: Loc>, + 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, + 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 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 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), diff --git a/compiler/parse/src/ident.rs b/compiler/parse/src/ident.rs index a310525f88..c2afb90038 100644 --- a/compiler/parse/src/ident.rs +++ b/compiler/parse/src/ident.rs @@ -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)), }, diff --git a/compiler/parse/src/parser.rs b/compiler/parse/src/parser.rs index d29b62f85b..130b382636 100644 --- a/compiler/parse/src/parser.rs +++ b/compiler/parse/src/parser.rs @@ -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), diff --git a/compiler/parse/src/pattern.rs b/compiler/parse/src/pattern.rs index aa54dde475..dd99a1be43 100644 --- a/compiler/parse/src/pattern.rs +++ b/compiler/parse/src/pattern.rs @@ -61,6 +61,7 @@ pub fn loc_pattern_help<'a>(min_indent: u32) -> impl Parser<'a, Loc> )), 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 } => { diff --git a/compiler/parse/src/string_literal.rs b/compiler/parse/src/string_literal.rs index cf2b4b5674..4d77dc523f 100644 --- a/compiler/parse/src/string_literal.rs +++ b/compiler/parse/src/string_literal.rs @@ -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::() + 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::*; diff --git a/compiler/parse/src/type_annotation.rs b/compiler/parse/src/type_annotation.rs index 9a08ea1694..0d7df8b1c2 100644 --- a/compiler/parse/src/type_annotation.rs +++ b/compiler/parse/src/type_annotation.rs @@ -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, -) -> impl Parser { +) -> impl Parser { 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>, EType<' ] ), |arena: &'a Bump, - (loc_ann, opt_as): (Loc>, Option<(&'a [_], AliasHeader<'a>)>)| { + (loc_ann, opt_as): (Loc>, Option<(&'a [_], TypeHeader<'a>)>)| { match opt_as { Some((spaces, alias)) => { let alias_vars_region = diff --git a/compiler/parse/tests/snapshots/pass/opaque_reference_expr.expr.result-ast b/compiler/parse/tests/snapshots/pass/opaque_reference_expr.expr.result-ast new file mode 100644 index 0000000000..10d11945aa --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/opaque_reference_expr.expr.result-ast @@ -0,0 +1,3 @@ +OpaqueRef( + "$Age", +) diff --git a/compiler/parse/tests/snapshots/pass/opaque_reference_expr.expr.roc b/compiler/parse/tests/snapshots/pass/opaque_reference_expr.expr.roc new file mode 100644 index 0000000000..fd75aeaae1 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/opaque_reference_expr.expr.roc @@ -0,0 +1 @@ +$Age diff --git a/compiler/parse/tests/snapshots/pass/opaque_reference_expr_with_arguments.expr.result-ast b/compiler/parse/tests/snapshots/pass/opaque_reference_expr_with_arguments.expr.result-ast new file mode 100644 index 0000000000..b9db6ccfa8 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/opaque_reference_expr_with_arguments.expr.result-ast @@ -0,0 +1,16 @@ +Apply( + @0-4 OpaqueRef( + "$Age", + ), + [ + @5-6 Var { + module_name: "", + ident: "m", + }, + @7-8 Var { + module_name: "", + ident: "n", + }, + ], + Space, +) diff --git a/compiler/parse/tests/snapshots/pass/opaque_reference_expr_with_arguments.expr.roc b/compiler/parse/tests/snapshots/pass/opaque_reference_expr_with_arguments.expr.roc new file mode 100644 index 0000000000..e8b6053bba --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/opaque_reference_expr_with_arguments.expr.roc @@ -0,0 +1 @@ +$Age m n diff --git a/compiler/parse/tests/snapshots/pass/opaque_reference_pattern.expr.result-ast b/compiler/parse/tests/snapshots/pass/opaque_reference_pattern.expr.result-ast new file mode 100644 index 0000000000..41f8660339 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/opaque_reference_pattern.expr.result-ast @@ -0,0 +1,24 @@ +When( + @5-6 Var { + module_name: "", + ident: "n", + }, + [ + WhenBranch { + patterns: [ + @12-16 SpaceBefore( + OpaqueRef( + "$Age", + ), + [ + Newline, + ], + ), + ], + value: @20-21 Num( + "1", + ), + guard: None, + }, + ], +) diff --git a/compiler/parse/tests/snapshots/pass/opaque_reference_pattern.expr.roc b/compiler/parse/tests/snapshots/pass/opaque_reference_pattern.expr.roc new file mode 100644 index 0000000000..49693af6b1 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/opaque_reference_pattern.expr.roc @@ -0,0 +1,2 @@ +when n is + $Age -> 1 diff --git a/compiler/parse/tests/snapshots/pass/opaque_reference_pattern_with_arguments.expr.result-ast b/compiler/parse/tests/snapshots/pass/opaque_reference_pattern_with_arguments.expr.result-ast new file mode 100644 index 0000000000..6f159172e8 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/opaque_reference_pattern_with_arguments.expr.result-ast @@ -0,0 +1,46 @@ +When( + @5-6 Var { + module_name: "", + ident: "n", + }, + [ + WhenBranch { + patterns: [ + @12-20 SpaceBefore( + Apply( + @12-16 OpaqueRef( + "$Add", + ), + [ + @17-18 Identifier( + "n", + ), + @19-20 Identifier( + "m", + ), + ], + ), + [ + Newline, + ], + ), + ], + value: @24-29 BinOps( + [ + ( + @24-25 Var { + module_name: "", + ident: "n", + }, + @26-27 Plus, + ), + ], + @28-29 Var { + module_name: "", + ident: "m", + }, + ), + guard: None, + }, + ], +) diff --git a/compiler/parse/tests/snapshots/pass/opaque_reference_pattern_with_arguments.expr.roc b/compiler/parse/tests/snapshots/pass/opaque_reference_pattern_with_arguments.expr.roc new file mode 100644 index 0000000000..d5fcf3be4b --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/opaque_reference_pattern_with_arguments.expr.roc @@ -0,0 +1,2 @@ +when n is + $Add n m -> n + m diff --git a/compiler/parse/tests/snapshots/pass/opaque_simple.module.result-ast b/compiler/parse/tests/snapshots/pass/opaque_simple.module.result-ast new file mode 100644 index 0000000000..d90ca292eb --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/opaque_simple.module.result-ast @@ -0,0 +1,21 @@ +[ + @0-9 SpaceAfter( + SpaceBefore( + Opaque { + header: TypeHeader { + name: @0-3 "Age", + vars: [], + }, + typ: @7-9 Apply( + "", + "U8", + [], + ), + }, + [], + ), + [ + Newline, + ], + ), +] diff --git a/compiler/parse/tests/snapshots/pass/opaque_simple.module.roc b/compiler/parse/tests/snapshots/pass/opaque_simple.module.roc new file mode 100644 index 0000000000..151cb0215e --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/opaque_simple.module.roc @@ -0,0 +1 @@ +Age := U8 diff --git a/compiler/parse/tests/snapshots/pass/opaque_with_type_arguments.module.result-ast b/compiler/parse/tests/snapshots/pass/opaque_with_type_arguments.module.result-ast new file mode 100644 index 0000000000..8c2e2d44f5 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/opaque_with_type_arguments.module.result-ast @@ -0,0 +1,50 @@ +[ + @0-53 SpaceAfter( + SpaceBefore( + Opaque { + header: TypeHeader { + name: @0-10 "Bookmark", + vars: [ + @9-10 Identifier( + "a", + ), + ], + }, + typ: @14-53 Record { + fields: [ + @16-28 RequiredValue( + @16-23 "chapter", + [], + @25-28 Apply( + "", + "Str", + [], + ), + ), + @30-41 RequiredValue( + @30-36 "stanza", + [], + @38-41 Apply( + "", + "Str", + [], + ), + ), + @43-51 RequiredValue( + @43-48 "notes", + [], + @50-51 BoundVariable( + "a", + ), + ), + ], + ext: None, + }, + }, + [], + ), + [ + Newline, + ], + ), +] diff --git a/compiler/parse/tests/snapshots/pass/opaque_with_type_arguments.module.roc b/compiler/parse/tests/snapshots/pass/opaque_with_type_arguments.module.roc new file mode 100644 index 0000000000..5d5aacb0bd --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/opaque_with_type_arguments.module.roc @@ -0,0 +1 @@ +Bookmark a := { chapter: Str, stanza: Str, notes: a } diff --git a/compiler/parse/tests/snapshots/pass/parse_alias.expr.result-ast b/compiler/parse/tests/snapshots/pass/parse_alias.expr.result-ast index 3deb39c07f..9beffce00a 100644 --- a/compiler/parse/tests/snapshots/pass/parse_alias.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/parse_alias.expr.result-ast @@ -1,7 +1,7 @@ Defs( [ @0-26 Alias { - header: AliasHeader { + header: TypeHeader { name: @0-4 "Blah", vars: [ @5-6 Identifier( diff --git a/compiler/parse/tests/snapshots/pass/parse_as_ann.expr.result-ast b/compiler/parse/tests/snapshots/pass/parse_as_ann.expr.result-ast index f99bd7c8fb..5e6b069d15 100644 --- a/compiler/parse/tests/snapshots/pass/parse_as_ann.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/parse_as_ann.expr.result-ast @@ -18,7 +18,7 @@ Defs( ], ), [], - AliasHeader { + TypeHeader { name: @25-29 "Blah", vars: [ @30-31 Identifier( diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index 7430e4b248..8b60876c94 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -191,6 +191,12 @@ mod test_parse { pass/one_minus_two.expr, pass/one_plus_two.expr, pass/one_spaced_def.expr, + pass/opaque_simple.module, + pass/opaque_with_type_arguments.module, + pass/opaque_reference_expr.expr, + pass/opaque_reference_expr_with_arguments.expr, + pass/opaque_reference_pattern.expr, + pass/opaque_reference_pattern_with_arguments.expr, pass/ops_with_newlines.expr, pass/packed_singleton_list.expr, pass/parenthetical_apply.expr, @@ -501,19 +507,28 @@ mod test_parse { } #[quickcheck] - fn all_f64_values_parse(num: f64) { - let string = num.to_string(); - if string.contains('.') { - assert_parses_to(&string, Float(&string)); - } else if num.is_nan() { - assert_parses_to(&string, Expr::GlobalTag(&string)); - } else if num.is_finite() { - // These are whole numbers. Add the `.0` back to make float. - let float_string = format!("{}.0", string); - assert_parses_to(&float_string, Float(&float_string)); + fn all_f64_values_parse(mut num: f64) { + // NaN, Infinity, -Infinity (these would all parse as tags in Roc) + if !num.is_finite() { + num = 0.0; } + + // These can potentially be whole numbers. `Display` omits the decimal point for those, + // causing them to no longer be parsed as fractional numbers by Roc. + // Using `Debug` instead of `Display` ensures they always have a decimal point. + let float_string = format!("{:?}", num); + + assert_parses_to(float_string.as_str(), Float(float_string.as_str())); } + // SINGLE QUOTE LITERAL + #[test] + fn single_quote() { + assert_parses_to("'b'", Expr::SingleQuote("b")); + } + + // RECORD LITERALS + // #[test] // fn type_signature_def() { // let arena = Bump::new(); diff --git a/compiler/problem/src/can.rs b/compiler/problem/src/can.rs index a4eeddebe7..9eeac0af8d 100644 --- a/compiler/problem/src/can.rs +++ b/compiler/problem/src/can.rs @@ -39,7 +39,7 @@ pub enum Problem { CyclicAlias(Symbol, Region, Vec), BadRecursion(Vec), PhantomTypeArgument { - alias: Symbol, + typ: Symbol, variable_region: Region, variable_name: Lowercase, }, @@ -155,16 +155,53 @@ pub enum RuntimeError { ErroneousType, LookupNotInScope(Loc, MutSet>), + OpaqueNotDefined { + usage: Loc, + opaques_in_scope: MutSet>, + opt_defined_alias: Option, + }, + OpaqueOutsideScope { + opaque: Ident, + referenced_region: Region, + imported_region: Region, + }, ValueNotExposed { module_name: ModuleName, ident: Ident, region: Region, exposed_values: Vec, }, + /// A module was referenced, but hasn't been imported anywhere in the program + /// + /// An example would be: + /// ```roc + /// app "hello" + /// packages { pf: "platform" } + /// imports [ pf.Stdout] + /// provides [ main ] to pf + /// + /// main : Task.Task {} [] // Task isn't imported! + /// main = Stdout.line "I'm a Roc application!" + /// ``` ModuleNotImported { + /// The name of the module that was referenced module_name: ModuleName, + /// A list of modules which *have* been imported imported_modules: MutSet>, + /// Where the problem occurred region: Region, + /// Whether or not the module exists at all + /// + /// This is used to suggest that the user import the module, as opposed to fix a + /// typo in the spelling. For example, if the user typed `Task`, and the platform + /// exposes a `Task` module that hasn't been imported, we can sugguest that they + /// add the import statement. + /// + /// On the other hand, if the user typed `Tesk`, they might want to check their + /// spelling. + /// + /// If unsure, this should be set to `false` + module_exists: bool, }, InvalidPrecedence(PrecedenceProblem, Region), MalformedIdentifier(Box, roc_parse::ident::BadIdent, Region), @@ -193,6 +230,11 @@ pub enum RuntimeError { VoidValue, ExposedButNotDefined(Symbol), + + /// where '' + EmptySingleQuote(Region), + /// where 'aa' + MultipleCharsInSingleQuote(Region), } #[derive(Clone, Copy, Debug, PartialEq)] @@ -203,4 +245,6 @@ pub enum MalformedPatternProblem { Unknown, QualifiedIdentifier, BadIdent(roc_parse::ident::BadIdent), + EmptySingleQuote, + MultipleCharsInSingleQuote, } diff --git a/compiler/region/src/all.rs b/compiler/region/src/all.rs index 1481e4a735..a22e325f60 100644 --- a/compiler/region/src/all.rs +++ b/compiler/region/src/all.rs @@ -311,6 +311,16 @@ impl Loc { value: transform(&self.value), } } + + pub fn map_owned(self, transform: F) -> Loc + where + F: (FnOnce(T) -> U), + { + Loc { + region: self.region, + value: transform(self.value), + } + } } impl fmt::Debug for Loc diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index e2ace30be3..37678aef34 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -914,6 +914,8 @@ fn type_to_variable<'a>( type_arguments, actual, lambda_set_variables, + // TODO(opaques): revisit kind + kind: _, } => { if let Some(reserved) = Variable::get_reserved(*symbol) { if rank.is_none() { diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index f524adca8a..6b1eab6f4f 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -5251,4 +5251,27 @@ mod solve_expr { "{ j : a, lst : List a, s : Str }", ) } + + #[test] + fn to_int() { + infer_eq_without_problem( + indoc!( + r#" + { + toI8: Num.toI8, + toI16: Num.toI16, + toI32: Num.toI32, + toI64: Num.toI64, + toI128: Num.toI128, + toU8: Num.toU8, + toU16: Num.toU16, + toU32: Num.toU32, + toU64: Num.toU64, + toU128: Num.toU128, + } + "# + ), + r#"{ toI128 : Int * -> I128, toI16 : Int * -> I16, toI32 : Int * -> I32, toI64 : Int * -> I64, toI8 : Int * -> I8, toU128 : Int * -> U128, toU16 : Int * -> U16, toU32 : Int * -> U32, toU64 : Int * -> U64, toU8 : Int * -> U8 }"#, + ) + } } diff --git a/compiler/test_gen/Cargo.toml b/compiler/test_gen/Cargo.toml index 607b14b7ff..203b669d48 100644 --- a/compiler/test_gen/Cargo.toml +++ b/compiler/test_gen/Cargo.toml @@ -30,7 +30,7 @@ roc_reporting = { path = "../../reporting" } roc_load = { path = "../load" } roc_can = { path = "../can" } roc_parse = { path = "../parse" } -roc_build = { path = "../build" } +roc_build = { path = "../build", features = ["target-aarch64", "target-x86_64", "target-wasm32"] } roc_target = { path = "../roc_target" } roc_std = { path = "../../roc_std" } bumpalo = { version = "3.8.0", features = ["collections"] } @@ -39,14 +39,20 @@ libc = "0.2.106" inkwell = { path = "../../vendor/inkwell" } target-lexicon = "0.12.2" libloading = "0.7.1" -wasmer = { version = "2.0.0", default-features = false, features = ["default-cranelift", "default-universal"] } wasmer-wasi = "2.0.0" tempfile = "3.2.0" indoc = "1.0.3" +# 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"] } + [features] default = ["gen-llvm"] -gen-llvm = [] +gen-llvm = ["roc_build/llvm"] gen-dev = [] gen-wasm = [] wasm-cli-run = [] diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index d7e238605f..9a4b67496a 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -28,7 +28,7 @@ fn roc_list_construction() { #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn empty_list_literal() { - assert_evals_to!("[]", RocList::from_slice(&[]), RocList); + assert_evals_to!("[]", RocList::::from_slice(&[]), RocList); } #[test] @@ -136,7 +136,7 @@ fn bool_list_literal() { #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn variously_sized_list_literals() { - assert_evals_to!("[]", RocList::from_slice(&[]), RocList); + assert_evals_to!("[]", RocList::::from_slice(&[]), RocList); assert_evals_to!("[1]", RocList::from_slice(&[1]), RocList); assert_evals_to!("[1, 2]", RocList::from_slice(&[1, 2]), RocList); assert_evals_to!("[1, 2, 3]", RocList::from_slice(&[1, 2, 3]), RocList); @@ -177,12 +177,12 @@ fn list_take_first() { ); assert_evals_to!( "List.takeFirst [1, 2, 3] 0", - RocList::from_slice(&[]), + RocList::::from_slice(&[]), RocList ); assert_evals_to!( "List.takeFirst [] 1", - RocList::from_slice(&[]), + RocList::::from_slice(&[]), RocList ); assert_evals_to!( @@ -202,10 +202,14 @@ fn list_take_last() { ); assert_evals_to!( "List.takeLast [1, 2, 3] 0", - RocList::from_slice(&[]), + RocList::::from_slice(&[]), + RocList + ); + assert_evals_to!( + "List.takeLast [] 1", + RocList::::from_slice(&[]), RocList ); - assert_evals_to!("List.takeLast [] 1", RocList::from_slice(&[]), RocList); assert_evals_to!( "List.takeLast [1,2] 5", RocList::from_slice(&[1, 2]), @@ -233,17 +237,17 @@ fn list_sublist() { ); assert_evals_to!( "List.sublist [1, 2, 3] { start: 3 , len: 2 } ", - RocList::from_slice(&[]), + RocList::::from_slice(&[]), RocList ); assert_evals_to!( "List.sublist [] { start: 1 , len: 1 } ", - RocList::from_slice(&[]), + RocList::::from_slice(&[]), RocList ); assert_evals_to!( "List.sublist [1, 2, 3] { start: 1 , len: 0 } ", - RocList::from_slice(&[]), + RocList::::from_slice(&[]), RocList ); assert_evals_to!( @@ -261,7 +265,7 @@ fn list_split() { list = List.split [1, 2, 3] 0 list.before "#, - RocList::from_slice(&[]), + RocList::::from_slice(&[]), RocList ); assert_evals_to!( @@ -280,17 +284,26 @@ fn list_split() { ); assert_evals_to!( "List.split [1, 2, 3] 3", - (RocList::from_slice(&[1, 2, 3]), RocList::from_slice(&[]),), + ( + RocList::from_slice(&[1, 2, 3]), + RocList::::from_slice(&[]), + ), (RocList, RocList,) ); assert_evals_to!( "List.split [1, 2, 3] 4", - (RocList::from_slice(&[1, 2, 3]), RocList::from_slice(&[]),), + ( + RocList::from_slice(&[1, 2, 3]), + RocList::::from_slice(&[]), + ), (RocList, RocList,) ); assert_evals_to!( "List.split [] 1", - (RocList::from_slice(&[]), RocList::from_slice(&[]),), + ( + RocList::::from_slice(&[]), + RocList::::from_slice(&[]), + ), (RocList, RocList,) ); } @@ -302,8 +315,16 @@ fn list_drop() { RocList::from_slice(&[3]), RocList ); - assert_evals_to!("List.drop [] 1", RocList::from_slice(&[]), RocList); - assert_evals_to!("List.drop [1,2] 5", RocList::from_slice(&[]), RocList); + assert_evals_to!( + "List.drop [] 1", + RocList::::from_slice(&[]), + RocList + ); + assert_evals_to!( + "List.drop [1,2] 5", + RocList::::from_slice(&[]), + RocList + ); } #[test] @@ -319,8 +340,16 @@ fn list_drop_at() { RocList::from_slice(&[0, 0, 0]), RocList ); - assert_evals_to!("List.dropAt [] 1", RocList::from_slice(&[]), RocList); - assert_evals_to!("List.dropAt [0] 0", RocList::from_slice(&[]), RocList); + assert_evals_to!( + "List.dropAt [] 1", + RocList::::from_slice(&[]), + RocList + ); + assert_evals_to!( + "List.dropAt [0] 0", + RocList::::from_slice(&[]), + RocList + ); } #[test] @@ -341,7 +370,7 @@ fn list_intersperse() { List.intersperse [] 1 "# ), - RocList::from_slice(&[]), + RocList::::from_slice(&[]), RocList ); } @@ -380,7 +409,7 @@ fn list_drop_if_empty_list_of_int() { List.dropIf empty \_ -> True "# ), - RocList::from_slice(&[]), + RocList::::from_slice(&[]), RocList ); } @@ -397,7 +426,7 @@ fn list_drop_if_empty_list() { List.dropIf [] alwaysTrue "# ), - RocList::from_slice(&[]), + RocList::::from_slice(&[]), RocList ); } @@ -425,7 +454,7 @@ fn list_drop_if_always_true_for_non_empty_list() { List.dropIf [1,2,3,4,5,6,7,8] (\_ -> True) "# ), - RocList::from_slice(&[]), + RocList::::from_slice(&[]), RocList ); } @@ -453,10 +482,7 @@ fn list_drop_if_string_eq() { List.dropIf ["x", "y", "x"] (\s -> s == "y") "# ), - RocList::from_slice(&[ - RocStr::from_slice("x".as_bytes()), - RocStr::from_slice("x".as_bytes()) - ]), + RocList::from_slice(&[RocStr::from("x"), RocStr::from("x")]), RocList ); } @@ -469,8 +495,16 @@ fn list_drop_last() { RocList::from_slice(&[1, 2]), RocList ); - assert_evals_to!("List.dropLast []", RocList::from_slice(&[]), RocList); - assert_evals_to!("List.dropLast [0]", RocList::from_slice(&[]), RocList); + assert_evals_to!( + "List.dropLast []", + RocList::::from_slice(&[]), + RocList + ); + assert_evals_to!( + "List.dropLast [0]", + RocList::::from_slice(&[]), + RocList + ); } #[test] @@ -503,14 +537,26 @@ fn list_drop_first() { RocList::from_slice(&[2, 3]), RocList ); - assert_evals_to!("List.dropFirst []", RocList::from_slice(&[]), RocList); - assert_evals_to!("List.dropFirst [0]", RocList::from_slice(&[]), RocList); + assert_evals_to!( + "List.dropFirst []", + RocList::::from_slice(&[]), + RocList + ); + assert_evals_to!( + "List.dropFirst [0]", + RocList::::from_slice(&[]), + RocList + ); } #[test] #[cfg(any(feature = "gen-llvm"))] fn list_swap() { - assert_evals_to!("List.swap [] 0 1", RocList::from_slice(&[]), RocList); + assert_evals_to!( + "List.swap [] 0 1", + RocList::::from_slice(&[]), + RocList + ); assert_evals_to!( "List.swap [ 0 ] 1 2", RocList::from_slice(&[0]), @@ -621,7 +667,7 @@ fn list_prepend() { List.prepend init "bar" "# ), - RocList::from_slice(&[RocStr::from_slice(b"bar"), RocStr::from_slice(b"foo"),]), + RocList::from_slice(&[RocStr::from("bar"), RocStr::from("foo"),]), RocList ); } @@ -783,7 +829,7 @@ fn list_keep_if_empty_list_of_int() { List.keepIf empty \_ -> True "# ), - RocList::from_slice(&[]), + RocList::::from_slice(&[]), RocList ); } @@ -802,7 +848,7 @@ fn list_keep_if_empty_list() { List.keepIf [] alwaysTrue "# ), - RocList::from_slice(&[]), + RocList::::from_slice(&[]), RocList ); } @@ -842,7 +888,7 @@ fn list_keep_if_always_false_for_non_empty_list() { List.keepIf [1,2,3,4,5,6,7,8] alwaysFalse "# ), - RocList::from_slice(&[]), + RocList::::from_slice(&[]), RocList ); } @@ -874,10 +920,7 @@ fn list_keep_if_str_is_hello() { List.keepIf ["x", "y", "x"] (\x -> x == "x") "# ), - RocList::from_slice(&[ - RocStr::from_slice("x".as_bytes()), - RocStr::from_slice("x".as_bytes()) - ]), + RocList::from_slice(&[RocStr::from("x"), RocStr::from("x")]), RocList ); } @@ -895,7 +938,7 @@ fn list_map_on_empty_list_with_int_layout() { List.map empty (\x -> x) "# ), - RocList::from_slice(&[]), + RocList::::from_slice(&[]), RocList ); } @@ -1006,7 +1049,7 @@ fn list_map_all_inline() { List.map [] (\x -> x > 0) "# ), - RocList::from_slice(&[]), + RocList::::from_slice(&[]), RocList ); } @@ -1060,7 +1103,7 @@ fn list_map4_different_length() { (\a, b, c, d -> Str.concat a (Str.concat b (Str.concat c d))) "# ), - RocList::from_slice(&[RocStr::from_slice("hola".as_bytes()),]), + RocList::from_slice(&[RocStr::from("hola"),]), RocList ); } @@ -1092,7 +1135,7 @@ fn list_map3_different_length() { (\a, b, c -> Str.concat a (Str.concat b c)) "# ), - RocList::from_slice(&[RocStr::from_slice("abc".as_bytes()),]), + RocList::from_slice(&[RocStr::from("abc"),]), RocList ); } @@ -1124,7 +1167,7 @@ fn list_map2_different_lengths() { (\a, b -> Str.concat a b) "# ), - RocList::from_slice(&[RocStr::from_slice("ab".as_bytes()),]), + RocList::from_slice(&[RocStr::from("ab"),]), RocList ); } @@ -1132,7 +1175,11 @@ fn list_map2_different_lengths() { #[test] #[cfg(any(feature = "gen-llvm"))] fn list_join_empty_list() { - assert_evals_to!("List.join []", RocList::from_slice(&[]), RocList); + assert_evals_to!( + "List.join []", + RocList::::from_slice(&[]), + RocList + ); } #[test] @@ -1214,7 +1261,7 @@ fn list_join_defined_empty_list() { fn list_join_all_empty_lists() { assert_evals_to!( "List.join [ [], [], [] ]", - RocList::from_slice(&[]), + RocList::::from_slice(&[]), RocList ); } @@ -1252,7 +1299,7 @@ fn list_repeat() { assert_evals_to!( "List.repeat [] 2", - RocList::from_slice(&[RocList::default(), RocList::default()]), + RocList::from_slice(&[RocList::::default(), RocList::default()]), RocList> ); @@ -1266,7 +1313,7 @@ fn list_repeat() { List.repeat noStrs 2 "# ), - RocList::from_slice(&[RocList::default(), RocList::default()]), + RocList::from_slice(&[RocList::::default(), RocList::default()]), RocList> ); @@ -1306,7 +1353,7 @@ fn list_reverse_empty_list_of_int() { List.reverse emptyList "# ), - RocList::from_slice(&[]), + RocList::::from_slice(&[]), RocList ); } @@ -1314,7 +1361,11 @@ fn list_reverse_empty_list_of_int() { #[test] #[cfg(any(feature = "gen-llvm"))] fn list_reverse_empty_list() { - assert_evals_to!("List.reverse []", RocList::from_slice(&[]), RocList); + assert_evals_to!( + "List.reverse []", + RocList::::from_slice(&[]), + RocList + ); } #[test] @@ -1334,7 +1385,7 @@ fn list_concat() { List.concat firstList secondList "# ), - RocList::from_slice(&[]), + RocList::::from_slice(&[]), RocList ); } @@ -1342,7 +1393,11 @@ fn list_concat() { #[test] #[cfg(any(feature = "gen-llvm"))] fn list_concat_two_empty_lists() { - assert_evals_to!("List.concat [] []", RocList::from_slice(&[]), RocList); + assert_evals_to!( + "List.concat [] []", + RocList::::from_slice(&[]), + RocList + ); } #[test] @@ -1362,7 +1417,7 @@ fn list_concat_two_empty_lists_of_int() { List.concat firstList secondList "# ), - RocList::from_slice(&[]), + RocList::::from_slice(&[]), RocList ); } @@ -2392,7 +2447,11 @@ fn cleanup_because_exception() { #[test] #[cfg(any(feature = "gen-llvm"))] fn list_range() { - assert_evals_to!("List.range 0 -1", RocList::from_slice(&[]), RocList); + assert_evals_to!( + "List.range 0 -1", + RocList::::from_slice(&[]), + RocList + ); assert_evals_to!("List.range 0 0", RocList::from_slice(&[0]), RocList); assert_evals_to!( "List.range 0 5", @@ -2406,7 +2465,7 @@ fn list_range() { fn list_sort_with() { assert_evals_to!( "List.sortWith [] Num.compare", - RocList::from_slice(&[]), + RocList::::from_slice(&[]), RocList ); assert_evals_to!( @@ -2424,7 +2483,11 @@ fn list_sort_with() { #[test] #[cfg(any(feature = "gen-llvm"))] fn list_sort_asc() { - assert_evals_to!("List.sortAsc []", RocList::from_slice(&[]), RocList); + assert_evals_to!( + "List.sortAsc []", + RocList::::from_slice(&[]), + RocList + ); assert_evals_to!( "List.sortAsc [ 4,3,2,1 ]", RocList::from_slice(&[1, 2, 3, 4]), @@ -2435,7 +2498,11 @@ fn list_sort_asc() { #[test] #[cfg(any(feature = "gen-llvm"))] fn list_sort_desc() { - assert_evals_to!("List.sortDesc []", RocList::from_slice(&[]), RocList); + assert_evals_to!( + "List.sortDesc []", + RocList::::from_slice(&[]), + RocList + ); assert_evals_to!( "List.sortDesc [ 1,2,3,4 ]", RocList::from_slice(&[4, 3, 2, 1]), @@ -2552,7 +2619,7 @@ fn empty_list_of_function_type() { Err _ -> "bad!" "# ), - RocStr::from_slice(b"bar"), + RocStr::from("bar"), RocStr ); } @@ -2567,11 +2634,11 @@ fn list_join_map() { "# ), RocList::from_slice(&[ - RocStr::from_slice("guava".as_bytes()), - RocStr::from_slice("apple".as_bytes()), - RocStr::from_slice("pear".as_bytes()), - RocStr::from_slice("bailey".as_bytes()), - RocStr::from_slice("cyrus".as_bytes()), + RocStr::from("guava"), + RocStr::from("apple"), + RocStr::from("pear"), + RocStr::from("bailey"), + RocStr::from("cyrus"), ]), RocList ) @@ -2602,7 +2669,7 @@ fn list_find() { Err _ -> "not found" "# ), - RocStr::from_slice(b"bc"), + RocStr::from("bc"), RocStr ); } @@ -2618,7 +2685,7 @@ fn list_find_not_found() { Err _ -> "not found" "# ), - RocStr::from_slice(b"not found"), + RocStr::from("not found"), RocStr ); } @@ -2634,7 +2701,7 @@ fn list_find_empty_typed_list() { Err _ -> "not found" "# ), - RocStr::from_slice(b"not found"), + RocStr::from("not found"), RocStr ); } diff --git a/compiler/test_gen/src/gen_num.rs b/compiler/test_gen/src/gen_num.rs index 3a4951a36d..f81c2c9d43 100644 --- a/compiler/test_gen/src/gen_num.rs +++ b/compiler/test_gen/src/gen_num.rs @@ -357,6 +357,66 @@ fn u8_hex_int_alias() { ); } +#[test] +fn character_literal() { + assert_evals_to!( + indoc!( + r#" + x = 'A' + + x + "# + ), + 65, + u32 + ); +} + +#[test] +fn character_literal_back_slash() { + assert_evals_to!( + indoc!( + r#" + x = '\\' + + x + "# + ), + 92, + u32 + ); +} + +#[test] +fn character_literal_single_quote() { + assert_evals_to!( + indoc!( + r#" + x = '\'' + + x + "# + ), + 39, + u32 + ); +} + +#[test] +fn character_literal_new_line() { + assert_evals_to!( + indoc!( + r#" + x = '\n' + + x + "# + ), + 10, + u32 + ); +} + #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn dec_float_alias() { @@ -2053,6 +2113,207 @@ fn max_u8() { ); } +macro_rules! to_int_tests { + ($($fn:expr, $typ:ty, ($($test_name:ident, $input:expr, $output:expr $(, [ $($support_gen:literal),* ])? )*))*) => {$($( + #[test] + #[cfg(any(feature = "gen-llvm", $($(feature = $support_gen)*)?))] + fn $test_name() { + let input = format!("{} {}", $fn, $input); + assert_evals_to!(&input, $output, $typ) + } + )*)*} +} + +to_int_tests! { + "Num.toI8", i8, ( + to_i8_same_width, "15u8", 15, ["gen-wasm"] + to_i8_truncate, "115i32", 115, ["gen-wasm"] + to_i8_truncate_wraps, "500i32", -12, ["gen-wasm"] + ) + "Num.toI16", i16, ( + to_i16_same_width, "15u16", 15, ["gen-wasm"] + to_i16_extend, "15i8", 15, ["gen-wasm"] + to_i16_truncate, "115i32", 115, ["gen-wasm"] + to_i16_truncate_wraps, "60000i32", -5536, ["gen-wasm"] + ) + "Num.toI32", i32, ( + to_i32_same_width, "15u32", 15, ["gen-wasm"] + to_i32_extend, "15i8", 15, ["gen-wasm"] + to_i32_truncate, "115i64", 115, ["gen-wasm"] + to_i32_truncate_wraps, "5000000000i64", 705032704, ["gen-wasm"] + ) + "Num.toI64", i64, ( + to_i64_same_width, "15u64", 15, ["gen-wasm"] + to_i64_extend, "15i8", 15, ["gen-wasm"] + to_i64_truncate, "115i128", 115 + to_i64_truncate_wraps, "10_000_000_000_000_000_000i128", -8446744073709551616 + ) + "Num.toI128", i128, ( + to_i128_same_width, "15u128", 15 + to_i128_extend, "15i8", 15 + ) + "Num.toU8", u8, ( + to_u8_same_width, "15i8", 15, ["gen-wasm"] + to_u8_truncate, "115i32", 115, ["gen-wasm"] + to_u8_truncate_wraps, "500i32", 244, ["gen-wasm"] + ) + "Num.toU16", u16, ( + to_u16_same_width, "15i16", 15, ["gen-wasm"] + to_u16_extend, "15i8", 15, ["gen-wasm"] + to_u16_truncate, "115i32", 115, ["gen-wasm"] + to_u16_truncate_wraps, "600000000i32", 17920, ["gen-wasm"] + ) + "Num.toU32", u32, ( + to_u32_same_width, "15i32", 15, ["gen-wasm"] + to_u32_extend, "15i8", 15, ["gen-wasm"] + to_u32_truncate, "115i64", 115, ["gen-wasm"] + to_u32_truncate_wraps, "5000000000000000000i64", 1156841472, ["gen-wasm"] + ) + "Num.toU64", u64, ( + to_u64_same_width, "15i64", 15, ["gen-wasm"] + to_u64_extend, "15i8", 15, ["gen-wasm"] + to_u64_truncate, "115i128", 115 + to_u64_truncate_wraps, "10_000_000_000_000_000_000_000i128", 1864712049423024128 + ) + "Num.toU128", u128, ( + to_u128_same_width, "15i128", 15 + to_u128_extend, "15i8", 15 + ) +} + +macro_rules! to_int_checked_tests { + ($($fn:expr, $typ:ty, ($($test_name:ident, $input:expr, $output:expr)*))*) => {$($( + #[test] + #[cfg(any(feature = "gen-llvm"))] + fn $test_name() { + let sentinel = 23; + // Some n = Ok n, None = OutOfBounds + let expected = match $output.into() { + None => sentinel, + Some(n) => { + assert_ne!(n, sentinel); + n + } + }; + let input = format!("Result.withDefault ({} {}) {}", $fn, $input, sentinel); + assert_evals_to!(&input, expected, $typ) + } + )*)*} +} + +to_int_checked_tests! { + "Num.toI8Checked", i8, ( + to_i8_checked_same, "15i8", 15 + to_i8_checked_same_width_unsigned_fits, "15u8", 15 + to_i8_checked_same_width_unsigned_oob, "128u8", None + to_i8_checked_larger_width_signed_fits_pos, "15i16", 15 + to_i8_checked_larger_width_signed_oob_pos, "128i16", None + to_i8_checked_larger_width_signed_fits_neg, "-15i16", -15 + to_i8_checked_larger_width_signed_oob_neg, "-129i16", None + to_i8_checked_larger_width_unsigned_fits_pos, "15u16", 15 + to_i8_checked_larger_width_unsigned_oob_pos, "128u16", None + ) + "Num.toI16Checked", i16, ( + to_i16_checked_smaller_width_pos, "15i8", 15 + to_i16_checked_smaller_width_neg, "-15i8", -15 + to_i16_checked_same, "15i16", 15 + to_i16_checked_same_width_unsigned_fits, "15u16", 15 + to_i16_checked_same_width_unsigned_oob, "32768u16", None + to_i16_checked_larger_width_signed_fits_pos, "15i32", 15 + to_i16_checked_larger_width_signed_oob_pos, "32768i32", None + to_i16_checked_larger_width_signed_fits_neg, "-15i32", -15 + to_i16_checked_larger_width_signed_oob_neg, "-32769i32", None + to_i16_checked_larger_width_unsigned_fits_pos, "15u32", 15 + to_i16_checked_larger_width_unsigned_oob_pos, "32768u32", None + ) + "Num.toI32Checked", i32, ( + to_i32_checked_smaller_width_pos, "15i8", 15 + to_i32_checked_smaller_width_neg, "-15i8", -15 + to_i32_checked_same, "15i32", 15 + to_i32_checked_same_width_unsigned_fits, "15u32", 15 + to_i32_checked_same_width_unsigned_oob, "2147483648u32", None + to_i32_checked_larger_width_signed_fits_pos, "15i64", 15 + to_i32_checked_larger_width_signed_oob_pos, "2147483648i64", None + to_i32_checked_larger_width_signed_fits_neg, "-15i64", -15 + to_i32_checked_larger_width_signed_oob_neg, "-2147483649i64", None + to_i32_checked_larger_width_unsigned_fits_pos, "15u64", 15 + to_i32_checked_larger_width_unsigned_oob_pos, "2147483648u64", None + ) + "Num.toI64Checked", i64, ( + to_i64_checked_smaller_width_pos, "15i8", 15 + to_i64_checked_smaller_width_neg, "-15i8", -15 + to_i64_checked_same, "15i64", 15 + to_i64_checked_same_width_unsigned_fits, "15u64", 15 + to_i64_checked_same_width_unsigned_oob, "9223372036854775808u64", None + to_i64_checked_larger_width_signed_fits_pos, "15i128", 15 + to_i64_checked_larger_width_signed_oob_pos, "9223372036854775808i128", None + to_i64_checked_larger_width_signed_fits_neg, "-15i128", -15 + to_i64_checked_larger_width_signed_oob_neg, "-9223372036854775809i128", None + to_i64_checked_larger_width_unsigned_fits_pos, "15u128", 15 + to_i64_checked_larger_width_unsigned_oob_pos, "9223372036854775808u128", None + ) + "Num.toI128Checked", i128, ( + to_i128_checked_smaller_width_pos, "15i8", 15 + to_i128_checked_smaller_width_neg, "-15i8", -15 + to_i128_checked_same, "15i128", 15 + to_i128_checked_same_width_unsigned_fits, "15u128", 15 + to_i128_checked_same_width_unsigned_oob, "170141183460469231731687303715884105728u128", None + ) + "Num.toU8Checked", u8, ( + to_u8_checked_same, "15u8", 15 + to_u8_checked_same_width_signed_fits, "15i8", 15 + to_u8_checked_same_width_signed_oob, "-1i8", None + to_u8_checked_larger_width_signed_fits_pos, "15i16", 15 + to_u8_checked_larger_width_signed_oob_pos, "256i16", None + to_u8_checked_larger_width_signed_oob_neg, "-1i16", None + to_u8_checked_larger_width_unsigned_fits_pos, "15u16", 15 + to_u8_checked_larger_width_unsigned_oob_pos, "256u16", None + ) + "Num.toU16Checked", u16, ( + to_u16_checked_smaller_width_pos, "15i8", 15 + to_u16_checked_smaller_width_neg_oob, "-15i8", None + to_u16_checked_same, "15u16", 15 + to_u16_checked_same_width_signed_fits, "15i16", 15 + to_u16_checked_same_width_signed_oob, "-1i16", None + to_u16_checked_larger_width_signed_fits_pos, "15i32", 15 + to_u16_checked_larger_width_signed_oob_pos, "65536i32", None + to_u16_checked_larger_width_signed_oob_neg, "-1i32", None + to_u16_checked_larger_width_unsigned_fits_pos, "15u32", 15 + to_u16_checked_larger_width_unsigned_oob_pos, "65536u32", None + ) + "Num.toU32Checked", u32, ( + to_u32_checked_smaller_width_pos, "15i8", 15 + to_u32_checked_smaller_width_neg_oob, "-15i8", None + to_u32_checked_same, "15u32", 15 + to_u32_checked_same_width_signed_fits, "15i32", 15 + to_u32_checked_same_width_signed_oob, "-1i32", None + to_u32_checked_larger_width_signed_fits_pos, "15i64", 15 + to_u32_checked_larger_width_signed_oob_pos, "4294967296i64", None + to_u32_checked_larger_width_signed_oob_neg, "-1i64", None + to_u32_checked_larger_width_unsigned_fits_pos, "15u64", 15 + to_u32_checked_larger_width_unsigned_oob_pos, "4294967296u64", None + ) + "Num.toU64Checked", u64, ( + to_u64_checked_smaller_width_pos, "15i8", 15 + to_u64_checked_smaller_width_neg_oob, "-15i8", None + to_u64_checked_same, "15u64", 15 + to_u64_checked_same_width_signed_fits, "15i64", 15 + to_u64_checked_same_width_signed_oob, "-1i64", None + to_u64_checked_larger_width_signed_fits_pos, "15i128", 15 + to_u64_checked_larger_width_signed_oob_pos, "18446744073709551616i128", None + to_u64_checked_larger_width_signed_oob_neg, "-1i128", None + to_u64_checked_larger_width_unsigned_fits_pos, "15u128", 15 + to_u64_checked_larger_width_unsigned_oob_pos, "18446744073709551616u128", None + ) + "Num.toU128Checked", u128, ( + to_u128_checked_smaller_width_pos, "15i8", 15 + to_u128_checked_smaller_width_neg_oob, "-15i8", None + to_u128_checked_same, "15u128", 15 + to_u128_checked_same_width_signed_fits, "15i128", 15 + to_u128_checked_same_width_signed_oob, "-1i128", None + ) +} + #[test] #[cfg(any(feature = "gen-llvm"))] fn is_multiple_of() { @@ -2285,29 +2546,21 @@ fn when_on_i16() { fn num_to_str() { use roc_std::RocStr; - assert_evals_to!( - r#"Num.toStr 1234"#, - RocStr::from_slice("1234".as_bytes()), - RocStr - ); - assert_evals_to!(r#"Num.toStr 0"#, RocStr::from_slice("0".as_bytes()), RocStr); - assert_evals_to!( - r#"Num.toStr -1"#, - RocStr::from_slice("-1".as_bytes()), - RocStr - ); + assert_evals_to!(r#"Num.toStr 1234"#, RocStr::from("1234"), RocStr); + assert_evals_to!(r#"Num.toStr 0"#, RocStr::from("0"), RocStr); + assert_evals_to!(r#"Num.toStr -1"#, RocStr::from("-1"), RocStr); let max = format!("{}", i64::MAX); assert_evals_to!( r#"Num.toStr Num.maxI64"#, - RocStr::from_slice(max.as_bytes()), + RocStr::from(max.as_str()), RocStr ); let min = format!("{}", i64::MIN); assert_evals_to!( r#"Num.toStr Num.minI64"#, - RocStr::from_slice(min.as_bytes()), + RocStr::from(min.as_str()), RocStr ); } diff --git a/compiler/test_gen/src/gen_primitives.rs b/compiler/test_gen/src/gen_primitives.rs index 2f97c23753..a720176ad3 100644 --- a/compiler/test_gen/src/gen_primitives.rs +++ b/compiler/test_gen/src/gen_primitives.rs @@ -1468,7 +1468,7 @@ fn rbtree_insert() { show (insert 0 {} Empty) "# ), - RocStr::from_slice("Node".as_bytes()), + RocStr::from("Node"), RocStr ); } @@ -1535,7 +1535,7 @@ fn rbtree_layout_issue() { main = show (balance Red zero zero Empty) "# ), - RocStr::from_slice("Empty".as_bytes()), + RocStr::from("Empty"), RocStr ); } @@ -1589,7 +1589,7 @@ fn rbtree_balance_mono_problem() { main = show (balance Red 0 0 Empty Empty) "# ), - RocStr::from_slice("Empty".as_bytes()), + RocStr::from("Empty"), RocStr ); } @@ -2382,7 +2382,7 @@ fn build_then_apply_closure() { (\_ -> x) {} "# ), - RocStr::from_slice(b"long string that is malloced"), + RocStr::from("long string that is malloced"), RocStr ); } @@ -2556,7 +2556,7 @@ fn module_thunk_is_function() { helper = Str.concat "# ), - RocStr::from_slice(b"foobar"), + RocStr::from("foobar"), RocStr ); } @@ -2580,7 +2580,7 @@ fn hit_unresolved_type_variable() { \input -> input "# ), - RocStr::from_slice(b"B"), + RocStr::from("B"), RocStr ); } @@ -2648,7 +2648,7 @@ fn mirror_llvm_alignment_padding() { "# ), - RocStr::from_slice(b"pass\npass"), + RocStr::from("pass\npass"), RocStr ); } @@ -2911,7 +2911,7 @@ fn mix_function_and_closure() { (if 1 == 1 then foo else (bar "nope nope nope")) "hello world" "# ), - RocStr::from_slice(b"hello world"), + RocStr::from("hello world"), RocStr ); } @@ -2936,7 +2936,7 @@ fn mix_function_and_closure_level_of_indirection() { f "hello world" "# ), - RocStr::from_slice(b"hello world"), + RocStr::from("hello world"), RocStr ); } @@ -3012,7 +3012,7 @@ fn do_pass_bool_byte_closure_layout() { main = [test1, test2, test3, test4] |> Str.joinWith ", " "# ), - RocStr::from_slice(b"PASS, PASS, PASS, PASS"), + RocStr::from("PASS, PASS, PASS, PASS"), RocStr ); } @@ -3037,7 +3037,7 @@ fn nested_rigid_list() { _ -> "hello world" "# ), - RocStr::from_slice(b"hello world"), + RocStr::from("hello world"), RocStr ); } @@ -3064,7 +3064,7 @@ fn nested_rigid_alias() { _ -> "hello world" "# ), - RocStr::from_slice(b"hello world"), + RocStr::from("hello world"), RocStr ); } @@ -3089,7 +3089,7 @@ fn nested_rigid_tag_union() { _ -> "hello world" "# ), - RocStr::from_slice(b"hello world"), + RocStr::from("hello world"), RocStr ); } @@ -3117,7 +3117,7 @@ fn call_that_needs_closure_parameter() { runTest manyAuxTest "# ), - RocStr::from_slice(b"FAIL"), + RocStr::from("FAIL"), RocStr ); } @@ -3138,7 +3138,7 @@ fn alias_defined_out_of_order() { "# ), - RocStr::from_slice(b"foo"), + RocStr::from("foo"), RocStr ); } @@ -3184,7 +3184,7 @@ fn recursively_build_effect() { e2 {} "# ), - RocStr::from_slice(b"Hello, World!"), + RocStr::from("Hello, World!"), RocStr ); } diff --git a/compiler/test_gen/src/gen_records.rs b/compiler/test_gen/src/gen_records.rs index baa3675acd..295bcf6fe8 100644 --- a/compiler/test_gen/src/gen_records.rs +++ b/compiler/test_gen/src/gen_records.rs @@ -13,6 +13,9 @@ use crate::helpers::wasm::assert_evals_to; // use crate::assert_wasm_evals_to as assert_evals_to; use indoc::indoc; +#[cfg(test)] +use roc_std::RocList; + #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn basic_record() { @@ -1007,6 +1010,36 @@ fn both_have_unique_fields() { ); } +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +// https://github.com/rtfeldman/roc/issues/2535 +fn different_proc_types_specialized_to_same_layout() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ nums ] to "./platform" + + # Top-level values compile to procedure calls with no args + # alpha has the generic type { a: Num *, b: Num * } + # and gets specialized to two procedure calls below + alpha = { a: 1, b: 2 } + + # The wider number always comes first in the layout, + # which makes the two specializations look very similar. + # Test that the compiler doesn't get them mixed up! + nums : List U8 + nums = + [ + alpha.a, # alpha specialized to layout { b: I64, a: U8 } + alpha.b, # alpha specialized to layout { a: I64, b: U8 } + ] + "# + ), + RocList::from_slice(&[1, 2]), + RocList + ); +} + #[test] #[cfg(any(feature = "gen-llvm"))] #[should_panic( diff --git a/compiler/test_gen/src/gen_result.rs b/compiler/test_gen/src/gen_result.rs index d919bf89ef..127c5bfa95 100644 --- a/compiler/test_gen/src/gen_result.rs +++ b/compiler/test_gen/src/gen_result.rs @@ -252,7 +252,7 @@ fn roc_result_err() { result "# ), - RocResult::err(RocStr::from_slice(b"foo")), + RocResult::err(RocStr::from("foo")), RocResult ); } diff --git a/compiler/test_gen/src/gen_set.rs b/compiler/test_gen/src/gen_set.rs index 77503c105e..4a2f922f55 100644 --- a/compiler/test_gen/src/gen_set.rs +++ b/compiler/test_gen/src/gen_set.rs @@ -238,7 +238,7 @@ fn from_list() { |> Set.toList "# ), - RocList::default(), + RocList::::default(), RocList ); } @@ -254,7 +254,7 @@ fn from_list_void() { |> Set.toList "# ), - RocList::default(), + RocList::::default(), RocList ); } diff --git a/compiler/test_gen/src/gen_str.rs b/compiler/test_gen/src/gen_str.rs index 2792b86fd7..633a7c56ab 100644 --- a/compiler/test_gen/src/gen_str.rs +++ b/compiler/test_gen/src/gen_str.rs @@ -94,7 +94,7 @@ fn str_split_str_concat_repeated() { "# ), - RocStr::from_slice(b"JJJJJJJJJJJJJJJJJJJJJJJJJ"), + RocStr::from("JJJJJJJJJJJJJJJJJJJJJJJJJ"), RocStr ); } @@ -113,7 +113,7 @@ fn str_split_small_str_bigger_delimiter() { _ -> "" "# ), - RocStr::from_slice(b"JJJ"), + RocStr::from("JJJ"), RocStr ); } @@ -128,8 +128,8 @@ fn str_split_big_str_small_delimiter() { "# ), RocList::from_slice(&[ - RocStr::from_slice(b"01234567789abcdefghi"), - RocStr::from_slice(b"01234567789abcdefghi") + RocStr::from("01234567789abcdefghi"), + RocStr::from("01234567789abcdefghi") ]), RocList ); @@ -141,8 +141,8 @@ fn str_split_big_str_small_delimiter() { "# ), RocList::from_slice(&[ - RocStr::from_slice(b"01234567789abcdefghi "), - RocStr::from_slice(b" 01234567789abcdefghi") + RocStr::from("01234567789abcdefghi "), + RocStr::from(" 01234567789abcdefghi") ]), RocList ); @@ -157,11 +157,7 @@ fn str_split_small_str_small_delimiter() { Str.split "J!J!J" "!" "# ), - RocList::from_slice(&[ - RocStr::from_slice(b"J"), - RocStr::from_slice(b"J"), - RocStr::from_slice(b"J") - ]), + RocList::from_slice(&[RocStr::from("J"), RocStr::from("J"), RocStr::from("J")]), RocList ); } @@ -177,7 +173,7 @@ fn str_split_bigger_delimiter_big_strs() { "than the delimiter which happens to be very very long" "# ), - RocList::from_slice(&[RocStr::from_slice(b"string to split is shorter")]), + RocList::from_slice(&[RocStr::from("string to split is shorter")]), RocList ); } @@ -191,7 +187,7 @@ fn str_split_empty_strs() { Str.split "" "" "# ), - RocList::from_slice(&[RocStr::from_slice(b"")]), + RocList::from_slice(&[RocStr::from("")]), RocList ); } @@ -205,7 +201,7 @@ fn str_split_minimal_example() { Str.split "a," "," "# ), - RocList::from_slice(&[RocStr::from_slice(b"a"), RocStr::from_slice(b"")]), + RocList::from_slice(&[RocStr::from("a"), RocStr::from("")]), RocList ) } @@ -234,11 +230,7 @@ fn str_split_small_str_big_delimiter() { "---- ---- ---- ---- ----" "# ), - RocList::from_slice(&[ - RocStr::from_slice(b"1"), - RocStr::from_slice(b"2"), - RocStr::from_slice(b"") - ]), + RocList::from_slice(&[RocStr::from("1"), RocStr::from("2"), RocStr::from("")]), RocList ); } @@ -254,11 +246,7 @@ fn str_split_small_str_20_char_delimiter() { "|-- -- -- -- -- -- |" "# ), - RocList::from_slice(&[ - RocStr::from_slice(b"3"), - RocStr::from_slice(b"4"), - RocStr::from_slice(b"") - ]), + RocList::from_slice(&[RocStr::from("3"), RocStr::from("4"), RocStr::from("")]), RocList ); } @@ -274,7 +262,7 @@ fn str_concat_big_to_big() { "Second string that is also fairly long. Two long strings test things that might not appear with short strings." "# ), - RocStr::from_slice(b"First string that is fairly long. Longer strings make for different errors. Second string that is also fairly long. Two long strings test things that might not appear with short strings."), + RocStr::from("First string that is fairly long. Longer strings make for different errors. Second string that is also fairly long. Two long strings test things that might not appear with short strings."), RocStr ); } @@ -395,7 +383,7 @@ fn small_str_concat_empty_second_arg() { fn small_str_concat_small_to_big() { assert_evals_to!( r#"Str.concat "abc" " this is longer than 15 chars""#, - RocStr::from_slice(b"abc this is longer than 15 chars"), + RocStr::from("abc this is longer than 15 chars"), RocStr ); } @@ -432,7 +420,7 @@ fn small_str_concat_small_to_small_staying_small() { fn small_str_concat_small_to_small_overflow_to_big() { assert_evals_to!( r#"Str.concat "abcdefghijklm" "nopqrstuvwxyz""#, - RocStr::from_slice(b"abcdefghijklmnopqrstuvwxyz"), + RocStr::from("abcdefghijklmnopqrstuvwxyz"), RocStr ); } @@ -568,7 +556,7 @@ fn str_from_utf8_pass_single_ascii() { Err _ -> "" "# ), - roc_std::RocStr::from_slice("a".as_bytes()), + roc_std::RocStr::from("a"), roc_std::RocStr ); } @@ -584,7 +572,7 @@ fn str_from_utf8_pass_many_ascii() { Err _ -> "" "# ), - roc_std::RocStr::from_slice("abc~".as_bytes()), + roc_std::RocStr::from("abc~"), roc_std::RocStr ); } @@ -600,7 +588,7 @@ fn str_from_utf8_pass_single_unicode() { Err _ -> "" "# ), - roc_std::RocStr::from_slice("∆".as_bytes()), + roc_std::RocStr::from("∆"), roc_std::RocStr ); } @@ -616,7 +604,7 @@ fn str_from_utf8_pass_many_unicode() { Err _ -> "" "# ), - roc_std::RocStr::from_slice("∆œ¬".as_bytes()), + roc_std::RocStr::from("∆œ¬"), roc_std::RocStr ); } @@ -632,7 +620,7 @@ fn str_from_utf8_pass_single_grapheme() { Err _ -> "" "# ), - roc_std::RocStr::from_slice("💖".as_bytes()), + roc_std::RocStr::from("💖"), roc_std::RocStr ); } @@ -648,7 +636,7 @@ fn str_from_utf8_pass_many_grapheme() { Err _ -> "" "# ), - roc_std::RocStr::from_slice("💖🤠🚀".as_bytes()), + roc_std::RocStr::from("💖🤠🚀"), roc_std::RocStr ); } @@ -664,7 +652,7 @@ fn str_from_utf8_pass_all() { Err _ -> "" "# ), - roc_std::RocStr::from_slice("💖b∆".as_bytes()), + roc_std::RocStr::from("💖b∆"), roc_std::RocStr ); } @@ -684,7 +672,7 @@ fn str_from_utf8_fail_invalid_start_byte() { _ -> "" "# ), - roc_std::RocStr::from_slice("a".as_bytes()), + roc_std::RocStr::from("a"), roc_std::RocStr ); } @@ -704,7 +692,7 @@ fn str_from_utf8_fail_unexpected_end_of_sequence() { _ -> "" "# ), - roc_std::RocStr::from_slice("a".as_bytes()), + roc_std::RocStr::from("a"), roc_std::RocStr ); } @@ -724,7 +712,7 @@ fn str_from_utf8_fail_expected_continuation() { _ -> "" "# ), - roc_std::RocStr::from_slice("a".as_bytes()), + roc_std::RocStr::from("a"), roc_std::RocStr ); } @@ -744,7 +732,7 @@ fn str_from_utf8_fail_overlong_encoding() { _ -> "" "# ), - roc_std::RocStr::from_slice("a".as_bytes()), + roc_std::RocStr::from("a"), roc_std::RocStr ); } @@ -764,7 +752,7 @@ fn str_from_utf8_fail_codepoint_too_large() { _ -> "" "# ), - roc_std::RocStr::from_slice("a".as_bytes()), + roc_std::RocStr::from("a"), roc_std::RocStr ); } @@ -784,7 +772,7 @@ fn str_from_utf8_fail_surrogate_half() { _ -> "" "# ), - roc_std::RocStr::from_slice("a".as_bytes()), + roc_std::RocStr::from("a"), roc_std::RocStr ); } @@ -805,9 +793,9 @@ fn str_equality() { #[test] fn str_clone() { use roc_std::RocStr; - let long = RocStr::from_slice("loremipsumdolarsitamet".as_bytes()); - let short = RocStr::from_slice("x".as_bytes()); - let empty = RocStr::from_slice("".as_bytes()); + let long = RocStr::from("loremipsumdolarsitamet"); + let short = RocStr::from("x"); + let empty = RocStr::from(""); debug_assert_eq!(long.clone(), long); debug_assert_eq!(short.clone(), short); @@ -840,7 +828,7 @@ fn nested_recursive_literal() { printExpr expr "# ), - RocStr::from_slice(b"Add (Add (Val 3) (Val 1)) (Add (Val 1) (Var 1))"), + RocStr::from("Add (Add (Val 3) (Val 1)) (Add (Val 1) (Var 1))"), RocStr ); } diff --git a/compiler/test_gen/src/gen_tags.rs b/compiler/test_gen/src/gen_tags.rs index 7d54bd9cbb..f3ce70ea69 100644 --- a/compiler/test_gen/src/gen_tags.rs +++ b/compiler/test_gen/src/gen_tags.rs @@ -1177,10 +1177,7 @@ fn applied_tag_function() { x "# ), - RocList::from_slice(&[ - RocStr::from_slice("a".as_bytes()), - RocStr::from_slice("b".as_bytes()) - ]), + RocList::from_slice(&[RocStr::from("a"), RocStr::from("b")]), RocList ); } @@ -1197,10 +1194,7 @@ fn applied_tag_function_result() { List.keepOks x (\y -> y) "# ), - RocList::from_slice(&[ - (RocStr::from_slice("a".as_bytes())), - (RocStr::from_slice("b".as_bytes())) - ]), + RocList::from_slice(&[(RocStr::from("a")), (RocStr::from("b"))]), RocList ); } @@ -1297,7 +1291,7 @@ fn monomorphized_applied_tag() { f a "# ), - RocStr::from_slice(b"abc"), + RocStr::from("abc"), RocStr ) } diff --git a/compiler/test_gen/src/helpers/from_wasmer_memory.rs b/compiler/test_gen/src/helpers/from_wasmer_memory.rs index 99f368f87f..e7468196c4 100644 --- a/compiler/test_gen/src/helpers/from_wasmer_memory.rs +++ b/compiler/test_gen/src/helpers/from_wasmer_memory.rs @@ -1,5 +1,5 @@ use roc_gen_wasm::wasm32_sized::Wasm32Sized; -use roc_std::{RocDec, RocList, RocOrder, RocStr}; +use roc_std::{ReferenceCount, RocDec, RocList, RocOrder, RocStr}; pub trait FromWasmerMemory: Wasm32Sized { fn decode(memory: &wasmer::Memory, offset: u32) -> Self; @@ -61,19 +61,19 @@ impl FromWasmerMemory for RocStr { let actual_length = (last_byte ^ 0b1000_0000) as usize; let slice = &bytes.to_ne_bytes()[..actual_length as usize]; - RocStr::from_slice(slice) + unsafe { RocStr::from_slice(slice) } } else { // this is a big string let ptr: wasmer::WasmPtr = wasmer::WasmPtr::new(elements); let foobar = (ptr.deref(memory, 0, length)).unwrap(); let wasm_slice = unsafe { std::mem::transmute(foobar) }; - RocStr::from_slice(wasm_slice) + unsafe { RocStr::from_slice(wasm_slice) } } } } -impl FromWasmerMemory for RocList { +impl FromWasmerMemory for RocList { fn decode(memory: &wasmer::Memory, offset: u32) -> Self { let bytes = ::decode(memory, offset); diff --git a/compiler/test_gen/src/helpers/llvm.rs b/compiler/test_gen/src/helpers/llvm.rs index 24569e3906..29854fbcde 100644 --- a/compiler/test_gen/src/helpers/llvm.rs +++ b/compiler/test_gen/src/helpers/llvm.rs @@ -489,10 +489,7 @@ where match test_wrapper.call(&[]) { Err(e) => Err(format!("call to `test_wrapper`: {:?}", e)), Ok(result) => { - let address = match result[0] { - wasmer::Value::I32(a) => a, - _ => panic!(), - }; + let address = result[0].unwrap_i32(); let output = ::decode( memory, diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index 3420cb2498..6813459933 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -194,10 +194,7 @@ where match test_wrapper.call(&[]) { Err(e) => Err(format!("{:?}", e)), Ok(result) => { - let address = match result[0] { - wasmer::Value::I32(a) => a, - _ => panic!(), - }; + let address = result[0].unwrap_i32(); if false { println!("test_wrapper returned 0x{:x}", address); @@ -239,10 +236,7 @@ where let init_result = init_refcount_test.call(&[wasmer::Value::I32(expected_len)]); let refcount_vector_addr = match init_result { Err(e) => return Err(format!("{:?}", e)), - Ok(result) => match result[0] { - wasmer::Value::I32(a) => a, - _ => panic!(), - }, + Ok(result) => result[0].unwrap_i32(), }; // Run the test diff --git a/compiler/test_gen/src/wasm_str.rs b/compiler/test_gen/src/wasm_str.rs index 69a88fd2b1..b76a693873 100644 --- a/compiler/test_gen/src/wasm_str.rs +++ b/compiler/test_gen/src/wasm_str.rs @@ -320,7 +320,7 @@ fn small_str_zeroed_literal() { fn long_str_literal() { assert_evals_to!( "\"0123456789 123456789 123456789\"", - RocStr::from_slice(b"0123456789 123456789 123456789"), + RocStr::from("0123456789 123456789 123456789"), RocStr ); } @@ -347,7 +347,7 @@ fn small_str_concat_empty_second_arg() { fn small_str_concat_small_to_big() { assert_evals_to!( r#"Str.concat "abc" " this is longer than 7 chars""#, - RocStr::from_slice(b"abc this is longer than 7 chars"), + RocStr::from("abc this is longer than 7 chars"), RocStr ); } @@ -365,7 +365,7 @@ fn small_str_concat_small_to_small_staying_small() { fn small_str_concat_small_to_small_overflow_to_big() { assert_evals_to!( r#"Str.concat "abcdefg" "hijklmn""#, - RocStr::from_slice(b"abcdefghijklmn"), + RocStr::from("abcdefghijklmn"), RocStr ); } diff --git a/compiler/test_mono/generated/alias_variable.txt b/compiler/test_mono/generated/alias_variable.txt index 657376d73a..ccb6249257 100644 --- a/compiler/test_mono/generated/alias_variable.txt +++ b/compiler/test_mono/generated/alias_variable.txt @@ -1,3 +1,3 @@ procedure Test.0 (): - let Test.3 : Builtin(Int(I64)) = 3i64; + let Test.3 : I64 = 3i64; ret Test.3; diff --git a/compiler/test_mono/generated/alias_variable_and_return_it.txt b/compiler/test_mono/generated/alias_variable_and_return_it.txt index 9e5824a185..63fd9ba01e 100644 --- a/compiler/test_mono/generated/alias_variable_and_return_it.txt +++ b/compiler/test_mono/generated/alias_variable_and_return_it.txt @@ -1,3 +1,3 @@ procedure Test.0 (): - let Test.2 : Builtin(Int(I64)) = 5i64; + let Test.2 : I64 = 5i64; ret Test.2; diff --git a/compiler/test_mono/generated/aliased_polymorphic_closure.txt b/compiler/test_mono/generated/aliased_polymorphic_closure.txt index bf1eb3d95d..cd870ec963 100644 --- a/compiler/test_mono/generated/aliased_polymorphic_closure.txt +++ b/compiler/test_mono/generated/aliased_polymorphic_closure.txt @@ -1,17 +1,17 @@ procedure Test.2 (Test.6, #Attr.12): - let Test.1 : Builtin(Int(U8)) = StructAtIndex 0 #Attr.12; - let Test.11 : LambdaSet(LambdaSet { set: [( Test.4, [Builtin(Int(U8))])], representation: Struct([Builtin(Int(U8))]) }) = Struct {Test.1}; + let Test.1 : U8 = StructAtIndex 0 #Attr.12; + let Test.11 : {U8} = Struct {Test.1}; ret Test.11; procedure Test.4 (Test.5, #Attr.12): - let Test.1 : Builtin(Int(U8)) = StructAtIndex 0 #Attr.12; + let Test.1 : U8 = StructAtIndex 0 #Attr.12; ret Test.1; procedure Test.0 (): - let Test.1 : Builtin(Int(U8)) = 1i64; - let Test.8 : Struct([]) = Struct {}; - let Test.10 : Struct([]) = Struct {}; - let Test.14 : LambdaSet(LambdaSet { set: [( Test.2, [Builtin(Int(U8))])], representation: Struct([Builtin(Int(U8))]) }) = Struct {Test.1}; - let Test.9 : LambdaSet(LambdaSet { set: [( Test.4, [Builtin(Int(U8))])], representation: Struct([Builtin(Int(U8))]) }) = CallByName Test.2 Test.10 Test.14; - let Test.7 : Builtin(Int(U8)) = CallByName Test.4 Test.8 Test.9; + let Test.1 : U8 = 1i64; + let Test.8 : {} = Struct {}; + let Test.10 : {} = Struct {}; + let Test.14 : {U8} = Struct {Test.1}; + let Test.9 : {U8} = CallByName Test.2 Test.10 Test.14; + let Test.7 : U8 = CallByName Test.4 Test.8 Test.9; ret Test.7; diff --git a/compiler/test_mono/generated/branch_store_variable.txt b/compiler/test_mono/generated/branch_store_variable.txt index 567b83bd1f..4731462e7b 100644 --- a/compiler/test_mono/generated/branch_store_variable.txt +++ b/compiler/test_mono/generated/branch_store_variable.txt @@ -1,9 +1,9 @@ procedure Test.0 (): - let Test.2 : Builtin(Int(I64)) = 0i64; - let Test.5 : Builtin(Int(I64)) = 1i64; - let Test.6 : Builtin(Bool) = lowlevel Eq Test.5 Test.2; + let Test.2 : I64 = 0i64; + let Test.5 : I64 = 1i64; + let Test.6 : Int1 = lowlevel Eq Test.5 Test.2; if Test.6 then - let Test.3 : Builtin(Int(I64)) = 12i64; + let Test.3 : I64 = 12i64; ret Test.3; else ret Test.2; diff --git a/compiler/test_mono/generated/closure_in_list.txt b/compiler/test_mono/generated/closure_in_list.txt index 0347966c3e..18496e4156 100644 --- a/compiler/test_mono/generated/closure_in_list.txt +++ b/compiler/test_mono/generated/closure_in_list.txt @@ -1,21 +1,21 @@ procedure List.7 (#Attr.2): - let Test.8 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; + let Test.8 : U64 = lowlevel ListLen #Attr.2; ret Test.8; procedure Test.1 (Test.5): - let Test.2 : Builtin(Int(I64)) = 41i64; - let Test.12 : LambdaSet(LambdaSet { set: [( Test.3, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }) = Struct {Test.2}; - let Test.11 : Builtin(List(LambdaSet(LambdaSet { set: [( Test.3, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }))) = Array [Test.12]; + let Test.2 : I64 = 41i64; + let Test.12 : {I64} = Struct {Test.2}; + let Test.11 : List {I64} = Array [Test.12]; ret Test.11; procedure Test.3 (Test.10, #Attr.12): - let Test.2 : Builtin(Int(I64)) = StructAtIndex 0 #Attr.12; - let Test.2 : Builtin(Int(I64)) = 41i64; + let Test.2 : I64 = StructAtIndex 0 #Attr.12; + let Test.2 : I64 = 41i64; ret Test.2; procedure Test.0 (): - let Test.9 : Struct([]) = Struct {}; - let Test.7 : Builtin(List(LambdaSet(LambdaSet { set: [( Test.3, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }))) = CallByName Test.1 Test.9; - let Test.6 : Builtin(Int(U64)) = CallByName List.7 Test.7; + let Test.9 : {} = Struct {}; + let Test.7 : List {I64} = CallByName Test.1 Test.9; + let Test.6 : U64 = CallByName List.7 Test.7; dec Test.7; ret Test.6; diff --git a/compiler/test_mono/generated/dict.txt b/compiler/test_mono/generated/dict.txt index ad2fbf5701..4244fb0e0c 100644 --- a/compiler/test_mono/generated/dict.txt +++ b/compiler/test_mono/generated/dict.txt @@ -1,13 +1,13 @@ procedure Dict.2 (): - let Test.4 : Builtin(Dict(Union(NonRecursive([])), Union(NonRecursive([])))) = lowlevel DictEmpty ; + let Test.4 : Dict [] [] = lowlevel DictEmpty ; ret Test.4; procedure Dict.8 (#Attr.2): - let Test.3 : Builtin(Int(U64)) = lowlevel DictSize #Attr.2; + let Test.3 : U64 = lowlevel DictSize #Attr.2; dec #Attr.2; ret Test.3; procedure Test.0 (): - let Test.2 : Builtin(Dict(Union(NonRecursive([])), Union(NonRecursive([])))) = CallByName Dict.2; - let Test.1 : Builtin(Int(U64)) = CallByName Dict.8 Test.2; + let Test.2 : Dict [] [] = CallByName Dict.2; + let Test.1 : U64 = CallByName Dict.8 Test.2; ret Test.1; diff --git a/compiler/test_mono/generated/empty_list_of_function_type.txt b/compiler/test_mono/generated/empty_list_of_function_type.txt index 44c1c42994..d25fa169f9 100644 --- a/compiler/test_mono/generated/empty_list_of_function_type.txt +++ b/compiler/test_mono/generated/empty_list_of_function_type.txt @@ -1,42 +1,42 @@ procedure List.3 (#Attr.2, #Attr.3): - let Test.20 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; - let Test.17 : Builtin(Bool) = lowlevel NumLt #Attr.3 Test.20; + let Test.20 : U64 = lowlevel ListLen #Attr.2; + let Test.17 : Int1 = lowlevel NumLt #Attr.3 Test.20; if Test.17 then - let Test.19 : LambdaSet(LambdaSet { set: [( Test.2, [])], representation: Struct([]) }) = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - let Test.18 : Union(NonRecursive([[Struct([])], [LambdaSet(LambdaSet { set: [( Test.2, [])], representation: Struct([]) })]])) = Ok Test.19; + let Test.19 : {} = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + let Test.18 : [C {}, C {}] = Ok Test.19; ret Test.18; else - let Test.16 : Struct([]) = Struct {}; - let Test.15 : Union(NonRecursive([[Struct([])], [LambdaSet(LambdaSet { set: [( Test.2, [])], representation: Struct([]) })]])) = Err Test.16; + let Test.16 : {} = Struct {}; + let Test.15 : [C {}, C {}] = Err Test.16; ret Test.15; procedure Test.2 (Test.6): - let Test.24 : Builtin(Str) = "bar"; + let Test.24 : Str = "bar"; ret Test.24; procedure Test.0 (): joinpoint Test.22 Test.3: - let Test.14 : Builtin(Int(U64)) = 0i64; - let Test.7 : Union(NonRecursive([[Struct([])], [LambdaSet(LambdaSet { set: [( Test.2, [])], representation: Struct([]) })]])) = CallByName List.3 Test.3 Test.14; + let Test.14 : U64 = 0i64; + let Test.7 : [C {}, C {}] = CallByName List.3 Test.3 Test.14; dec Test.3; - let Test.11 : Builtin(Int(U8)) = 1i64; - let Test.12 : Builtin(Int(U8)) = GetTagId Test.7; - let Test.13 : Builtin(Bool) = lowlevel Eq Test.11 Test.12; + let Test.11 : U8 = 1i64; + let Test.12 : U8 = GetTagId Test.7; + let Test.13 : Int1 = lowlevel Eq Test.11 Test.12; if Test.13 then - let Test.5 : LambdaSet(LambdaSet { set: [( Test.2, [])], representation: Struct([]) }) = UnionAtIndex (Id 1) (Index 0) Test.7; - let Test.9 : Builtin(Str) = "foo"; - let Test.8 : Builtin(Str) = CallByName Test.2 Test.9; + let Test.5 : {} = UnionAtIndex (Id 1) (Index 0) Test.7; + let Test.9 : Str = "foo"; + let Test.8 : Str = CallByName Test.2 Test.9; dec Test.9; ret Test.8; else - let Test.10 : Builtin(Str) = "bad!"; + let Test.10 : Str = "bad!"; ret Test.10; in - let Test.25 : Builtin(Bool) = false; + let Test.25 : Int1 = false; if Test.25 then - let Test.1 : Builtin(List(LambdaSet(LambdaSet { set: [( Test.2, [])], representation: Struct([]) }))) = Array []; + let Test.1 : List {} = Array []; jump Test.22 Test.1; else - let Test.23 : LambdaSet(LambdaSet { set: [( Test.2, [])], representation: Struct([]) }) = Struct {}; - let Test.21 : Builtin(List(LambdaSet(LambdaSet { set: [( Test.2, [])], representation: Struct([]) }))) = Array [Test.23]; + let Test.23 : {} = Struct {}; + let Test.21 : List {} = Array [Test.23]; jump Test.22 Test.21; diff --git a/compiler/test_mono/generated/factorial.txt b/compiler/test_mono/generated/factorial.txt index d0c12615da..f318bd7aa3 100644 --- a/compiler/test_mono/generated/factorial.txt +++ b/compiler/test_mono/generated/factorial.txt @@ -1,27 +1,27 @@ procedure Num.23 (#Attr.2, #Attr.3): - let Test.14 : Builtin(Int(I64)) = lowlevel NumSub #Attr.2 #Attr.3; + let Test.14 : I64 = lowlevel NumSub #Attr.2 #Attr.3; ret Test.14; procedure Num.24 (#Attr.2, #Attr.3): - let Test.12 : Builtin(Int(I64)) = lowlevel NumMul #Attr.2 #Attr.3; + let Test.12 : I64 = lowlevel NumMul #Attr.2 #Attr.3; ret Test.12; procedure Test.1 (Test.17, Test.18): joinpoint Test.7 Test.2 Test.3: - let Test.15 : Builtin(Int(I64)) = 0i64; - let Test.16 : Builtin(Bool) = lowlevel Eq Test.15 Test.2; + let Test.15 : I64 = 0i64; + let Test.16 : Int1 = lowlevel Eq Test.15 Test.2; if Test.16 then ret Test.3; else - let Test.13 : Builtin(Int(I64)) = 1i64; - let Test.10 : Builtin(Int(I64)) = CallByName Num.23 Test.2 Test.13; - let Test.11 : Builtin(Int(I64)) = CallByName Num.24 Test.2 Test.3; + let Test.13 : I64 = 1i64; + let Test.10 : I64 = CallByName Num.23 Test.2 Test.13; + let Test.11 : I64 = CallByName Num.24 Test.2 Test.3; jump Test.7 Test.10 Test.11; in jump Test.7 Test.17 Test.18; procedure Test.0 (): - let Test.5 : Builtin(Int(I64)) = 10i64; - let Test.6 : Builtin(Int(I64)) = 1i64; - let Test.4 : Builtin(Int(I64)) = CallByName Test.1 Test.5 Test.6; + let Test.5 : I64 = 10i64; + let Test.6 : I64 = 1i64; + let Test.4 : I64 = CallByName Test.1 Test.5 Test.6; ret Test.4; diff --git a/compiler/test_mono/generated/fst.txt b/compiler/test_mono/generated/fst.txt index f85b59b85b..c8c3da9a89 100644 --- a/compiler/test_mono/generated/fst.txt +++ b/compiler/test_mono/generated/fst.txt @@ -3,9 +3,9 @@ procedure Test.1 (Test.2, Test.3): ret Test.2; procedure Test.0 (): - let Test.5 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64, 3i64]; - let Test.6 : Builtin(List(Builtin(Int(I64)))) = Array [3i64, 2i64, 1i64]; - let Test.4 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.1 Test.5 Test.6; + let Test.5 : List I64 = Array [1i64, 2i64, 3i64]; + let Test.6 : List I64 = Array [3i64, 2i64, 1i64]; + let Test.4 : List I64 = CallByName Test.1 Test.5 Test.6; dec Test.6; dec Test.5; ret Test.4; diff --git a/compiler/test_mono/generated/guard_pattern_true.txt b/compiler/test_mono/generated/guard_pattern_true.txt index b93ad5f177..47bfc64e1f 100644 --- a/compiler/test_mono/generated/guard_pattern_true.txt +++ b/compiler/test_mono/generated/guard_pattern_true.txt @@ -1,25 +1,25 @@ procedure Test.1 (Test.3): - let Test.6 : Builtin(Int(I64)) = 2i64; + let Test.6 : I64 = 2i64; joinpoint Test.11: - let Test.10 : Builtin(Int(I64)) = 0i64; + let Test.10 : I64 = 0i64; ret Test.10; in - let Test.13 : Builtin(Int(I64)) = 2i64; - let Test.14 : Builtin(Bool) = lowlevel Eq Test.13 Test.6; + let Test.13 : I64 = 2i64; + let Test.14 : Int1 = lowlevel Eq Test.13 Test.6; if Test.14 then joinpoint Test.8 Test.12: if Test.12 then - let Test.7 : Builtin(Int(I64)) = 42i64; + let Test.7 : I64 = 42i64; ret Test.7; else jump Test.11; in - let Test.9 : Builtin(Bool) = false; + let Test.9 : Int1 = false; jump Test.8 Test.9; else jump Test.11; procedure Test.0 (): - let Test.5 : Struct([]) = Struct {}; - let Test.4 : Builtin(Int(I64)) = CallByName Test.1 Test.5; + let Test.5 : {} = Struct {}; + let Test.4 : I64 = CallByName Test.1 Test.5; ret Test.4; diff --git a/compiler/test_mono/generated/has_none.txt b/compiler/test_mono/generated/has_none.txt index b97fc22ad0..5cd44fc90f 100644 --- a/compiler/test_mono/generated/has_none.txt +++ b/compiler/test_mono/generated/has_none.txt @@ -1,30 +1,30 @@ procedure Test.3 (Test.29): joinpoint Test.13 Test.4: - let Test.23 : Builtin(Bool) = 1i64; - let Test.24 : Builtin(Bool) = GetTagId Test.4; - let Test.25 : Builtin(Bool) = lowlevel Eq Test.23 Test.24; + let Test.23 : Int1 = 1i64; + let Test.24 : Int1 = GetTagId Test.4; + let Test.25 : Int1 = lowlevel Eq Test.23 Test.24; if Test.25 then - let Test.14 : Builtin(Bool) = false; + let Test.14 : Int1 = false; ret Test.14; else - let Test.19 : Union(NonRecursive([[Builtin(Int(I64))], []])) = UnionAtIndex (Id 0) (Index 0) Test.4; - let Test.20 : Builtin(Int(U8)) = 1i64; - let Test.21 : Builtin(Int(U8)) = GetTagId Test.19; - let Test.22 : Builtin(Bool) = lowlevel Eq Test.20 Test.21; + let Test.19 : [C I64, C ] = UnionAtIndex (Id 0) (Index 0) Test.4; + let Test.20 : U8 = 1i64; + let Test.21 : U8 = GetTagId Test.19; + let Test.22 : Int1 = lowlevel Eq Test.20 Test.21; if Test.22 then - let Test.15 : Builtin(Bool) = true; + let Test.15 : Int1 = true; ret Test.15; else - let Test.7 : Union(NullableUnwrapped { nullable_id: true, other_fields: [Union(NonRecursive([[Builtin(Int(I64))], []])), RecursivePointer] }) = UnionAtIndex (Id 0) (Index 1) Test.4; + let Test.7 : TODO = UnionAtIndex (Id 0) (Index 1) Test.4; jump Test.13 Test.7; in jump Test.13 Test.29; procedure Test.0 (): - let Test.28 : Builtin(Int(I64)) = 3i64; - let Test.26 : Union(NonRecursive([[Builtin(Int(I64))], []])) = Just Test.28; - let Test.27 : Union(NullableUnwrapped { nullable_id: true, other_fields: [Union(NonRecursive([[Builtin(Int(I64))], []])), RecursivePointer] }) = Nil ; - let Test.12 : Union(NullableUnwrapped { nullable_id: true, other_fields: [Union(NonRecursive([[Builtin(Int(I64))], []])), RecursivePointer] }) = Cons Test.26 Test.27; - let Test.11 : Builtin(Bool) = CallByName Test.3 Test.12; + let Test.28 : I64 = 3i64; + let Test.26 : [C I64, C ] = Just Test.28; + let Test.27 : TODO = Nil ; + let Test.12 : TODO = Cons Test.26 Test.27; + let Test.11 : Int1 = CallByName Test.3 Test.12; dec Test.12; ret Test.11; diff --git a/compiler/test_mono/generated/if_guard_bind_variable_false.txt b/compiler/test_mono/generated/if_guard_bind_variable_false.txt index d5a0da7ccc..abd10f26af 100644 --- a/compiler/test_mono/generated/if_guard_bind_variable_false.txt +++ b/compiler/test_mono/generated/if_guard_bind_variable_false.txt @@ -1,22 +1,22 @@ procedure Bool.7 (#Attr.2, #Attr.3): - let Test.11 : Builtin(Bool) = lowlevel Eq #Attr.2 #Attr.3; + let Test.11 : Int1 = lowlevel Eq #Attr.2 #Attr.3; ret Test.11; procedure Test.1 (Test.3): - let Test.6 : Builtin(Int(I64)) = 10i64; + let Test.6 : I64 = 10i64; joinpoint Test.8 Test.13: if Test.13 then - let Test.7 : Builtin(Int(I64)) = 0i64; + let Test.7 : I64 = 0i64; ret Test.7; else - let Test.12 : Builtin(Int(I64)) = 42i64; + let Test.12 : I64 = 42i64; ret Test.12; in - let Test.10 : Builtin(Int(I64)) = 5i64; - let Test.9 : Builtin(Bool) = CallByName Bool.7 Test.6 Test.10; + let Test.10 : I64 = 5i64; + let Test.9 : Int1 = CallByName Bool.7 Test.6 Test.10; jump Test.8 Test.9; procedure Test.0 (): - let Test.5 : Struct([]) = Struct {}; - let Test.4 : Builtin(Int(I64)) = CallByName Test.1 Test.5; + let Test.5 : {} = Struct {}; + let Test.4 : I64 = CallByName Test.1 Test.5; ret Test.4; diff --git a/compiler/test_mono/generated/if_multi_branch.txt b/compiler/test_mono/generated/if_multi_branch.txt index 778549a457..6178c6a0db 100644 --- a/compiler/test_mono/generated/if_multi_branch.txt +++ b/compiler/test_mono/generated/if_multi_branch.txt @@ -1,13 +1,13 @@ procedure Test.0 (): - let Test.6 : Builtin(Bool) = true; + let Test.6 : Int1 = true; if Test.6 then - let Test.7 : Builtin(Int(I64)) = 1i64; + let Test.7 : I64 = 1i64; ret Test.7; else - let Test.4 : Builtin(Bool) = false; + let Test.4 : Int1 = false; if Test.4 then - let Test.5 : Builtin(Int(I64)) = 2i64; + let Test.5 : I64 = 2i64; ret Test.5; else - let Test.3 : Builtin(Int(I64)) = 3i64; + let Test.3 : I64 = 3i64; ret Test.3; diff --git a/compiler/test_mono/generated/ir_assignment.txt b/compiler/test_mono/generated/ir_assignment.txt index ff5f614740..47e452bf72 100644 --- a/compiler/test_mono/generated/ir_assignment.txt +++ b/compiler/test_mono/generated/ir_assignment.txt @@ -1,3 +1,3 @@ procedure Test.0 (): - let Test.1 : Builtin(Int(I64)) = 5i64; + let Test.1 : I64 = 5i64; ret Test.1; diff --git a/compiler/test_mono/generated/ir_int_add.txt b/compiler/test_mono/generated/ir_int_add.txt index e4ef7ac607..bee729da6d 100644 --- a/compiler/test_mono/generated/ir_int_add.txt +++ b/compiler/test_mono/generated/ir_int_add.txt @@ -1,19 +1,19 @@ procedure List.7 (#Attr.2): - let Test.7 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; + let Test.7 : U64 = lowlevel ListLen #Attr.2; ret Test.7; procedure Num.22 (#Attr.2, #Attr.3): - let Test.5 : Builtin(Int(U64)) = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.5 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.5; procedure Test.0 (): - let Test.10 : Builtin(Int(U64)) = 5i64; - let Test.11 : Builtin(Int(U64)) = 4i64; - let Test.8 : Builtin(Int(U64)) = CallByName Num.22 Test.10 Test.11; - let Test.9 : Builtin(Int(U64)) = 3i64; - let Test.3 : Builtin(Int(U64)) = CallByName Num.22 Test.8 Test.9; - let Test.6 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64]; - let Test.4 : Builtin(Int(U64)) = CallByName List.7 Test.6; + let Test.10 : U64 = 5i64; + let Test.11 : U64 = 4i64; + let Test.8 : U64 = CallByName Num.22 Test.10 Test.11; + let Test.9 : U64 = 3i64; + let Test.3 : U64 = CallByName Num.22 Test.8 Test.9; + let Test.6 : List I64 = Array [1i64, 2i64]; + let Test.4 : U64 = CallByName List.7 Test.6; dec Test.6; - let Test.2 : Builtin(Int(U64)) = CallByName Num.22 Test.3 Test.4; + let Test.2 : U64 = CallByName Num.22 Test.3 Test.4; ret Test.2; diff --git a/compiler/test_mono/generated/ir_int_literal.txt b/compiler/test_mono/generated/ir_int_literal.txt index ff5f614740..47e452bf72 100644 --- a/compiler/test_mono/generated/ir_int_literal.txt +++ b/compiler/test_mono/generated/ir_int_literal.txt @@ -1,3 +1,3 @@ procedure Test.0 (): - let Test.1 : Builtin(Int(I64)) = 5i64; + let Test.1 : I64 = 5i64; ret Test.1; diff --git a/compiler/test_mono/generated/ir_plus.txt b/compiler/test_mono/generated/ir_plus.txt index b0c0e1a060..3bcafd0029 100644 --- a/compiler/test_mono/generated/ir_plus.txt +++ b/compiler/test_mono/generated/ir_plus.txt @@ -1,9 +1,9 @@ procedure Num.22 (#Attr.2, #Attr.3): - let Test.4 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.4 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.4; procedure Test.0 (): - let Test.2 : Builtin(Int(I64)) = 1i64; - let Test.3 : Builtin(Int(I64)) = 2i64; - let Test.1 : Builtin(Int(I64)) = CallByName Num.22 Test.2 Test.3; + let Test.2 : I64 = 1i64; + let Test.3 : I64 = 2i64; + let Test.1 : I64 = CallByName Num.22 Test.2 Test.3; ret Test.1; diff --git a/compiler/test_mono/generated/ir_round.txt b/compiler/test_mono/generated/ir_round.txt index 7f16bbcd71..a62c2c4a09 100644 --- a/compiler/test_mono/generated/ir_round.txt +++ b/compiler/test_mono/generated/ir_round.txt @@ -1,8 +1,8 @@ procedure Num.45 (#Attr.2): - let Test.3 : Builtin(Int(I64)) = lowlevel NumRound #Attr.2; + let Test.3 : I64 = lowlevel NumRound #Attr.2; ret Test.3; procedure Test.0 (): - let Test.2 : Builtin(Float(F64)) = 3.6f64; - let Test.1 : Builtin(Int(I64)) = CallByName Num.45 Test.2; + let Test.2 : Float64 = 3.6f64; + let Test.1 : I64 = CallByName Num.45 Test.2; ret Test.1; diff --git a/compiler/test_mono/generated/ir_two_defs.txt b/compiler/test_mono/generated/ir_two_defs.txt index b39fc79319..39d09c2318 100644 --- a/compiler/test_mono/generated/ir_two_defs.txt +++ b/compiler/test_mono/generated/ir_two_defs.txt @@ -1,9 +1,9 @@ procedure Num.22 (#Attr.2, #Attr.3): - let Test.6 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.6 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.6; procedure Test.0 (): - let Test.4 : Builtin(Int(I64)) = 3i64; - let Test.5 : Builtin(Int(I64)) = 4i64; - let Test.3 : Builtin(Int(I64)) = CallByName Num.22 Test.4 Test.5; + let Test.4 : I64 = 3i64; + let Test.5 : I64 = 4i64; + let Test.3 : I64 = CallByName Num.22 Test.4 Test.5; ret Test.3; diff --git a/compiler/test_mono/generated/ir_when_idiv.txt b/compiler/test_mono/generated/ir_when_idiv.txt index 6b60e0547c..999d7f762e 100644 --- a/compiler/test_mono/generated/ir_when_idiv.txt +++ b/compiler/test_mono/generated/ir_when_idiv.txt @@ -1,25 +1,25 @@ procedure Num.40 (#Attr.2, #Attr.3): - let Test.15 : Builtin(Int(I64)) = 0i64; - let Test.12 : Builtin(Bool) = lowlevel NotEq #Attr.3 Test.15; + let Test.15 : I64 = 0i64; + let Test.12 : Int1 = lowlevel NotEq #Attr.3 Test.15; if Test.12 then - let Test.14 : Builtin(Int(I64)) = lowlevel NumDivUnchecked #Attr.2 #Attr.3; - let Test.13 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = Ok Test.14; + let Test.14 : I64 = lowlevel NumDivUnchecked #Attr.2 #Attr.3; + let Test.13 : [C {}, C I64] = Ok Test.14; ret Test.13; else - let Test.11 : Struct([]) = Struct {}; - let Test.10 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = Err Test.11; + let Test.11 : {} = Struct {}; + let Test.10 : [C {}, C I64] = Err Test.11; ret Test.10; procedure Test.0 (): - let Test.8 : Builtin(Int(I64)) = 1000i64; - let Test.9 : Builtin(Int(I64)) = 10i64; - let Test.2 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = CallByName Num.40 Test.8 Test.9; - let Test.5 : Builtin(Int(U8)) = 1i64; - let Test.6 : Builtin(Int(U8)) = GetTagId Test.2; - let Test.7 : Builtin(Bool) = lowlevel Eq Test.5 Test.6; + let Test.8 : I64 = 1000i64; + let Test.9 : I64 = 10i64; + let Test.2 : [C {}, C I64] = CallByName Num.40 Test.8 Test.9; + let Test.5 : U8 = 1i64; + let Test.6 : U8 = GetTagId Test.2; + let Test.7 : Int1 = lowlevel Eq Test.5 Test.6; if Test.7 then - let Test.1 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) Test.2; + let Test.1 : I64 = UnionAtIndex (Id 1) (Index 0) Test.2; ret Test.1; else - let Test.4 : Builtin(Int(I64)) = -1i64; + let Test.4 : I64 = -1i64; ret Test.4; diff --git a/compiler/test_mono/generated/ir_when_just.txt b/compiler/test_mono/generated/ir_when_just.txt index a2ab04d249..c3e60612d4 100644 --- a/compiler/test_mono/generated/ir_when_just.txt +++ b/compiler/test_mono/generated/ir_when_just.txt @@ -1,18 +1,18 @@ procedure Num.22 (#Attr.2, #Attr.3): - let Test.6 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.6 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.6; procedure Test.0 (): - let Test.11 : Builtin(Int(I64)) = 41i64; - let Test.1 : Union(NonRecursive([[Builtin(Int(I64))], []])) = Just Test.11; - let Test.8 : Builtin(Int(U8)) = 0i64; - let Test.9 : Builtin(Int(U8)) = GetTagId Test.1; - let Test.10 : Builtin(Bool) = lowlevel Eq Test.8 Test.9; + let Test.11 : I64 = 41i64; + let Test.1 : [C I64, C ] = Just Test.11; + let Test.8 : U8 = 0i64; + let Test.9 : U8 = GetTagId Test.1; + let Test.10 : Int1 = lowlevel Eq Test.8 Test.9; if Test.10 then - let Test.3 : Builtin(Int(I64)) = UnionAtIndex (Id 0) (Index 0) Test.1; - let Test.5 : Builtin(Int(I64)) = 1i64; - let Test.4 : Builtin(Int(I64)) = CallByName Num.22 Test.3 Test.5; + let Test.3 : I64 = UnionAtIndex (Id 0) (Index 0) Test.1; + let Test.5 : I64 = 1i64; + let Test.4 : I64 = CallByName Num.22 Test.3 Test.5; ret Test.4; else - let Test.7 : Builtin(Int(I64)) = 1i64; + let Test.7 : I64 = 1i64; ret Test.7; diff --git a/compiler/test_mono/generated/ir_when_maybe.txt b/compiler/test_mono/generated/ir_when_maybe.txt index 348604c968..e5a573c182 100644 --- a/compiler/test_mono/generated/ir_when_maybe.txt +++ b/compiler/test_mono/generated/ir_when_maybe.txt @@ -1,12 +1,12 @@ procedure Test.0 (): - let Test.9 : Builtin(Int(I64)) = 3i64; - let Test.3 : Union(NonRecursive([[Builtin(Int(I64))], []])) = Just Test.9; - let Test.6 : Builtin(Int(U8)) = 0i64; - let Test.7 : Builtin(Int(U8)) = GetTagId Test.3; - let Test.8 : Builtin(Bool) = lowlevel Eq Test.6 Test.7; + let Test.9 : I64 = 3i64; + let Test.3 : [C I64, C ] = Just Test.9; + let Test.6 : U8 = 0i64; + let Test.7 : U8 = GetTagId Test.3; + let Test.8 : Int1 = lowlevel Eq Test.6 Test.7; if Test.8 then - let Test.2 : Builtin(Int(I64)) = UnionAtIndex (Id 0) (Index 0) Test.3; + let Test.2 : I64 = UnionAtIndex (Id 0) (Index 0) Test.3; ret Test.2; else - let Test.5 : Builtin(Int(I64)) = 0i64; + let Test.5 : I64 = 0i64; ret Test.5; diff --git a/compiler/test_mono/generated/ir_when_record.txt b/compiler/test_mono/generated/ir_when_record.txt index b9e3ef94fc..bbac521770 100644 --- a/compiler/test_mono/generated/ir_when_record.txt +++ b/compiler/test_mono/generated/ir_when_record.txt @@ -1,6 +1,6 @@ procedure Test.0 (): - let Test.4 : Builtin(Int(I64)) = 1i64; - let Test.5 : Builtin(Float(F64)) = 3.14f64; - let Test.2 : Struct([Builtin(Int(I64)), Builtin(Float(F64))]) = Struct {Test.4, Test.5}; - let Test.1 : Builtin(Int(I64)) = StructAtIndex 0 Test.2; + let Test.4 : I64 = 1i64; + let Test.5 : Float64 = 3.14f64; + let Test.2 : {I64, Float64} = Struct {Test.4, Test.5}; + let Test.1 : I64 = StructAtIndex 0 Test.2; ret Test.1; diff --git a/compiler/test_mono/generated/ir_when_these.txt b/compiler/test_mono/generated/ir_when_these.txt index 08c3614351..0c7453e2b7 100644 --- a/compiler/test_mono/generated/ir_when_these.txt +++ b/compiler/test_mono/generated/ir_when_these.txt @@ -1,18 +1,18 @@ procedure Test.0 (): - let Test.10 : Builtin(Int(I64)) = 1i64; - let Test.11 : Builtin(Int(I64)) = 2i64; - let Test.5 : Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64)), Builtin(Int(I64))], [Builtin(Int(I64))]])) = These Test.10 Test.11; - let Test.9 : Builtin(Int(U8)) = GetTagId Test.5; + let Test.10 : I64 = 1i64; + let Test.11 : I64 = 2i64; + let Test.5 : [C I64, C I64 I64, C I64] = These Test.10 Test.11; + let Test.9 : U8 = GetTagId Test.5; switch Test.9: case 2: - let Test.2 : Builtin(Int(I64)) = UnionAtIndex (Id 2) (Index 0) Test.5; + let Test.2 : I64 = UnionAtIndex (Id 2) (Index 0) Test.5; ret Test.2; case 0: - let Test.3 : Builtin(Int(I64)) = UnionAtIndex (Id 0) (Index 0) Test.5; + let Test.3 : I64 = UnionAtIndex (Id 0) (Index 0) Test.5; ret Test.3; default: - let Test.4 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) Test.5; + let Test.4 : I64 = UnionAtIndex (Id 1) (Index 0) Test.5; ret Test.4; diff --git a/compiler/test_mono/generated/is_nil.txt b/compiler/test_mono/generated/is_nil.txt index 5968c5178c..72af4853cd 100644 --- a/compiler/test_mono/generated/is_nil.txt +++ b/compiler/test_mono/generated/is_nil.txt @@ -1,18 +1,18 @@ procedure Test.2 (Test.3): - let Test.12 : Builtin(Bool) = 1i64; - let Test.13 : Builtin(Bool) = GetTagId Test.3; - let Test.14 : Builtin(Bool) = lowlevel Eq Test.12 Test.13; + let Test.12 : Int1 = 1i64; + let Test.13 : Int1 = GetTagId Test.3; + let Test.14 : Int1 = lowlevel Eq Test.12 Test.13; if Test.14 then - let Test.10 : Builtin(Bool) = true; + let Test.10 : Int1 = true; ret Test.10; else - let Test.11 : Builtin(Bool) = false; + let Test.11 : Int1 = false; ret Test.11; procedure Test.0 (): - let Test.15 : Builtin(Int(I64)) = 2i64; - let Test.16 : Union(NullableUnwrapped { nullable_id: true, other_fields: [Builtin(Int(I64)), RecursivePointer] }) = Nil ; - let Test.9 : Union(NullableUnwrapped { nullable_id: true, other_fields: [Builtin(Int(I64)), RecursivePointer] }) = Cons Test.15 Test.16; - let Test.8 : Builtin(Bool) = CallByName Test.2 Test.9; + let Test.15 : I64 = 2i64; + let Test.16 : TODO = Nil ; + let Test.9 : TODO = Cons Test.15 Test.16; + let Test.8 : Int1 = CallByName Test.2 Test.9; dec Test.9; ret Test.8; diff --git a/compiler/test_mono/generated/issue_2535_polymorphic_fields_referenced_in_list.txt b/compiler/test_mono/generated/issue_2535_polymorphic_fields_referenced_in_list.txt new file mode 100644 index 0000000000..0b14e41d86 --- /dev/null +++ b/compiler/test_mono/generated/issue_2535_polymorphic_fields_referenced_in_list.txt @@ -0,0 +1,19 @@ +procedure Test.1 (): + let Test.11 : I64 = 2i64; + let Test.12 : U8 = 1i64; + let Test.10 : {I64, U8} = Struct {Test.11, Test.12}; + ret Test.10; + +procedure Test.1 (): + let Test.7 : I64 = 1i64; + let Test.8 : U8 = 2i64; + let Test.6 : {I64, U8} = Struct {Test.7, Test.8}; + ret Test.6; + +procedure Test.0 (): + let Test.9 : {I64, U8} = CallByName Test.1; + let Test.3 : U8 = StructAtIndex 1 Test.9; + let Test.5 : {I64, U8} = CallByName Test.1; + let Test.4 : U8 = StructAtIndex 1 Test.5; + let Test.2 : List U8 = Array [Test.3, Test.4]; + ret Test.2; diff --git a/compiler/test_mono/generated/let_with_record_pattern.txt b/compiler/test_mono/generated/let_with_record_pattern.txt index 302c1de690..58abbb1866 100644 --- a/compiler/test_mono/generated/let_with_record_pattern.txt +++ b/compiler/test_mono/generated/let_with_record_pattern.txt @@ -1,6 +1,6 @@ procedure Test.0 (): - let Test.4 : Builtin(Int(I64)) = 2i64; - let Test.5 : Builtin(Float(F64)) = 3.14f64; - let Test.3 : Struct([Builtin(Int(I64)), Builtin(Float(F64))]) = Struct {Test.4, Test.5}; - let Test.1 : Builtin(Int(I64)) = StructAtIndex 0 Test.3; + let Test.4 : I64 = 2i64; + let Test.5 : Float64 = 3.14f64; + let Test.3 : {I64, Float64} = Struct {Test.4, Test.5}; + let Test.1 : I64 = StructAtIndex 0 Test.3; ret Test.1; diff --git a/compiler/test_mono/generated/let_with_record_pattern_list.txt b/compiler/test_mono/generated/let_with_record_pattern_list.txt index 1c22f23866..9475864c6c 100644 --- a/compiler/test_mono/generated/let_with_record_pattern_list.txt +++ b/compiler/test_mono/generated/let_with_record_pattern_list.txt @@ -1,8 +1,8 @@ procedure Test.0 (): - let Test.4 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 3i64, 4i64]; - let Test.5 : Builtin(Float(F64)) = 3.14f64; - let Test.3 : Struct([Builtin(List(Builtin(Int(I64)))), Builtin(Float(F64))]) = Struct {Test.4, Test.5}; - let Test.1 : Builtin(List(Builtin(Int(I64)))) = StructAtIndex 0 Test.3; + let Test.4 : List I64 = Array [1i64, 3i64, 4i64]; + let Test.5 : Float64 = 3.14f64; + let Test.3 : {List I64, Float64} = Struct {Test.4, Test.5}; + let Test.1 : List I64 = StructAtIndex 0 Test.3; inc Test.1; dec Test.3; ret Test.1; diff --git a/compiler/test_mono/generated/let_x_in_x.txt b/compiler/test_mono/generated/let_x_in_x.txt index 1ae2b5dd47..72af22119a 100644 --- a/compiler/test_mono/generated/let_x_in_x.txt +++ b/compiler/test_mono/generated/let_x_in_x.txt @@ -1,3 +1,3 @@ procedure Test.0 (): - let Test.2 : Builtin(Int(I64)) = 1337i64; + let Test.2 : I64 = 1337i64; ret Test.2; diff --git a/compiler/test_mono/generated/let_x_in_x_indirect.txt b/compiler/test_mono/generated/let_x_in_x_indirect.txt index 8897207353..6215e7f0af 100644 --- a/compiler/test_mono/generated/let_x_in_x_indirect.txt +++ b/compiler/test_mono/generated/let_x_in_x_indirect.txt @@ -1,6 +1,6 @@ procedure Test.0 (): - let Test.2 : Builtin(Int(I64)) = 1337i64; - let Test.3 : Builtin(Int(I64)) = 17i64; - let Test.7 : Struct([Builtin(Int(I64)), Builtin(Int(I64))]) = Struct {Test.2, Test.3}; - let Test.6 : Builtin(Int(I64)) = StructAtIndex 0 Test.7; + let Test.2 : I64 = 1337i64; + let Test.3 : I64 = 17i64; + let Test.7 : {I64, I64} = Struct {Test.2, Test.3}; + let Test.6 : I64 = StructAtIndex 0 Test.7; ret Test.6; diff --git a/compiler/test_mono/generated/linked_list_length_twice.txt b/compiler/test_mono/generated/linked_list_length_twice.txt index c791ad1cd4..3b1d0984f3 100644 --- a/compiler/test_mono/generated/linked_list_length_twice.txt +++ b/compiler/test_mono/generated/linked_list_length_twice.txt @@ -1,25 +1,25 @@ procedure Num.22 (#Attr.2, #Attr.3): - let Test.10 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.10 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.10; procedure Test.3 (Test.5): - let Test.16 : Builtin(Bool) = 1i64; - let Test.17 : Builtin(Bool) = GetTagId Test.5; - let Test.18 : Builtin(Bool) = lowlevel Eq Test.16 Test.17; + let Test.16 : Int1 = 1i64; + let Test.17 : Int1 = GetTagId Test.5; + let Test.18 : Int1 = lowlevel Eq Test.16 Test.17; if Test.18 then - let Test.12 : Builtin(Int(I64)) = 0i64; + let Test.12 : I64 = 0i64; ret Test.12; else - let Test.6 : Union(NullableUnwrapped { nullable_id: true, other_fields: [Builtin(Int(I64)), RecursivePointer] }) = UnionAtIndex (Id 0) (Index 1) Test.5; - let Test.14 : Builtin(Int(I64)) = 1i64; - let Test.15 : Builtin(Int(I64)) = CallByName Test.3 Test.6; - let Test.13 : Builtin(Int(I64)) = CallByName Num.22 Test.14 Test.15; + let Test.6 : TODO = UnionAtIndex (Id 0) (Index 1) Test.5; + let Test.14 : I64 = 1i64; + let Test.15 : I64 = CallByName Test.3 Test.6; + let Test.13 : I64 = CallByName Num.22 Test.14 Test.15; ret Test.13; procedure Test.0 (): - let Test.2 : Union(NullableUnwrapped { nullable_id: true, other_fields: [Builtin(Int(I64)), RecursivePointer] }) = Nil ; - let Test.8 : Builtin(Int(I64)) = CallByName Test.3 Test.2; - let Test.9 : Builtin(Int(I64)) = CallByName Test.3 Test.2; + let Test.2 : TODO = Nil ; + let Test.8 : I64 = CallByName Test.3 Test.2; + let Test.9 : I64 = CallByName Test.3 Test.2; dec Test.2; - let Test.7 : Builtin(Int(I64)) = CallByName Num.22 Test.8 Test.9; + let Test.7 : I64 = CallByName Num.22 Test.8 Test.9; ret Test.7; diff --git a/compiler/test_mono/generated/list_append.txt b/compiler/test_mono/generated/list_append.txt index 2b0be9dd16..c899f2abd7 100644 --- a/compiler/test_mono/generated/list_append.txt +++ b/compiler/test_mono/generated/list_append.txt @@ -1,9 +1,9 @@ procedure List.5 (#Attr.2, #Attr.3): - let Test.4 : Builtin(List(Builtin(Int(I64)))) = lowlevel ListAppend #Attr.2 #Attr.3; + let Test.4 : List I64 = lowlevel ListAppend #Attr.2 #Attr.3; ret Test.4; procedure Test.0 (): - let Test.2 : Builtin(List(Builtin(Int(I64)))) = Array [1i64]; - let Test.3 : Builtin(Int(I64)) = 2i64; - let Test.1 : Builtin(List(Builtin(Int(I64)))) = CallByName List.5 Test.2 Test.3; + let Test.2 : List I64 = Array [1i64]; + let Test.3 : I64 = 2i64; + let Test.1 : List I64 = CallByName List.5 Test.2 Test.3; ret Test.1; diff --git a/compiler/test_mono/generated/list_append_closure.txt b/compiler/test_mono/generated/list_append_closure.txt index 3cd9bffd51..43227f7089 100644 --- a/compiler/test_mono/generated/list_append_closure.txt +++ b/compiler/test_mono/generated/list_append_closure.txt @@ -1,13 +1,13 @@ procedure List.5 (#Attr.2, #Attr.3): - let Test.7 : Builtin(List(Builtin(Int(I64)))) = lowlevel ListAppend #Attr.2 #Attr.3; + let Test.7 : List I64 = lowlevel ListAppend #Attr.2 #Attr.3; ret Test.7; procedure Test.1 (Test.2): - let Test.6 : Builtin(Int(I64)) = 42i64; - let Test.5 : Builtin(List(Builtin(Int(I64)))) = CallByName List.5 Test.2 Test.6; + let Test.6 : I64 = 42i64; + let Test.5 : List I64 = CallByName List.5 Test.2 Test.6; ret Test.5; procedure Test.0 (): - let Test.4 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64]; - let Test.3 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.1 Test.4; + let Test.4 : List I64 = Array [1i64, 2i64]; + let Test.3 : List I64 = CallByName Test.1 Test.4; ret Test.3; diff --git a/compiler/test_mono/generated/list_cannot_update_inplace.txt b/compiler/test_mono/generated/list_cannot_update_inplace.txt index c2087e2d74..08123887b1 100644 --- a/compiler/test_mono/generated/list_cannot_update_inplace.txt +++ b/compiler/test_mono/generated/list_cannot_update_inplace.txt @@ -1,37 +1,37 @@ procedure List.4 (#Attr.2, #Attr.3, #Attr.4): - let Test.19 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; - let Test.17 : Builtin(Bool) = lowlevel NumLt #Attr.3 Test.19; + let Test.19 : U64 = lowlevel ListLen #Attr.2; + let Test.17 : Int1 = lowlevel NumLt #Attr.3 Test.19; if Test.17 then - let Test.18 : Builtin(List(Builtin(Int(I64)))) = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; + let Test.18 : List I64 = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; ret Test.18; else ret #Attr.2; procedure List.7 (#Attr.2): - let Test.9 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; + let Test.9 : U64 = lowlevel ListLen #Attr.2; ret Test.9; procedure Num.22 (#Attr.2, #Attr.3): - let Test.7 : Builtin(Int(U64)) = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.7 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.7; procedure Test.1 (): - let Test.10 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64, 3i64]; + let Test.10 : List I64 = Array [1i64, 2i64, 3i64]; ret Test.10; procedure Test.2 (Test.3): - let Test.14 : Builtin(Int(U64)) = 0i64; - let Test.15 : Builtin(Int(I64)) = 0i64; - let Test.13 : Builtin(List(Builtin(Int(I64)))) = CallByName List.4 Test.3 Test.14 Test.15; + let Test.14 : U64 = 0i64; + let Test.15 : I64 = 0i64; + let Test.13 : List I64 = CallByName List.4 Test.3 Test.14 Test.15; ret Test.13; procedure Test.0 (): - let Test.12 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.1; - let Test.11 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.2 Test.12; - let Test.5 : Builtin(Int(U64)) = CallByName List.7 Test.11; + let Test.12 : List I64 = CallByName Test.1; + let Test.11 : List I64 = CallByName Test.2 Test.12; + let Test.5 : U64 = CallByName List.7 Test.11; dec Test.11; - let Test.8 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.1; - let Test.6 : Builtin(Int(U64)) = CallByName List.7 Test.8; + let Test.8 : List I64 = CallByName Test.1; + let Test.6 : U64 = CallByName List.7 Test.8; dec Test.8; - let Test.4 : Builtin(Int(U64)) = CallByName Num.22 Test.5 Test.6; + let Test.4 : U64 = CallByName Num.22 Test.5 Test.6; ret Test.4; diff --git a/compiler/test_mono/generated/list_get.txt b/compiler/test_mono/generated/list_get.txt index 1ea41edce5..b8d8636c97 100644 --- a/compiler/test_mono/generated/list_get.txt +++ b/compiler/test_mono/generated/list_get.txt @@ -1,23 +1,23 @@ procedure List.3 (#Attr.2, #Attr.3): - let Test.13 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; - let Test.10 : Builtin(Bool) = lowlevel NumLt #Attr.3 Test.13; + let Test.13 : U64 = lowlevel ListLen #Attr.2; + let Test.10 : Int1 = lowlevel NumLt #Attr.3 Test.13; if Test.10 then - let Test.12 : Builtin(Int(I64)) = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - let Test.11 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = Ok Test.12; + let Test.12 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + let Test.11 : [C {}, C I64] = Ok Test.12; ret Test.11; else - let Test.9 : Struct([]) = Struct {}; - let Test.8 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = Err Test.9; + let Test.9 : {} = Struct {}; + let Test.8 : [C {}, C I64] = Err Test.9; ret Test.8; procedure Test.1 (Test.2): - let Test.6 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64, 3i64]; - let Test.7 : Builtin(Int(U64)) = 0i64; - let Test.5 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = CallByName List.3 Test.6 Test.7; + let Test.6 : List I64 = Array [1i64, 2i64, 3i64]; + let Test.7 : U64 = 0i64; + let Test.5 : [C {}, C I64] = CallByName List.3 Test.6 Test.7; dec Test.6; ret Test.5; procedure Test.0 (): - let Test.4 : Struct([]) = Struct {}; - let Test.3 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = CallByName Test.1 Test.4; + let Test.4 : {} = Struct {}; + let Test.3 : [C {}, C I64] = CallByName Test.1 Test.4; ret Test.3; diff --git a/compiler/test_mono/generated/list_len.txt b/compiler/test_mono/generated/list_len.txt index 80b6093747..13d10a54ce 100644 --- a/compiler/test_mono/generated/list_len.txt +++ b/compiler/test_mono/generated/list_len.txt @@ -1,21 +1,21 @@ procedure List.7 (#Attr.2): - let Test.10 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; + let Test.10 : U64 = lowlevel ListLen #Attr.2; ret Test.10; procedure List.7 (#Attr.2): - let Test.8 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; + let Test.8 : U64 = lowlevel ListLen #Attr.2; ret Test.8; procedure Num.22 (#Attr.2, #Attr.3): - let Test.6 : Builtin(Int(U64)) = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.6 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.6; procedure Test.0 (): - let Test.9 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64, 3i64]; - let Test.4 : Builtin(Int(U64)) = CallByName List.7 Test.9; + let Test.9 : List I64 = Array [1i64, 2i64, 3i64]; + let Test.4 : U64 = CallByName List.7 Test.9; dec Test.9; - let Test.7 : Builtin(List(Builtin(Float(F64)))) = Array [1f64]; - let Test.5 : Builtin(Int(U64)) = CallByName List.7 Test.7; + let Test.7 : List Float64 = Array [1f64]; + let Test.5 : U64 = CallByName List.7 Test.7; dec Test.7; - let Test.3 : Builtin(Int(U64)) = CallByName Num.22 Test.4 Test.5; + let Test.3 : U64 = CallByName Num.22 Test.4 Test.5; ret Test.3; diff --git a/compiler/test_mono/generated/list_pass_to_function.txt b/compiler/test_mono/generated/list_pass_to_function.txt index 090b5affdd..b235671dcf 100644 --- a/compiler/test_mono/generated/list_pass_to_function.txt +++ b/compiler/test_mono/generated/list_pass_to_function.txt @@ -1,19 +1,19 @@ procedure List.4 (#Attr.2, #Attr.3, #Attr.4): - let Test.11 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; - let Test.9 : Builtin(Bool) = lowlevel NumLt #Attr.3 Test.11; + let Test.11 : U64 = lowlevel ListLen #Attr.2; + let Test.9 : Int1 = lowlevel NumLt #Attr.3 Test.11; if Test.9 then - let Test.10 : Builtin(List(Builtin(Int(I64)))) = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; + let Test.10 : List I64 = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; ret Test.10; else ret #Attr.2; procedure Test.2 (Test.3): - let Test.6 : Builtin(Int(U64)) = 0i64; - let Test.7 : Builtin(Int(I64)) = 0i64; - let Test.5 : Builtin(List(Builtin(Int(I64)))) = CallByName List.4 Test.3 Test.6 Test.7; + let Test.6 : U64 = 0i64; + let Test.7 : I64 = 0i64; + let Test.5 : List I64 = CallByName List.4 Test.3 Test.6 Test.7; ret Test.5; procedure Test.0 (): - let Test.1 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64, 3i64]; - let Test.4 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.2 Test.1; + let Test.1 : List I64 = Array [1i64, 2i64, 3i64]; + let Test.4 : List I64 = CallByName Test.2 Test.1; ret Test.4; diff --git a/compiler/test_mono/generated/mk_pair_of.txt b/compiler/test_mono/generated/mk_pair_of.txt index 98bd4b0d18..3931112c9e 100644 --- a/compiler/test_mono/generated/mk_pair_of.txt +++ b/compiler/test_mono/generated/mk_pair_of.txt @@ -1,9 +1,9 @@ procedure Test.1 (Test.2): inc Test.2; - let Test.6 : Struct([Builtin(List(Builtin(Int(I64)))), Builtin(List(Builtin(Int(I64))))]) = Struct {Test.2, Test.2}; + let Test.6 : {List I64, List I64} = Struct {Test.2, Test.2}; ret Test.6; procedure Test.0 (): - let Test.5 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64, 3i64]; - let Test.4 : Struct([Builtin(List(Builtin(Int(I64)))), Builtin(List(Builtin(Int(I64))))]) = CallByName Test.1 Test.5; + let Test.5 : List I64 = Array [1i64, 2i64, 3i64]; + let Test.4 : {List I64, List I64} = CallByName Test.1 Test.5; ret Test.4; diff --git a/compiler/test_mono/generated/monomorphized_applied_tag.txt b/compiler/test_mono/generated/monomorphized_applied_tag.txt index 90dd27c931..4e672c7597 100644 --- a/compiler/test_mono/generated/monomorphized_applied_tag.txt +++ b/compiler/test_mono/generated/monomorphized_applied_tag.txt @@ -1,20 +1,20 @@ procedure Test.2 (Test.4): - let Test.11 : Builtin(Int(U8)) = 0i64; - let Test.12 : Builtin(Int(U8)) = GetTagId Test.4; - let Test.13 : Builtin(Bool) = lowlevel Eq Test.11 Test.12; + let Test.11 : U8 = 0i64; + let Test.12 : U8 = GetTagId Test.4; + let Test.13 : Int1 = lowlevel Eq Test.11 Test.12; if Test.13 then - let Test.5 : Builtin(Str) = UnionAtIndex (Id 0) (Index 0) Test.4; + let Test.5 : Str = UnionAtIndex (Id 0) (Index 0) Test.4; inc Test.5; dec Test.4; ret Test.5; else - let Test.6 : Builtin(Str) = UnionAtIndex (Id 1) (Index 0) Test.4; + let Test.6 : Str = UnionAtIndex (Id 1) (Index 0) Test.4; inc Test.6; dec Test.4; ret Test.6; procedure Test.0 (): - let Test.14 : Builtin(Str) = "A"; - let Test.8 : Union(NonRecursive([[Builtin(Str)], [Builtin(Str)]])) = A Test.14; - let Test.7 : Builtin(Str) = CallByName Test.2 Test.8; + let Test.14 : Str = "A"; + let Test.8 : [C Str, C Str] = A Test.14; + let Test.7 : Str = CallByName Test.2 Test.8; ret Test.7; diff --git a/compiler/test_mono/generated/monomorphized_floats.txt b/compiler/test_mono/generated/monomorphized_floats.txt index 90eef8acc7..6fc9243f46 100644 --- a/compiler/test_mono/generated/monomorphized_floats.txt +++ b/compiler/test_mono/generated/monomorphized_floats.txt @@ -1,9 +1,9 @@ procedure Test.2 (Test.3, Test.4): - let Test.8 : Builtin(Int(U64)) = 18i64; + let Test.8 : U64 = 18i64; ret Test.8; procedure Test.0 (): - let Test.6 : Builtin(Float(F32)) = 100f64; - let Test.7 : Builtin(Float(F64)) = 100f64; - let Test.5 : Builtin(Int(U64)) = CallByName Test.2 Test.6 Test.7; + let Test.6 : Float32 = 100f64; + let Test.7 : Float64 = 100f64; + let Test.5 : U64 = CallByName Test.2 Test.6 Test.7; ret Test.5; diff --git a/compiler/test_mono/generated/monomorphized_ints.txt b/compiler/test_mono/generated/monomorphized_ints.txt index 5ffdd51f13..408f1ff44b 100644 --- a/compiler/test_mono/generated/monomorphized_ints.txt +++ b/compiler/test_mono/generated/monomorphized_ints.txt @@ -1,9 +1,9 @@ procedure Test.2 (Test.3, Test.4): - let Test.8 : Builtin(Int(U64)) = 18i64; + let Test.8 : U64 = 18i64; ret Test.8; procedure Test.0 (): - let Test.6 : Builtin(Int(U8)) = 100i64; - let Test.7 : Builtin(Int(U32)) = 100i64; - let Test.5 : Builtin(Int(U64)) = CallByName Test.2 Test.6 Test.7; + let Test.6 : U8 = 100i64; + let Test.7 : U32 = 100i64; + let Test.5 : U64 = CallByName Test.2 Test.6 Test.7; ret Test.5; diff --git a/compiler/test_mono/generated/monomorphized_ints_aliased.txt b/compiler/test_mono/generated/monomorphized_ints_aliased.txt index 50bd650f9d..c55c6b4f07 100644 --- a/compiler/test_mono/generated/monomorphized_ints_aliased.txt +++ b/compiler/test_mono/generated/monomorphized_ints_aliased.txt @@ -1,23 +1,23 @@ procedure Num.22 (#Attr.2, #Attr.3): - let Test.12 : Builtin(Int(U64)) = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.12 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.12; procedure Test.4 (Test.7, Test.8): - let Test.17 : Builtin(Int(U64)) = 1i64; + let Test.17 : U64 = 1i64; ret Test.17; procedure Test.4 (Test.7, Test.8): - let Test.18 : Builtin(Int(U64)) = 1i64; + let Test.18 : U64 = 1i64; ret Test.18; procedure Test.0 (): - let Test.4 : LambdaSet(LambdaSet { set: [( Test.4, [])], representation: Struct([]) }) = Struct {}; - let Test.4 : LambdaSet(LambdaSet { set: [( Test.4, [])], representation: Struct([]) }) = Struct {}; - let Test.15 : Builtin(Int(U8)) = 100i64; - let Test.16 : Builtin(Int(U32)) = 100i64; - let Test.10 : Builtin(Int(U64)) = CallByName Test.4 Test.15 Test.16; - let Test.13 : Builtin(Int(U32)) = 100i64; - let Test.14 : Builtin(Int(U8)) = 100i64; - let Test.11 : Builtin(Int(U64)) = CallByName Test.4 Test.13 Test.14; - let Test.9 : Builtin(Int(U64)) = CallByName Num.22 Test.10 Test.11; + let Test.4 : {} = Struct {}; + let Test.4 : {} = Struct {}; + let Test.15 : U8 = 100i64; + let Test.16 : U32 = 100i64; + let Test.10 : U64 = CallByName Test.4 Test.15 Test.16; + let Test.13 : U32 = 100i64; + let Test.14 : U8 = 100i64; + let Test.11 : U64 = CallByName Test.4 Test.13 Test.14; + let Test.9 : U64 = CallByName Num.22 Test.10 Test.11; ret Test.9; diff --git a/compiler/test_mono/generated/monomorphized_list.txt b/compiler/test_mono/generated/monomorphized_list.txt index d38a0682ff..813339d654 100644 --- a/compiler/test_mono/generated/monomorphized_list.txt +++ b/compiler/test_mono/generated/monomorphized_list.txt @@ -1,11 +1,11 @@ procedure Test.2 (Test.3, Test.4): - let Test.8 : Builtin(Int(U64)) = 18i64; + let Test.8 : U64 = 18i64; ret Test.8; procedure Test.0 (): - let Test.6 : Builtin(List(Builtin(Int(U8)))) = Array [1i64, 2i64, 3i64]; - let Test.7 : Builtin(List(Builtin(Int(U16)))) = Array [1i64, 2i64, 3i64]; - let Test.5 : Builtin(Int(U64)) = CallByName Test.2 Test.6 Test.7; + let Test.6 : List U8 = Array [1i64, 2i64, 3i64]; + let Test.7 : List U16 = Array [1i64, 2i64, 3i64]; + let Test.5 : U64 = CallByName Test.2 Test.6 Test.7; dec Test.7; dec Test.6; ret Test.5; diff --git a/compiler/test_mono/generated/monomorphized_tag.txt b/compiler/test_mono/generated/monomorphized_tag.txt index a1cb92209d..3e2dd8f0bb 100644 --- a/compiler/test_mono/generated/monomorphized_tag.txt +++ b/compiler/test_mono/generated/monomorphized_tag.txt @@ -1,9 +1,9 @@ procedure Test.2 (Test.4, Test.5): - let Test.9 : Builtin(Int(U8)) = 18i64; + let Test.9 : U8 = 18i64; ret Test.9; procedure Test.0 (): - let Test.7 : Builtin(Bool) = false; - let Test.8 : Builtin(Int(U8)) = 0u8; - let Test.6 : Builtin(Int(U8)) = CallByName Test.2 Test.7 Test.8; + let Test.7 : Int1 = false; + let Test.8 : U8 = 0u8; + let Test.6 : U8 = CallByName Test.2 Test.7 Test.8; ret Test.6; diff --git a/compiler/test_mono/generated/monomorphized_tag_with_aliased_args.txt b/compiler/test_mono/generated/monomorphized_tag_with_aliased_args.txt index 02a88fb375..5126202c19 100644 --- a/compiler/test_mono/generated/monomorphized_tag_with_aliased_args.txt +++ b/compiler/test_mono/generated/monomorphized_tag_with_aliased_args.txt @@ -1,10 +1,10 @@ procedure Test.4 (Test.8): - let Test.11 : Builtin(Int(U64)) = 1i64; + let Test.11 : U64 = 1i64; ret Test.11; procedure Test.0 (): - let Test.13 : Builtin(Bool) = false; - let Test.12 : Builtin(Bool) = false; - let Test.10 : Struct([Builtin(Bool), Builtin(Bool)]) = Struct {Test.12, Test.13}; - let Test.9 : Builtin(Int(U64)) = CallByName Test.4 Test.10; + let Test.13 : Int1 = false; + let Test.12 : Int1 = false; + let Test.10 : {Int1, Int1} = Struct {Test.12, Test.13}; + let Test.9 : U64 = CallByName Test.4 Test.10; ret Test.9; diff --git a/compiler/test_mono/generated/nested_closure.txt b/compiler/test_mono/generated/nested_closure.txt index 9b15123922..fe4e104d86 100644 --- a/compiler/test_mono/generated/nested_closure.txt +++ b/compiler/test_mono/generated/nested_closure.txt @@ -1,16 +1,16 @@ procedure Test.1 (Test.5): - let Test.2 : Builtin(Int(I64)) = 42i64; - let Test.3 : LambdaSet(LambdaSet { set: [( Test.3, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }) = Struct {Test.2}; + let Test.2 : I64 = 42i64; + let Test.3 : {I64} = Struct {Test.2}; ret Test.3; procedure Test.3 (Test.10, #Attr.12): - let Test.2 : Builtin(Int(I64)) = StructAtIndex 0 #Attr.12; - let Test.2 : Builtin(Int(I64)) = 42i64; + let Test.2 : I64 = StructAtIndex 0 #Attr.12; + let Test.2 : I64 = 42i64; ret Test.2; procedure Test.0 (): - let Test.7 : Struct([]) = Struct {}; - let Test.9 : Struct([]) = Struct {}; - let Test.8 : LambdaSet(LambdaSet { set: [( Test.3, [Builtin(Int(I64))])], representation: Struct([Builtin(Int(I64))]) }) = CallByName Test.1 Test.9; - let Test.6 : Builtin(Int(I64)) = CallByName Test.3 Test.7 Test.8; + let Test.7 : {} = Struct {}; + let Test.9 : {} = Struct {}; + let Test.8 : {I64} = CallByName Test.1 Test.9; + let Test.6 : I64 = CallByName Test.3 Test.7 Test.8; ret Test.6; diff --git a/compiler/test_mono/generated/nested_pattern_match.txt b/compiler/test_mono/generated/nested_pattern_match.txt index facbc46055..6bb9eebf35 100644 --- a/compiler/test_mono/generated/nested_pattern_match.txt +++ b/compiler/test_mono/generated/nested_pattern_match.txt @@ -1,28 +1,28 @@ procedure Num.22 (#Attr.2, #Attr.3): - let Test.8 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.8 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.8; procedure Test.0 (): - let Test.20 : Builtin(Int(I64)) = 41i64; - let Test.19 : Union(NonRecursive([[Builtin(Int(I64))], []])) = Just Test.20; - let Test.2 : Union(NonRecursive([[Union(NonRecursive([[Builtin(Int(I64))], []]))], []])) = Just Test.19; + let Test.20 : I64 = 41i64; + let Test.19 : [C I64, C ] = Just Test.20; + let Test.2 : [C [C I64, C ], C ] = Just Test.19; joinpoint Test.16: - let Test.9 : Builtin(Int(I64)) = 1i64; + let Test.9 : I64 = 1i64; ret Test.9; in - let Test.14 : Builtin(Int(U8)) = 0i64; - let Test.15 : Builtin(Int(U8)) = GetTagId Test.2; - let Test.18 : Builtin(Bool) = lowlevel Eq Test.14 Test.15; + let Test.14 : U8 = 0i64; + let Test.15 : U8 = GetTagId Test.2; + let Test.18 : Int1 = lowlevel Eq Test.14 Test.15; if Test.18 then - let Test.11 : Union(NonRecursive([[Builtin(Int(I64))], []])) = UnionAtIndex (Id 0) (Index 0) Test.2; - let Test.12 : Builtin(Int(U8)) = 0i64; - let Test.13 : Builtin(Int(U8)) = GetTagId Test.11; - let Test.17 : Builtin(Bool) = lowlevel Eq Test.12 Test.13; + let Test.11 : [C I64, C ] = UnionAtIndex (Id 0) (Index 0) Test.2; + let Test.12 : U8 = 0i64; + let Test.13 : U8 = GetTagId Test.11; + let Test.17 : Int1 = lowlevel Eq Test.12 Test.13; if Test.17 then - let Test.10 : Union(NonRecursive([[Builtin(Int(I64))], []])) = UnionAtIndex (Id 0) (Index 0) Test.2; - let Test.5 : Builtin(Int(I64)) = UnionAtIndex (Id 0) (Index 0) Test.10; - let Test.7 : Builtin(Int(I64)) = 1i64; - let Test.6 : Builtin(Int(I64)) = CallByName Num.22 Test.5 Test.7; + let Test.10 : [C I64, C ] = UnionAtIndex (Id 0) (Index 0) Test.2; + let Test.5 : I64 = UnionAtIndex (Id 0) (Index 0) Test.10; + let Test.7 : I64 = 1i64; + let Test.6 : I64 = CallByName Num.22 Test.5 Test.7; ret Test.6; else jump Test.16; diff --git a/compiler/test_mono/generated/one_element_tag.txt b/compiler/test_mono/generated/one_element_tag.txt index 701a8080f6..1e61737429 100644 --- a/compiler/test_mono/generated/one_element_tag.txt +++ b/compiler/test_mono/generated/one_element_tag.txt @@ -1,3 +1,3 @@ procedure Test.0 (): - let Test.4 : Builtin(Int(I64)) = 2i64; + let Test.4 : I64 = 2i64; ret Test.4; diff --git a/compiler/test_mono/generated/optional_when.txt b/compiler/test_mono/generated/optional_when.txt index 1dc1fdca6b..b2649e0a96 100644 --- a/compiler/test_mono/generated/optional_when.txt +++ b/compiler/test_mono/generated/optional_when.txt @@ -1,42 +1,42 @@ procedure Num.24 (#Attr.2, #Attr.3): - let Test.18 : Builtin(Int(I64)) = lowlevel NumMul #Attr.2 #Attr.3; + let Test.18 : I64 = lowlevel NumMul #Attr.2 #Attr.3; ret Test.18; procedure Test.1 (Test.6): - let Test.22 : Builtin(Bool) = false; - let Test.23 : Builtin(Bool) = lowlevel Eq Test.22 Test.6; + let Test.22 : Int1 = false; + let Test.23 : Int1 = lowlevel Eq Test.22 Test.6; if Test.23 then - let Test.8 : Builtin(Int(I64)) = 3i64; + let Test.8 : I64 = 3i64; ret Test.8; else - let Test.10 : Builtin(Int(I64)) = 5i64; + let Test.10 : I64 = 5i64; ret Test.10; procedure Test.1 (Test.6): - let Test.30 : Builtin(Bool) = StructAtIndex 1 Test.6; - let Test.31 : Builtin(Bool) = false; - let Test.32 : Builtin(Bool) = lowlevel Eq Test.31 Test.30; + let Test.30 : Int1 = StructAtIndex 1 Test.6; + let Test.31 : Int1 = false; + let Test.32 : Int1 = lowlevel Eq Test.31 Test.30; if Test.32 then - let Test.8 : Builtin(Int(I64)) = StructAtIndex 0 Test.6; + let Test.8 : I64 = StructAtIndex 0 Test.6; ret Test.8; else - let Test.10 : Builtin(Int(I64)) = StructAtIndex 0 Test.6; + let Test.10 : I64 = StructAtIndex 0 Test.6; ret Test.10; procedure Test.0 (): - let Test.40 : Builtin(Int(I64)) = 7i64; - let Test.41 : Builtin(Bool) = false; - let Test.39 : Struct([Builtin(Int(I64)), Builtin(Bool)]) = Struct {Test.40, Test.41}; - let Test.35 : Builtin(Int(I64)) = CallByName Test.1 Test.39; - let Test.38 : Builtin(Bool) = false; - let Test.36 : Builtin(Int(I64)) = CallByName Test.1 Test.38; - let Test.25 : Builtin(Int(I64)) = CallByName Num.24 Test.35 Test.36; - let Test.33 : Builtin(Int(I64)) = 11i64; - let Test.34 : Builtin(Bool) = true; - let Test.27 : Struct([Builtin(Int(I64)), Builtin(Bool)]) = Struct {Test.33, Test.34}; - let Test.26 : Builtin(Int(I64)) = CallByName Test.1 Test.27; - let Test.16 : Builtin(Int(I64)) = CallByName Num.24 Test.25 Test.26; - let Test.24 : Builtin(Bool) = true; - let Test.17 : Builtin(Int(I64)) = CallByName Test.1 Test.24; - let Test.15 : Builtin(Int(I64)) = CallByName Num.24 Test.16 Test.17; + let Test.40 : I64 = 7i64; + let Test.41 : Int1 = false; + let Test.39 : {I64, Int1} = Struct {Test.40, Test.41}; + let Test.35 : I64 = CallByName Test.1 Test.39; + let Test.38 : Int1 = false; + let Test.36 : I64 = CallByName Test.1 Test.38; + let Test.25 : I64 = CallByName Num.24 Test.35 Test.36; + let Test.33 : I64 = 11i64; + let Test.34 : Int1 = true; + let Test.27 : {I64, Int1} = Struct {Test.33, Test.34}; + let Test.26 : I64 = CallByName Test.1 Test.27; + let Test.16 : I64 = CallByName Num.24 Test.25 Test.26; + let Test.24 : Int1 = true; + let Test.17 : I64 = CallByName Test.1 Test.24; + let Test.15 : I64 = CallByName Num.24 Test.16 Test.17; ret Test.15; diff --git a/compiler/test_mono/generated/peano.txt b/compiler/test_mono/generated/peano.txt index b20043d155..bee928bf10 100644 --- a/compiler/test_mono/generated/peano.txt +++ b/compiler/test_mono/generated/peano.txt @@ -1,6 +1,6 @@ procedure Test.0 (): - let Test.10 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = Z ; - let Test.9 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = S Test.10; - let Test.8 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = S Test.9; - let Test.2 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = S Test.8; + let Test.10 : TODO = Z ; + let Test.9 : TODO = S Test.10; + let Test.8 : TODO = S Test.9; + let Test.2 : TODO = S Test.8; ret Test.2; diff --git a/compiler/test_mono/generated/peano1.txt b/compiler/test_mono/generated/peano1.txt index 55c0c229b4..8d6b38bd55 100644 --- a/compiler/test_mono/generated/peano1.txt +++ b/compiler/test_mono/generated/peano1.txt @@ -1,15 +1,15 @@ procedure Test.0 (): - let Test.14 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = Z ; - let Test.13 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = S Test.14; - let Test.12 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = S Test.13; - let Test.2 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = S Test.12; - let Test.9 : Builtin(Bool) = 1i64; - let Test.10 : Builtin(Bool) = GetTagId Test.2; + let Test.14 : TODO = Z ; + let Test.13 : TODO = S Test.14; + let Test.12 : TODO = S Test.13; + let Test.2 : TODO = S Test.12; + let Test.9 : Int1 = 1i64; + let Test.10 : Int1 = GetTagId Test.2; dec Test.2; - let Test.11 : Builtin(Bool) = lowlevel Eq Test.9 Test.10; + let Test.11 : Int1 = lowlevel Eq Test.9 Test.10; if Test.11 then - let Test.7 : Builtin(Int(I64)) = 0i64; + let Test.7 : I64 = 0i64; ret Test.7; else - let Test.8 : Builtin(Int(I64)) = 1i64; + let Test.8 : I64 = 1i64; ret Test.8; diff --git a/compiler/test_mono/generated/peano2.txt b/compiler/test_mono/generated/peano2.txt index 152e09e19f..a708c54baf 100644 --- a/compiler/test_mono/generated/peano2.txt +++ b/compiler/test_mono/generated/peano2.txt @@ -1,26 +1,26 @@ procedure Test.0 (): - let Test.20 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = Z ; - let Test.19 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = S Test.20; - let Test.18 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = S Test.19; - let Test.2 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = S Test.18; - let Test.15 : Builtin(Bool) = 0i64; - let Test.16 : Builtin(Bool) = GetTagId Test.2; - let Test.17 : Builtin(Bool) = lowlevel Eq Test.15 Test.16; + let Test.20 : TODO = Z ; + let Test.19 : TODO = S Test.20; + let Test.18 : TODO = S Test.19; + let Test.2 : TODO = S Test.18; + let Test.15 : Int1 = 0i64; + let Test.16 : Int1 = GetTagId Test.2; + let Test.17 : Int1 = lowlevel Eq Test.15 Test.16; if Test.17 then - let Test.11 : Union(NullableUnwrapped { nullable_id: true, other_fields: [RecursivePointer] }) = UnionAtIndex (Id 0) (Index 0) Test.2; + let Test.11 : TODO = UnionAtIndex (Id 0) (Index 0) Test.2; inc Test.11; dec Test.2; - let Test.12 : Builtin(Bool) = 0i64; - let Test.13 : Builtin(Bool) = GetTagId Test.11; + let Test.12 : Int1 = 0i64; + let Test.13 : Int1 = GetTagId Test.11; dec Test.11; - let Test.14 : Builtin(Bool) = lowlevel Eq Test.12 Test.13; + let Test.14 : Int1 = lowlevel Eq Test.12 Test.13; if Test.14 then - let Test.7 : Builtin(Int(I64)) = 1i64; + let Test.7 : I64 = 1i64; ret Test.7; else - let Test.8 : Builtin(Int(I64)) = 0i64; + let Test.8 : I64 = 0i64; ret Test.8; else dec Test.2; - let Test.9 : Builtin(Int(I64)) = 0i64; + let Test.9 : I64 = 0i64; ret Test.9; diff --git a/compiler/test_mono/generated/quicksort_help.txt b/compiler/test_mono/generated/quicksort_help.txt index 911b38e71c..c9bad93164 100644 --- a/compiler/test_mono/generated/quicksort_help.txt +++ b/compiler/test_mono/generated/quicksort_help.txt @@ -1,32 +1,32 @@ procedure Num.22 (#Attr.2, #Attr.3): - let Test.19 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.19 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.19; procedure Num.23 (#Attr.2, #Attr.3): - let Test.22 : Builtin(Int(I64)) = lowlevel NumSub #Attr.2 #Attr.3; + let Test.22 : I64 = lowlevel NumSub #Attr.2 #Attr.3; ret Test.22; procedure Num.25 (#Attr.2, #Attr.3): - let Test.26 : Builtin(Bool) = lowlevel NumLt #Attr.2 #Attr.3; + let Test.26 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; ret Test.26; procedure Test.1 (Test.27, Test.28, Test.29): joinpoint Test.12 Test.2 Test.3 Test.4: - let Test.14 : Builtin(Bool) = CallByName Num.25 Test.3 Test.4; + let Test.14 : Int1 = CallByName Num.25 Test.3 Test.4; if Test.14 then dec Test.2; - let Test.25 : Builtin(List(Union(NonRecursive([])))) = Array []; - let Test.24 : Builtin(Int(I64)) = 0i64; - let Test.23 : Struct([Builtin(Int(I64)), Builtin(List(Union(NonRecursive([]))))]) = Struct {Test.24, Test.25}; - let Test.5 : Builtin(Int(I64)) = StructAtIndex 0 Test.23; - let Test.6 : Builtin(List(Union(NonRecursive([])))) = StructAtIndex 1 Test.23; + let Test.25 : List [] = Array []; + let Test.24 : I64 = 0i64; + let Test.23 : {I64, List []} = Struct {Test.24, Test.25}; + let Test.5 : I64 = StructAtIndex 0 Test.23; + let Test.6 : List [] = StructAtIndex 1 Test.23; inc Test.6; dec Test.23; - let Test.21 : Builtin(Int(I64)) = 1i64; - let Test.20 : Builtin(Int(I64)) = CallByName Num.23 Test.5 Test.21; - let Test.16 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.1 Test.6 Test.3 Test.20; - let Test.18 : Builtin(Int(I64)) = 1i64; - let Test.17 : Builtin(Int(I64)) = CallByName Num.22 Test.5 Test.18; + let Test.21 : I64 = 1i64; + let Test.20 : I64 = CallByName Num.23 Test.5 Test.21; + let Test.16 : List I64 = CallByName Test.1 Test.6 Test.3 Test.20; + let Test.18 : I64 = 1i64; + let Test.17 : I64 = CallByName Num.22 Test.5 Test.18; jump Test.12 Test.16 Test.17 Test.4; else ret Test.2; @@ -34,8 +34,8 @@ procedure Test.1 (Test.27, Test.28, Test.29): jump Test.12 Test.27 Test.28 Test.29; procedure Test.0 (): - let Test.9 : Builtin(List(Builtin(Int(I64)))) = Array []; - let Test.10 : Builtin(Int(I64)) = 0i64; - let Test.11 : Builtin(Int(I64)) = 0i64; - let Test.8 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.1 Test.9 Test.10 Test.11; + let Test.9 : List I64 = Array []; + let Test.10 : I64 = 0i64; + let Test.11 : I64 = 0i64; + let Test.8 : List I64 = CallByName Test.1 Test.9 Test.10 Test.11; ret Test.8; diff --git a/compiler/test_mono/generated/quicksort_swap.txt b/compiler/test_mono/generated/quicksort_swap.txt index 13e3c2824f..d42217aaa2 100644 --- a/compiler/test_mono/generated/quicksort_swap.txt +++ b/compiler/test_mono/generated/quicksort_swap.txt @@ -1,52 +1,52 @@ procedure List.3 (#Attr.2, #Attr.3): - let Test.37 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; - let Test.34 : Builtin(Bool) = lowlevel NumLt #Attr.3 Test.37; + let Test.37 : U64 = lowlevel ListLen #Attr.2; + let Test.34 : Int1 = lowlevel NumLt #Attr.3 Test.37; if Test.34 then - let Test.36 : Builtin(Int(I64)) = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - let Test.35 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = Ok Test.36; + let Test.36 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + let Test.35 : [C {}, C I64] = Ok Test.36; ret Test.35; else - let Test.33 : Struct([]) = Struct {}; - let Test.32 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = Err Test.33; + let Test.33 : {} = Struct {}; + let Test.32 : [C {}, C I64] = Err Test.33; ret Test.32; procedure List.4 (#Attr.2, #Attr.3, #Attr.4): - let Test.15 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; - let Test.13 : Builtin(Bool) = lowlevel NumLt #Attr.3 Test.15; + let Test.15 : U64 = lowlevel ListLen #Attr.2; + let Test.13 : Int1 = lowlevel NumLt #Attr.3 Test.15; if Test.13 then - let Test.14 : Builtin(List(Builtin(Int(I64)))) = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; + let Test.14 : List I64 = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; ret Test.14; else ret #Attr.2; procedure Test.1 (Test.2): - let Test.38 : Builtin(Int(U64)) = 0i64; - let Test.30 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = CallByName List.3 Test.2 Test.38; - let Test.31 : Builtin(Int(U64)) = 0i64; - let Test.29 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = CallByName List.3 Test.2 Test.31; - let Test.8 : Struct([Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])), Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]]))]) = Struct {Test.29, Test.30}; + let Test.38 : U64 = 0i64; + let Test.30 : [C {}, C I64] = CallByName List.3 Test.2 Test.38; + let Test.31 : U64 = 0i64; + let Test.29 : [C {}, C I64] = CallByName List.3 Test.2 Test.31; + let Test.8 : {[C {}, C I64], [C {}, C I64]} = Struct {Test.29, Test.30}; joinpoint Test.26: - let Test.17 : Builtin(List(Builtin(Int(I64)))) = Array []; + let Test.17 : List I64 = Array []; ret Test.17; in - let Test.23 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = StructAtIndex 1 Test.8; - let Test.24 : Builtin(Int(U8)) = 1i64; - let Test.25 : Builtin(Int(U8)) = GetTagId Test.23; - let Test.28 : Builtin(Bool) = lowlevel Eq Test.24 Test.25; + let Test.23 : [C {}, C I64] = StructAtIndex 1 Test.8; + let Test.24 : U8 = 1i64; + let Test.25 : U8 = GetTagId Test.23; + let Test.28 : Int1 = lowlevel Eq Test.24 Test.25; if Test.28 then - let Test.20 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = StructAtIndex 0 Test.8; - let Test.21 : Builtin(Int(U8)) = 1i64; - let Test.22 : Builtin(Int(U8)) = GetTagId Test.20; - let Test.27 : Builtin(Bool) = lowlevel Eq Test.21 Test.22; + let Test.20 : [C {}, C I64] = StructAtIndex 0 Test.8; + let Test.21 : U8 = 1i64; + let Test.22 : U8 = GetTagId Test.20; + let Test.27 : Int1 = lowlevel Eq Test.21 Test.22; if Test.27 then - let Test.19 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = StructAtIndex 0 Test.8; - let Test.4 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) Test.19; - let Test.18 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = StructAtIndex 1 Test.8; - let Test.5 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) Test.18; - let Test.16 : Builtin(Int(U64)) = 0i64; - let Test.10 : Builtin(List(Builtin(Int(I64)))) = CallByName List.4 Test.2 Test.16 Test.5; - let Test.11 : Builtin(Int(U64)) = 0i64; - let Test.9 : Builtin(List(Builtin(Int(I64)))) = CallByName List.4 Test.10 Test.11 Test.4; + let Test.19 : [C {}, C I64] = StructAtIndex 0 Test.8; + let Test.4 : I64 = UnionAtIndex (Id 1) (Index 0) Test.19; + let Test.18 : [C {}, C I64] = StructAtIndex 1 Test.8; + let Test.5 : I64 = UnionAtIndex (Id 1) (Index 0) Test.18; + let Test.16 : U64 = 0i64; + let Test.10 : List I64 = CallByName List.4 Test.2 Test.16 Test.5; + let Test.11 : U64 = 0i64; + let Test.9 : List I64 = CallByName List.4 Test.10 Test.11 Test.4; ret Test.9; else dec Test.2; @@ -56,6 +56,6 @@ procedure Test.1 (Test.2): jump Test.26; procedure Test.0 (): - let Test.7 : Builtin(List(Builtin(Int(I64)))) = Array [1i64, 2i64]; - let Test.6 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.1 Test.7; + let Test.7 : List I64 = Array [1i64, 2i64]; + let Test.6 : List I64 = CallByName Test.1 Test.7; ret Test.6; diff --git a/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt b/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt index 3c98d6876b..efbc0ca6e2 100644 --- a/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt @@ -1,16 +1,16 @@ procedure Num.22 (#Attr.2, #Attr.3): - let Test.8 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.8 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.8; procedure Test.1 (Test.4): - let Test.2 : Builtin(Int(I64)) = StructAtIndex 0 Test.4; - let Test.3 : Builtin(Int(I64)) = StructAtIndex 1 Test.4; - let Test.7 : Builtin(Int(I64)) = CallByName Num.22 Test.2 Test.3; + let Test.2 : I64 = StructAtIndex 0 Test.4; + let Test.3 : I64 = StructAtIndex 1 Test.4; + let Test.7 : I64 = CallByName Num.22 Test.2 Test.3; ret Test.7; procedure Test.0 (): - let Test.9 : Builtin(Int(I64)) = 4i64; - let Test.10 : Builtin(Int(I64)) = 9i64; - let Test.6 : Struct([Builtin(Int(I64)), Builtin(Int(I64))]) = Struct {Test.9, Test.10}; - let Test.5 : Builtin(Int(I64)) = CallByName Test.1 Test.6; + let Test.9 : I64 = 4i64; + let Test.10 : I64 = 9i64; + let Test.6 : {I64, I64} = Struct {Test.9, Test.10}; + let Test.5 : I64 = CallByName Test.1 Test.6; ret Test.5; diff --git a/compiler/test_mono/generated/record_optional_field_function_use_default.txt b/compiler/test_mono/generated/record_optional_field_function_use_default.txt index aee1aa3793..b3a418d3f3 100644 --- a/compiler/test_mono/generated/record_optional_field_function_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_function_use_default.txt @@ -1,13 +1,13 @@ procedure Num.22 (#Attr.2, #Attr.3): - let Test.9 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.9 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.9; procedure Test.1 (Test.4): - let Test.8 : Builtin(Int(I64)) = 10i64; - let Test.7 : Builtin(Int(I64)) = CallByName Num.22 Test.8 Test.4; + let Test.8 : I64 = 10i64; + let Test.7 : I64 = CallByName Num.22 Test.8 Test.4; ret Test.7; procedure Test.0 (): - let Test.10 : Builtin(Int(I64)) = 9i64; - let Test.5 : Builtin(Int(I64)) = CallByName Test.1 Test.10; + let Test.10 : I64 = 9i64; + let Test.5 : I64 = CallByName Test.1 Test.10; ret Test.5; diff --git a/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt b/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt index 255130f13b..4c10690d37 100644 --- a/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt @@ -1,16 +1,16 @@ procedure Num.22 (#Attr.2, #Attr.3): - let Test.8 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.8 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.8; procedure Test.1 (Test.2): - let Test.3 : Builtin(Int(I64)) = StructAtIndex 0 Test.2; - let Test.4 : Builtin(Int(I64)) = StructAtIndex 1 Test.2; - let Test.7 : Builtin(Int(I64)) = CallByName Num.22 Test.3 Test.4; + let Test.3 : I64 = StructAtIndex 0 Test.2; + let Test.4 : I64 = StructAtIndex 1 Test.2; + let Test.7 : I64 = CallByName Num.22 Test.3 Test.4; ret Test.7; procedure Test.0 (): - let Test.9 : Builtin(Int(I64)) = 4i64; - let Test.10 : Builtin(Int(I64)) = 9i64; - let Test.6 : Struct([Builtin(Int(I64)), Builtin(Int(I64))]) = Struct {Test.9, Test.10}; - let Test.5 : Builtin(Int(I64)) = CallByName Test.1 Test.6; + let Test.9 : I64 = 4i64; + let Test.10 : I64 = 9i64; + let Test.6 : {I64, I64} = Struct {Test.9, Test.10}; + let Test.5 : I64 = CallByName Test.1 Test.6; ret Test.5; diff --git a/compiler/test_mono/generated/record_optional_field_let_use_default.txt b/compiler/test_mono/generated/record_optional_field_let_use_default.txt index 978c5b026d..14a00e3638 100644 --- a/compiler/test_mono/generated/record_optional_field_let_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_let_use_default.txt @@ -1,13 +1,13 @@ procedure Num.22 (#Attr.2, #Attr.3): - let Test.8 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.8 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.8; procedure Test.1 (Test.2): - let Test.3 : Builtin(Int(I64)) = 10i64; - let Test.7 : Builtin(Int(I64)) = CallByName Num.22 Test.3 Test.2; + let Test.3 : I64 = 10i64; + let Test.7 : I64 = CallByName Num.22 Test.3 Test.2; ret Test.7; procedure Test.0 (): - let Test.9 : Builtin(Int(I64)) = 9i64; - let Test.5 : Builtin(Int(I64)) = CallByName Test.1 Test.9; + let Test.9 : I64 = 9i64; + let Test.5 : I64 = CallByName Test.1 Test.9; ret Test.5; diff --git a/compiler/test_mono/generated/rigids.txt b/compiler/test_mono/generated/rigids.txt index 5f0124657f..9a030e6c5e 100644 --- a/compiler/test_mono/generated/rigids.txt +++ b/compiler/test_mono/generated/rigids.txt @@ -1,48 +1,48 @@ procedure List.3 (#Attr.2, #Attr.3): - let Test.39 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; - let Test.36 : Builtin(Bool) = lowlevel NumLt #Attr.3 Test.39; + let Test.39 : U64 = lowlevel ListLen #Attr.2; + let Test.36 : Int1 = lowlevel NumLt #Attr.3 Test.39; if Test.36 then - let Test.38 : Builtin(Int(I64)) = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - let Test.37 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = Ok Test.38; + let Test.38 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + let Test.37 : [C {}, C I64] = Ok Test.38; ret Test.37; else - let Test.35 : Struct([]) = Struct {}; - let Test.34 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = Err Test.35; + let Test.35 : {} = Struct {}; + let Test.34 : [C {}, C I64] = Err Test.35; ret Test.34; procedure List.4 (#Attr.2, #Attr.3, #Attr.4): - let Test.19 : Builtin(Int(U64)) = lowlevel ListLen #Attr.2; - let Test.17 : Builtin(Bool) = lowlevel NumLt #Attr.3 Test.19; + let Test.19 : U64 = lowlevel ListLen #Attr.2; + let Test.17 : Int1 = lowlevel NumLt #Attr.3 Test.19; if Test.17 then - let Test.18 : Builtin(List(Builtin(Int(I64)))) = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; + let Test.18 : List I64 = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; ret Test.18; else ret #Attr.2; procedure Test.1 (Test.2, Test.3, Test.4): - let Test.33 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = CallByName List.3 Test.4 Test.3; - let Test.32 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = CallByName List.3 Test.4 Test.2; - let Test.13 : Struct([Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])), Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]]))]) = Struct {Test.32, Test.33}; + let Test.33 : [C {}, C I64] = CallByName List.3 Test.4 Test.3; + let Test.32 : [C {}, C I64] = CallByName List.3 Test.4 Test.2; + let Test.13 : {[C {}, C I64], [C {}, C I64]} = Struct {Test.32, Test.33}; joinpoint Test.29: - let Test.20 : Builtin(List(Builtin(Int(I64)))) = Array []; + let Test.20 : List I64 = Array []; ret Test.20; in - let Test.26 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = StructAtIndex 1 Test.13; - let Test.27 : Builtin(Int(U8)) = 1i64; - let Test.28 : Builtin(Int(U8)) = GetTagId Test.26; - let Test.31 : Builtin(Bool) = lowlevel Eq Test.27 Test.28; + let Test.26 : [C {}, C I64] = StructAtIndex 1 Test.13; + let Test.27 : U8 = 1i64; + let Test.28 : U8 = GetTagId Test.26; + let Test.31 : Int1 = lowlevel Eq Test.27 Test.28; if Test.31 then - let Test.23 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = StructAtIndex 0 Test.13; - let Test.24 : Builtin(Int(U8)) = 1i64; - let Test.25 : Builtin(Int(U8)) = GetTagId Test.23; - let Test.30 : Builtin(Bool) = lowlevel Eq Test.24 Test.25; + let Test.23 : [C {}, C I64] = StructAtIndex 0 Test.13; + let Test.24 : U8 = 1i64; + let Test.25 : U8 = GetTagId Test.23; + let Test.30 : Int1 = lowlevel Eq Test.24 Test.25; if Test.30 then - let Test.22 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = StructAtIndex 0 Test.13; - let Test.6 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) Test.22; - let Test.21 : Union(NonRecursive([[Struct([])], [Builtin(Int(I64))]])) = StructAtIndex 1 Test.13; - let Test.7 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) Test.21; - let Test.15 : Builtin(List(Builtin(Int(I64)))) = CallByName List.4 Test.4 Test.2 Test.7; - let Test.14 : Builtin(List(Builtin(Int(I64)))) = CallByName List.4 Test.15 Test.3 Test.6; + let Test.22 : [C {}, C I64] = StructAtIndex 0 Test.13; + let Test.6 : I64 = UnionAtIndex (Id 1) (Index 0) Test.22; + let Test.21 : [C {}, C I64] = StructAtIndex 1 Test.13; + let Test.7 : I64 = UnionAtIndex (Id 1) (Index 0) Test.21; + let Test.15 : List I64 = CallByName List.4 Test.4 Test.2 Test.7; + let Test.14 : List I64 = CallByName List.4 Test.15 Test.3 Test.6; ret Test.14; else dec Test.4; @@ -52,8 +52,8 @@ procedure Test.1 (Test.2, Test.3, Test.4): jump Test.29; procedure Test.0 (): - let Test.10 : Builtin(Int(U64)) = 0i64; - let Test.11 : Builtin(Int(U64)) = 0i64; - let Test.12 : Builtin(List(Builtin(Int(I64)))) = Array [1i64]; - let Test.9 : Builtin(List(Builtin(Int(I64)))) = CallByName Test.1 Test.10 Test.11 Test.12; + let Test.10 : U64 = 0i64; + let Test.11 : U64 = 0i64; + let Test.12 : List I64 = Array [1i64]; + let Test.9 : List I64 = CallByName Test.1 Test.10 Test.11 Test.12; ret Test.9; diff --git a/compiler/test_mono/generated/simple_if.txt b/compiler/test_mono/generated/simple_if.txt index ba3e24661c..04a1917375 100644 --- a/compiler/test_mono/generated/simple_if.txt +++ b/compiler/test_mono/generated/simple_if.txt @@ -1,8 +1,8 @@ procedure Test.0 (): - let Test.3 : Builtin(Bool) = true; + let Test.3 : Int1 = true; if Test.3 then - let Test.4 : Builtin(Int(I64)) = 1i64; + let Test.4 : I64 = 1i64; ret Test.4; else - let Test.2 : Builtin(Int(I64)) = 2i64; + let Test.2 : I64 = 2i64; ret Test.2; diff --git a/compiler/test_mono/generated/somehow_drops_definitions.txt b/compiler/test_mono/generated/somehow_drops_definitions.txt index 1dd4ed9c40..067e533b9c 100644 --- a/compiler/test_mono/generated/somehow_drops_definitions.txt +++ b/compiler/test_mono/generated/somehow_drops_definitions.txt @@ -1,27 +1,27 @@ procedure Num.22 (#Attr.2, #Attr.3): - let Test.27 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.27 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.27; procedure Num.24 (#Attr.2, #Attr.3): - let Test.22 : Builtin(Int(I64)) = lowlevel NumMul #Attr.2 #Attr.3; + let Test.22 : I64 = lowlevel NumMul #Attr.2 #Attr.3; ret Test.22; procedure Test.1 (): - let Test.28 : Builtin(Int(I64)) = 1i64; + let Test.28 : I64 = 1i64; ret Test.28; procedure Test.2 (): - let Test.23 : Builtin(Int(I64)) = 2i64; + let Test.23 : I64 = 2i64; ret Test.23; procedure Test.3 (Test.6): - let Test.26 : Builtin(Int(I64)) = CallByName Test.1; - let Test.25 : Builtin(Int(I64)) = CallByName Num.22 Test.6 Test.26; + let Test.26 : I64 = CallByName Test.1; + let Test.25 : I64 = CallByName Num.22 Test.6 Test.26; ret Test.25; procedure Test.4 (Test.7): - let Test.21 : Builtin(Int(I64)) = CallByName Test.2; - let Test.20 : Builtin(Int(I64)) = CallByName Num.24 Test.7 Test.21; + let Test.21 : I64 = CallByName Test.2; + let Test.20 : I64 = CallByName Num.24 Test.7 Test.21; ret Test.20; procedure Test.5 (Test.8, Test.9): @@ -30,24 +30,24 @@ procedure Test.5 (Test.8, Test.9): in switch Test.8: case 0: - let Test.16 : Builtin(Int(I64)) = CallByName Test.3 Test.9; + let Test.16 : I64 = CallByName Test.3 Test.9; jump Test.15 Test.16; default: - let Test.17 : Builtin(Int(I64)) = CallByName Test.4 Test.9; + let Test.17 : I64 = CallByName Test.4 Test.9; jump Test.15 Test.17; procedure Test.0 (): joinpoint Test.19 Test.12: - let Test.13 : Builtin(Int(I64)) = 42i64; - let Test.11 : Builtin(Int(I64)) = CallByName Test.5 Test.12 Test.13; + let Test.13 : I64 = 42i64; + let Test.11 : I64 = CallByName Test.5 Test.12 Test.13; ret Test.11; in - let Test.24 : Builtin(Bool) = true; + let Test.24 : Int1 = true; if Test.24 then - let Test.3 : LambdaSet(LambdaSet { set: [( Test.3, []), ( Test.4, [])], representation: Builtin(Bool) }) = false; + let Test.3 : Int1 = false; jump Test.19 Test.3; else - let Test.4 : LambdaSet(LambdaSet { set: [( Test.3, []), ( Test.4, [])], representation: Builtin(Bool) }) = true; + let Test.4 : Int1 = true; jump Test.19 Test.4; diff --git a/compiler/test_mono/generated/specialize_closures.txt b/compiler/test_mono/generated/specialize_closures.txt index 772676526b..8d39d6b960 100644 --- a/compiler/test_mono/generated/specialize_closures.txt +++ b/compiler/test_mono/generated/specialize_closures.txt @@ -1,53 +1,53 @@ procedure Num.22 (#Attr.2, #Attr.3): - let Test.28 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.28 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.28; procedure Num.24 (#Attr.2, #Attr.3): - let Test.25 : Builtin(Int(I64)) = lowlevel NumMul #Attr.2 #Attr.3; + let Test.25 : I64 = lowlevel NumMul #Attr.2 #Attr.3; ret Test.25; procedure Test.1 (Test.2, Test.3): - let Test.17 : Builtin(Int(U8)) = GetTagId Test.2; + let Test.17 : U8 = GetTagId Test.2; joinpoint Test.18 Test.16: ret Test.16; in switch Test.17: case 0: - let Test.19 : Builtin(Int(I64)) = CallByName Test.7 Test.3 Test.2; + let Test.19 : I64 = CallByName Test.7 Test.3 Test.2; jump Test.18 Test.19; default: - let Test.20 : Builtin(Int(I64)) = CallByName Test.8 Test.3 Test.2; + let Test.20 : I64 = CallByName Test.8 Test.3 Test.2; jump Test.18 Test.20; procedure Test.7 (Test.10, #Attr.12): - let Test.4 : Builtin(Int(I64)) = UnionAtIndex (Id 0) (Index 0) #Attr.12; - let Test.27 : Builtin(Int(I64)) = CallByName Num.22 Test.10 Test.4; + let Test.4 : I64 = UnionAtIndex (Id 0) (Index 0) #Attr.12; + let Test.27 : I64 = CallByName Num.22 Test.10 Test.4; ret Test.27; procedure Test.8 (Test.11, #Attr.12): - let Test.6 : Builtin(Bool) = UnionAtIndex (Id 1) (Index 1) #Attr.12; - let Test.5 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) #Attr.12; + let Test.6 : Int1 = UnionAtIndex (Id 1) (Index 1) #Attr.12; + let Test.5 : I64 = UnionAtIndex (Id 1) (Index 0) #Attr.12; if Test.6 then - let Test.24 : Builtin(Int(I64)) = CallByName Num.24 Test.11 Test.5; + let Test.24 : I64 = CallByName Num.24 Test.11 Test.5; ret Test.24; else ret Test.11; procedure Test.0 (): - let Test.6 : Builtin(Bool) = true; - let Test.4 : Builtin(Int(I64)) = 1i64; - let Test.5 : Builtin(Int(I64)) = 2i64; + let Test.6 : Int1 = true; + let Test.4 : I64 = 1i64; + let Test.5 : I64 = 2i64; joinpoint Test.22 Test.14: - let Test.15 : Builtin(Int(I64)) = 42i64; - let Test.13 : Builtin(Int(I64)) = CallByName Test.1 Test.14 Test.15; + let Test.15 : I64 = 42i64; + let Test.13 : I64 = CallByName Test.1 Test.14 Test.15; ret Test.13; in - let Test.26 : Builtin(Bool) = true; + let Test.26 : Int1 = true; if Test.26 then - let Test.7 : LambdaSet(LambdaSet { set: [( Test.7, [Builtin(Int(I64))]), ( Test.8, [Builtin(Int(I64)), Builtin(Bool)])], representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64)), Builtin(Bool)]])) }) = ClosureTag(Test.7) Test.4; + let Test.7 : [C I64, C I64 Int1] = ClosureTag(Test.7) Test.4; jump Test.22 Test.7; else - let Test.8 : LambdaSet(LambdaSet { set: [( Test.7, [Builtin(Int(I64))]), ( Test.8, [Builtin(Int(I64)), Builtin(Bool)])], representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64)), Builtin(Bool)]])) }) = ClosureTag(Test.8) Test.5 Test.6; + let Test.8 : [C I64, C I64 Int1] = ClosureTag(Test.8) Test.5 Test.6; jump Test.22 Test.8; diff --git a/compiler/test_mono/generated/specialize_lowlevel.txt b/compiler/test_mono/generated/specialize_lowlevel.txt index 4dba44f752..9706cdd1bf 100644 --- a/compiler/test_mono/generated/specialize_lowlevel.txt +++ b/compiler/test_mono/generated/specialize_lowlevel.txt @@ -1,44 +1,44 @@ procedure Num.22 (#Attr.2, #Attr.3): - let Test.24 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.24 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.24; procedure Num.24 (#Attr.2, #Attr.3): - let Test.21 : Builtin(Int(I64)) = lowlevel NumMul #Attr.2 #Attr.3; + let Test.21 : I64 = lowlevel NumMul #Attr.2 #Attr.3; ret Test.21; procedure Test.6 (Test.8, #Attr.12): - let Test.4 : Builtin(Int(I64)) = UnionAtIndex (Id 0) (Index 0) #Attr.12; - let Test.23 : Builtin(Int(I64)) = CallByName Num.22 Test.8 Test.4; + let Test.4 : I64 = UnionAtIndex (Id 0) (Index 0) #Attr.12; + let Test.23 : I64 = CallByName Num.22 Test.8 Test.4; ret Test.23; procedure Test.7 (Test.9, #Attr.12): - let Test.5 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) #Attr.12; - let Test.20 : Builtin(Int(I64)) = CallByName Num.24 Test.9 Test.5; + let Test.5 : I64 = UnionAtIndex (Id 1) (Index 0) #Attr.12; + let Test.20 : I64 = CallByName Num.24 Test.9 Test.5; ret Test.20; procedure Test.0 (): - let Test.4 : Builtin(Int(I64)) = 1i64; - let Test.5 : Builtin(Int(I64)) = 2i64; - let Test.12 : Builtin(Int(I64)) = 42i64; + let Test.4 : I64 = 1i64; + let Test.5 : I64 = 2i64; + let Test.12 : I64 = 42i64; joinpoint Test.19 Test.13: - let Test.14 : Builtin(Int(U8)) = GetTagId Test.13; + let Test.14 : U8 = GetTagId Test.13; joinpoint Test.15 Test.11: ret Test.11; in switch Test.14: case 0: - let Test.16 : Builtin(Int(I64)) = CallByName Test.6 Test.12 Test.13; + let Test.16 : I64 = CallByName Test.6 Test.12 Test.13; jump Test.15 Test.16; default: - let Test.17 : Builtin(Int(I64)) = CallByName Test.7 Test.12 Test.13; + let Test.17 : I64 = CallByName Test.7 Test.12 Test.13; jump Test.15 Test.17; in - let Test.22 : Builtin(Bool) = true; + let Test.22 : Int1 = true; if Test.22 then - let Test.6 : LambdaSet(LambdaSet { set: [( Test.6, [Builtin(Int(I64))]), ( Test.7, [Builtin(Int(I64))])], representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64))]])) }) = ClosureTag(Test.6) Test.4; + let Test.6 : [C I64, C I64] = ClosureTag(Test.6) Test.4; jump Test.19 Test.6; else - let Test.7 : LambdaSet(LambdaSet { set: [( Test.6, [Builtin(Int(I64))]), ( Test.7, [Builtin(Int(I64))])], representation: Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64))]])) }) = ClosureTag(Test.7) Test.5; + let Test.7 : [C I64, C I64] = ClosureTag(Test.7) Test.5; jump Test.19 Test.7; diff --git a/compiler/test_mono/generated/when_joinpoint.txt b/compiler/test_mono/generated/when_joinpoint.txt index 3366717fca..c3f2529c59 100644 --- a/compiler/test_mono/generated/when_joinpoint.txt +++ b/compiler/test_mono/generated/when_joinpoint.txt @@ -1,23 +1,23 @@ procedure Test.1 (Test.5): - let Test.2 : Builtin(Int(U8)) = 0u8; + let Test.2 : U8 = 0u8; joinpoint Test.9 Test.3: ret Test.3; in switch Test.2: case 1: - let Test.10 : Builtin(Int(I64)) = 1i64; + let Test.10 : I64 = 1i64; jump Test.9 Test.10; case 2: - let Test.11 : Builtin(Int(I64)) = 2i64; + let Test.11 : I64 = 2i64; jump Test.9 Test.11; default: - let Test.12 : Builtin(Int(I64)) = 3i64; + let Test.12 : I64 = 3i64; jump Test.9 Test.12; procedure Test.0 (): - let Test.7 : Struct([]) = Struct {}; - let Test.6 : Builtin(Int(I64)) = CallByName Test.1 Test.7; + let Test.7 : {} = Struct {}; + let Test.6 : I64 = CallByName Test.1 Test.7; ret Test.6; diff --git a/compiler/test_mono/generated/when_nested_maybe.txt b/compiler/test_mono/generated/when_nested_maybe.txt index facbc46055..6bb9eebf35 100644 --- a/compiler/test_mono/generated/when_nested_maybe.txt +++ b/compiler/test_mono/generated/when_nested_maybe.txt @@ -1,28 +1,28 @@ procedure Num.22 (#Attr.2, #Attr.3): - let Test.8 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.8 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.8; procedure Test.0 (): - let Test.20 : Builtin(Int(I64)) = 41i64; - let Test.19 : Union(NonRecursive([[Builtin(Int(I64))], []])) = Just Test.20; - let Test.2 : Union(NonRecursive([[Union(NonRecursive([[Builtin(Int(I64))], []]))], []])) = Just Test.19; + let Test.20 : I64 = 41i64; + let Test.19 : [C I64, C ] = Just Test.20; + let Test.2 : [C [C I64, C ], C ] = Just Test.19; joinpoint Test.16: - let Test.9 : Builtin(Int(I64)) = 1i64; + let Test.9 : I64 = 1i64; ret Test.9; in - let Test.14 : Builtin(Int(U8)) = 0i64; - let Test.15 : Builtin(Int(U8)) = GetTagId Test.2; - let Test.18 : Builtin(Bool) = lowlevel Eq Test.14 Test.15; + let Test.14 : U8 = 0i64; + let Test.15 : U8 = GetTagId Test.2; + let Test.18 : Int1 = lowlevel Eq Test.14 Test.15; if Test.18 then - let Test.11 : Union(NonRecursive([[Builtin(Int(I64))], []])) = UnionAtIndex (Id 0) (Index 0) Test.2; - let Test.12 : Builtin(Int(U8)) = 0i64; - let Test.13 : Builtin(Int(U8)) = GetTagId Test.11; - let Test.17 : Builtin(Bool) = lowlevel Eq Test.12 Test.13; + let Test.11 : [C I64, C ] = UnionAtIndex (Id 0) (Index 0) Test.2; + let Test.12 : U8 = 0i64; + let Test.13 : U8 = GetTagId Test.11; + let Test.17 : Int1 = lowlevel Eq Test.12 Test.13; if Test.17 then - let Test.10 : Union(NonRecursive([[Builtin(Int(I64))], []])) = UnionAtIndex (Id 0) (Index 0) Test.2; - let Test.5 : Builtin(Int(I64)) = UnionAtIndex (Id 0) (Index 0) Test.10; - let Test.7 : Builtin(Int(I64)) = 1i64; - let Test.6 : Builtin(Int(I64)) = CallByName Num.22 Test.5 Test.7; + let Test.10 : [C I64, C ] = UnionAtIndex (Id 0) (Index 0) Test.2; + let Test.5 : I64 = UnionAtIndex (Id 0) (Index 0) Test.10; + let Test.7 : I64 = 1i64; + let Test.6 : I64 = CallByName Num.22 Test.5 Test.7; ret Test.6; else jump Test.16; diff --git a/compiler/test_mono/generated/when_on_record.txt b/compiler/test_mono/generated/when_on_record.txt index b606928426..b313695135 100644 --- a/compiler/test_mono/generated/when_on_record.txt +++ b/compiler/test_mono/generated/when_on_record.txt @@ -1,9 +1,9 @@ procedure Num.22 (#Attr.2, #Attr.3): - let Test.5 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.5 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.5; procedure Test.0 (): - let Test.6 : Builtin(Int(I64)) = 2i64; - let Test.4 : Builtin(Int(I64)) = 3i64; - let Test.3 : Builtin(Int(I64)) = CallByName Num.22 Test.6 Test.4; + let Test.6 : I64 = 2i64; + let Test.4 : I64 = 3i64; + let Test.3 : I64 = CallByName Num.22 Test.6 Test.4; ret Test.3; diff --git a/compiler/test_mono/generated/when_on_result.txt b/compiler/test_mono/generated/when_on_result.txt index 84144767fc..6fbe2699d4 100644 --- a/compiler/test_mono/generated/when_on_result.txt +++ b/compiler/test_mono/generated/when_on_result.txt @@ -1,27 +1,27 @@ procedure Test.1 (Test.5): - let Test.19 : Builtin(Int(I64)) = 2i64; - let Test.2 : Union(NonRecursive([[Builtin(Int(I64))], [Builtin(Int(I64))]])) = Ok Test.19; + let Test.19 : I64 = 2i64; + let Test.2 : [C I64, C I64] = Ok Test.19; joinpoint Test.9 Test.3: ret Test.3; in - let Test.16 : Builtin(Int(U8)) = 1i64; - let Test.17 : Builtin(Int(U8)) = GetTagId Test.2; - let Test.18 : Builtin(Bool) = lowlevel Eq Test.16 Test.17; + let Test.16 : U8 = 1i64; + let Test.17 : U8 = GetTagId Test.2; + let Test.18 : Int1 = lowlevel Eq Test.16 Test.17; if Test.18 then - let Test.13 : Builtin(Int(I64)) = UnionAtIndex (Id 1) (Index 0) Test.2; - let Test.14 : Builtin(Int(I64)) = 3i64; - let Test.15 : Builtin(Bool) = lowlevel Eq Test.14 Test.13; + let Test.13 : I64 = UnionAtIndex (Id 1) (Index 0) Test.2; + let Test.14 : I64 = 3i64; + let Test.15 : Int1 = lowlevel Eq Test.14 Test.13; if Test.15 then - let Test.10 : Builtin(Int(I64)) = 1i64; + let Test.10 : I64 = 1i64; jump Test.9 Test.10; else - let Test.11 : Builtin(Int(I64)) = 2i64; + let Test.11 : I64 = 2i64; jump Test.9 Test.11; else - let Test.12 : Builtin(Int(I64)) = 3i64; + let Test.12 : I64 = 3i64; jump Test.9 Test.12; procedure Test.0 (): - let Test.7 : Struct([]) = Struct {}; - let Test.6 : Builtin(Int(I64)) = CallByName Test.1 Test.7; + let Test.7 : {} = Struct {}; + let Test.6 : I64 = CallByName Test.1 Test.7; ret Test.6; diff --git a/compiler/test_mono/generated/when_on_two_values.txt b/compiler/test_mono/generated/when_on_two_values.txt index 4af8530b49..2085f6c7df 100644 --- a/compiler/test_mono/generated/when_on_two_values.txt +++ b/compiler/test_mono/generated/when_on_two_values.txt @@ -1,26 +1,26 @@ procedure Num.22 (#Attr.2, #Attr.3): - let Test.7 : Builtin(Int(I64)) = lowlevel NumAdd #Attr.2 #Attr.3; + let Test.7 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.7; procedure Test.0 (): - let Test.16 : Builtin(Int(I64)) = 3i64; - let Test.15 : Builtin(Int(I64)) = 2i64; - let Test.4 : Struct([Builtin(Int(I64)), Builtin(Int(I64))]) = Struct {Test.15, Test.16}; + let Test.16 : I64 = 3i64; + let Test.15 : I64 = 2i64; + let Test.4 : {I64, I64} = Struct {Test.15, Test.16}; joinpoint Test.12: - let Test.2 : Builtin(Int(I64)) = StructAtIndex 0 Test.4; - let Test.3 : Builtin(Int(I64)) = StructAtIndex 1 Test.4; - let Test.6 : Builtin(Int(I64)) = CallByName Num.22 Test.2 Test.3; + let Test.2 : I64 = StructAtIndex 0 Test.4; + let Test.3 : I64 = StructAtIndex 1 Test.4; + let Test.6 : I64 = CallByName Num.22 Test.2 Test.3; ret Test.6; in - let Test.10 : Builtin(Int(I64)) = StructAtIndex 1 Test.4; - let Test.11 : Builtin(Int(I64)) = 3i64; - let Test.14 : Builtin(Bool) = lowlevel Eq Test.11 Test.10; + let Test.10 : I64 = StructAtIndex 1 Test.4; + let Test.11 : I64 = 3i64; + let Test.14 : Int1 = lowlevel Eq Test.11 Test.10; if Test.14 then - let Test.8 : Builtin(Int(I64)) = StructAtIndex 0 Test.4; - let Test.9 : Builtin(Int(I64)) = 4i64; - let Test.13 : Builtin(Bool) = lowlevel Eq Test.9 Test.8; + let Test.8 : I64 = StructAtIndex 0 Test.4; + let Test.9 : I64 = 4i64; + let Test.13 : Int1 = lowlevel Eq Test.9 Test.8; if Test.13 then - let Test.5 : Builtin(Int(I64)) = 9i64; + let Test.5 : I64 = 9i64; ret Test.5; else jump Test.12; diff --git a/compiler/test_mono/src/tests.rs b/compiler/test_mono/src/tests.rs index 6903714eaa..28bb791955 100644 --- a/compiler/test_mono/src/tests.rs +++ b/compiler/test_mono/src/tests.rs @@ -1249,6 +1249,24 @@ fn aliased_polymorphic_closure() { ) } +#[mono_test] +fn issue_2535_polymorphic_fields_referenced_in_list() { + indoc!( + r#" + app "test" provides [ nums ] to "./platform" + + alpha = { a: 1, b: 2 } + + nums : List U8 + nums = + [ + alpha.a, + alpha.b, + ] + "# + ) +} + // #[ignore] // #[mono_test] // fn static_str_closure() { diff --git a/compiler/types/src/solved_types.rs b/compiler/types/src/solved_types.rs index fb5f20e7f0..f6286a50a3 100644 --- a/compiler/types/src/solved_types.rs +++ b/compiler/types/src/solved_types.rs @@ -1,5 +1,5 @@ use crate::subs::{FlatType, GetSubsSlice, Subs, VarId, VarStore, Variable}; -use crate::types::{Problem, RecordField, Type}; +use crate::types::{AliasKind, Problem, RecordField, Type}; use roc_collections::all::{ImMap, MutSet, SendMap}; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; @@ -559,6 +559,8 @@ pub fn to_type( type_arguments: type_variables, lambda_set_variables, actual: Box::new(actual), + // TODO(opaques): revisit when opaques are in the solver + kind: AliasKind::Structural, } } HostExposedAlias { diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 3227c58796..c7868ee67f 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -1862,6 +1862,13 @@ impl UnionTags { slice.length == 1 } + pub fn is_newtype_wrapper_of_global_tag(&self, subs: &Subs) -> bool { + self.is_newtype_wrapper(subs) && { + let tags = &subs.tag_names[self.tag_names().indices()]; + matches!(tags[0], TagName::Global(_)) + } + } + pub fn from_tag_name_index(index: SubsIndex) -> Self { Self::from_slices( SubsSlice::new(index.index, 1), diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index af99cb5303..186aac2a93 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -185,6 +185,7 @@ pub enum Type { type_arguments: Vec<(Lowercase, Type)>, lambda_set_variables: Vec, actual: Box, + kind: AliasKind, }, HostExposedAlias { name: Symbol, @@ -853,6 +854,7 @@ impl Type { type_arguments: named_args, lambda_set_variables, actual: Box::new(actual), + kind: alias.kind, }; } else { // one of the special-cased Apply types. @@ -1302,6 +1304,7 @@ pub enum Category { Num, List, Str, + Character, // records Record, @@ -1323,6 +1326,20 @@ pub enum PatternCategory { Num, Int, Float, + Character, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum AliasKind { + /// A structural alias is something like + /// List a : [ Nil, Cons a (List a) ] + /// It is typed structurally, so that a `List U8` is always equal to a `[ Nil ]_`, for example. + Structural, + /// An opaque alias corresponds to an opaque type from the language syntax, like + /// Age := U32 + /// It is type nominally, so that `Age` is never equal to `U8` - the only way to unwrap the + /// structural type inside `Age` is to unwrap the opaque, so `Age` = `@Age U8`. + Opaque, } #[derive(Clone, Debug, PartialEq)] @@ -1337,6 +1354,8 @@ pub struct Alias { pub recursion_variables: MutSet, pub typ: Type, + + pub kind: AliasKind, } impl Alias { diff --git a/editor/src/editor/render_ast.rs b/editor/src/editor/render_ast.rs index 8dcda658e7..d66457a79e 100644 --- a/editor/src/editor/render_ast.rs +++ b/editor/src/editor/render_ast.rs @@ -3,9 +3,15 @@ use crate::editor::{ed_error::EdResult, theme::EdTheme, util::map_get}; use crate::graphics::primitives::rect::Rect; use crate::graphics::primitives::text as gr_text; use cgmath::Vector2; -use roc_code_markup::markup::nodes::{MarkupNode, BLANK_PLACEHOLDER}; -use roc_code_markup::slow_pool::{MarkNodeId, SlowPool}; -use roc_code_markup::syntax_highlight::HighlightStyle; +use roc_code_markup::{ + markup::{ + attribute::Attribute, + nodes::{MarkupNode, BLANK_PLACEHOLDER}, + }, + slow_pool::{MarkNodeId, SlowPool}, + syntax_highlight::HighlightStyle, + underline_style::UnderlineStyle, +}; use winit::dpi::PhysicalSize; use crate::{editor::config::Config, graphics::colors}; @@ -94,6 +100,9 @@ fn markup_to_wgpu_helper<'a>( txt_row_col: &mut (usize, usize), mark_node_pool: &'a SlowPool, ) -> EdResult<()> { + let char_width = code_style.glyph_dim_rect.width; + let char_height = code_style.glyph_dim_rect.height; + match markup_node { MarkupNode::Nested { ast_node_id: _, @@ -124,7 +133,7 @@ fn markup_to_wgpu_helper<'a>( content, ast_node_id: _, syn_high_style, - attributes: _, + attributes, parent_id_opt: _, newlines_at_end, } => { @@ -132,10 +141,38 @@ fn markup_to_wgpu_helper<'a>( let full_content = markup_node.get_full_content().replace("\n", "\\n"); // any \n left here should be escaped so that it can be shown as \n - let glyph_text = glyph_brush::OwnedText::new(full_content) + let glyph_text = glyph_brush::OwnedText::new(&full_content) .with_color(colors::to_slice(*highlight_color)) .with_scale(code_style.font_size); + for attribute in &attributes.all { + match attribute { + Attribute::Underline { underline_spec: _ } => { + // TODO use underline_spec + let top_left_coords = ( + code_style.txt_coords.x + (txt_row_col.1 as f32) * char_width, + code_style.txt_coords.y + + (txt_row_col.0 as f32) * char_height + + 1.0 * char_height, + ); + + let underline_rect = Rect { + top_left_coords: top_left_coords.into(), + width: char_width * (full_content.len() as f32), + height: 5.0, + color: *code_style + .ed_theme + .underline_color_map + .get(&UnderlineStyle::Error) + .unwrap(), + }; + + rects.push(underline_rect); + } + rest => todo!("handle Attribute: {:?}", rest), + } + } + txt_row_col.1 += content.len(); for _ in 0..*newlines_at_end { @@ -160,9 +197,6 @@ fn markup_to_wgpu_helper<'a>( let highlight_color = map_get(&code_style.ed_theme.syntax_high_map, &HighlightStyle::Blank)?; - let char_width = code_style.glyph_dim_rect.width; - let char_height = code_style.glyph_dim_rect.height; - let blank_rect = Rect { top_left_coords: ( code_style.txt_coords.x + (txt_row_col.1 as f32) * char_width, diff --git a/editor/src/editor/theme.rs b/editor/src/editor/theme.rs index 30e45a47a9..d3482f2cbe 100644 --- a/editor/src/editor/theme.rs +++ b/editor/src/editor/theme.rs @@ -1,5 +1,8 @@ use gr_colors::{from_hsb, RgbaTup}; -use roc_code_markup::syntax_highlight::{default_highlight_map, HighlightStyle}; +use roc_code_markup::{ + syntax_highlight::{default_highlight_map, HighlightStyle}, + underline_style::{default_underline_color_map, UnderlineStyle}, +}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -12,6 +15,7 @@ pub struct EdTheme { pub subtle_text: RgbaTup, pub syntax_high_map: HashMap, pub ui_theme: UITheme, + pub underline_color_map: HashMap, } impl Default for EdTheme { @@ -21,6 +25,7 @@ impl Default for EdTheme { subtle_text: from_hsb(240, 5, 60), syntax_high_map: default_highlight_map(), ui_theme: UITheme::default(), + underline_color_map: default_underline_color_map(), } } } diff --git a/editor/src/graphics/primitives/rect.rs b/editor/src/graphics/primitives/rect.rs index 7560c0d10a..ddf26f1199 100644 --- a/editor/src/graphics/primitives/rect.rs +++ b/editor/src/graphics/primitives/rect.rs @@ -1,9 +1,19 @@ use cgmath::Vector2; +/// These fields are ordered this way because in Roc, the corresponding stuct is: +/// +/// { top : F32, left : F32, width : F32, height : F32 } +/// +/// alphabetically, that's { height, left, top, width } - which works out to the same as: +/// +/// height: f32, pos: Vector2, width: f32 +/// +/// ...because Vector2 is a repr(C) struct of { x: f32, y: f32 } #[derive(Debug, Copy, Clone)] +#[repr(C)] pub struct Rect { + pub color: (f32, f32, f32, f32), + pub height: f32, pub top_left_coords: Vector2, pub width: f32, - pub height: f32, - pub color: (f32, f32, f32, f32), } diff --git a/error_macros/src/lib.rs b/error_macros/src/lib.rs index 39aeca5aa0..2cd1eeb732 100644 --- a/error_macros/src/lib.rs +++ b/error_macros/src/lib.rs @@ -65,3 +65,47 @@ macro_rules! assert_sizeof_all { static_assertions::assert_eq_size!($t, [u8; $expected_size]); }; } + +// LARGE SCALE PROJECTS +// +// This section is for "todo!"-style macros enabled in sections where large-scale changes to the +// language are in progress. + +#[macro_export] +macro_rules! _incomplete_project { + ($project_name:literal, $tracking_issue_no:literal) => { + panic!( + "[{}] not yet implemented. Tracking issue: https://github.com/rtfeldman/roc/issues/{}", + $project_name, $tracking_issue_no, + ) + }; + ($project_name:literal, $tracking_issue_no:literal, $($arg:tt)+) => { + panic!( + "[{}] not yet implemented. Tracking issue: https://github.com/rtfeldman/roc/issues/{}.\nAdditional information: {}", + $project_name, $tracking_issue_no, + format_args!($($arg)+), + ) + }; +} + +#[macro_export] +macro_rules! todo_abilities { + () => { + $crate::_incomplete_project!("Abilities", 2463) + }; + ($($arg:tt)+) => { + $crate::_incomplete_project!("Abilities", 2463, $($arg)+) + }; +} + +#[macro_export] +macro_rules! todo_opaques { + () => { + $crate::_incomplete_project!("Abilities (opaques)", 2463) + }; + ($($arg:tt)+) => { + $crate::_incomplete_project!("Abilities (opaques)", 2463, $($arg)+) + }; +} + +// END LARGE SCALE PROJECTS diff --git a/examples/cli/platform/src/lib.rs b/examples/cli/platform/src/lib.rs index d584592716..721b9d7025 100644 --- a/examples/cli/platform/src/lib.rs +++ b/examples/cli/platform/src/lib.rs @@ -115,12 +115,11 @@ pub extern "C" fn roc_fx_getLine() -> RocStr { let stdin = io::stdin(); let line1 = stdin.lock().lines().next().unwrap().unwrap(); - RocStr::from_slice(line1.as_bytes()) + RocStr::from(line1.as_str()) } #[no_mangle] pub extern "C" fn roc_fx_putLine(line: ManuallyDrop) { - let bytes = line.as_slice(); - let string = unsafe { std::str::from_utf8_unchecked(bytes) }; + let string = line.as_str(); println!("{}", string); } diff --git a/examples/false-interpreter/platform/src/lib.rs b/examples/false-interpreter/platform/src/lib.rs index 4f97dfb72d..4ff7d6dfae 100644 --- a/examples/false-interpreter/platform/src/lib.rs +++ b/examples/false-interpreter/platform/src/lib.rs @@ -75,7 +75,7 @@ pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut #[no_mangle] pub extern "C" fn rust_main() -> i32 { let arg = env::args().skip(1).next().unwrap(); - let arg = RocStr::from_slice(arg.as_bytes()); + let arg = RocStr::from(arg.as_str()); let size = unsafe { roc_main_size() } as usize; let layout = Layout::array::(size).unwrap(); @@ -121,7 +121,7 @@ pub extern "C" fn roc_fx_getLine() -> RocStr { let stdin = io::stdin(); let line1 = stdin.lock().lines().next().unwrap().unwrap(); - RocStr::from_slice(line1.as_bytes()) + RocStr::from(line1.as_str()) } #[no_mangle] @@ -142,16 +142,14 @@ pub extern "C" fn roc_fx_getChar() -> u8 { #[no_mangle] pub extern "C" fn roc_fx_putLine(line: ManuallyDrop) { - let bytes = line.as_slice(); - let string = unsafe { std::str::from_utf8_unchecked(bytes) }; + let string = line.as_str(); println!("{}", string); std::io::stdout().lock().flush(); } #[no_mangle] pub extern "C" fn roc_fx_putRaw(line: ManuallyDrop) { - let bytes = line.as_slice(); - let string = unsafe { std::str::from_utf8_unchecked(bytes) }; + let string = line.as_str(); print!("{}", string); std::io::stdout().lock().flush(); } @@ -164,7 +162,7 @@ pub extern "C" fn roc_fx_getFileLine(br_ptr: *mut BufReader) -> RocStr { br.read_line(&mut line1) .expect("Failed to read line from file"); - RocStr::from_slice(line1.as_bytes()) + RocStr::from(line1.as_str()) } #[no_mangle] diff --git a/examples/gui/.gitignore b/examples/gui/.gitignore new file mode 100644 index 0000000000..8f5562e399 --- /dev/null +++ b/examples/gui/.gitignore @@ -0,0 +1 @@ +hello-gui diff --git a/examples/gui/Hello.roc b/examples/gui/Hello.roc new file mode 100644 index 0000000000..c15f5bd21a --- /dev/null +++ b/examples/gui/Hello.roc @@ -0,0 +1,23 @@ +app "hello-gui" + packages { pf: "platform" } + imports []# [ pf.Action.{ Action }, pf.Elem.{ button, text, row, col } ] + provides [ render ] to pf + +render = + div0 = \numerator, denominator -> (numerator / denominator) |> Result.withDefault 0 + + rgba = \r, g, b, a -> { r: div0 r 255, g: div0 g 255, b: div0 b 255, a } + + styles = { bgColor: rgba 100 50 50 1, borderColor: rgba 10 20 30 1, borderWidth: 10, textColor: rgba 220 220 250 1 } + + Col + [ + Row + [ + Button (Text "Corner ") styles, + Button (Text "Top Mid ") { styles & bgColor: rgba 100 100 50 1 }, + Button (Text "Top Right ") { styles & bgColor: rgba 50 50 150 1 }, + ], + Button (Text "Mid Left ") { styles & bgColor: rgba 150 100 100 1 }, + Button (Text "Bottom Left") { styles & bgColor: rgba 150 50 50 1 }, + ] diff --git a/examples/gui/platform/Action.roc b/examples/gui/platform/Action.roc new file mode 100644 index 0000000000..ad15ee728b --- /dev/null +++ b/examples/gui/platform/Action.roc @@ -0,0 +1,20 @@ +interface Action + exposes [ Action, none, update, map ] + imports [] + +Action state : [ None, Update state ] + +none : Action * +none = None + +update : state -> Action state +update = Update + +map : Action a, (a -> b) -> Action b +map = \action, transform -> + when action is + None -> + None + + Update state -> + Update (transform state) diff --git a/examples/gui/platform/Cargo.lock b/examples/gui/platform/Cargo.lock new file mode 100644 index 0000000000..9ce6ea7d8d --- /dev/null +++ b/examples/gui/platform/Cargo.lock @@ -0,0 +1,2826 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ab_glyph" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61caed9aec6daeee1ea38ccf5fb225e4f96c1eeead1b4a5c267324a63cf02326" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13739d7177fbd22bb0ed28badfff9f372f8bef46c863db4e1c6248f6b223b6e" + +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "alsa" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75c4da790adcb2ce5e758c064b4f3ec17a30349f9961d3e5e6c9688b052a9e18" +dependencies = [ + "alsa-sys", + "bitflags", + "libc", + "nix 0.20.0", +] + +[[package]] +name = "alsa-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "approx" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f2a05fd1bd10b2527e20a2cd32d8873d115b8b39fe219ee25f42a8aca6ba278" +dependencies = [ + "num-traits", +] + +[[package]] +name = "approx" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "072df7202e63b127ab55acfe16ce97013d5b97bf160489336d3f1840fd78e99e" +dependencies = [ + "num-traits", +] + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "ash" +version = "0.35.1+1.2.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7fd04def1c9101b5fb488c131022d2d6f87753ef4b1b11b279e2af404fae6b9" +dependencies = [ + "libloading", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "backtrace" +version = "0.3.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "321629d8ba6513061f26707241fa9bc89524ff1cd7a915a97ef0c62c666ce1b6" +dependencies = [ + "addr2line", + "cc", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bindgen" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da379dbebc0b76ef63ca68d8fc6e71c0f13e59432e0987e508c1820e6ab5239" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", +] + +[[package]] +name = "bit-set" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + +[[package]] +name = "bumpalo" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "bytemuck" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439989e6b8c38d1b6570a384ef1e49c8848128f5a97f3914baef02920842712f" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e215f8c2f9f79cb53c8335e687ffd07d5bfcb6fe5fc80723762d0be46e7cc54" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "calloop" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf2eec61efe56aa1e813f5126959296933cf0700030e4314786c48779a66ab82" +dependencies = [ + "log", + "nix 0.22.0", +] + +[[package]] +name = "cc" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cexpr" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" +dependencies = [ + "nom 5.1.2", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "cgmath" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a98d30140e3296250832bbaaff83b27dcd6fa3cc70fb6f1f3e5c9c0023b5317" +dependencies = [ + "approx 0.4.0", + "num-traits", +] + +[[package]] +name = "clang-sys" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa66045b9cb23c2e9c1520732030608b02ee07e5cfaa5a521ec15ded7fa24c90" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "claxon" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bfbf56724aa9eca8afa4fcfadeb479e722935bb2a0900c2d37e0cc477af0688" + +[[package]] +name = "clipboard-win" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fdf5e01086b6be750428ba4a40619f847eb2e95756eee84b18e06e5f0b50342" +dependencies = [ + "lazy-bytes-cast", + "winapi", +] + +[[package]] +name = "cocoa" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832" +dependencies = [ + "bitflags", + "block", + "cocoa-foundation", + "core-foundation 0.9.2", + "core-graphics 0.22.3", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" +dependencies = [ + "bitflags", + "block", + "core-foundation 0.9.2", + "core-graphics-types", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "colored" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +dependencies = [ + "atty", + "lazy_static", + "winapi", +] + +[[package]] +name = "combine" +version = "4.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b727aacc797f9fc28e355d21f34709ac4fc9adecfe470ad07b8f4464f53062" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "confy" +version = "0.4.0" +source = "git+https://github.com/rust-cli/confy#664992aecd97b4af0eda8d9d2825885662e1c6b4" +dependencies = [ + "directories-next", + "serde", + "serde_yaml", +] + +[[package]] +name = "copyless" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536" + +[[package]] +name = "copypasta" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4423d79fed83ebd9ab81ec21fa97144300a961782158287dc9bf7eddac37ff0b" +dependencies = [ + "clipboard-win", + "objc", + "objc-foundation", + "objc_id", + "smithay-clipboard", + "x11-clipboard", +] + +[[package]] +name = "core-foundation" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" +dependencies = [ + "core-foundation-sys 0.7.0", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3" +dependencies = [ + "core-foundation-sys 0.8.3", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "core-graphics" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3889374e6ea6ab25dba90bb5d96202f61108058361f6dc72e8b03e6f8bbe923" +dependencies = [ + "bitflags", + "core-foundation 0.7.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags", + "core-foundation 0.9.2", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +dependencies = [ + "bitflags", + "core-foundation 0.9.2", + "foreign-types", + "libc", +] + +[[package]] +name = "core-video-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ecad23610ad9757664d644e369246edde1803fcb43ed72876565098a5d3828" +dependencies = [ + "cfg-if 0.1.10", + "core-foundation-sys 0.7.0", + "core-graphics 0.19.2", + "libc", + "objc", +] + +[[package]] +name = "coreaudio-rs" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11894b20ebfe1ff903cbdc52259693389eea03b94918a2def2c30c3bf227ad88" +dependencies = [ + "bitflags", + "coreaudio-sys", +] + +[[package]] +name = "coreaudio-sys" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b7e3347be6a09b46aba228d6608386739fb70beff4f61e07422da87b0bb31fa" +dependencies = [ + "bindgen", +] + +[[package]] +name = "cpal" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98f45f0a21f617cd2c788889ef710b63f075c949259593ea09c826f1e47a2418" +dependencies = [ + "alsa", + "core-foundation-sys 0.8.3", + "coreaudio-rs", + "jni", + "js-sys", + "lazy_static", + "libc", + "mach", + "ndk 0.3.0", + "ndk-glue 0.3.0", + "nix 0.20.0", + "oboe", + "parking_lot", + "stdweb", + "thiserror", + "web-sys", + "winapi", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97242a70df9b89a65d0b6df3c4bf5b9ce03c5b7309019777fbde37e7537f8762" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcae03edb34f947e64acdb1c33ec169824e20657e9ecb61cef6c8c74dcb8120" +dependencies = [ + "cfg-if 1.0.0", + "lazy_static", +] + +[[package]] +name = "cty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" + +[[package]] +name = "d3d12" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2daefd788d1e96e0a9d66dee4b828b883509bc3ea9ce30665f04c3246372690c" +dependencies = [ + "bitflags", + "libloading", + "winapi", +] + +[[package]] +name = "darling" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" +dependencies = [ + "darling_core 0.10.2", + "darling_macro 0.10.2", +] + +[[package]] +name = "darling" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d720b8683f8dd83c65155f0530560cba68cd2bf395f6513a483caee57ff7f4" +dependencies = [ + "darling_core 0.13.1", + "darling_macro 0.13.1", +] + +[[package]] +name = "darling_core" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.9.3", + "syn", +] + +[[package]] +name = "darling_core" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a340f241d2ceed1deb47ae36c4144b2707ec7dd0b649f894cb39bb595986324" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" +dependencies = [ + "darling_core 0.10.2", + "quote", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c41b3b7352feb3211a0d743dc5700a4e3b60f51bd2b368892d1e0f9a95f44b" +dependencies = [ + "darling_core 0.13.1", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if 1.0.0", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dlib" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +dependencies = [ + "libloading", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "env_logger" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "find-crate" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2" +dependencies = [ + "toml", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "fs_extra" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" + +[[package]] +name = "futures" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28560757fe2bb34e79f907794bb6b22ae8b0e5c669b638a1132f2592b19035b4" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7" + +[[package]] +name = "futures-executor" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29d6d2ff5bb10fb95c85b8ce46538a2e5f5e7fdc755623a7d4529ab8a4ed9d2a" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2" + +[[package]] +name = "futures-macro" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbd947adfffb0efc70599b3ddcf7b5597bb5fa9e245eb99f62b3a5f7bb8bd3c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3055baccb68d74ff6480350f8d6eb8fcfa3aa11bdc1a1ae3afdd0514617d508" + +[[package]] +name = "futures-task" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72" + +[[package]] +name = "futures-util" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + +[[package]] +name = "getrandom" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "glow" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bd5877156a19b8ac83a29b2306fe20537429d318f3ff0a1a2119f8d9c61919" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glyph_brush" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21932fbf719272848eec4583740d978203c6e7da4c4e203358f5b95946c97409" +dependencies = [ + "glyph_brush_draw_cache", + "glyph_brush_layout", + "log", + "ordered-float", + "rustc-hash", + "twox-hash", +] + +[[package]] +name = "glyph_brush_draw_cache" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6010675390f6889e09a21e2c8b575b3ee25667ea8237a8d59423f73cb8c28610" +dependencies = [ + "ab_glyph", + "crossbeam-channel", + "crossbeam-deque", + "linked-hash-map", + "rayon", + "rustc-hash", +] + +[[package]] +name = "glyph_brush_layout" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc32c2334f00ca5ac3695c5009ae35da21da8c62d255b5b96d56e2597a637a38" +dependencies = [ + "ab_glyph", + "approx 0.5.0", + "xi-unicode", +] + +[[package]] +name = "gpu-alloc" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc59e5f710e310e76e6707f86c561dd646f69a8876da9131703b2f717de818d" +dependencies = [ + "bitflags", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54804d0d6bc9d7f26db4eaec1ad10def69b599315f487d32c334a80d1efe67a5" +dependencies = [ + "bitflags", +] + +[[package]] +name = "gpu-descriptor" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a538f217be4d405ff4719a283ca68323cc2384003eca5baaa87501e821c81dda" +dependencies = [ + "bitflags", + "gpu-descriptor-types", + "hashbrown", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "363e3677e55ad168fef68cf9de3a4a310b53124c5e784c53a1d70e92d23f2126" +dependencies = [ + "bitflags", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "host" +version = "0.1.0" +dependencies = [ + "arrayvec", + "bytemuck", + "cgmath", + "colored", + "confy", + "copypasta", + "env_logger", + "fs_extra", + "futures", + "glyph_brush", + "libc", + "log", + "nonempty", + "page_size", + "palette", + "pest", + "pest_derive", + "roc_std", + "rodio", + "serde", + "snafu", + "threadpool", + "wgpu", + "wgpu_glyph", + "winit", +] + +[[package]] +name = "hound" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a164bb2ceaeff4f42542bdb847c41517c78a60f5649671b2a07312b6e117549" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "inplace_it" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90953f308a79fe6d62a4643e51f848fbfddcd05975a38e69fdf4ab86a7baf7ca" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "jni" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "khronos-egl" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3" +dependencies = [ + "libc", + "libloading", + "pkg-config", +] + +[[package]] +name = "lazy-bytes-cast" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10257499f089cd156ad82d0a9cd57d9501fa2c989068992a97eb3c27836f206b" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "lewton" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777b48df9aaab155475a83a7df3070395ea1ac6902f5cd062b8f2b028075c030" +dependencies = [ + "byteorder", + "ogg", + "tinyvec", +] + +[[package]] +name = "libc" +version = "0.2.113" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef78b64d87775463c549fbd80e19249ef436ea3bf1de2a1eb7e717ec7fab1e9" + +[[package]] +name = "libloading" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +dependencies = [ + "cfg-if 1.0.0", + "winapi", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" + +[[package]] +name = "lock_api" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "memmap2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b6c2ebff6180198788f5db08d7ce3bc1d0b617176678831a7510825973e357" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metal" +version = "0.23.1" +source = "git+https://github.com/gfx-rs/metal-rs?rev=44af5cc#44af5cca340617d42d701264f9bf71d1f3e68096" +dependencies = [ + "bitflags", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "minimp3" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985438f75febf74c392071a975a29641b420dd84431135a6e6db721de4b74372" +dependencies = [ + "minimp3-sys", + "slice-deque", + "thiserror", +] + +[[package]] +name = "minimp3-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e21c73734c69dc95696c9ed8926a2b393171d98b3f5f5935686a26a487ab9b90" +dependencies = [ + "cc", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "mio" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi", +] + +[[package]] +name = "naga" +version = "0.8.0" +source = "git+https://github.com/gfx-rs/naga?rev=8e2e39e#8e2e39e4d8fa5bbb657c3b170b4f6607d703e284" +dependencies = [ + "bit-set", + "bitflags", + "codespan-reporting", + "hexf-parse", + "indexmap", + "log", + "num-traits", + "rustc-hash", + "spirv", + "thiserror", +] + +[[package]] +name = "ndk" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8794322172319b972f528bf90c6b467be0079f1fa82780ffb431088e741a73ab" +dependencies = [ + "jni-sys", + "ndk-sys 0.2.2", + "num_enum", + "thiserror", +] + +[[package]] +name = "ndk" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d868f654c72e75f8687572699cdabe755f03effbb62542768e995d5b8d699d" +dependencies = [ + "bitflags", + "jni-sys", + "ndk-sys 0.2.2", + "num_enum", + "thiserror", +] + +[[package]] +name = "ndk" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" +dependencies = [ + "bitflags", + "jni-sys", + "ndk-sys 0.3.0", + "num_enum", + "thiserror", +] + +[[package]] +name = "ndk-glue" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5caf0c24d51ac1c905c27d4eda4fa0635bbe0de596b8f79235e0b17a4d29385" +dependencies = [ + "lazy_static", + "libc", + "log", + "ndk 0.3.0", + "ndk-macro 0.2.0", + "ndk-sys 0.2.2", +] + +[[package]] +name = "ndk-glue" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc291b8de2095cba8dab7cf381bf582ff4c17a09acf854c32e46545b08085d28" +dependencies = [ + "lazy_static", + "libc", + "log", + "ndk 0.5.0", + "ndk-macro 0.3.0", + "ndk-sys 0.2.2", +] + +[[package]] +name = "ndk-glue" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04c0d14b0858eb9962a5dac30b809b19f19da7e4547d64af2b0bb051d2e55d79" +dependencies = [ + "lazy_static", + "libc", + "log", + "ndk 0.6.0", + "ndk-macro 0.3.0", + "ndk-sys 0.3.0", +] + +[[package]] +name = "ndk-macro" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d1c6307dc424d0f65b9b06e94f88248e6305726b14729fd67a5e47b2dc481d" +dependencies = [ + "darling 0.10.2", + "proc-macro-crate 0.1.5", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ndk-macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c" +dependencies = [ + "darling 0.13.1", + "proc-macro-crate 1.1.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ndk-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121" + +[[package]] +name = "ndk-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "nix" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", +] + +[[package]] +name = "nix" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1e25ee6b412c2a1e3fcb6a4499a5c1bfe7f43e014bdce9a6b6666e5aa2d187" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", + "memoffset", +] + +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "memchr", + "version_check", +] + +[[package]] +name = "nom" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" +dependencies = [ + "memchr", + "minimal-lexical", + "version_check", +] + +[[package]] +name = "nonempty" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" + +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "720d3ea1055e4e4574c0c0b0f8c3fd4f24c4cdaf465948206dea090b57b526ad" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d992b768490d7fe0d8586d9b5745f6c49f557da6d81dc982b1d167ad4edbb21" +dependencies = [ + "proc-macro-crate 1.1.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "object" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" +dependencies = [ + "memchr", +] + +[[package]] +name = "oboe" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2463c8f2e19b4e0d0710a21f8e4011501ff28db1c95d7a5482a553b2100502d2" +dependencies = [ + "jni", + "ndk 0.6.0", + "ndk-glue 0.6.0", + "num-derive", + "num-traits", + "oboe-sys", +] + +[[package]] +name = "oboe-sys" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3370abb7372ed744232c12954d920d1a40f1c4686de9e79e800021ef492294bd" +dependencies = [ + "cc", +] + +[[package]] +name = "ogg" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6951b4e8bf21c8193da321bcce9c9dd2e13c858fe078bf9054a288b419ae5d6e" +dependencies = [ + "byteorder", +] + +[[package]] +name = "once_cell" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "ordered-float" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" +dependencies = [ + "num-traits", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef05f2882a8b3e7acc10c153ade2631f7bfc8ce00d2bf3fb8f4e9d2ae6ea5c3" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "page_size" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eebde548fbbf1ea81a99b128872779c437752fb99f217c45245e1a61dcd9edcd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "palette" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9735f7e1e51a3f740bacd5dc2724b61a7806f23597a8736e679f38ee3435d18" +dependencies = [ + "approx 0.5.0", + "num-traits", + "palette_derive", + "phf", +] + +[[package]] +name = "palette_derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7799c3053ea8a6d8a1193c7ba42f534e7863cf52e378a7f90406f4a645d33bad" +dependencies = [ + "find-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +dependencies = [ + "maplit", + "pest", + "sha-1", +] + +[[package]] +name = "phf" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ac8b67553a7ca9457ce0e526948cad581819238f4a9d1ea74545851fa24f37" +dependencies = [ + "phf_macros", + "phf_shared", + "proc-macro-hack", +] + +[[package]] +name = "phf_generator" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43f3220d96e0080cc9ea234978ccd80d904eafb17be31bb0f76daaea6493082" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b706f5936eb50ed880ae3009395b43ed19db5bff2ebd459c95e7bf013a89ab86" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68318426de33640f02be62b4ae8eb1261be2efbc337b60c54d845bf4484e0d9" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro-crate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83" +dependencies = [ + "thiserror", + "toml", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "profiling" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9145ac0af1d93c638c98c40cf7d25665f427b2a44ad0a99b1dccf3e2f25bb987" + +[[package]] +name = "quick-xml" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8533f14c8382aaad0d592c812ac3b826162128b65662331e1127b45c3d18536b" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core", +] + +[[package]] +name = "range-alloc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e935c45e09cc6dcf00d2f0b2d630a58f4095320223d47fc68918722f0538b6" + +[[package]] +name = "raw-window-handle" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba75eee94a9d5273a68c9e1e105d9cffe1ef700532325788389e5a83e2522b7" +dependencies = [ + "cty", +] + +[[package]] +name = "rayon" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom", + "redox_syscall", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "renderdoc-sys" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157" + +[[package]] +name = "roc_std" +version = "0.1.0" + +[[package]] +name = "rodio" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d98f5e557b61525057e2bc142c8cd7f0e70d75dc32852309bec440e6e046bf9" +dependencies = [ + "claxon", + "cpal", + "hound", + "lewton", + "minimp3", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "ryu" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.135" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cf9235533494ea2ddcdb794665461814781c53f19d87b76e571a1c35acbad2b" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.135" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dcde03d87d4c973c04be249e7d8f0b35db1c848c487bd43032808e59dd8328d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_yaml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a521f2940385c165a24ee286aa8599633d162077a54bdcae2a6fd5a7bfa7a0" +dependencies = [ + "indexmap", + "ryu", + "serde", + "yaml-rust", +] + +[[package]] +name = "sha-1" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +dependencies = [ + "block-buffer", + "digest", + "fake-simd", + "opaque-debug", +] + +[[package]] +name = "shlex" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" + +[[package]] +name = "siphasher" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a86232ab60fa71287d7f2ddae4a7073f6b7aac33631c3015abb556f08c6d0a3e" + +[[package]] +name = "slab" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" + +[[package]] +name = "slice-deque" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31ef6ee280cdefba6d2d0b4b78a84a1c1a3f3a4cec98c2d4231c8bc225de0f25" +dependencies = [ + "libc", + "mach", + "winapi", +] + +[[package]] +name = "slotmap" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + +[[package]] +name = "smithay-client-toolkit" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1325f292209cee78d5035530932422a30aa4c8fda1a16593ac083c1de211e68a" +dependencies = [ + "bitflags", + "calloop", + "dlib", + "lazy_static", + "log", + "memmap2", + "nix 0.22.0", + "pkg-config", + "wayland-client", + "wayland-cursor", + "wayland-protocols", +] + +[[package]] +name = "smithay-clipboard" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610b551bd25378bfd2b8e7a0fcbd83d427e8f2f6a40c47ae0f70688e9949dd55" +dependencies = [ + "smithay-client-toolkit", + "wayland-client", +] + +[[package]] +name = "snafu" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab12d3c261b2308b0d80c26fffb58d17eba81a4be97890101f416b478c79ca7" +dependencies = [ + "backtrace", + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "spirv" +version = "0.2.0+1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830" +dependencies = [ + "bitflags", + "num-traits", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stdweb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef5430c8e36b713e13b48a9f709cc21e046723fe44ce34587b73a830203b533e" + +[[package]] +name = "strsim" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "tinyvec" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + +[[package]] +name = "ttf-parser" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ccbe8381883510b6a2d8f1e32905bddd178c11caef8083086d0c0c9ab0ac281" + +[[package]] +name = "twox-hash" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee73e6e4924fe940354b8d4d98cad5231175d615cd855b758adc658c0aac6a0" +dependencies = [ + "cfg-if 1.0.0", + "rand", + "static_assertions", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "wasm-bindgen" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" + +[[package]] +name = "wayland-client" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91223460e73257f697d9e23d401279123d36039a3f7a449e983f123292d4458f" +dependencies = [ + "bitflags", + "downcast-rs", + "libc", + "nix 0.22.0", + "scoped-tls", + "wayland-commons", + "wayland-scanner", + "wayland-sys", +] + +[[package]] +name = "wayland-commons" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f6e5e340d7c13490eca867898c4cec5af56c27a5ffe5c80c6fc4708e22d33e" +dependencies = [ + "nix 0.22.0", + "once_cell", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-cursor" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c52758f13d5e7861fc83d942d3d99bf270c83269575e52ac29e5b73cb956a6bd" +dependencies = [ + "nix 0.22.0", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60147ae23303402e41fe034f74fb2c35ad0780ee88a1c40ac09a3be1e7465741" +dependencies = [ + "bitflags", + "wayland-client", + "wayland-commons", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39a1ed3143f7a143187156a2ab52742e89dac33245ba505c17224df48939f9e0" +dependencies = [ + "proc-macro2", + "quote", + "xml-rs", +] + +[[package]] +name = "wayland-sys" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9341df79a8975679188e37dab3889bfa57c44ac2cb6da166f519a81cbe452d4" +dependencies = [ + "dlib", + "lazy_static", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wgpu" +version = "0.12.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=0545e36#0545e36aa82709cca78c14eb3813f10eea7a9275" +dependencies = [ + "arrayvec", + "js-sys", + "log", + "naga", + "parking_lot", + "raw-window-handle", + "smallvec", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "0.12.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=0545e36#0545e36aa82709cca78c14eb3813f10eea7a9275" +dependencies = [ + "arrayvec", + "bitflags", + "cfg_aliases", + "codespan-reporting", + "copyless", + "fxhash", + "log", + "naga", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "thiserror", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "0.12.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=0545e36#0545e36aa82709cca78c14eb3813f10eea7a9275" +dependencies = [ + "arrayvec", + "ash", + "bit-set", + "bitflags", + "block", + "core-graphics-types", + "d3d12", + "foreign-types", + "fxhash", + "glow", + "gpu-alloc", + "gpu-descriptor", + "inplace_it", + "js-sys", + "khronos-egl", + "libloading", + "log", + "metal", + "naga", + "objc", + "parking_lot", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "thiserror", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "winapi", +] + +[[package]] +name = "wgpu-types" +version = "0.12.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=0545e36#0545e36aa82709cca78c14eb3813f10eea7a9275" +dependencies = [ + "bitflags", +] + +[[package]] +name = "wgpu_glyph" +version = "0.16.0" +source = "git+https://github.com/Anton-4/wgpu_glyph?rev=257d109#257d1098cbafa3c8a0a2465937b06fc730fc6ffb" +dependencies = [ + "bytemuck", + "glyph_brush", + "log", + "wgpu", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winit" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b43cc931d58b99461188607efd7acb2a093e65fc621f54cad78517a6063e73a" +dependencies = [ + "bitflags", + "cocoa", + "core-foundation 0.9.2", + "core-graphics 0.22.3", + "core-video-sys", + "dispatch", + "instant", + "lazy_static", + "libc", + "log", + "mio", + "ndk 0.5.0", + "ndk-glue 0.5.0", + "ndk-sys 0.2.2", + "objc", + "parking_lot", + "percent-encoding", + "raw-window-handle", + "smithay-client-toolkit", + "wasm-bindgen", + "wayland-client", + "wayland-protocols", + "web-sys", + "winapi", + "x11-dl", +] + +[[package]] +name = "x11-clipboard" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473068b7b80ac86a18328824f1054e5e007898c47b5bbc281bd7abe32bc3653c" +dependencies = [ + "xcb", +] + +[[package]] +name = "x11-dl" +version = "2.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea26926b4ce81a6f5d9d0f3a0bc401e5a37c6ae14a1bfaa8ff6099ca80038c59" +dependencies = [ + "lazy_static", + "libc", + "pkg-config", +] + +[[package]] +name = "xcb" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771e2b996df720cd1c6dd9ff90f62d91698fd3610cc078388d0564bdd6622a9c" +dependencies = [ + "libc", + "log", + "quick-xml", +] + +[[package]] +name = "xcursor" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" +dependencies = [ + "nom 7.1.0", +] + +[[package]] +name = "xi-unicode" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a" + +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] diff --git a/examples/gui/platform/Cargo.toml b/examples/gui/platform/Cargo.toml new file mode 100644 index 0000000000..b82712e562 --- /dev/null +++ b/examples/gui/platform/Cargo.toml @@ -0,0 +1,76 @@ +[package] +name = "host" +version = "0.1.0" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +edition = "2018" +links = "app" + +# Needed to be able to run on non-Windows systems for some reason. Without this, cargo panics with: +# +# error: DX12 API enabled on non-Windows OS. If your project is not using resolver="2" in Cargo.toml, it should. +resolver = "2" + +[lib] +name = "host" +path = "src/lib.rs" +crate-type = ["staticlib", "rlib"] + +[[bin]] +name = "host" +path = "src/main.rs" + +[dependencies] +roc_std = { path = "../../../roc_std" } +libc = "0.2" +arrayvec = "0.7.2" +page_size = "0.4.2" +# when changing winit version, check if copypasta can be updated simultaneously so they use the same versions for their dependencies. This will save build time. +winit = "0.26.1" +wgpu = { git = "https://github.com/gfx-rs/wgpu", rev = "0545e36" } +wgpu_glyph = { git = "https://github.com/Anton-4/wgpu_glyph", rev = "257d109" } +glyph_brush = "0.7.2" +log = "0.4.14" +env_logger = "0.9.0" +futures = "0.3.17" +cgmath = "0.18.0" +snafu = { version = "0.6.10", features = ["backtraces"] } +colored = "2.0.0" +pest = "2.1.3" +pest_derive = "2.1.0" +copypasta = "0.7.1" +palette = "0.6.0" +confy = { git = 'https://github.com/rust-cli/confy', features = [ + "yaml_conf" +], default-features = false } +serde = { version = "1.0.130", features = ["derive"] } +nonempty = "0.7.0" +fs_extra = "1.2.0" +rodio = { version = "0.14.0", optional = true } # to play sounds +threadpool = "1.8.1" + +[package.metadata.cargo-udeps.ignore] +# confy is currently unused but should not be removed +normal = ["confy"] +#development = [] +#build = [] + +[features] +default = [] +with_sound = ["rodio"] + +[dependencies.bytemuck] +version = "1.7.2" +features = ["derive"] + +[workspace] + +# Optimizations based on https://deterministic.space/high-performance-rust.html +[profile.release] +lto = "thin" +codegen-units = 1 + +# debug = true # enable when profiling +[profile.bench] +lto = "thin" +codegen-units = 1 diff --git a/examples/gui/platform/Elem.roc b/examples/gui/platform/Elem.roc new file mode 100644 index 0000000000..519d007b99 --- /dev/null +++ b/examples/gui/platform/Elem.roc @@ -0,0 +1,193 @@ +interface Elem + exposes [ Elem, PressEvent, row, col, text, button, none, translate, list ] + imports [ Action.{ Action } ] + +Elem state : + # PERFORMANCE NOTE: + # If there are 8 or fewer tags here, then on a 64-bit system, the tag can be stored + # in the pointer - for massive memory savings. Try extremely hard to always limit the number + # of tags in this union to 8 or fewer! + [ + Button (ButtonConfig state) (Elem state), + Text Str, + Col (List (Elem state)), + Row (List (Elem state)), + Lazy (Result { state, elem : Elem state } [ NotCached ] -> { state, elem : Elem state }), + # TODO FIXME: using this definition of Lazy causes a stack overflow in the compiler! + # Lazy (Result (Cached state) [ NotCached ] -> Cached state), + None, + ] + +## Used internally in the type definition of Lazy +Cached state : { state, elem : Elem state } + +ButtonConfig state : { onPress : state, PressEvent -> Action state } + +PressEvent : { button : [ Touch, Mouse [ Left, Right, Middle ] ] } + +text : Str -> Elem * +text = \str -> + Text str + +button : { onPress : state, PressEvent -> Action state }, Elem state -> Elem state +button = \config, label -> + Button config label + +row : List (Elem state) -> Elem state +row = \children -> + Row children + +col : List (Elem state) -> Elem state +col = \children -> + Col children + +lazy : state, (state -> Elem state) -> Elem state +lazy = \state, render -> + # This function gets called by the host during rendering. It will + # receive the cached state and element (wrapped in Ok) if we've + # ever rendered this before, and Err otherwise. + Lazy + \result -> + when result is + Ok cached if cached.state == state -> + # If we have a cached value, and the new state is the + # same as the cached one, then we can return exactly + # what we had cached. + cached + + _ -> + # Either the state changed or else we didn't have a + # cached value to use. Either way, we need to render + # with the new state and store that for future use. + { state, elem: render state } + +none : Elem * +none = None# I've often wanted this in elm/html. Usually end up resorting to (Html.text "") - this seems nicer. +## Change an element's state type. +## +## TODO: indent the following once https://github.com/rtfeldman/roc/issues/2585 is fixed. +## State : { photo : Photo } +## +## render : State -> Elem State +## render = \state -> +## child : Elem State +## child = +## Photo.render state.photo +## |> Elem.translate .photo &photo +## +## col {} [ child, otherElems ] +## +translate = \child, toChild, toParent -> + when child is + Text str -> + Text str + + Col elems -> + Col (List.map elems \elem -> translate elem toChild toParent) + + Row elems -> + Row (List.map elems \elem -> translate elem toChild toParent) + + Button config label -> + onPress = \parentState, event -> + toChild parentState + |> config.onPress event + |> Action.map \c -> toParent parentState c + + Button { onPress } (translate label toChild toParent) + + Lazy renderChild -> + Lazy + \parentState -> + { elem, state } = renderChild (toChild parentState) + + { + elem: translate toChild toParent newChild, + state: toParent parentState state, + } + + None -> + None + +## Render a list of elements, using [Elem.translate] on each of them. +## +## Convenient when you have a [List] in your state and want to make +## a [List] of child elements out of it. +## +## TODO: indent the following once https://github.com/rtfeldman/roc/issues/2585 is fixed. +## State : { photos : List Photo } +## +## render : State -> Elem State +## render = \state -> +## children : List (Elem State) +## children = +## Elem.list Photo.render state .photos &photos +## +## col {} children +## TODO: format as multiline type annotation once https://github.com/rtfeldman/roc/issues/2586 is fixed +list : (child -> Elem child), parent, (parent -> List child), (parent, List child -> parent) -> List (Elem parent) +list = \renderChild, parent, toChildren, toParent -> + List.mapWithIndex + (toChildren parent) + \index, child -> + toChild = \par -> List.get (toChildren par) index + + newChild = translateOrDrop + child + toChild + \par, ch -> + toChildren par + |> List.set ch index + |> toParent + + renderChild newChild + +## Internal helper function for Elem.list +## +## Tries to translate a child to a parent, but +## if the child has been removed from the parent, +## drops it. +## +## TODO: format as multiline type annotation once https://github.com/rtfeldman/roc/issues/2586 is fixed +translateOrDrop : Elem child, (parent -> Result child *), (parent, child -> parent) -> Elem parent +translateOrDrop = \child, toChild, toParent -> + when child is + Text str -> + Text str + + Col elems -> + Col (List.map elems \elem -> translateOrDrop elem toChild toParent) + + Row elems -> + Row (List.map elems \elem -> translateOrDrop elem toChild toParent) + + Button config label -> + onPress = \parentState, event -> + when toChild parentState is + Ok newChild -> + newChild + |> config.onPress event + |> Action.map \c -> toParent parentState c + + Err _ -> + # The child was removed from the list before this onPress handler resolved. + # (For example, by a previous event handler that fired simultaneously.) + Action.none + + Button { onPress } (translateOrDrop label toChild toParent) + + Lazy childState renderChild -> + Lazy + (toParent childState) + \parentState -> + when toChild parentState is + Ok newChild -> + renderChild newChild + |> translateOrDrop toChild toParent + + Err _ -> + None + + # I don't think this should ever happen in practice. + None -> + None diff --git a/examples/gui/platform/Package-Config.roc b/examples/gui/platform/Package-Config.roc new file mode 100644 index 0000000000..855c45e23b --- /dev/null +++ b/examples/gui/platform/Package-Config.roc @@ -0,0 +1,15 @@ +platform "examples/hello-world" + requires {} { render : Elem } + exposes [] + packages {} + imports [] + provides [ renderForHost ] + +Rgba : { r : F32, g : F32, b : F32, a : F32 } + +ButtonStyles : { bgColor : Rgba, borderColor : Rgba, borderWidth : F32, textColor : Rgba } + +Elem : [ Button Elem ButtonStyles, Col (List Elem), Row (List Elem), Text Str ] + +renderForHost : Elem +renderForHost = render diff --git a/examples/gui/platform/build.rs b/examples/gui/platform/build.rs new file mode 100644 index 0000000000..73159e387c --- /dev/null +++ b/examples/gui/platform/build.rs @@ -0,0 +1,4 @@ +fn main() { + println!("cargo:rustc-link-lib=dylib=app"); + println!("cargo:rustc-link-search=."); +} diff --git a/examples/gui/platform/host.c b/examples/gui/platform/host.c new file mode 100644 index 0000000000..b9214bcf33 --- /dev/null +++ b/examples/gui/platform/host.c @@ -0,0 +1,3 @@ +extern int rust_main(); + +int main() { return rust_main(); } \ No newline at end of file diff --git a/examples/gui/platform/src/graphics/colors.rs b/examples/gui/platform/src/graphics/colors.rs new file mode 100644 index 0000000000..e0932a1d69 --- /dev/null +++ b/examples/gui/platform/src/graphics/colors.rs @@ -0,0 +1,50 @@ +use cgmath::Vector4; +use palette::{FromColor, Hsv, Srgb}; + +/// This order is optimized for what Roc will send +#[repr(C)] +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct Rgba { + a: f32, + b: f32, + g: f32, + r: f32, +} + +impl Rgba { + pub const WHITE: Self = Self::new(1.0, 1.0, 1.0, 1.0); + + pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self { + Self { r, g, b, a } + } + + pub const fn to_array(self) -> [f32; 4] { + [self.r, self.b, self.g, self.a] + } + + pub fn from_hsb(hue: usize, saturation: usize, brightness: usize) -> Self { + Self::from_hsba(hue, saturation, brightness, 1.0) + } + + pub fn from_hsba(hue: usize, saturation: usize, brightness: usize, alpha: f32) -> Self { + let rgb = Srgb::from_color(Hsv::new( + hue as f32, + (saturation as f32) / 100.0, + (brightness as f32) / 100.0, + )); + + Self::new(rgb.red, rgb.green, rgb.blue, alpha) + } +} + +impl From for [f32; 4] { + fn from(rgba: Rgba) -> Self { + rgba.to_array() + } +} + +impl From for Vector4 { + fn from(rgba: Rgba) -> Self { + Vector4::new(rgba.r, rgba.b, rgba.g, rgba.a) + } +} diff --git a/examples/gui/platform/src/graphics/lowlevel/buffer.rs b/examples/gui/platform/src/graphics/lowlevel/buffer.rs new file mode 100644 index 0000000000..dbe0270e57 --- /dev/null +++ b/examples/gui/platform/src/graphics/lowlevel/buffer.rs @@ -0,0 +1,96 @@ +// Contains parts of https://github.com/sotrh/learn-wgpu +// by Benjamin Hansen - license information can be found in the LEGAL_DETAILS +// file in the root directory of this distribution. +// +// Thank you, Benjamin! + +// Contains parts of https://github.com/iced-rs/iced/blob/adce9e04213803bd775538efddf6e7908d1c605e/wgpu/src/shader/quad.wgsl +// By Héctor Ramón, Iced contributors Licensed under the MIT license. +// The license is included in the LEGAL_DETAILS file in the root directory of this distribution. + +// Thank you Héctor Ramón and Iced contributors! + +use std::mem; + +use super::{quad::Quad, vertex::Vertex}; +use crate::graphics::primitives::rect::RectElt; +use wgpu::util::DeviceExt; + +pub struct RectBuffers { + pub vertex_buffer: wgpu::Buffer, + pub index_buffer: wgpu::Buffer, + pub quad_buffer: wgpu::Buffer, +} + +pub const QUAD_INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3]; + +const QUAD_VERTS: [Vertex; 4] = [ + Vertex { + _position: [0.0, 0.0], + }, + Vertex { + _position: [1.0, 0.0], + }, + Vertex { + _position: [1.0, 1.0], + }, + Vertex { + _position: [0.0, 1.0], + }, +]; + +pub const MAX_QUADS: usize = 100_000; + +pub fn create_rect_buffers( + gpu_device: &wgpu::Device, + cmd_encoder: &mut wgpu::CommandEncoder, + rects: &[RectElt], +) -> RectBuffers { + let vertex_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(&QUAD_VERTS), + usage: wgpu::BufferUsages::VERTEX, + }); + + let index_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(&QUAD_INDICES), + usage: wgpu::BufferUsages::INDEX, + }); + + let quad_buffer = gpu_device.create_buffer(&wgpu::BufferDescriptor { + label: Some("iced_wgpu::quad instance buffer"), + size: mem::size_of::() as u64 * MAX_QUADS as u64, + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + let quads: Vec = rects.iter().map(|rect| to_quad(rect)).collect(); + + let buffer_size = (quads.len() as u64) * Quad::SIZE; + + let staging_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(&quads), + usage: wgpu::BufferUsages::COPY_SRC, + }); + + cmd_encoder.copy_buffer_to_buffer(&staging_buffer, 0, &quad_buffer, 0, buffer_size); + + RectBuffers { + vertex_buffer, + index_buffer, + quad_buffer, + } +} + +pub fn to_quad(rect_elt: &RectElt) -> Quad { + Quad { + pos: rect_elt.rect.pos.into(), + width: rect_elt.rect.width, + height: rect_elt.rect.height, + color: (rect_elt.color.to_array()), + border_color: rect_elt.border_color.into(), + border_width: rect_elt.border_width, + } +} diff --git a/examples/gui/platform/src/graphics/lowlevel/mod.rs b/examples/gui/platform/src/graphics/lowlevel/mod.rs new file mode 100644 index 0000000000..0add45385d --- /dev/null +++ b/examples/gui/platform/src/graphics/lowlevel/mod.rs @@ -0,0 +1,5 @@ +pub mod buffer; +pub mod ortho; +pub mod pipelines; +pub mod vertex; +pub mod quad; diff --git a/examples/gui/platform/src/graphics/lowlevel/ortho.rs b/examples/gui/platform/src/graphics/lowlevel/ortho.rs new file mode 100644 index 0000000000..2f4577871a --- /dev/null +++ b/examples/gui/platform/src/graphics/lowlevel/ortho.rs @@ -0,0 +1,118 @@ +use cgmath::{Matrix4, Ortho}; +use wgpu::util::DeviceExt; +use wgpu::{ + BindGroup, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, Buffer, + ShaderStages, +}; + +// orthographic projection is used to transform pixel coords to the coordinate system used by wgpu + +#[repr(C)] +#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +struct Uniforms { + // We can't use cgmath with bytemuck directly so we'll have + // to convert the Matrix4 into a 4x4 f32 array + ortho: [[f32; 4]; 4], +} + +impl Uniforms { + fn new(w: u32, h: u32) -> Self { + let ortho: Matrix4 = Ortho:: { + left: 0.0, + right: w as f32, + bottom: h as f32, + top: 0.0, + near: -1.0, + far: 1.0, + } + .into(); + Self { + ortho: ortho.into(), + } + } +} + +// update orthographic buffer according to new window size +pub fn update_ortho_buffer( + inner_width: u32, + inner_height: u32, + gpu_device: &wgpu::Device, + ortho_buffer: &Buffer, + cmd_queue: &wgpu::Queue, +) { + let new_uniforms = Uniforms::new(inner_width, inner_height); + + let new_ortho_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Ortho uniform buffer"), + contents: bytemuck::cast_slice(&[new_uniforms]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_SRC, + }); + + // get a command encoder for the current frame + let mut encoder = gpu_device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Resize"), + }); + + // overwrite the new buffer over the old one + encoder.copy_buffer_to_buffer( + &new_ortho_buffer, + 0, + ortho_buffer, + 0, + (std::mem::size_of::() * vec![new_uniforms].as_slice().len()) + as wgpu::BufferAddress, + ); + + cmd_queue.submit(Some(encoder.finish())); +} + +#[derive(Debug)] +pub struct OrthoResources { + pub buffer: Buffer, + pub bind_group_layout: BindGroupLayout, + pub bind_group: BindGroup, +} + +pub fn init_ortho( + inner_width: u32, + inner_height: u32, + gpu_device: &wgpu::Device, +) -> OrthoResources { + let uniforms = Uniforms::new(inner_width, inner_height); + + let ortho_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Ortho uniform buffer"), + contents: bytemuck::cast_slice(&[uniforms]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + + // bind groups consist of extra resources that are provided to the shaders + let ortho_bind_group_layout = gpu_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::VERTEX, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + label: Some("Ortho bind group layout"), + }); + + let ortho_bind_group = gpu_device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &ortho_bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: ortho_buffer.as_entire_binding(), + }], + label: Some("Ortho bind group"), + }); + + OrthoResources { + buffer: ortho_buffer, + bind_group_layout: ortho_bind_group_layout, + bind_group: ortho_bind_group, + } +} diff --git a/examples/gui/platform/src/graphics/lowlevel/pipelines.rs b/examples/gui/platform/src/graphics/lowlevel/pipelines.rs new file mode 100644 index 0000000000..a0dc7908ec --- /dev/null +++ b/examples/gui/platform/src/graphics/lowlevel/pipelines.rs @@ -0,0 +1,72 @@ +use super::ortho::{init_ortho, OrthoResources}; +use super::quad::Quad; +use super::vertex::Vertex; +use std::borrow::Cow; + +pub struct RectResources { + pub pipeline: wgpu::RenderPipeline, + pub ortho: OrthoResources, +} + +pub fn make_rect_pipeline( + gpu_device: &wgpu::Device, + surface_config: &wgpu::SurfaceConfiguration, +) -> RectResources { + let ortho = init_ortho(surface_config.width, surface_config.height, gpu_device); + + let pipeline_layout = gpu_device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[&ortho.bind_group_layout], + push_constant_ranges: &[], + }); + let pipeline = create_render_pipeline( + gpu_device, + &pipeline_layout, + surface_config.format, + &wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../shaders/quad.wgsl"))), + }, + ); + + RectResources { pipeline, ortho } +} + +pub fn create_render_pipeline( + device: &wgpu::Device, + layout: &wgpu::PipelineLayout, + color_format: wgpu::TextureFormat, + shader_module_desc: &wgpu::ShaderModuleDescriptor, +) -> wgpu::RenderPipeline { + let shader = device.create_shader_module(shader_module_desc); + + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Render pipeline"), + layout: Some(layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[Vertex::DESC, Quad::DESC], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[wgpu::ColorTargetState { + format: color_format, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + operation: wgpu::BlendOperation::Add, + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + }, + alpha: wgpu::BlendComponent::REPLACE, + }), + write_mask: wgpu::ColorWrites::ALL, + }], + }), + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + }) +} diff --git a/examples/gui/platform/src/graphics/lowlevel/quad.rs b/examples/gui/platform/src/graphics/lowlevel/quad.rs new file mode 100644 index 0000000000..9c1fd85ae6 --- /dev/null +++ b/examples/gui/platform/src/graphics/lowlevel/quad.rs @@ -0,0 +1,31 @@ + + +/// A polygon with 4 corners +#[derive(Copy, Clone)] +pub struct Quad { + pub pos: [f32; 2], + pub width: f32, + pub height: f32, + pub color: [f32; 4], + pub border_color: [f32; 4], + pub border_width: f32, +} + +unsafe impl bytemuck::Pod for Quad {} +unsafe impl bytemuck::Zeroable for Quad {} + +impl Quad { + pub const SIZE: wgpu::BufferAddress = std::mem::size_of::() as wgpu::BufferAddress; + pub const DESC: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout { + array_stride: Self::SIZE, + step_mode: wgpu::VertexStepMode::Instance, + attributes: &wgpu::vertex_attr_array!( + 1 => Float32x2, + 2 => Float32, + 3 => Float32, + 4 => Float32x4, + 5 => Float32x4, + 6 => Float32, + ), + }; +} \ No newline at end of file diff --git a/examples/gui/platform/src/graphics/lowlevel/vertex.rs b/examples/gui/platform/src/graphics/lowlevel/vertex.rs new file mode 100644 index 0000000000..aa45bb7fb7 --- /dev/null +++ b/examples/gui/platform/src/graphics/lowlevel/vertex.rs @@ -0,0 +1,35 @@ +// Inspired by https://github.com/sotrh/learn-wgpu +// by Benjamin Hansen - license information can be found in the LEGAL_DETAILS +// file in the root directory of this distribution. +// +// Thank you, Benjamin! + +// Inspired by https://github.com/iced-rs/iced/blob/adce9e04213803bd775538efddf6e7908d1c605e/wgpu/src/shader/quad.wgsl +// By Héctor Ramón, Iced contributors Licensed under the MIT license. +// The license is included in the LEGAL_DETAILS file in the root directory of this distribution. + +// Thank you Héctor Ramón and Iced contributors! +use bytemuck::{Pod, Zeroable}; + + +#[repr(C)] +#[derive(Copy, Clone, Zeroable, Pod)] +pub struct Vertex { + pub _position: [f32; 2], +} + +impl Vertex { + pub const SIZE: wgpu::BufferAddress = std::mem::size_of::() as wgpu::BufferAddress; + pub const DESC: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout { + array_stride: Self::SIZE, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[ + // position + wgpu::VertexAttribute { + offset: 0, + shader_location: 0, + format: wgpu::VertexFormat::Float32x2, + }, + ], + }; +} diff --git a/examples/gui/platform/src/graphics/mod.rs b/examples/gui/platform/src/graphics/mod.rs new file mode 100644 index 0000000000..0eb7fcd6da --- /dev/null +++ b/examples/gui/platform/src/graphics/mod.rs @@ -0,0 +1,4 @@ +pub mod colors; +pub mod lowlevel; +pub mod primitives; +pub mod style; diff --git a/examples/gui/platform/src/graphics/primitives/mod.rs b/examples/gui/platform/src/graphics/primitives/mod.rs new file mode 100644 index 0000000000..a9adb18862 --- /dev/null +++ b/examples/gui/platform/src/graphics/primitives/mod.rs @@ -0,0 +1,2 @@ +pub mod rect; +pub mod text; diff --git a/examples/gui/platform/src/graphics/primitives/rect.rs b/examples/gui/platform/src/graphics/primitives/rect.rs new file mode 100644 index 0000000000..8183fc6f7a --- /dev/null +++ b/examples/gui/platform/src/graphics/primitives/rect.rs @@ -0,0 +1,27 @@ +use crate::graphics::colors::Rgba; +use cgmath::Vector2; + +#[derive(Debug, Copy, Clone)] +pub struct RectElt { + pub rect: Rect, + pub color: Rgba, + pub border_width: f32, + pub border_color: Rgba, +} + +/// These fields are ordered this way because in Roc, the corresponding stuct is: +/// +/// { top : F32, left : F32, width : F32, height : F32 } +/// +/// alphabetically, that's { height, left, top, width } - which works out to the same as: +/// +/// struct Rect { height: f32, pos: Vector2, width: f32 } +/// +/// ...because Vector2 is a repr(C) struct of { x: f32, y: f32 } +#[derive(Debug, Copy, Clone)] +#[repr(C)] +pub struct Rect { + pub height: f32, + pub pos: Vector2, + pub width: f32, +} diff --git a/examples/gui/platform/src/graphics/primitives/text.rs b/examples/gui/platform/src/graphics/primitives/text.rs new file mode 100644 index 0000000000..f002d506e7 --- /dev/null +++ b/examples/gui/platform/src/graphics/primitives/text.rs @@ -0,0 +1,137 @@ +// Adapted from https://github.com/sotrh/learn-wgpu +// by Benjamin Hansen - license information can be found in the COPYRIGHT +// file in the root directory of this distribution. +// +// Thank you, Benjamin! + +use crate::graphics::colors::Rgba; +use crate::graphics::style::DEFAULT_FONT_SIZE; +use ab_glyph::{FontArc, Glyph, InvalidFont}; +use cgmath::{Vector2, Vector4}; +use glyph_brush::OwnedSection; +use wgpu_glyph::{ab_glyph, GlyphBrush, GlyphBrushBuilder, Section}; + +use super::rect::Rect; + +#[derive(Debug)] +pub struct Text<'a> { + pub position: Vector2, + pub area_bounds: Vector2, + pub color: Rgba, + pub text: &'a str, + pub size: f32, + pub visible: bool, + pub centered: bool, +} + +impl<'a> Default for Text<'a> { + fn default() -> Self { + Self { + position: (0.0, 0.0).into(), + area_bounds: (std::f32::INFINITY, std::f32::INFINITY).into(), + color: Rgba::WHITE, + text: "", + size: DEFAULT_FONT_SIZE, + visible: true, + centered: false, + } + } +} + +pub fn layout_from_text(text: &Text) -> wgpu_glyph::Layout { + wgpu_glyph::Layout::default().h_align(if text.centered { + wgpu_glyph::HorizontalAlign::Center + } else { + wgpu_glyph::HorizontalAlign::Left + }) +} + +fn section_from_text<'a>( + text: &'a Text, + layout: wgpu_glyph::Layout, +) -> wgpu_glyph::Section<'a> { + Section { + screen_position: text.position.into(), + bounds: text.area_bounds.into(), + layout, + ..Section::default() + } + .add_text( + wgpu_glyph::Text::new(text.text) + .with_color(text.color) + .with_scale(text.size), + ) +} + +pub fn owned_section_from_text(text: &Text) -> OwnedSection { + let layout = layout_from_text(text); + + OwnedSection { + screen_position: text.position.into(), + bounds: text.area_bounds.into(), + layout, + ..OwnedSection::default() + } + .add_text( + glyph_brush::OwnedText::new(text.text) + .with_color(Vector4::from(text.color)) + .with_scale(text.size), + ) +} + +pub fn owned_section_from_glyph_texts( + text: Vec, + screen_position: (f32, f32), + area_bounds: (f32, f32), + layout: wgpu_glyph::Layout, +) -> glyph_brush::OwnedSection { + glyph_brush::OwnedSection { + screen_position, + bounds: area_bounds, + layout, + text, + } +} + +pub fn queue_text_draw(text: &Text, glyph_brush: &mut GlyphBrush<()>) { + let layout = layout_from_text(text); + + let section = section_from_text(text, layout); + + glyph_brush.queue(section.clone()); +} + +fn glyph_to_rect(glyph: &wgpu_glyph::SectionGlyph) -> Rect { + let position = glyph.glyph.position; + let px_scale = glyph.glyph.scale; + let width = glyph_width(&glyph.glyph); + let height = px_scale.y; + let top_y = glyph_top_y(&glyph.glyph); + + Rect { + pos: [position.x, top_y].into(), + width, + height, + } +} + +pub fn glyph_top_y(glyph: &Glyph) -> f32 { + let height = glyph.scale.y; + + glyph.position.y - height * 0.75 +} + +pub fn glyph_width(glyph: &Glyph) -> f32 { + glyph.scale.x * 0.4765 +} + +pub fn build_glyph_brush( + gpu_device: &wgpu::Device, + render_format: wgpu::TextureFormat, +) -> Result, InvalidFont> { + let inconsolata = FontArc::try_from_slice(include_bytes!( + "../../../../../../editor/Inconsolata-Regular.ttf" + ))?; + + Ok(GlyphBrushBuilder::using_font(inconsolata).build(gpu_device, render_format)) +} diff --git a/examples/gui/platform/src/graphics/shaders/quad.wgsl b/examples/gui/platform/src/graphics/shaders/quad.wgsl new file mode 100644 index 0000000000..a561e2fc24 --- /dev/null +++ b/examples/gui/platform/src/graphics/shaders/quad.wgsl @@ -0,0 +1,60 @@ + + +struct Globals { + ortho: mat4x4; +}; + +@group(0) +@binding(0) +var globals: Globals; + +struct VertexInput { + @location(0) position: vec2; +}; + +struct Quad { + @location(1) pos: vec2; // can't use the name "position" twice for compatibility with metal on MacOS + @location(2) width: f32; + @location(3) height: f32; + @location(4) color: vec4; + @location(5) border_color: vec4; + @location(6) border_width: f32; +}; + +struct VertexOutput { + @builtin(position) position: vec4; + @location(0) color: vec4; + @location(1) border_color: vec4; + @location(2) border_width: f32; +}; + +@stage(vertex) +fn vs_main( + input: VertexInput, + quad: Quad +) -> VertexOutput { + + var transform: mat4x4 = mat4x4( + vec4(quad.width, 0.0, 0.0, 0.0), + vec4(0.0, quad.height, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(quad.pos, 0.0, 1.0) + ); + + var out: VertexOutput; + + out.position = globals.ortho * transform * vec4(input.position, 0.0, 1.0);; + out.color = quad.color; + out.border_color = quad.border_color; + out.border_width = quad.border_width; + + return out; +} + + +@stage(fragment) +fn fs_main( + input: VertexOutput +) -> @location(0) vec4 { + return input.color; +} diff --git a/examples/gui/platform/src/graphics/style.rs b/examples/gui/platform/src/graphics/style.rs new file mode 100644 index 0000000000..11e609075b --- /dev/null +++ b/examples/gui/platform/src/graphics/style.rs @@ -0,0 +1 @@ +pub const DEFAULT_FONT_SIZE: f32 = 30.0; diff --git a/examples/gui/platform/src/gui.rs b/examples/gui/platform/src/gui.rs new file mode 100644 index 0000000000..0e6bc8b24b --- /dev/null +++ b/examples/gui/platform/src/gui.rs @@ -0,0 +1,650 @@ +use crate::{ + graphics::{ + colors::Rgba, + lowlevel::buffer::create_rect_buffers, + lowlevel::{buffer::MAX_QUADS, ortho::update_ortho_buffer}, + lowlevel::{buffer::QUAD_INDICES, pipelines}, + primitives::{ + rect::{Rect, RectElt}, + text::build_glyph_brush, + }, + }, + roc::{RocElem, RocElemTag}, +}; +use cgmath::{Vector2, Vector4}; +use glyph_brush::OwnedSection; +use pipelines::RectResources; +use roc_std::RocStr; +use std::error::Error; +use wgpu::{CommandEncoder, LoadOp, RenderPass, TextureView}; +use wgpu_glyph::{GlyphBrush, GlyphCruncher}; +use winit::{ + dpi::PhysicalSize, + event, + event::{Event, ModifiersState}, + event_loop::ControlFlow, + platform::run_return::EventLoopExtRunReturn, +}; + +// Inspired by: +// https://github.com/sotrh/learn-wgpu by Benjamin Hansen, which is licensed under the MIT license +// https://github.com/cloudhead/rgx by Alexis Sellier, which is licensed under the MIT license +// +// See this link to learn wgpu: https://sotrh.github.io/learn-wgpu/ + +fn run_event_loop(title: &str, root: RocElem) -> Result<(), Box> { + // Open window and create a surface + let mut event_loop = winit::event_loop::EventLoop::new(); + + let window = winit::window::WindowBuilder::new() + .with_inner_size(PhysicalSize::new(1900.0, 1000.0)) + .with_title(title) + .build(&event_loop) + .unwrap(); + + let instance = wgpu::Instance::new(wgpu::Backends::all()); + + let surface = unsafe { instance.create_surface(&window) }; + + // Initialize GPU + let (gpu_device, cmd_queue) = futures::executor::block_on(async { + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::HighPerformance, + compatible_surface: Some(&surface), + force_fallback_adapter: false, + }) + .await + .expect(r#"Request adapter + If you're running this from inside nix, follow the instructions here to resolve this: https://github.com/rtfeldman/roc/blob/trunk/BUILDING_FROM_SOURCE.md#editor + "#); + + adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + features: wgpu::Features::empty(), + limits: wgpu::Limits::default(), + }, + None, + ) + .await + .expect("Request device") + }); + + // Create staging belt and a local pool + let mut staging_belt = wgpu::util::StagingBelt::new(1024); + let mut local_pool = futures::executor::LocalPool::new(); + let local_spawner = local_pool.spawner(); + + // Prepare swap chain + let render_format = wgpu::TextureFormat::Bgra8Unorm; + let mut size = window.inner_size(); + + let surface_config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: render_format, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::Mailbox, + }; + + surface.configure(&gpu_device, &surface_config); + + let rect_resources = pipelines::make_rect_pipeline(&gpu_device, &surface_config); + + let mut glyph_brush = build_glyph_brush(&gpu_device, render_format)?; + + let is_animating = true; + + let mut keyboard_modifiers = ModifiersState::empty(); + + // Render loop + window.request_redraw(); + + event_loop.run_return(|event, _, control_flow| { + // TODO dynamically switch this on/off depending on whether any + // animations are running. Should conserve CPU usage and battery life! + if is_animating { + *control_flow = ControlFlow::Poll; + } else { + *control_flow = ControlFlow::Wait; + } + + match event { + //Close + Event::WindowEvent { + event: event::WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + //Resize + Event::WindowEvent { + event: event::WindowEvent::Resized(new_size), + .. + } => { + size = new_size; + + surface.configure( + &gpu_device, + &wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: render_format, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::Mailbox, + }, + ); + + update_ortho_buffer( + size.width, + size.height, + &gpu_device, + &rect_resources.ortho.buffer, + &cmd_queue, + ); + } + //Received Character + Event::WindowEvent { + event: event::WindowEvent::ReceivedCharacter(_ch), + .. + } => { + // let input_outcome_res = + // app_update::handle_new_char(&ch, &mut app_model, keyboard_modifiers); + // if let Err(e) = input_outcome_res { + // print_err(&e) + // } else if let Ok(InputOutcome::Ignored) = input_outcome_res { + // println!("Input '{}' ignored!", ch); + // } + todo!("TODO handle character input"); + } + //Keyboard Input + Event::WindowEvent { + event: event::WindowEvent::KeyboardInput { input: _, .. }, + .. + } => { + // if let Some(virtual_keycode) = input.virtual_keycode { + // if let Some(ref mut ed_model) = app_model.ed_model_opt { + // if ed_model.has_focus { + // let keydown_res = keyboard_input::handle_keydown( + // input.state, + // virtual_keycode, + // keyboard_modifiers, + // &mut app_model, + // ); + + // if let Err(e) = keydown_res { + // print_err(&e) + // } + // } + // } + // } + todo!("TODO handle keyboard input"); + } + //Modifiers Changed + Event::WindowEvent { + event: event::WindowEvent::ModifiersChanged(modifiers), + .. + } => { + keyboard_modifiers = modifiers; + } + Event::MainEventsCleared => window.request_redraw(), + Event::RedrawRequested { .. } => { + // Get a command cmd_encoder for the current frame + let mut cmd_encoder = + gpu_device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Redraw"), + }); + + let surface_texture = surface + .get_current_texture() + .expect("Failed to acquire next SwapChainTexture"); + + let view = surface_texture + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + // for text_section in &rects_and_texts.text_sections_behind { + // let borrowed_text = text_section.to_borrowed(); + + // glyph_brush.queue(borrowed_text); + // } + + // draw first layer of text + // glyph_brush + // .draw_queued( + // &gpu_device, + // &mut staging_belt, + // &mut cmd_encoder, + // &view, + // size.width, + // size.height, + // ) + // .expect("Failed to draw first layer of text."); + + // draw rects on top of first text layer + // draw_rects( + // &rects_and_texts.rects_front, + // &mut cmd_encoder, + // &view, + // &gpu_device, + // &rect_resources, + // wgpu::LoadOp::Load, + // ); + + // TODO use with_capacity based on some heuristic + let (_bounds, drawable) = to_drawable( + &root, + Bounds { + width: size.width as f32, + height: size.height as f32, + }, + &mut glyph_brush, + ); + + process_drawable( + drawable, + &mut staging_belt, + &mut glyph_brush, + &mut cmd_encoder, + &view, + &gpu_device, + &rect_resources, + wgpu::LoadOp::Load, + Bounds { + width: size.width as f32, + height: size.height as f32, + }, + ); + + // for text_section in &rects_and_texts.text_sections_front { + // let borrowed_text = text_section.to_borrowed(); + + // glyph_brush.queue(borrowed_text); + // } + + // draw text + // glyph_brush + // .draw_queued( + // &gpu_device, + // &mut staging_belt, + // &mut cmd_encoder, + // &view, + // size.width, + // size.height, + // ) + // .expect("Failed to draw queued text."); + + staging_belt.finish(); + cmd_queue.submit(Some(cmd_encoder.finish())); + surface_texture.present(); + + // Recall unused staging buffers + use futures::task::SpawnExt; + + local_spawner + .spawn(staging_belt.recall()) + .expect("Recall staging belt"); + + local_pool.run_until_stalled(); + } + _ => { + *control_flow = winit::event_loop::ControlFlow::Wait; + } + } + }); + + Ok(()) +} + +fn draw_rects( + all_rects: &[RectElt], + cmd_encoder: &mut CommandEncoder, + texture_view: &TextureView, + gpu_device: &wgpu::Device, + rect_resources: &RectResources, + load_op: LoadOp, +) { + let rect_buffers = create_rect_buffers(gpu_device, cmd_encoder, all_rects); + + let mut render_pass = begin_render_pass(cmd_encoder, texture_view, load_op); + + render_pass.set_pipeline(&rect_resources.pipeline); + render_pass.set_bind_group(0, &rect_resources.ortho.bind_group, &[]); + + render_pass.set_vertex_buffer(0, rect_buffers.vertex_buffer.slice(..)); + render_pass.set_vertex_buffer(1, rect_buffers.quad_buffer.slice(..)); + + render_pass.set_index_buffer( + rect_buffers.index_buffer.slice(..), + wgpu::IndexFormat::Uint16, + ); + + render_pass.draw_indexed(0..QUAD_INDICES.len() as u32, 0, 0..MAX_QUADS as u32); +} + +fn begin_render_pass<'a>( + cmd_encoder: &'a mut CommandEncoder, + texture_view: &'a TextureView, + load_op: LoadOp, +) -> RenderPass<'a> { + cmd_encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[wgpu::RenderPassColorAttachment { + view: texture_view, + resolve_target: None, + ops: wgpu::Operations { + load: load_op, + store: true, + }, + }], + depth_stencil_attachment: None, + label: None, + }) +} + +pub fn render(title: RocStr, root: RocElem) { + run_event_loop(title.as_str(), root).expect("Error running event loop"); +} + +#[derive(Copy, Clone, Debug, Default)] +struct Bounds { + width: f32, + height: f32, +} + +#[derive(Clone, Debug)] +struct Drawable { + bounds: Bounds, + content: DrawableContent, +} + +#[derive(Clone, Debug)] +enum DrawableContent { + /// This stores an actual Section because an earlier step needs to know the bounds of + /// the text, and making a Section is a convenient way to compute those bounds. + Text(OwnedSection, Vector2), + FillRect { + color: Rgba, + border_width: f32, + border_color: Rgba, + }, + Multi(Vec), + Offset(Vec<(Vector2, Drawable)>), +} + +fn process_drawable( + drawable: Drawable, + staging_belt: &mut wgpu::util::StagingBelt, + glyph_brush: &mut GlyphBrush<()>, + cmd_encoder: &mut CommandEncoder, + texture_view: &TextureView, + gpu_device: &wgpu::Device, + rect_resources: &RectResources, + load_op: LoadOp, + texture_size: Bounds, +) { + // TODO iterate through drawables, + // calculating a pos using offset, + // calling draw and updating bounding boxes + let pos: Vector2 = (0.0, 0.0).into(); + + draw( + drawable.bounds, + drawable.content, + pos, + staging_belt, + glyph_brush, + cmd_encoder, + texture_view, + gpu_device, + rect_resources, + load_op, + texture_size, + ); +} + +fn draw( + bounds: Bounds, + content: DrawableContent, + pos: Vector2, + staging_belt: &mut wgpu::util::StagingBelt, + glyph_brush: &mut GlyphBrush<()>, + cmd_encoder: &mut CommandEncoder, + texture_view: &TextureView, + gpu_device: &wgpu::Device, + rect_resources: &RectResources, + load_op: LoadOp, + texture_size: Bounds, +) { + use DrawableContent::*; + + match content { + Text(section, offset) => { + glyph_brush.queue(section.with_screen_position(pos + offset).to_borrowed()); + + glyph_brush + .draw_queued( + gpu_device, + staging_belt, + cmd_encoder, + texture_view, + texture_size.width as u32, // TODO why do we make these be u32 and then cast to f32 in orthorgraphic_projection? + texture_size.height as u32, + ) + .expect("Failed to draw text element"); + } + FillRect { + color, + border_width, + border_color, + } => { + // TODO store all these colors and things in FillRect + let rect_elt = RectElt { + rect: Rect { + pos, + width: bounds.width, + height: bounds.height, + }, + color, + border_width, + border_color, + }; + + // TODO inline draw_rects into here! + draw_rects( + &[rect_elt], + cmd_encoder, + texture_view, + gpu_device, + rect_resources, + load_op, + ); + } + Offset(children) => { + for (offset, child) in children.into_iter() { + draw( + child.bounds, + child.content, + pos + offset, + staging_belt, + glyph_brush, + cmd_encoder, + texture_view, + gpu_device, + rect_resources, + load_op, + texture_size, + ); + } + } + Multi(children) => { + for child in children.into_iter() { + draw( + child.bounds, + child.content, + pos, + staging_belt, + glyph_brush, + cmd_encoder, + texture_view, + gpu_device, + rect_resources, + load_op, + texture_size, + ); + } + } + } +} + +fn to_drawable( + elem: &RocElem, + bounds: Bounds, + glyph_brush: &mut GlyphBrush<()>, +) -> (Bounds, Drawable) { + use RocElemTag::*; + + match elem.tag() { + Button => { + let button = unsafe { &elem.entry().button }; + let styles = button.styles; + let (child_bounds, child_drawable) = to_drawable(&*button.child, bounds, glyph_brush); + + let button_drawable = Drawable { + bounds: child_bounds, + content: DrawableContent::FillRect { + color: styles.bg_color, + border_width: styles.border_width, + border_color: styles.border_color, + }, + }; + + let drawable = Drawable { + bounds: child_bounds, + content: DrawableContent::Multi(vec![button_drawable, child_drawable]), + }; + + (child_bounds, drawable) + } + Text => { + // TODO let text color and font settings inherit from parent + let text = unsafe { &elem.entry().text }; + let is_centered = true; // TODO don't hardcode this + let layout = wgpu_glyph::Layout::default().h_align(if is_centered { + wgpu_glyph::HorizontalAlign::Center + } else { + wgpu_glyph::HorizontalAlign::Left + }); + + let section = owned_section_from_str(text.as_str(), bounds, layout); + + // Calculate the bounds and offset by measuring glyphs + let text_bounds; + let offset; + + match glyph_brush.glyph_bounds(section.to_borrowed()) { + Some(glyph_bounds) => { + text_bounds = Bounds { + width: glyph_bounds.max.x - glyph_bounds.min.x, + height: glyph_bounds.max.y - glyph_bounds.min.y, + }; + + offset = (-glyph_bounds.min.x, -glyph_bounds.min.y).into(); + } + None => { + text_bounds = Bounds { + width: 0.0, + height: 0.0, + }; + + offset = (0.0, 0.0).into(); + } + } + + let drawable = Drawable { + bounds: text_bounds, + content: DrawableContent::Text(section, offset), + }; + + (text_bounds, drawable) + } + Row => { + let row = unsafe { &elem.entry().row_or_col }; + let mut final_bounds = Bounds::default(); + let mut offset: Vector2 = (0.0, 0.0).into(); + let mut offset_entries = Vec::with_capacity(row.children.len()); + + for child in row.children.as_slice().iter() { + let (child_bounds, child_drawable) = to_drawable(&child, bounds, glyph_brush); + + offset_entries.push((offset, child_drawable)); + + // Make sure the final height is enough to fit this child + final_bounds.height = final_bounds.height.max(child_bounds.height); + + // Add the child's width to the final width + final_bounds.width = final_bounds.width + child_bounds.width; + + // Offset the next child to make sure it appears after this one. + offset.x += child_bounds.width; + } + + ( + final_bounds, + Drawable { + bounds: final_bounds, + content: DrawableContent::Offset(offset_entries), + }, + ) + } + Col => { + let col = unsafe { &elem.entry().row_or_col }; + let mut final_bounds = Bounds::default(); + let mut offset: Vector2 = (0.0, 0.0).into(); + let mut offset_entries = Vec::with_capacity(col.children.len()); + + for child in col.children.as_slice().iter() { + let (child_bounds, child_drawable) = to_drawable(&child, bounds, glyph_brush); + + offset_entries.push((offset, child_drawable)); + + // Make sure the final width is enough to fit this child + final_bounds.width = final_bounds.width.max(child_bounds.width); + + // Add the child's height to the final height + final_bounds.height = final_bounds.height + child_bounds.height; + + // Offset the next child to make sure it appears after this one. + offset.y += child_bounds.height; + } + + ( + final_bounds, + Drawable { + bounds: final_bounds, + content: DrawableContent::Offset(offset_entries), + }, + ) + } + } +} + +fn owned_section_from_str( + string: &str, + bounds: Bounds, + layout: wgpu_glyph::Layout, +) -> OwnedSection { + // TODO don't hardcode any of this! + let color = Rgba::WHITE; + let size: f32 = 40.0; + + OwnedSection { + bounds: (bounds.width, bounds.height), + layout, + ..OwnedSection::default() + } + .add_text( + glyph_brush::OwnedText::new(string) + .with_color(Vector4::from(color)) + .with_scale(size), + ) +} diff --git a/examples/gui/platform/src/lib.rs b/examples/gui/platform/src/lib.rs new file mode 100644 index 0000000000..17fa0b4d4a --- /dev/null +++ b/examples/gui/platform/src/lib.rs @@ -0,0 +1,21 @@ +mod graphics; +mod gui; +mod rects_and_texts; +mod roc; + +use crate::roc::RocElem; + +extern "C" { + #[link_name = "roc__renderForHost_1_exposed"] + fn roc_render() -> RocElem; +} + +#[no_mangle] +pub extern "C" fn rust_main() -> i32 { + let root_elem = unsafe { roc_render() }; + + gui::render("test title".into(), root_elem); + + // Exit code + 0 +} diff --git a/examples/gui/platform/src/main.rs b/examples/gui/platform/src/main.rs new file mode 100644 index 0000000000..51175f934b --- /dev/null +++ b/examples/gui/platform/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + std::process::exit(host::rust_main()); +} diff --git a/examples/gui/platform/src/rects_and_texts.rs b/examples/gui/platform/src/rects_and_texts.rs new file mode 100644 index 0000000000..3eed8654ac --- /dev/null +++ b/examples/gui/platform/src/rects_and_texts.rs @@ -0,0 +1,70 @@ +use crate::graphics::primitives::rect::RectElt; +use crate::graphics::primitives::text::{owned_section_from_text, Text}; + +#[derive(Debug)] +pub struct RectsAndTexts { + pub text_sections_behind: Vec, // displayed in front of rect_behind, behind everything else + pub text_sections_front: Vec, // displayed in front of everything + pub rects_behind: Vec, // displayed at lowest depth + pub rects_front: Vec, // displayed in front of text_sections_behind, behind text_sections_front +} + +impl RectsAndTexts { + pub fn new() -> Self { + Self { + text_sections_behind: Vec::new(), + text_sections_front: Vec::new(), + rects_behind: Vec::new(), + rects_front: Vec::new(), + } + } + + pub fn init( + rects_behind: Vec, + texts_behind: Vec, + rects_front: Vec, + texts_front: Vec, + ) -> Self { + Self { + text_sections_behind: texts_behind + .iter() + .map(|txt| owned_section_from_text(txt)) + .collect(), + text_sections_front: texts_front + .iter() + .map(|txt| owned_section_from_text(txt)) + .collect(), + rects_behind, + rects_front, + } + } + + pub fn add_text_behind(&mut self, new_text_section: glyph_brush::OwnedSection) { + self.text_sections_behind.push(new_text_section); + } + + pub fn add_text_front(&mut self, new_text_section: glyph_brush::OwnedSection) { + self.text_sections_front.push(new_text_section); + } + + pub fn add_rect_behind(&mut self, new_rect: RectElt) { + self.rects_behind.push(new_rect); + } + + pub fn add_rects_behind(&mut self, new_rects: Vec) { + self.rects_behind.extend(new_rects); + } + + pub fn add_rect_front(&mut self, new_rect: RectElt) { + self.rects_front.push(new_rect); + } + + pub fn extend(&mut self, rects_and_texts: RectsAndTexts) { + self.text_sections_behind + .extend(rects_and_texts.text_sections_behind); + self.text_sections_front + .extend(rects_and_texts.text_sections_front); + self.rects_behind.extend(rects_and_texts.rects_behind); + self.rects_front.extend(rects_and_texts.rects_front); + } +} diff --git a/examples/gui/platform/src/roc.rs b/examples/gui/platform/src/roc.rs new file mode 100644 index 0000000000..034181deed --- /dev/null +++ b/examples/gui/platform/src/roc.rs @@ -0,0 +1,150 @@ +use crate::graphics::colors::Rgba; +use core::ffi::c_void; +use core::mem::{self, ManuallyDrop}; +use roc_std::{ReferenceCount, RocList, RocStr}; +use std::ffi::CStr; +use std::os::raw::c_char; + +#[no_mangle] +pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { + return libc::malloc(size); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_realloc( + c_ptr: *mut c_void, + new_size: usize, + _old_size: usize, + _alignment: u32, +) -> *mut c_void { + return libc::realloc(c_ptr, new_size); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { + return libc::free(c_ptr); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { + match tag_id { + 0 => { + let slice = CStr::from_ptr(c_ptr as *const c_char); + let string = slice.to_str().unwrap(); + eprintln!("Roc hit a panic: {}", string); + std::process::exit(1); + } + _ => todo!(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn roc_memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void { + libc::memcpy(dst, src, n) +} + +#[no_mangle] +pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void { + libc::memset(dst, c, n) +} + +#[repr(transparent)] +#[cfg(target_pointer_width = "64")] // on a 64-bit system, the tag fits in this pointer's spare 3 bits +pub struct RocElem { + entry: *const RocElemEntry, +} + +impl RocElem { + #[cfg(target_pointer_width = "64")] + pub fn tag(&self) -> RocElemTag { + // On a 64-bit system, the last 3 bits of the pointer store the tag + unsafe { mem::transmute::((self.entry as u8) & 0b0000_0111) } + } + + pub fn entry(&self) -> &RocElemEntry { + // On a 64-bit system, the last 3 bits of the pointer store the tag + let cleared = self.entry as usize & !0b111; + + unsafe { &*(cleared as *const RocElemEntry) } + } +} + +#[repr(u8)] +#[allow(unused)] // This is actually used, just via a mem::transmute from u8 +#[derive(Debug, Clone, Copy)] +pub enum RocElemTag { + Button = 0, + Col, + Row, + Text, +} + +#[repr(C)] +pub struct RocButton { + pub child: ManuallyDrop, + pub styles: ButtonStyles, +} + +#[repr(C)] +pub struct RocRowOrCol { + pub children: RocList, +} + +unsafe impl ReferenceCount for RocElem { + /// Increment the reference count. + fn increment(&self) { + use RocElemTag::*; + + match self.tag() { + Button => unsafe { &*self.entry().button.child }.increment(), + Text => unsafe { &*self.entry().text }.increment(), + Row | Col => { + let children = unsafe { &self.entry().row_or_col.children }; + + for child in children.as_slice().iter() { + child.increment(); + } + } + } + } + + /// Decrement the reference count. + /// + /// # Safety + /// + /// The caller must ensure that `ptr` points to a value with a non-zero + /// reference count. + unsafe fn decrement(ptr: *const Self) { + use RocElemTag::*; + + let elem = &*ptr; + + match elem.tag() { + Button => ReferenceCount::decrement(&*elem.entry().button.child), + Text => ReferenceCount::decrement(&*elem.entry().text), + Row | Col => { + let children = &elem.entry().row_or_col.children; + + for child in children.as_slice().iter() { + ReferenceCount::decrement(child); + } + } + } + } +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct ButtonStyles { + pub bg_color: Rgba, + pub border_color: Rgba, + pub border_width: f32, + pub text_color: Rgba, +} + +#[repr(C)] +pub union RocElemEntry { + pub button: ManuallyDrop, + pub text: ManuallyDrop, + pub row_or_col: ManuallyDrop, +} diff --git a/examples/hello-rust/platform/src/lib.rs b/examples/hello-rust/platform/src/lib.rs index 6b054aa551..5c90b91be8 100644 --- a/examples/hello-rust/platform/src/lib.rs +++ b/examples/hello-rust/platform/src/lib.rs @@ -59,7 +59,7 @@ pub extern "C" fn rust_main() -> i32 { let roc_str = roc_main(); let len = roc_str.len(); - let str_bytes = roc_str.get_bytes() as *const libc::c_void; + let str_bytes = roc_str.as_bytes().as_ptr() as *const libc::c_void; if libc::write(1, str_bytes, len) < 0 { panic!("Writing to stdout failed!"); diff --git a/linker/Cargo.toml b/linker/Cargo.toml index 39cfdcdd37..7543f9438b 100644 --- a/linker/Cargo.toml +++ b/linker/Cargo.toml @@ -19,10 +19,10 @@ bench = false [dependencies] roc_mono = { path = "../compiler/mono" } -roc_build = { path = "../compiler/build", default-features = false } +roc_build = { path = "../compiler/build" } roc_collections = { path = "../compiler/collections" } bumpalo = { version = "3.8.0", features = ["collections"] } -clap = { version = "= 3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] } +clap = { version = "3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] } iced-x86 = { version = "1.15.0", default-features = false, features = ["std", "decoder", "op_code_info", "instr_info"] } memmap2 = "0.5.0" object = { version = "0.26.2", features = ["read", "write"] } diff --git a/repl_cli/Cargo.toml b/repl_cli/Cargo.toml index 240d7113ba..1ae373c4d5 100644 --- a/repl_cli/Cargo.toml +++ b/repl_cli/Cargo.toml @@ -5,16 +5,25 @@ version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +# pipe target to roc_build +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"] + [dependencies] -bumpalo = {version = "3.8.0", features = ["collections"]} +bumpalo = { version = "3.8.0", features = ["collections"] } const_format = "0.2.22" inkwell = {path = "../vendor/inkwell"} -libloading = {version = "0.7.1"} +libloading = "0.7.1" rustyline = {git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1"} rustyline-derive = {git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1"} target-lexicon = "0.12.2" -roc_build = {path = "../compiler/build"} +# TODO: make llvm optional +roc_build = {path = "../compiler/build", features = ["llvm"]} roc_collections = {path = "../compiler/collections"} roc_gen_llvm = {path = "../compiler/gen_llvm"} roc_load = {path = "../compiler/load"} diff --git a/repl_eval/Cargo.toml b/repl_eval/Cargo.toml index 562cb27c9e..ebb3843553 100644 --- a/repl_eval/Cargo.toml +++ b/repl_eval/Cargo.toml @@ -6,7 +6,7 @@ version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bumpalo = {version = "3.8.0", features = ["collections"]} +bumpalo = { version = "3.8.0", features = ["collections"] } roc_builtins = {path = "../compiler/builtins"} roc_can = {path = "../compiler/can"} diff --git a/repl_eval/src/eval.rs b/repl_eval/src/eval.rs index f97ce94494..4ec51afef4 100644 --- a/repl_eval/src/eval.rs +++ b/repl_eval/src/eval.rs @@ -5,11 +5,11 @@ use std::cmp::{max_by_key, min_by_key}; use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_collections::all::MutMap; use roc_module::called_via::CalledVia; -use roc_module::ident::TagName; +use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_mono::ir::ProcLayout; use roc_mono::layout::{ - union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant, WrappedVariant, + union_sorted_tags_help, Builtin, Layout, LayoutCache, UnionLayout, UnionVariant, WrappedVariant, }; use roc_parse::ast::{AssignedField, Collection, Expr, StrLiteral}; use roc_region::all::{Loc, Region}; @@ -70,6 +70,7 @@ pub fn jit_to_ast<'a, A: ReplApp<'a>>( } } +#[derive(Debug)] enum NewtypeKind<'a> { Tag(&'a TagName), RecordField(&'a str), @@ -89,10 +90,11 @@ fn unroll_newtypes<'a>( mut content: &'a Content, ) -> (Vec<'a, NewtypeKind<'a>>, &'a Content) { let mut newtype_containers = Vec::with_capacity_in(1, env.arena); + let mut force_alias_content = None; loop { match content { Content::Structure(FlatType::TagUnion(tags, _)) - if tags.is_newtype_wrapper(env.subs) => + if tags.is_newtype_wrapper_of_global_tag(env.subs) => { let (tag_name, vars): (&TagName, &[Variable]) = tags .unsorted_iterator(env.subs, Variable::EMPTY_TAG_UNION) @@ -113,7 +115,20 @@ fn unroll_newtypes<'a>( let field_var = *field.as_inner(); content = env.subs.get_content_without_compacting(field_var); } - _ => return (newtype_containers, content), + Content::Alias(_, _, real_var) => { + // We need to pass through aliases too, because their underlying types may have + // unrolled newtypes. In such cases return the list of unrolled newtypes, but keep + // the content as the alias for readability. For example, + // T : { a : Str } + // v : T + // v = { a : "value" } + // v + // Here we need the newtype container to be `[RecordField(a)]`, but the content to + // remain as the alias `T`. + force_alias_content = Some(content); + content = env.subs.get_content_without_compacting(*real_var); + } + _ => return (newtype_containers, force_alias_content.unwrap_or(content)), } } } @@ -331,18 +346,14 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( Layout::Builtin(other) => { todo!("add support for rendering builtin {:?} to the REPL", other) } - Layout::Struct(field_layouts) => { + Layout::Struct { field_layouts, .. } => { let struct_addr_to_ast = |mem: &'a A::Memory, addr: usize| match content { Content::Structure(FlatType::Record(fields, _)) => { - Ok(struct_to_ast(env, mem, addr, field_layouts, *fields)) + Ok(struct_to_ast(env, mem, addr, *fields)) + } + Content::Structure(FlatType::EmptyRecord) => { + Ok(struct_to_ast(env, mem, addr, RecordFields::empty())) } - Content::Structure(FlatType::EmptyRecord) => Ok(struct_to_ast( - env, - mem, - addr, - field_layouts, - RecordFields::empty(), - )), Content::Structure(FlatType::TagUnion(tags, _)) => { debug_assert_eq!(tags.len(), 1); @@ -382,7 +393,7 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( }; let fields = [Layout::u64(), *layout]; - let layout = Layout::Struct(&fields); + let layout = Layout::struct_no_name_order(&fields); let result_stack_size = layout.stack_size(env.target_info); @@ -516,9 +527,9 @@ fn addr_to_ast<'a, M: ReplAppMemory>( str_to_ast(env.arena, arena_str) } - (_, Layout::Struct(field_layouts)) => match content { + (_, Layout::Struct{field_layouts, ..}) => match content { Content::Structure(FlatType::Record(fields, _)) => { - struct_to_ast(env, mem, addr, field_layouts, *fields) + struct_to_ast(env, mem, addr, *fields) } Content::Structure(FlatType::TagUnion(tags, _)) => { debug_assert_eq!(tags.len(), 1); @@ -531,7 +542,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( single_tag_union_to_ast(env, mem, addr, field_layouts, tag_name, &[]) } Content::Structure(FlatType::EmptyRecord) => { - struct_to_ast(env, mem, addr, &[], RecordFields::empty()) + struct_to_ast(env, mem, addr, RecordFields::empty()) } other => { unreachable!( @@ -796,7 +807,7 @@ fn single_tag_union_to_ast<'a, M: ReplAppMemory>( sequence_of_expr(env, mem, addr, it, WhenRecursive::Unreachable).into_bump_slice() } else if field_layouts.is_empty() && !payload_vars.is_empty() { // happens for e.g. `Foo Bar` where unit structures are nested and the inner one is dropped - let it = payload_vars.iter().copied().zip([&Layout::Struct(&[])]); + let it = payload_vars.iter().copied().zip([&Layout::UNIT]); sequence_of_expr(env, mem, addr, it, WhenRecursive::Unreachable).into_bump_slice() } else { unreachable!() @@ -841,30 +852,46 @@ fn struct_to_ast<'a, M: ReplAppMemory>( env: &Env<'a, 'a>, mem: &'a M, addr: usize, - field_layouts: &'a [Layout<'a>], record_fields: RecordFields, ) -> Expr<'a> { let arena = env.arena; let subs = env.subs; - let mut output = Vec::with_capacity_in(field_layouts.len(), arena); + let mut output = Vec::with_capacity_in(record_fields.len(), arena); let sorted_fields: Vec<_> = Vec::from_iter_in( record_fields.sorted_iterator(env.subs, Variable::EMPTY_RECORD), - env.arena, + arena, ); + let mut layout_cache = LayoutCache::new(env.target_info); + // We recalculate the layouts here because we will have compiled the record so that its fields + // are sorted by descending alignment, and then alphabetic, but the type of the record is + // always only sorted alphabetically. We want to arrange the rendered record in the order of + // the type. + let field_to_layout: MutMap = sorted_fields + .iter() + .map(|(label, field)| { + let layout = layout_cache + .from_var(arena, *field.as_inner(), env.subs) + .unwrap(); + (label.clone(), layout) + }) + .collect(); + if sorted_fields.len() == 1 { // this is a 1-field wrapper record around another record or 1-tag tag union let (label, field) = sorted_fields.into_iter().next().unwrap(); let inner_content = env.subs.get_content_without_compacting(field.into_inner()); + debug_assert_eq!(field_to_layout.len(), 1); + let inner_layouts = arena.alloc([field_to_layout.into_values().next().unwrap()]); let loc_expr = &*arena.alloc(Loc { value: addr_to_ast( env, mem, addr, - &Layout::Struct(field_layouts), + &Layout::struct_no_name_order(inner_layouts), WhenRecursive::Unreachable, inner_content, ), @@ -880,19 +907,20 @@ fn struct_to_ast<'a, M: ReplAppMemory>( region: Region::zero(), }; - let output = env.arena.alloc([loc_field]); + let output = arena.alloc([loc_field]); Expr::Record(Collection::with_items(output)) } else { - debug_assert_eq!(sorted_fields.len(), field_layouts.len()); + debug_assert_eq!(sorted_fields.len(), field_to_layout.len()); // We'll advance this as we iterate through the fields let mut field_addr = addr; - for ((label, field), field_layout) in sorted_fields.into_iter().zip(field_layouts.iter()) { + for (label, field) in sorted_fields.into_iter() { let var = field.into_inner(); let content = subs.get_content_without_compacting(var); + let field_layout = field_to_layout.get(&label).unwrap(); let loc_expr = &*arena.alloc(Loc { value: addr_to_ast( diff --git a/repl_test/Cargo.toml b/repl_test/Cargo.toml index 2d86ad19d4..736276ba9b 100644 --- a/repl_test/Cargo.toml +++ b/repl_test/Cargo.toml @@ -12,13 +12,18 @@ roc_cli = {path = "../cli"} [dev-dependencies] indoc = "1.0.3" -roc_test_utils = {path = "../test_utils"} strip-ansi-escapes = "0.1.1" +wasmer-wasi = "2.0.0" roc_repl_cli = {path = "../repl_cli"} -# roc_repl_wasm = {path = "../repl_wasm"} +roc_test_utils = {path = "../test_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"] } [features] -default = ["cli"] -cli = [] wasm = [] diff --git a/repl_test/run_wasm.sh b/repl_test/run_wasm.sh new file mode 100755 index 0000000000..4d5ffbce3c --- /dev/null +++ b/repl_test/run_wasm.sh @@ -0,0 +1,6 @@ +# At the moment we are using this script instead of `cargo test` +# Cargo doesn't really have a good way to build two targets (host and wasm). +# We can try to make the build nicer later + +cargo build --target wasm32-unknown-unknown -p roc_repl_wasm --features wasmer --release +cargo test -p repl_test --features wasm diff --git a/repl_test/src/tests.rs b/repl_test/src/tests.rs index 49a166386b..c24aa75b54 100644 --- a/repl_test/src/tests.rs +++ b/repl_test/src/tests.rs @@ -1,10 +1,16 @@ use indoc::indoc; +#[cfg(not(feature = "wasm"))] mod cli; - -#[cfg(feature = "cli")] +#[cfg(not(feature = "wasm"))] use crate::cli::{expect_failure, expect_success}; +#[cfg(feature = "wasm")] +mod wasm; +#[cfg(feature = "wasm")] +#[allow(unused_imports)] +use crate::wasm::{expect_failure, expect_success}; + #[test] fn literal_0() { expect_success("0", "0 : Num *"); @@ -50,16 +56,19 @@ fn float_addition() { expect_success("1.1 + 2", "3.1 : F64"); } +#[cfg(not(feature = "wasm"))] #[test] fn num_rem() { expect_success("299 % 10", "Ok 9 : Result (Int *) [ DivByZero ]*"); } +#[cfg(not(feature = "wasm"))] #[test] fn num_floor_division_success() { expect_success("Num.divFloor 4 3", "Ok 1 : Result (Int *) [ DivByZero ]*"); } +#[cfg(not(feature = "wasm"))] #[test] fn num_floor_division_divby_zero() { expect_success( @@ -68,11 +77,13 @@ fn num_floor_division_divby_zero() { ); } +#[cfg(not(feature = "wasm"))] #[test] fn num_ceil_division_success() { expect_success("Num.divCeil 4 3", "Ok 2 : Result (Int *) [ DivByZero ]*") } +#[cfg(not(feature = "wasm"))] #[test] fn bool_in_record() { expect_success("{ x: 1 == 1 }", "{ x: True } : { x : Bool }"); @@ -87,18 +98,21 @@ fn bool_in_record() { ); } +#[cfg(not(feature = "wasm"))] #[test] fn bool_basic_equality() { expect_success("1 == 1", "True : Bool"); expect_success("1 != 1", "False : Bool"); } +#[cfg(not(feature = "wasm"))] #[test] fn arbitrary_tag_unions() { expect_success("if 1 == 1 then Red else Green", "Red : [ Green, Red ]*"); expect_success("if 1 != 1 then Red else Green", "Green : [ Green, Red ]*"); } +#[cfg(not(feature = "wasm"))] #[test] fn tag_without_arguments() { expect_success("True", "True : [ True ]*"); @@ -118,6 +132,7 @@ fn byte_tag_union() { ); } +#[cfg(not(feature = "wasm"))] #[test] fn tag_in_record() { expect_success( @@ -137,11 +152,13 @@ fn single_element_tag_union() { expect_success("Foo 1 3.14", "Foo 1 3.14 : [ Foo (Num *) (Float *) ]*"); } +#[cfg(not(feature = "wasm"))] #[test] fn newtype_of_unit() { expect_success("Foo Bar", "Foo Bar : [ Foo [ Bar ]* ]*"); } +#[cfg(not(feature = "wasm"))] #[test] fn newtype_of_big_data() { expect_success( @@ -157,6 +174,7 @@ fn newtype_of_big_data() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn newtype_nested() { expect_success( @@ -172,6 +190,7 @@ fn newtype_nested() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn newtype_of_big_of_newtype() { expect_success( @@ -202,16 +221,19 @@ fn literal_empty_str() { expect_success("\"\"", "\"\" : Str"); } +#[cfg(not(feature = "wasm"))] #[test] fn literal_ascii_str() { expect_success("\"Hello, World!\"", "\"Hello, World!\" : Str"); } +#[cfg(not(feature = "wasm"))] #[test] fn literal_utf8_str() { expect_success("\"👩‍👩‍👦‍👦\"", "\"👩‍👩‍👦‍👦\" : Str"); } +#[cfg(not(feature = "wasm"))] #[test] fn str_concat() { expect_success( @@ -230,6 +252,7 @@ fn literal_empty_list() { expect_success("[]", "[] : List *"); } +#[cfg(not(feature = "wasm"))] #[test] fn literal_empty_list_empty_record() { expect_success("[ {} ]", "[ {} ] : List {}"); @@ -250,11 +273,13 @@ fn literal_float_list() { expect_success("[ 1.1, 2.2, 3.3 ]", "[ 1.1, 2.2, 3.3 ] : List (Float *)"); } +#[cfg(not(feature = "wasm"))] #[test] fn literal_string_list() { expect_success(r#"[ "a", "b", "cd" ]"#, r#"[ "a", "b", "cd" ] : List Str"#); } +#[cfg(not(feature = "wasm"))] #[test] fn nested_string_list() { expect_success( @@ -322,6 +347,7 @@ fn num_mul_wrap() { expect_success("Num.mulWrap Num.maxI64 2", "-2 : I64"); } +#[cfg(not(feature = "wasm"))] #[test] fn num_add_checked() { expect_success("Num.addChecked 1 1", "Ok 2 : Result (Num *) [ Overflow ]*"); @@ -331,6 +357,7 @@ fn num_add_checked() { ); } +#[cfg(not(feature = "wasm"))] #[test] fn num_sub_checked() { expect_success("Num.subChecked 1 1", "Ok 0 : Result (Num *) [ Overflow ]*"); @@ -340,6 +367,7 @@ fn num_sub_checked() { ); } +#[cfg(not(feature = "wasm"))] #[test] fn num_mul_checked() { expect_success( @@ -352,6 +380,7 @@ fn num_mul_checked() { ); } +#[cfg(not(feature = "wasm"))] #[test] fn list_concat() { expect_success( @@ -360,6 +389,7 @@ fn list_concat() { ); } +#[cfg(not(feature = "wasm"))] #[test] fn list_contains() { expect_success("List.contains [] 0", "False : Bool"); @@ -367,6 +397,7 @@ fn list_contains() { expect_success("List.contains [ 1, 2, 3 ] 4", "False : Bool"); } +#[cfg(not(feature = "wasm"))] #[test] fn list_sum() { expect_success("List.sum []", "0 : Num *"); @@ -374,6 +405,7 @@ fn list_sum() { expect_success("List.sum [ 1.1, 2.2, 3.3 ]", "6.6 : F64"); } +#[cfg(not(feature = "wasm"))] #[test] fn list_first() { expect_success( @@ -386,6 +418,7 @@ fn list_first() { ); } +#[cfg(not(feature = "wasm"))] #[test] fn list_last() { expect_success( @@ -399,6 +432,7 @@ fn list_last() { ); } +#[cfg(not(feature = "wasm"))] #[test] fn empty_record() { expect_success("{}", "{} : {}"); @@ -522,16 +556,19 @@ fn identity_lambda() { expect_success("\\x -> x", " : a -> a"); } +#[cfg(not(feature = "wasm"))] #[test] fn sum_lambda() { expect_success("\\x, y -> x + y", " : Num a, Num a -> Num a"); } +#[cfg(not(feature = "wasm"))] #[test] fn stdlib_function() { expect_success("Num.abs", " : Num a -> Num a"); } +#[cfg(not(feature = "wasm"))] #[test] fn too_few_args() { expect_failure( @@ -552,6 +589,7 @@ fn too_few_args() { ); } +#[cfg(not(feature = "wasm"))] #[test] fn type_problem() { expect_failure( @@ -608,6 +646,7 @@ fn multiline_input() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn recursive_tag_union_flat_variant() { expect_success( @@ -623,6 +662,7 @@ fn recursive_tag_union_flat_variant() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn large_recursive_tag_union_flat_variant() { expect_success( @@ -639,6 +679,7 @@ fn large_recursive_tag_union_flat_variant() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn recursive_tag_union_recursive_variant() { expect_success( @@ -654,6 +695,7 @@ fn recursive_tag_union_recursive_variant() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn large_recursive_tag_union_recursive_variant() { expect_success( @@ -670,6 +712,7 @@ fn large_recursive_tag_union_recursive_variant() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn recursive_tag_union_into_flat_tag_union() { expect_success( @@ -685,6 +728,7 @@ fn recursive_tag_union_into_flat_tag_union() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn non_nullable_unwrapped_tag_union() { expect_success( @@ -704,6 +748,7 @@ fn non_nullable_unwrapped_tag_union() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn nullable_unwrapped_tag_union() { expect_success( @@ -723,6 +768,7 @@ fn nullable_unwrapped_tag_union() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn nullable_wrapped_tag_union() { expect_success( @@ -746,6 +792,7 @@ fn nullable_wrapped_tag_union() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn large_nullable_wrapped_tag_union() { // > 7 non-empty variants so that to force tag storage alongside the data @@ -770,6 +817,7 @@ fn large_nullable_wrapped_tag_union() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn issue_2300() { expect_success( @@ -778,6 +826,7 @@ fn issue_2300() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn function_in_list() { expect_success( @@ -786,14 +835,16 @@ fn function_in_list() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn function_in_record() { expect_success( r#"{ n: 1, adder: \x -> x + 1 }"#, - r#"{ adder: , n: } : { adder : Num a -> Num a, n : Num * }"#, + r#"{ adder: , n: 1 } : { adder : Num a -> Num a, n : Num * }"#, ) } +#[cfg(not(feature = "wasm"))] #[test] fn function_in_unwrapped_record() { expect_success( @@ -802,6 +853,7 @@ fn function_in_unwrapped_record() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn function_in_tag() { expect_success( @@ -832,6 +884,7 @@ fn print_u8s() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn parse_problem() { expect_failure( @@ -855,6 +908,7 @@ fn parse_problem() { ); } +#[cfg(not(feature = "wasm"))] #[test] fn mono_problem() { expect_failure( @@ -887,6 +941,7 @@ fn mono_problem() { ); } +#[cfg(not(feature = "wasm"))] #[test] fn issue_2343_complete_mono_with_shadowed_vars() { expect_failure( @@ -920,3 +975,48 @@ fn issue_2343_complete_mono_with_shadowed_vars() { ), ); } + +#[test] +fn record_with_type_behind_alias() { + expect_success( + indoc!( + r#" + T : { a: Str } + v : T + v = { a: "value" } + v + "# + ), + r#"{ a: "value" } : T"#, + ); +} + +#[test] +fn tag_with_type_behind_alias() { + expect_success( + indoc!( + r#" + T : [ A Str ] + v : T + v = A "value" + v + "# + ), + r#"A "value" : T"#, + ); +} + +#[cfg(not(feature = "wasm"))] +#[test] +fn issue_2588_record_with_function_and_nonfunction() { + expect_success( + indoc!( + r#" + x = 1 + f = \n -> n * 2 + { y: f x, f } + "# + ), + r#"{ f: , y: 2 } : { f : Num a -> Num a, y : Num * }"#, + ) +} diff --git a/repl_test/src/wasm.rs b/repl_test/src/wasm.rs new file mode 100644 index 0000000000..a6f0a86a99 --- /dev/null +++ b/repl_test/src/wasm.rs @@ -0,0 +1,261 @@ +use std::{ + cell::RefCell, + fs, + ops::{Deref, DerefMut}, + path::Path, + thread_local, +}; +use wasmer::{imports, Function, Instance, Module, Store, Value}; +use wasmer_wasi::WasiState; + +const WASM_REPL_COMPILER_PATH: &str = "../target/wasm32-unknown-unknown/release/roc_repl_wasm.wasm"; + +thread_local! { + static COMPILER: RefCell> = RefCell::new(None) +} + +thread_local! { + static REPL_STATE: RefCell> = RefCell::new(None) +} + +struct ReplState { + src: &'static str, + app: Option, + result_addr: Option, + output: Option, +} + +fn wasmer_create_app(app_bytes_ptr: u32, app_bytes_len: u32) { + let app = COMPILER.with(|f| { + if let Some(compiler) = f.borrow().deref() { + let memory = compiler.exports.get_memory("memory").unwrap(); + let memory_bytes: &[u8] = unsafe { memory.data_unchecked() }; + + // Find the slice of bytes for the compiled Roc app + let ptr = app_bytes_ptr as usize; + let len = app_bytes_len as usize; + let app_module_bytes: &[u8] = &memory_bytes[ptr..][..len]; + + // Parse the bytes into a Wasmer module + let store = Store::default(); + let wasmer_module = Module::new(&store, app_module_bytes).unwrap(); + + // Get the WASI imports for the app + let mut wasi_env = WasiState::new("hello").finalize().unwrap(); + let import_object = wasi_env + .import_object(&wasmer_module) + .unwrap_or_else(|_| imports!()); + + // Create an executable instance. (Give it a stack & heap, etc. If this was ELF, it would be the OS's job.) + Instance::new(&wasmer_module, &import_object).unwrap() + } else { + unreachable!() + } + }); + + REPL_STATE.with(|f| { + if let Some(state) = f.borrow_mut().deref_mut() { + state.app = Some(app) + } else { + unreachable!() + } + }); +} + +fn wasmer_run_app() -> u32 { + REPL_STATE.with(|f| { + if let Some(state) = f.borrow_mut().deref_mut() { + if let Some(app) = &state.app { + let wrapper = app.exports.get_function("wrapper").unwrap(); + + let result_addr: i32 = match wrapper.call(&[]) { + Err(e) => panic!("{:?}", e), + Ok(result) => result[0].unwrap_i32(), + }; + state.result_addr = Some(result_addr as u32); + + let memory = app.exports.get_memory("memory").unwrap(); + memory.size().bytes().0 as u32 + } else { + unreachable!() + } + } else { + unreachable!() + } + }) +} + +fn wasmer_get_result_and_memory(buffer_alloc_addr: u32) -> u32 { + REPL_STATE.with(|f| { + if let Some(state) = f.borrow().deref() { + if let Some(app) = &state.app { + let app_memory = app.exports.get_memory("memory").unwrap(); + let result_addr = state.result_addr.unwrap(); + + let app_memory_bytes: &[u8] = unsafe { app_memory.data_unchecked() }; + + let buf_addr = buffer_alloc_addr as usize; + let len = app_memory_bytes.len(); + + COMPILER.with(|f| { + if let Some(compiler) = f.borrow().deref() { + let memory = compiler.exports.get_memory("memory").unwrap(); + let compiler_memory_bytes: &mut [u8] = + unsafe { memory.data_unchecked_mut() }; + compiler_memory_bytes[buf_addr..][..len].copy_from_slice(app_memory_bytes); + } else { + unreachable!() + } + }); + + result_addr + } else { + panic!("REPL app not found") + } + } else { + panic!("REPL state not found") + } + }) +} + +fn wasmer_copy_input_string(src_buffer_addr: u32) { + let src = REPL_STATE.with(|rs| { + if let Some(state) = rs.borrow_mut().deref_mut() { + std::mem::take(&mut state.src) + } else { + unreachable!() + } + }); + + COMPILER.with(|c| { + if let Some(compiler) = c.borrow().deref() { + let memory = compiler.exports.get_memory("memory").unwrap(); + let memory_bytes: &mut [u8] = unsafe { memory.data_unchecked_mut() }; + + let buf_addr = src_buffer_addr as usize; + let len = src.len(); + memory_bytes[buf_addr..][..len].copy_from_slice(src.as_bytes()); + } else { + unreachable!() + } + }) +} + +fn wasmer_copy_output_string(output_ptr: u32, output_len: u32) { + let output: String = COMPILER.with(|c| { + if let Some(compiler) = c.borrow().deref() { + let memory = compiler.exports.get_memory("memory").unwrap(); + let memory_bytes: &[u8] = unsafe { memory.data_unchecked() }; + + // Find the slice of bytes for the output string + let ptr = output_ptr as usize; + let len = output_len as usize; + let output_bytes: &[u8] = &memory_bytes[ptr..][..len]; + + // Copy it out of the Wasm module + let copied_bytes = output_bytes.to_vec(); + unsafe { String::from_utf8_unchecked(copied_bytes) } + } else { + unreachable!() + } + }); + + REPL_STATE.with(|f| { + if let Some(state) = f.borrow_mut().deref_mut() { + state.output = Some(output) + } + }) +} + +fn init_compiler() -> Instance { + let path = Path::new(WASM_REPL_COMPILER_PATH); + let wasm_module_bytes = match fs::read(&path) { + Ok(bytes) => bytes, + Err(e) => panic!("{}", format_compiler_load_error(e)), + }; + + let store = Store::default(); + let wasmer_module = Module::new(&store, &wasm_module_bytes).unwrap(); + + let import_object = imports! { + "env" => { + "wasmer_create_app" => Function::new_native(&store, wasmer_create_app), + "wasmer_run_app" => Function::new_native(&store, wasmer_run_app), + "wasmer_get_result_and_memory" => Function::new_native(&store, wasmer_get_result_and_memory), + "wasmer_copy_input_string" => Function::new_native(&store, wasmer_copy_input_string), + "wasmer_copy_output_string" => Function::new_native(&store, wasmer_copy_output_string), + "now" => Function::new_native(&store, dummy_system_time_now), + } + }; + + Instance::new(&wasmer_module, &import_object).unwrap() +} + +fn run(src: &'static str) -> (bool, String) { + REPL_STATE.with(|rs| { + *rs.borrow_mut().deref_mut() = Some(ReplState { + src, + app: None, + result_addr: None, + output: None, + }); + }); + + let ok = COMPILER.with(|c| { + *c.borrow_mut().deref_mut() = Some(init_compiler()); + + if let Some(compiler) = c.borrow().deref() { + let entrypoint = compiler + .exports + .get_function("entrypoint_from_test") + .unwrap(); + + let src_len = Value::I32(src.len() as i32); + let wasm_ok: i32 = entrypoint.call(&[src_len]).unwrap().deref()[0].unwrap_i32(); + wasm_ok != 0 + } else { + unreachable!() + } + }); + + let final_state: ReplState = REPL_STATE.with(|rs| rs.take()).unwrap(); + let output: String = final_state.output.unwrap(); + + (ok, output) +} + +fn format_compiler_load_error(e: std::io::Error) -> String { + if matches!(e.kind(), std::io::ErrorKind::NotFound) { + format!( + "\n\n {}\n\n", + [ + "CANNOT BUILD WASM REPL TESTS", + "Please run these tests using repl_test/run_wasm.sh!", + "", + "These tests combine two builds for two different targets,", + "which Cargo doesn't handle very well.", + "It probably requires a second target directory to avoid locks.", + "We'll get to it eventually but it's not done yet.", + ] + .join("\n ") + ) + } else { + format!("{:?}", e) + } +} + +fn dummy_system_time_now() -> f64 { + 0.0 +} + +pub fn expect_success(input: &'static str, expected: &str) { + let (ok, output) = run(input); + assert_eq!(ok, true); + assert_eq!(output, expected); +} + +pub fn expect_failure(input: &'static str, expected: &str) { + let (ok, output) = run(input); + assert_eq!(ok, false); + assert_eq!(output, expected); +} diff --git a/repl_wasm/Cargo.toml b/repl_wasm/Cargo.toml index 03dd9536a2..27f9ab6830 100644 --- a/repl_wasm/Cargo.toml +++ b/repl_wasm/Cargo.toml @@ -10,7 +10,8 @@ crate-type = ["cdylib"] roc_builtins = {path = "../compiler/builtins"} [dependencies] -bumpalo = {version = "3.8.0", features = ["collections"]} +bumpalo = { version = "3.8.0", features = ["collections"] } +futures = { version = "0.3.17", optional = true } js-sys = "0.3.56" wasm-bindgen = "0.2.79" wasm-bindgen-futures = "0.4.29" @@ -22,3 +23,6 @@ roc_parse = {path = "../compiler/parse"} roc_repl_eval = {path = "../repl_eval"} roc_target = {path = "../compiler/roc_target"} roc_types = {path = "../compiler/types"} + +[features] +wasmer = ["futures"] diff --git a/repl_wasm/src/interface_js.rs b/repl_wasm/src/interface_js.rs new file mode 100644 index 0000000000..ec29dded39 --- /dev/null +++ b/repl_wasm/src/interface_js.rs @@ -0,0 +1,24 @@ +// wasm_bindgen procedural macro breaks this clippy rule +// https://github.com/rustwasm/wasm-bindgen/issues/2774 +#![allow(clippy::unused_unit)] + +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsValue; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(catch)] + pub async fn js_create_app(wasm_module_bytes: &[u8]) -> Result<(), JsValue>; + + pub fn js_run_app() -> usize; + + pub fn js_get_result_and_memory(buffer_alloc_addr: *mut u8) -> usize; +} + +/// Async entrypoint for the browser +/// The browser only has an async API to generate a Wasm module from bytes +/// wasm_bindgen manages the interaction between Rust Futures and JS Promises +#[wasm_bindgen] +pub async fn entrypoint_from_js(src: String) -> Result { + crate::repl::entrypoint_from_js(src).await +} diff --git a/repl_wasm/src/interface_test.rs b/repl_wasm/src/interface_test.rs new file mode 100644 index 0000000000..8fc99f2c87 --- /dev/null +++ b/repl_wasm/src/interface_test.rs @@ -0,0 +1,47 @@ +use futures::executor; + +extern "C" { + fn wasmer_create_app(app_bytes_ptr: *const u8, app_bytes_len: usize); + fn wasmer_run_app() -> usize; + fn wasmer_get_result_and_memory(buffer_alloc_addr: *mut u8) -> usize; + fn wasmer_copy_input_string(src_buffer_addr: *mut u8); + fn wasmer_copy_output_string(output_ptr: *const u8, output_len: usize); +} + +/// Async wrapper to match the equivalent JS function +pub async fn js_create_app(wasm_module_bytes: &[u8]) -> Result<(), String> { + unsafe { + wasmer_create_app(wasm_module_bytes.as_ptr(), wasm_module_bytes.len()); + } + Ok(()) +} + +pub fn js_run_app() -> usize { + unsafe { wasmer_run_app() } +} + +pub fn js_get_result_and_memory(buffer_alloc_addr: *mut u8) -> usize { + unsafe { wasmer_get_result_and_memory(buffer_alloc_addr) } +} + +/// Entrypoint for Wasmer tests +/// - Synchronous API, to avoid the need to run an async executor across the Wasm/native boundary. +/// (wasmer has a sync API for creating an Instance, whereas browsers don't) +/// - Uses an extra callback to allocate & copy the input string (wasm_bindgen does this for JS) +#[no_mangle] +pub extern "C" fn entrypoint_from_test(src_len: usize) -> bool { + let mut src_buffer = std::vec![0; src_len]; + let src = unsafe { + wasmer_copy_input_string(src_buffer.as_mut_ptr()); + String::from_utf8_unchecked(src_buffer) + }; + let result_async = crate::repl::entrypoint_from_js(src); + let result = executor::block_on(result_async); + let ok = result.is_ok(); + + let output = result.unwrap_or_else(|s| s); + + unsafe { wasmer_copy_output_string(output.as_ptr(), output.len()) } + + ok +} diff --git a/repl_wasm/src/lib.rs b/repl_wasm/src/lib.rs index 48910defba..be90212c28 100644 --- a/repl_wasm/src/lib.rs +++ b/repl_wasm/src/lib.rs @@ -1,258 +1,19 @@ -// wasm_bindgen procedural macro breaks this clippy rule -// https://github.com/rustwasm/wasm-bindgen/issues/2774 -#![allow(clippy::unused_unit)] +mod repl; -use bumpalo::{collections::vec::Vec, Bump}; -use std::mem::size_of; -use wasm_bindgen::prelude::wasm_bindgen; -use wasm_bindgen::JsValue; +// +// Interface with external JS in the browser +// +#[cfg(not(feature = "wasmer"))] +mod interface_js; +#[cfg(not(feature = "wasmer"))] +pub use interface_js::{entrypoint_from_js, js_create_app, js_get_result_and_memory, js_run_app}; -use roc_collections::all::MutSet; -use roc_gen_wasm::wasm32_result; -use roc_load::file::MonomorphizedModule; -use roc_parse::ast::Expr; -use roc_repl_eval::{ - eval::jit_to_ast, - gen::{compile_to_mono, format_answer, ReplOutput}, - ReplApp, ReplAppMemory, +// +// Interface with test code outside the Wasm module +// +#[cfg(feature = "wasmer")] +mod interface_test; +#[cfg(feature = "wasmer")] +pub use interface_test::{ + entrypoint_from_test, js_create_app, js_get_result_and_memory, js_run_app, }; -use roc_target::TargetInfo; -use roc_types::pretty_print::{content_to_string, name_all_type_vars}; - -const WRAPPER_NAME: &str = "wrapper"; - -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(catch)] - async fn js_create_app(wasm_module_bytes: &[u8]) -> Result<(), JsValue>; - - fn js_run_app() -> usize; - - fn js_get_result_and_memory(buffer_alloc_addr: *mut u8) -> usize; - - #[wasm_bindgen(js_namespace = console)] - fn log(s: &str); -} - -// In-browser debugging -#[allow(unused_macros)] -macro_rules! console_log { - ($($t:tt)*) => (log(&format_args!($($t)*).to_string())) -} - -pub struct WasmReplApp<'a> { - arena: &'a Bump, -} - -/// A copy of the app's memory, made after running the main function -/// The Wasm app ran in a separate address space from the compiler and the eval code. -/// This means we can't simply dereference its pointers as if they were local, because -/// an unrelated value may exist at the same-numbered address in our own address space! -/// Instead we have dereferencing methods that index into the copied bytes. -pub struct WasmMemory<'a> { - copied_bytes: &'a [u8], -} - -macro_rules! deref_number { - ($name: ident, $t: ty) => { - fn $name(&self, address: usize) -> $t { - const N: usize = size_of::<$t>(); - let mut array = [0; N]; - array.copy_from_slice(&self.copied_bytes[address..][..N]); - <$t>::from_le_bytes(array) - } - }; -} - -impl<'a> ReplAppMemory for WasmMemory<'a> { - fn deref_bool(&self, address: usize) -> bool { - self.copied_bytes[address] != 0 - } - - deref_number!(deref_u8, u8); - deref_number!(deref_u16, u16); - deref_number!(deref_u32, u32); - deref_number!(deref_u64, u64); - deref_number!(deref_u128, u128); - deref_number!(deref_usize, usize); - - deref_number!(deref_i8, i8); - deref_number!(deref_i16, i16); - deref_number!(deref_i32, i32); - deref_number!(deref_i64, i64); - deref_number!(deref_i128, i128); - deref_number!(deref_isize, isize); - - deref_number!(deref_f32, f32); - deref_number!(deref_f64, f64); - - fn deref_str(&self, addr: usize) -> &str { - let elems_addr = self.deref_usize(addr); - let len = self.deref_usize(addr + size_of::()); - let bytes = &self.copied_bytes[elems_addr..][..len]; - std::str::from_utf8(bytes).unwrap() - } -} - -impl<'a> ReplApp<'a> for WasmReplApp<'a> { - type Memory = WasmMemory<'a>; - - /// Run user code that returns a type with a `Builtin` layout - /// Size of the return value is statically determined from its Rust type - /// The `transform` callback takes the app's memory and the returned value - /// _main_fn_name is always the same and we don't use it here - fn call_function(&self, _main_fn_name: &str, transform: F) -> Expr<'a> - where - F: Fn(&'a Self::Memory, Return) -> Expr<'a>, - Self::Memory: 'a, - { - let app_final_memory_size: usize = js_run_app(); - - // Allocate a buffer to copy the app memory into - // Aligning it to 64 bits will preserve the original alignment of all Wasm numbers - let copy_buffer_aligned: &mut [u64] = self - .arena - .alloc_slice_fill_default((app_final_memory_size / size_of::()) + 1); - let copied_bytes: &mut [u8] = unsafe { std::mem::transmute(copy_buffer_aligned) }; - - let app_result_addr = js_get_result_and_memory(copied_bytes.as_mut_ptr()); - - let result: Return = unsafe { - let ptr: *const Return = std::mem::transmute(&copied_bytes[app_result_addr]); - ptr.read() - }; - let mem = self.arena.alloc(WasmMemory { copied_bytes }); - - transform(mem, result) - } - - /// Run user code that returns a struct or union, whose size is provided as an argument - /// The `transform` callback takes the app's memory and the address of the returned value - /// _main_fn_name and _ret_bytes are only used for the CLI REPL. For Wasm they are compiled-in - /// to the test_wrapper function of the app itself - fn call_function_dynamic_size( - &self, - _main_fn_name: &str, - _ret_bytes: usize, - transform: F, - ) -> T - where - F: Fn(&'a Self::Memory, usize) -> T, - Self::Memory: 'a, - { - let app_final_memory_size: usize = js_run_app(); - - // Allocate a buffer to copy the app memory into - // Aligning it to 64 bits will preserve the original alignment of all Wasm numbers - let copy_buffer_aligned: &mut [u64] = self - .arena - .alloc_slice_fill_default((app_final_memory_size / size_of::()) + 1); - let copied_bytes: &mut [u8] = unsafe { std::mem::transmute(copy_buffer_aligned) }; - - let app_result_addr = js_get_result_and_memory(copied_bytes.as_mut_ptr()); - let mem = self.arena.alloc(WasmMemory { copied_bytes }); - - transform(mem, app_result_addr) - } -} - -#[wasm_bindgen] -pub async fn entrypoint_from_js(src: String) -> Result { - let arena = &Bump::new(); - let pre_linked_binary: &'static [u8] = include_bytes!("../data/pre_linked_binary.o"); - - // Compile the app - let target_info = TargetInfo::default_wasm32(); - let mono = match compile_to_mono(arena, &src, target_info) { - Ok(m) => m, - Err(messages) => return Err(messages.join("\n\n")), - }; - - let MonomorphizedModule { - module_id, - procedures, - mut interns, - mut subs, - exposed_to_host, - .. - } = mono; - - debug_assert_eq!(exposed_to_host.values.len(), 1); - let (main_fn_symbol, main_fn_var) = exposed_to_host.values.iter().next().unwrap(); - let main_fn_symbol = *main_fn_symbol; - let main_fn_var = *main_fn_var; - - // pretty-print the expr type string for later. - name_all_type_vars(main_fn_var, &mut subs); - let content = subs.get_content_without_compacting(main_fn_var); - let expr_type_str = content_to_string(content, &subs, module_id, &interns); - - let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) { - Some(layout) => *layout, - None => return Ok(format!(" : {}", expr_type_str)), - }; - - let app_module_bytes = { - let env = roc_gen_wasm::Env { - arena, - module_id, - exposed_to_host: exposed_to_host - .values - .keys() - .copied() - .collect::>(), - }; - - let (mut module, called_preload_fns, main_fn_index) = { - roc_gen_wasm::build_module_without_wrapper( - &env, - &mut interns, // NOTE: must drop this mutable ref before jit_to_ast - pre_linked_binary, - procedures, - ) - }; - - wasm32_result::insert_wrapper_for_layout( - arena, - &mut module, - WRAPPER_NAME, - main_fn_index, - &main_fn_layout.result, - ); - - module.remove_dead_preloads(env.arena, called_preload_fns); - - let mut buffer = Vec::with_capacity_in(module.size(), arena); - module.serialize(&mut buffer); - - buffer - }; - - // Send the compiled binary out to JS and create an executable instance from it - js_create_app(&app_module_bytes) - .await - .map_err(|js| format!("{:?}", js))?; - - let app = WasmReplApp { arena }; - - // Run the app and transform the result value to an AST `Expr` - // Restore type constructor names, and other user-facing info that was erased during compilation. - let res_answer = jit_to_ast( - arena, - &app, - "", // main_fn_name is ignored (only passed to WasmReplApp methods) - main_fn_layout, - content, - &interns, - module_id, - &subs, - target_info, - ); - - // Transform the Expr to a string - // `Result::Err` becomes a JS exception that will be caught and displayed - match format_answer(arena, res_answer, expr_type_str) { - ReplOutput::NoProblems { expr, expr_type } => Ok(format!("\n{}: {}", expr, expr_type)), - ReplOutput::Problems(lines) => Err(format!("\n{}\n", lines.join("\n\n"))), - } -} diff --git a/repl_wasm/src/repl.rs b/repl_wasm/src/repl.rs new file mode 100644 index 0000000000..5c3686b664 --- /dev/null +++ b/repl_wasm/src/repl.rs @@ -0,0 +1,243 @@ +use bumpalo::{collections::vec::Vec, Bump}; +use std::mem::size_of; + +use roc_collections::all::MutSet; +use roc_gen_wasm::wasm32_result; +use roc_load::file::MonomorphizedModule; +use roc_parse::ast::Expr; +use roc_repl_eval::{ + eval::jit_to_ast, + gen::{compile_to_mono, format_answer, ReplOutput}, + ReplApp, ReplAppMemory, +}; +use roc_target::TargetInfo; +use roc_types::pretty_print::{content_to_string, name_all_type_vars}; + +use crate::{js_create_app, js_get_result_and_memory, js_run_app}; + +const WRAPPER_NAME: &str = "wrapper"; + +pub struct WasmReplApp<'a> { + arena: &'a Bump, +} + +/// A copy of the app's memory, made after running the main function +/// The Wasm app ran in a separate address space from the compiler and the eval code. +/// This means we can't simply dereference its pointers as if they were local, because +/// an unrelated value may exist at the same-numbered address in our own address space! +/// Instead we have dereferencing methods that index into the copied bytes. +pub struct WasmMemory<'a> { + copied_bytes: &'a [u8], +} + +macro_rules! deref_number { + ($name: ident, $t: ty) => { + fn $name(&self, address: usize) -> $t { + const N: usize = size_of::<$t>(); + let mut array = [0; N]; + array.copy_from_slice(&self.copied_bytes[address..][..N]); + <$t>::from_le_bytes(array) + } + }; +} + +impl<'a> ReplAppMemory for WasmMemory<'a> { + fn deref_bool(&self, address: usize) -> bool { + self.copied_bytes[address] != 0 + } + + deref_number!(deref_u8, u8); + deref_number!(deref_u16, u16); + deref_number!(deref_u32, u32); + deref_number!(deref_u64, u64); + deref_number!(deref_u128, u128); + deref_number!(deref_usize, usize); + + deref_number!(deref_i8, i8); + deref_number!(deref_i16, i16); + deref_number!(deref_i32, i32); + deref_number!(deref_i64, i64); + deref_number!(deref_i128, i128); + deref_number!(deref_isize, isize); + + deref_number!(deref_f32, f32); + deref_number!(deref_f64, f64); + + fn deref_str(&self, addr: usize) -> &str { + let elems_addr = self.deref_usize(addr); + let len = self.deref_usize(addr + size_of::()); + let bytes = &self.copied_bytes[elems_addr..][..len]; + std::str::from_utf8(bytes).unwrap() + } +} + +impl<'a> WasmReplApp<'a> { + /// Allocate a buffer to copy the app memory into + /// Buffer is aligned to 64 bits to preserve the original alignment of all Wasm numbers + fn allocate_buffer(&self, size: usize) -> &'a mut [u8] { + let size64 = (size / size_of::()) + 1; + let buffer64: &mut [u64] = self.arena.alloc_slice_fill_default(size64); + + // Note: Need `from_raw_parts_mut` as well as `transmute` to ensure slice has correct length! + let buffer: &mut [u8] = unsafe { + let ptr8: *mut u8 = std::mem::transmute(buffer64.as_mut_ptr()); + std::slice::from_raw_parts_mut(ptr8, size) + }; + + buffer + } +} + +impl<'a> ReplApp<'a> for WasmReplApp<'a> { + type Memory = WasmMemory<'a>; + + /// Run user code that returns a type with a `Builtin` layout + /// Size of the return value is statically determined from its Rust type + /// The `transform` callback takes the app's memory and the returned value + /// _main_fn_name is always the same and we don't use it here + fn call_function(&self, _main_fn_name: &str, transform: F) -> Expr<'a> + where + F: Fn(&'a Self::Memory, Return) -> Expr<'a>, + Self::Memory: 'a, + { + let app_final_memory_size: usize = js_run_app(); + + let copied_bytes: &mut [u8] = self.allocate_buffer(app_final_memory_size); + + let app_result_addr = js_get_result_and_memory(copied_bytes.as_mut_ptr()); + + let result_bytes = &copied_bytes[app_result_addr..]; + let result: Return = unsafe { + let ptr: *const Return = std::mem::transmute(result_bytes.as_ptr()); + ptr.read() + }; + + let mem = self.arena.alloc(WasmMemory { copied_bytes }); + + transform(mem, result) + } + + /// Run user code that returns a struct or union, whose size is provided as an argument + /// The `transform` callback takes the app's memory and the address of the returned value + /// _main_fn_name and _ret_bytes are only used for the CLI REPL. For Wasm they are compiled-in + /// to the test_wrapper function of the app itself + fn call_function_dynamic_size( + &self, + _main_fn_name: &str, + _ret_bytes: usize, + transform: F, + ) -> T + where + F: Fn(&'a Self::Memory, usize) -> T, + Self::Memory: 'a, + { + let app_final_memory_size: usize = js_run_app(); + + let copied_bytes: &mut [u8] = self.allocate_buffer(app_final_memory_size); + + let app_result_addr = js_get_result_and_memory(copied_bytes.as_mut_ptr()); + let mem = self.arena.alloc(WasmMemory { copied_bytes }); + + transform(mem, app_result_addr) + } +} + +pub async fn entrypoint_from_js(src: String) -> Result { + let arena = &Bump::new(); + let pre_linked_binary: &'static [u8] = include_bytes!("../data/pre_linked_binary.o"); + + // Compile the app + let target_info = TargetInfo::default_wasm32(); + let mono = match compile_to_mono(arena, &src, target_info) { + Ok(m) => m, + Err(messages) => return Err(messages.join("\n\n")), + }; + + let MonomorphizedModule { + module_id, + procedures, + mut interns, + mut subs, + exposed_to_host, + .. + } = mono; + + debug_assert_eq!(exposed_to_host.values.len(), 1); + let (main_fn_symbol, main_fn_var) = exposed_to_host.values.iter().next().unwrap(); + let main_fn_symbol = *main_fn_symbol; + let main_fn_var = *main_fn_var; + + // pretty-print the expr type string for later. + name_all_type_vars(main_fn_var, &mut subs); + let content = subs.get_content_without_compacting(main_fn_var); + let expr_type_str = content_to_string(content, &subs, module_id, &interns); + + let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) { + Some(layout) => *layout, + None => return Ok(format!(" : {}", expr_type_str)), + }; + + let app_module_bytes = { + let env = roc_gen_wasm::Env { + arena, + module_id, + exposed_to_host: exposed_to_host + .values + .keys() + .copied() + .collect::>(), + }; + + let (mut module, called_preload_fns, main_fn_index) = { + roc_gen_wasm::build_module_without_wrapper( + &env, + &mut interns, // NOTE: must drop this mutable ref before jit_to_ast + pre_linked_binary, + procedures, + ) + }; + + wasm32_result::insert_wrapper_for_layout( + arena, + &mut module, + WRAPPER_NAME, + main_fn_index, + &main_fn_layout.result, + ); + + module.remove_dead_preloads(env.arena, called_preload_fns); + + let mut buffer = Vec::with_capacity_in(module.size(), arena); + module.serialize(&mut buffer); + + buffer + }; + + // Send the compiled binary out to JS and create an executable instance from it + js_create_app(&app_module_bytes) + .await + .map_err(|js| format!("{:?}", js))?; + + let app = WasmReplApp { arena }; + + // Run the app and transform the result value to an AST `Expr` + // Restore type constructor names, and other user-facing info that was erased during compilation. + let res_answer = jit_to_ast( + arena, + &app, + "", // main_fn_name is ignored (only passed to WasmReplApp methods) + main_fn_layout, + content, + &interns, + module_id, + &subs, + target_info, + ); + + // Transform the Expr to a string + // `Result::Err` becomes a JS exception that will be caught and displayed + match format_answer(arena, res_answer, expr_type_str) { + ReplOutput::NoProblems { expr, expr_type } => Ok(format!("{} : {}", expr, expr_type)), + ReplOutput::Problems(lines) => Err(format!("\n{}\n", lines.join("\n\n"))), + } +} diff --git a/repl_www/build.sh b/repl_www/build.sh index 549dbfaf1c..bf1d944864 100755 --- a/repl_www/build.sh +++ b/repl_www/build.sh @@ -1,6 +1,6 @@ #!/bin/bash -set -eux +set -ex if [[ ! -d repl_www ]] then @@ -17,12 +17,15 @@ WWW_DIR="repl_www/build" mkdir -p $WWW_DIR cp repl_www/public/* $WWW_DIR -# Pass all script arguments through to wasm-pack # For debugging, pass the --profiling option, which enables optimizations + debug info # (We need optimizations to get rid of dead code that otherwise causes compile errors!) -cargo build --target wasm32-unknown-unknown -p roc_repl_wasm --release -wasm-bindgen --target web --keep-debug target/wasm32-unknown-unknown/release/roc_repl_wasm.wasm --out-dir repl_wasm/pkg/ -# wasm-pack build --target web "$@" repl_wasm +if [ -n "$REPL_DEBUG" ] +then + cargo build --target wasm32-unknown-unknown -p roc_repl_wasm --release + wasm-bindgen --target web --keep-debug target/wasm32-unknown-unknown/release/roc_repl_wasm.wasm --out-dir repl_wasm/pkg/ +else + wasm-pack build --target web repl_wasm +fi cp repl_wasm/pkg/*.wasm $WWW_DIR diff --git a/repl_www/public/index.html b/repl_www/public/index.html index 20e4ed3ba2..e08ec457a1 100644 --- a/repl_www/public/index.html +++ b/repl_www/public/index.html @@ -70,7 +70,7 @@ margin-bottom: 16px; } - Mock REPL + Roc REPL
diff --git a/repl_www/public/repl.js b/repl_www/public/repl.js index ecd07d8b5b..350ac9a066 100644 --- a/repl_www/public/repl.js +++ b/repl_www/public/repl.js @@ -3,6 +3,7 @@ window.js_create_app = js_create_app; window.js_run_app = js_run_app; window.js_get_result_and_memory = js_get_result_and_memory; import * as roc_repl_wasm from "./roc_repl_wasm.js"; +import { getMockWasiImports } from "./wasi.js"; // ---------------------------------------------------------------------------- // REPL state @@ -74,15 +75,21 @@ async function processInputQueue() { // Create an executable Wasm instance from an array of bytes // (Browser validates the module and does the final compilation to the host's machine code.) async function js_create_app(wasm_module_bytes) { - const { instance } = await WebAssembly.instantiate(wasm_module_bytes); + const wasiLinkObject = {}; // gives the WASI functions a reference to the app so they can write to its memory + const importObj = getMockWasiImports(wasiLinkObject); + const { instance } = await WebAssembly.instantiate( + wasm_module_bytes, + importObj + ); + wasiLinkObject.instance = instance; repl.app = instance; } // Call the main function of the app, via the test wrapper // Cache the result and return the size of the app's memory function js_run_app() { - const { run, memory } = repl.app.exports; - const addr = run(); + const { wrapper, memory } = repl.app.exports; + const addr = wrapper(); const { buffer } = memory; repl.result = { addr, buffer }; diff --git a/repl_www/public/wasi.js b/repl_www/public/wasi.js new file mode 100644 index 0000000000..f636d055c7 --- /dev/null +++ b/repl_www/public/wasi.js @@ -0,0 +1,192 @@ +/** + * Browser implementation of the WebAssembly System Interface (WASI) + * The REPL generates an "app" from the user's Roc code and it can import from WASI + * + * We only implement writes to stdout/stderr as console.log/console.err, and proc_exit as `throw` + * The rest of the interface just consists of dummy functions with the right number of arguments + * + * The wasiLinkObject provides a reference to the app so we can write to its memory + */ + +export function getMockWasiImports(wasiLinkObject) { + const decoder = new TextDecoder(); + + // If the app ever resizes its memory, there will be a new buffer instance + // so we get a fresh reference every time, just in case + function getMemory8() { + return new Uint8Array(wasiLinkObject.instance.exports.memory.buffer); + } + + function getMemory32() { + return new Uint32Array(wasiLinkObject.instance.exports.memory.buffer); + } + + // fd_close : (i32) -> i32 + // Close a file descriptor. Note: This is similar to close in POSIX. + // https://docs.rs/wasi/latest/wasi/wasi_snapshot_preview1/fn.fd_close.html + function fd_close(fd) { + console.warn(`fd_close: ${{ fd }}`); + return 0; // error code + } + + // fd_fdstat_get : (i32, i32) -> i32 + // Get the attributes of a file descriptor. + // https://docs.rs/wasi/latest/wasi/wasi_snapshot_preview1/fn.fd_fdstat_get.html + function fd_fdstat_get(fd, stat_mut_ptr) { + /* + Tell WASI that stdout is a tty (no seek or tell) + + https://github.com/WebAssembly/wasi-libc/blob/659ff414560721b1660a19685110e484a081c3d4/libc-bottom-half/sources/isatty.c + + *Not* a tty if: + (statbuf.fs_filetype != __WASI_FILETYPE_CHARACTER_DEVICE || + (statbuf.fs_rights_base & (__WASI_RIGHTS_FD_SEEK | __WASI_RIGHTS_FD_TELL)) != 0) + + So it's sufficient to set: + .fs_filetype = __WASI_FILETYPE_CHARACTER_DEVICE + .fs_rights_base = 0 + + https://github.com/WebAssembly/wasi-libc/blob/659ff414560721b1660a19685110e484a081c3d4/libc-bottom-half/headers/public/wasi/api.h + + typedef uint8_t __wasi_filetype_t; + typedef uint16_t __wasi_fdflags_t; + typedef uint64_t __wasi_rights_t; + #define __WASI_FILETYPE_CHARACTER_DEVICE (UINT8_C(2)) + typedef struct __wasi_fdstat_t { // 24 bytes total + __wasi_filetype_t fs_filetype; // 1 byte + // 1 byte padding + __wasi_fdflags_t fs_flags; // 2 bytes + // 4 bytes padding + __wasi_rights_t fs_rights_base; // 8 bytes + __wasi_rights_t fs_rights_inheriting; // 8 bytes + } __wasi_fdstat_t; + */ + // console.warn(`fd_fdstat_get: ${{ fd, stat_mut_ptr }}`); + const WASI_FILETYPE_CHARACTER_DEVICE = 2; + const memory8 = getMemory8(); + memory8[stat_mut_ptr] = WASI_FILETYPE_CHARACTER_DEVICE; + memory8.slice(stat_mut_ptr + 1, stat_mut_ptr + 24).fill(0); + + return 0; // error code + } + + // fd_seek : (i32, i64, i32, i32) -> i32 + // Move the offset of a file descriptor. Note: This is similar to lseek in POSIX. + // https://docs.rs/wasi/latest/wasi/wasi_snapshot_preview1/fn.fd_seek.html + function fd_seek(fd, offset, whence, newoffset_mut_ptr) { + console.warn(`fd_seek: ${{ fd, offset, whence, newoffset_mut_ptr }}`); + return 0; + } + + // fd_write : (i32, i32, i32, i32) -> i32 + // Write to a file descriptor. Note: This is similar to `writev` in POSIX. + // https://docs.rs/wasi/latest/wasi/wasi_snapshot_preview1/fn.fd_write.html + function fd_write(fd, iovs_ptr, iovs_len, nwritten_mut_ptr) { + let string_buffer = ""; + let nwritten = 0; + const memory32 = getMemory32(); + + for (let i = 0; i < iovs_len; i++) { + const index32 = iovs_ptr >> 2; + const base = memory32[index32]; + const len = memory32[index32 + 1]; + iovs_ptr += 8; + + if (!len) continue; + + nwritten += len; + + // For some reason we often get negative-looking buffer lengths with junk data. + // Just skip the console.log, but still increase nwritten or it will loop forever. + // Dunno why this happens, but it's working fine for printf debugging ¯\_(ツ)_/¯ + if (len >> 31) { + break; + } + + const buf = getMemory8().slice(base, base + len); + const chunk = decoder.decode(buf); + string_buffer += chunk; + } + memory32[nwritten_mut_ptr >> 2] = nwritten; + if (string_buffer) { + console.log(string_buffer); + } + return 0; + } + + // proc_exit : (i32) -> nil + function proc_exit(exit_code) { + if (exit_code) { + throw new Error(`Wasm exited with code ${exit_code}`); + } + } + + // Signatures from wasm_test_platform.o + const sig2 = (i32) => {}; + const sig6 = (i32a, i32b) => 0; + const sig7 = (i32a, i32b, i32c) => 0; + const sig9 = (i32a, i64b, i32c) => 0; + const sig10 = (i32a, i64b, i64c, i32d) => 0; + const sig11 = (i32a, i64b, i64c) => 0; + const sig12 = (i32a) => 0; + const sig13 = (i32a, i64b) => 0; + const sig14 = (i32a, i32b, i32c, i64d, i32e) => 0; + const sig15 = (i32a, i32b, i32c, i32d) => 0; + const sig16 = (i32a, i64b, i32c, i32d) => 0; + const sig17 = (i32a, i32b, i32c, i32d, i32e) => 0; + const sig18 = (i32a, i32b, i32c, i32d, i64e, i64f, i32g) => 0; + const sig19 = (i32a, i32b, i32c, i32d, i32e, i32f, i32g) => 0; + const sig20 = (i32a, i32b, i32c, i32d, i32e, i64f, i64g, i32h, i32i) => 0; + const sig21 = (i32a, i32b, i32c, i32d, i32e, i32f) => 0; + const sig22 = () => 0; + + return { + wasi_snapshot_preview1: { + args_get: sig6, + args_sizes_get: sig6, + environ_get: sig6, + environ_sizes_get: sig6, + clock_res_get: sig6, + clock_time_get: sig9, + fd_advise: sig10, + fd_allocate: sig11, + fd_close, + fd_datasync: sig12, + fd_fdstat_get, + fd_fdstat_set_flags: sig6, + fd_fdstat_set_rights: sig11, + fd_filestat_get: sig6, + fd_filestat_set_size: sig13, + fd_filestat_set_times: sig10, + fd_pread: sig14, + fd_prestat_get: sig6, + fd_prestat_dir_name: sig7, + fd_pwrite: sig14, + fd_read: sig15, + fd_readdir: sig14, + fd_renumber: sig6, + fd_seek, + fd_sync: sig12, + fd_tell: sig6, + fd_write, + path_create_directory: sig7, + path_filestat_get: sig17, + path_filestat_set_times: sig18, + path_link: sig19, + path_open: sig20, + path_readlink: sig21, + path_remove_directory: sig7, + path_rename: sig21, + path_symlink: sig17, + path_unlink_file: sig7, + poll_oneoff: sig15, + proc_exit, + proc_raise: sig12, + sched_yield: sig22, + random_get: sig6, + sock_recv: sig21, + sock_send: sig17, + sock_shutdown: sig6, + }, + }; +} diff --git a/reporting/src/error/canonicalize.rs b/reporting/src/error/canonicalize.rs index 3759c26afa..351d2e9a23 100644 --- a/reporting/src/error/canonicalize.rs +++ b/reporting/src/error/canonicalize.rs @@ -29,6 +29,8 @@ const NESTED_DATATYPE: &str = "NESTED DATATYPE"; const CONFLICTING_NUMBER_SUFFIX: &str = "CONFLICTING NUMBER SUFFIX"; const NUMBER_OVERFLOWS_SUFFIX: &str = "NUMBER OVERFLOWS SUFFIX"; const NUMBER_UNDERFLOWS_SUFFIX: &str = "NUMBER UNDERFLOWS SUFFIX"; +const OPAQUE_NOT_DEFINED: &str = "OPAQUE NOT DEFINED"; +const OPAQUE_DECLARED_OUTSIDE_SCOPE: &str = "OPAQUE DECLARED OUTSIDE SCOPE"; pub fn can_problem<'b>( alloc: &'b RocDocAllocator<'b>, @@ -221,7 +223,7 @@ pub fn can_problem<'b>( severity = Severity::RuntimeError; } Problem::PhantomTypeArgument { - alias, + typ: alias, variable_region, variable_name, } => { @@ -386,11 +388,14 @@ pub fn can_problem<'b>( title = NAMING_PROBLEM.to_string(); severity = Severity::RuntimeError; } - Problem::InvalidAliasRigid { alias_name, region } => { + Problem::InvalidAliasRigid { + alias_name: type_name, + region, + } => { doc = alloc.stack(vec![ alloc.concat(vec![ alloc.reflow("This pattern in the definition of "), - alloc.symbol_unqualified(alias_name), + alloc.symbol_unqualified(type_name), alloc.reflow(" is not what I expect:"), ]), alloc.region(lines.convert_region(region)), @@ -631,8 +636,13 @@ fn to_bad_ident_expr_report<'b>( ]) } - BadPrivateTag(pos) => { + BadPrivateTag(pos) | BadOpaqueRef(pos) => { use BadIdentNext::*; + let kind = if matches!(bad_ident, BadPrivateTag(..)) { + "a private tag" + } else { + "an opaque reference" + }; match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { LowercaseAccess(width) => { let region = Region::new(pos, pos.bump_column(width)); @@ -643,7 +653,9 @@ fn to_bad_ident_expr_report<'b>( lines.convert_region(region), ), alloc.concat(vec![ - alloc.reflow(r"It looks like a record field access on a private tag.") + alloc.reflow(r"It looks like a record field access on "), + alloc.reflow(kind), + alloc.text("."), ]), ]) } @@ -656,9 +668,9 @@ fn to_bad_ident_expr_report<'b>( lines.convert_region(region), ), alloc.concat(vec![ - alloc.reflow( - r"Looks like a private tag is treated like a module name. ", - ), + alloc.reflow(r"Looks like "), + alloc.reflow(kind), + alloc.reflow(" is treated like a module name. "), alloc.reflow(r"Maybe you wanted a qualified name, like "), alloc.parser_suggestion("Json.Decode.string"), alloc.text("?"), @@ -669,7 +681,11 @@ fn to_bad_ident_expr_report<'b>( let region = Region::new(surroundings.start().bump_column(1), pos.bump_column(1)); alloc.stack(vec![ - alloc.reflow("I am trying to parse a private tag here:"), + alloc.concat(vec![ + alloc.reflow("I am trying to parse "), + alloc.reflow(kind), + alloc.reflow(" here:"), + ]), alloc.region_with_subregion( lines.convert_region(surroundings), lines.convert_region(region), @@ -929,13 +945,17 @@ fn pretty_runtime_error<'b>( } Unknown => " ", QualifiedIdentifier => " qualified ", + EmptySingleQuote => " empty character literal ", + MultipleCharsInSingleQuote => " overfull literal ", }; let tip = match problem { MalformedInt | MalformedFloat | MalformedBase(_) => alloc .tip() .append(alloc.reflow("Learn more about number literals at TODO")), - Unknown | BadIdent(_) => alloc.nil(), + EmptySingleQuote | MultipleCharsInSingleQuote | Unknown | BadIdent(_) => { + alloc.nil() + } QualifiedIdentifier => alloc.tip().append( alloc.reflow("In patterns, only private and global tags can be qualified"), ), @@ -999,8 +1019,16 @@ fn pretty_runtime_error<'b>( module_name, imported_modules, region, + module_exists, } => { - doc = module_not_found(alloc, lines, region, &module_name, imported_modules); + doc = module_not_found( + alloc, + lines, + region, + &module_name, + imported_modules, + module_exists, + ); title = MODULE_NOT_IMPORTED; } @@ -1317,6 +1345,108 @@ fn pretty_runtime_error<'b>( title = MISSING_DEFINITION; } + RuntimeError::EmptySingleQuote(region) => { + let tip = alloc + .tip() + .append(alloc.reflow("Learn more about character literals at TODO")); + + doc = alloc.stack(vec![ + alloc.concat(vec![alloc.reflow("This character literal is empty.")]), + alloc.region(lines.convert_region(region)), + tip, + ]); + + title = SYNTAX_PROBLEM; + } + RuntimeError::MultipleCharsInSingleQuote(region) => { + let tip = alloc + .tip() + .append(alloc.reflow("Learn more about character literals at TODO")); + + doc = alloc.stack(vec![ + alloc.concat(vec![ + alloc.reflow("This character literal contains more than one code point.") + ]), + alloc.region(lines.convert_region(region)), + alloc.concat(vec![ + alloc.reflow("Character literals can only contain one code point.") + ]), + tip, + ]); + + title = SYNTAX_PROBLEM; + } + RuntimeError::OpaqueNotDefined { + usage: + Loc { + region: used_region, + value: opaque, + }, + opaques_in_scope, + opt_defined_alias, + } => { + let mut suggestions = suggest::sort( + opaque.as_inline_str().as_str(), + opaques_in_scope.iter().map(|v| v.as_ref()).collect(), + ); + suggestions.truncate(4); + + let details = if suggestions.is_empty() { + alloc.note("It looks like there are no opaque types declared in this scope yet!") + } else { + let qualified_suggestions = + suggestions.into_iter().map(|v| alloc.string(v.to_string())); + alloc.stack(vec![ + alloc + .tip() + .append(alloc.reflow("Did you mean one of these opaque types?")), + alloc.vcat(qualified_suggestions).indent(4), + ]) + }; + + let mut stack = vec![ + alloc.concat(vec![ + alloc.reflow("The opaque type "), + alloc.type_str(opaque.as_inline_str().as_str()), + alloc.reflow(" referenced here is not defined:"), + ]), + alloc.region(lines.convert_region(used_region)), + ]; + + if let Some(defined_alias_region) = opt_defined_alias { + stack.push(alloc.stack(vec![ + alloc.note("There is an alias of the same name:"), + alloc.region(lines.convert_region(defined_alias_region)), + ])); + } + + stack.push(details); + + doc = alloc.stack(stack); + + title = OPAQUE_NOT_DEFINED; + } + RuntimeError::OpaqueOutsideScope { + opaque, + referenced_region, + imported_region, + } => { + doc = alloc.stack(vec![ + alloc.concat(vec![ + alloc.reflow("The unwrapped opaque type "), + alloc.type_str(opaque.as_inline_str().as_str()), + alloc.reflow(" referenced here:"), + ]), + alloc.region(lines.convert_region(referenced_region)), + alloc.reflow("is imported from another module:"), + alloc.region(lines.convert_region(imported_region)), + alloc.note( + "Opaque types can only be wrapped and unwrapped in the module they are defined in!", + ), + ]); + + title = OPAQUE_DECLARED_OUTSIDE_SCOPE; + } } (doc, title) @@ -1414,34 +1544,39 @@ fn not_found<'b>( ]) } +/// Generate a message informing the user that a module was referenced, but not found +/// +/// See [`roc_problem::can::ModuleNotImported`] fn module_not_found<'b>( alloc: &'b RocDocAllocator<'b>, lines: &LineInfo, region: roc_region::all::Region, name: &ModuleName, options: MutSet>, + module_exists: bool, ) -> RocDocBuilder<'b> { - let mut suggestions = - suggest::sort(name.as_str(), options.iter().map(|v| v.as_ref()).collect()); - suggestions.truncate(4); + // If the module exists, sugguest that the user import it + let details = if module_exists { + // TODO: Maybe give an example of how to do that + alloc.reflow("Did you mean to import it?") + } else { + // If the module might not exist, sugguest that it's a typo + let mut suggestions = + suggest::sort(name.as_str(), options.iter().map(|v| v.as_ref()).collect()); + suggestions.truncate(4); - let default_no = alloc.concat(vec![ - alloc.reflow("Is there an "), - alloc.keyword("import"), - alloc.reflow(" or "), - alloc.keyword("exposing"), - alloc.reflow(" missing up-top"), - ]); - - let default_yes = alloc - .reflow("Is there an import missing? Perhaps there is a typo. Did you mean one of these?"); - - let to_details = |no_suggestion_details, yes_suggestion_details| { if suggestions.is_empty() { - no_suggestion_details + // We don't have any recommended spelling corrections + alloc.concat(vec![ + alloc.reflow("Is there an "), + alloc.keyword("import"), + alloc.reflow(" or "), + alloc.keyword("exposing"), + alloc.reflow(" missing up-top"), + ]) } else { alloc.stack(vec![ - yes_suggestion_details, + alloc.reflow("Is there an import missing? Perhaps there is a typo. Did you mean one of these?"), alloc .vcat(suggestions.into_iter().map(|v| alloc.string(v.to_string()))) .indent(4), @@ -1456,6 +1591,6 @@ fn module_not_found<'b>( alloc.reflow("` module is not imported:"), ]), alloc.region(lines.convert_region(region)), - to_details(default_no, default_yes), + details, ]) } diff --git a/reporting/src/error/type.rs b/reporting/src/error/type.rs index a9c1251647..8f10a978fb 100644 --- a/reporting/src/error/type.rs +++ b/reporting/src/error/type.rs @@ -1100,7 +1100,6 @@ fn format_category<'b>( ]), alloc.text(" produces:"), ), - List => ( alloc.concat(vec![this_is, alloc.text(" a list")]), alloc.text(" of type:"), @@ -1128,17 +1127,18 @@ fn format_category<'b>( ]), alloc.text(" which was of type:"), ), - + Character => ( + alloc.concat(vec![this_is, alloc.text(" a character")]), + alloc.text(" of type:"), + ), Lambda => ( alloc.concat(vec![this_is, alloc.text(" an anonymous function")]), alloc.text(" of type:"), ), - ClosureSize => ( alloc.concat(vec![this_is, alloc.text(" the closure size of a function")]), alloc.text(" of type:"), ), - TagApply { tag_name: TagName::Global(name), args_count: 0, @@ -1472,6 +1472,7 @@ fn add_pattern_category<'b>( Num => alloc.reflow(" numbers:"), Int => alloc.reflow(" integers:"), Float => alloc.reflow(" floats:"), + Character => alloc.reflow(" characters:"), }; alloc.concat(vec![i_am_trying_to_match, rest]) diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 20011557c9..c16690ec0d 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -3134,6 +3134,32 @@ mod test_reporting { ) } + #[test] + fn invalid_opaque_rigid_var_pattern() { + report_problem_as( + indoc!( + r#" + Age 1 := I64 + + a : Age + a + "# + ), + indoc!( + r#" + ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + + This pattern in the definition of `Age` is not what I expect: + + 1│ Age 1 := I64 + ^ + + Only type variables like `a` or `value` can occur in this position. + "# + ), + ) + } + #[test] fn invalid_num() { report_problem_as( @@ -5820,6 +5846,29 @@ I need all branches in an `if` to have the same type! ) } + #[test] + fn opaque_ref_field_access() { + report_problem_as( + indoc!( + r#" + $UUID.bar + "# + ), + indoc!( + r#" + ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + + I am very confused by this field access: + + 1│ $UUID.bar + ^^^^ + + It looks like a record field access on an opaque reference. + "# + ), + ) + } + #[test] fn weird_accessor() { report_problem_as( @@ -8087,4 +8136,173 @@ I need all branches in an `if` to have the same type! "", ) } + + #[test] + fn opaque_type_not_in_scope() { + report_problem_as( + indoc!( + r#" + $Age 21 + "# + ), + indoc!( + r#" + ── OPAQUE NOT DEFINED ────────────────────────────────────────────────────────── + + The opaque type Age referenced here is not defined: + + 1│ $Age 21 + ^^^^ + + Note: It looks like there are no opaque types declared in this scope yet! + "# + ), + ) + } + + #[test] + fn opaque_reference_not_opaque_type() { + report_problem_as( + indoc!( + r#" + Age : U8 + + $Age 21 + "# + ), + indoc!( + r#" + ── OPAQUE NOT DEFINED ────────────────────────────────────────────────────────── + + The opaque type Age referenced here is not defined: + + 3│ $Age 21 + ^^^^ + + Note: There is an alias of the same name: + + 1│ Age : U8 + ^^^ + + Note: It looks like there are no opaque types declared in this scope yet! + + ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + + `Age` is not used anywhere in your code. + + 1│ Age : U8 + ^^^^^^^^ + + If you didn't intend on using `Age` then remove it so future readers of + your code don't wonder why it is there. + "# + ), + ) + } + + #[test] + fn qualified_opaque_reference() { + report_problem_as( + indoc!( + r#" + OtherModule.$Age 21 + "# + ), + // TODO: get rid of the second error. Consider parsing OtherModule.$Age to completion + // and checking it during can. + indoc!( + r#" + ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + + I am trying to parse a qualified name here: + + 1│ OtherModule.$Age 21 + ^ + + I was expecting to see an identifier next, like height. A complete + qualified name looks something like Json.Decode.string. + + ── OPAQUE NOT DEFINED ────────────────────────────────────────────────────────── + + The opaque type Age referenced here is not defined: + + 1│ OtherModule.$Age 21 + ^^^^ + + Note: It looks like there are no opaque types declared in this scope yet! + "# + ), + ) + } + + #[test] + fn opaque_used_outside_declaration_scope() { + report_problem_as( + indoc!( + r#" + age = + Age := U8 + 21u8 + + $Age age + "# + ), + // TODO: there is a potential for a better error message here, if the usage of `$Age` can + // be linked to the declaration of `Age` inside `age`, and a suggestion to raise that + // declaration to the outer scope. + indoc!( + r#" + ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + + `Age` is not used anywhere in your code. + + 2│ Age := U8 + ^^^^^^^^^ + + If you didn't intend on using `Age` then remove it so future readers of + your code don't wonder why it is there. + + ── OPAQUE NOT DEFINED ────────────────────────────────────────────────────────── + + The opaque type Age referenced here is not defined: + + 5│ $Age age + ^^^^ + + Note: It looks like there are no opaque types declared in this scope yet! + "# + ), + ) + } + + #[test] + fn unimported_modules_reported() { + report_problem_as( + indoc!( + r#" + main : Task.Task {} [] + main = "whatever man you don't even know my type" + main + "# + ), + indoc!( + r#" + ── MODULE NOT IMPORTED ───────────────────────────────────────────────────────── + + The `Task` module is not imported: + + 1│ main : Task.Task {} [] + ^^^^^^^^^^^^^^^ + + Is there an import missing? Perhaps there is a typo. Did you mean one + of these? + + Test + List + Num + Set + "# + ), + ) + } } diff --git a/roc_std/Cargo.toml b/roc_std/Cargo.toml index ebdd072f11..7565655d4c 100644 --- a/roc_std/Cargo.toml +++ b/roc_std/Cargo.toml @@ -13,3 +13,8 @@ indoc = "1.0.3" pretty_assertions = "1.0.0" quickcheck = "1.0.3" quickcheck_macros = "1.0.0" +libc = "0.2.106" + +[features] +default = ["platform"] +platform = [] diff --git a/roc_std/src/lib.rs b/roc_std/src/lib.rs index 0880c83038..d40457eac4 100644 --- a/roc_std/src/lib.rs +++ b/roc_std/src/lib.rs @@ -1,13 +1,21 @@ #![crate_type = "lib"] #![no_std] -use core::convert::From; use core::ffi::c_void; -use core::fmt::{self, Display, Formatter}; +use core::fmt; use core::mem::{ManuallyDrop, MaybeUninit}; -use core::ops::{Deref, DerefMut, Drop}; -use core::{mem, ptr, slice}; +use core::ops::Drop; + +mod rc; +mod roc_list; +mod roc_str; +mod storage; + +pub use rc::ReferenceCount; +pub use roc_list::RocList; +pub use roc_str::RocStr; // A list of C functions that are being imported +#[cfg(feature = "platform")] extern "C" { pub fn roc_alloc(size: usize, alignment: u32) -> *mut c_void; pub fn roc_realloc( @@ -19,7 +27,29 @@ extern "C" { pub fn roc_dealloc(ptr: *mut c_void, alignment: u32); } -const REFCOUNT_1: isize = isize::MIN; +/// # Safety +/// This is only marked unsafe to typecheck without warnings in the rest of the code here. +#[cfg(not(feature = "platform"))] +pub unsafe extern "C" fn roc_alloc(_size: usize, _alignment: u32) -> *mut c_void { + unimplemented!("It is not valid to call roc alloc from within the compiler. Please use the \"platform\" feature if this is a platform.") +} +/// # Safety +/// This is only marked unsafe to typecheck without warnings in the rest of the code here. +#[cfg(not(feature = "platform"))] +pub unsafe extern "C" fn roc_realloc( + _ptr: *mut c_void, + _new_size: usize, + _old_size: usize, + _alignment: u32, +) -> *mut c_void { + unimplemented!("It is not valid to call roc realloc from within the compiler. Please use the \"platform\" feature if this is a platform.") +} +/// # Safety +/// This is only marked unsafe to typecheck without warnings in the rest of the code here. +#[cfg(not(feature = "platform"))] +pub unsafe extern "C" fn roc_dealloc(_ptr: *mut c_void, _alignment: u32) { + unimplemented!("It is not valid to call roc dealloc from within the compiler. Please use the \"platform\" feature if this is a platform.") +} #[repr(u8)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -29,781 +59,6 @@ pub enum RocOrder { Lt = 2, } -//#[macro_export] -//macro_rules! roclist { -// () => ( -// $crate::RocList::default() -// ); -// ($($x:expr),+ $(,)?) => ( -// $crate::RocList::from_slice(&[$($x),+]) -// ); -//} - -#[repr(C)] -pub struct RocList { - elements: *mut T, - length: usize, -} - -impl Clone for RocList { - fn clone(&self) -> Self { - Self::from_slice(self.as_slice()) - } -} - -#[derive(Clone, Copy, Debug)] -pub enum Storage { - ReadOnly, - Refcounted(isize), - Capacity(usize), -} - -impl RocList { - pub fn len(&self) -> usize { - self.length - } - - pub fn is_empty(&self) -> bool { - self.length == 0 - } - - pub fn get(&self, index: usize) -> Option<&T> { - if index < self.len() { - Some(unsafe { - let raw = self.elements.add(index); - - &*raw - }) - } else { - None - } - } - - pub fn storage(&self) -> Option { - use core::cmp::Ordering::*; - - if self.length == 0 { - return None; - } - - unsafe { - let value = *self.get_storage_ptr(); - - // NOTE doesn't work with elements of 16 or more bytes - match isize::cmp(&value, &0) { - Equal => Some(Storage::ReadOnly), - Less => Some(Storage::Refcounted(value)), - Greater => Some(Storage::Capacity(value as usize)), - } - } - } - - fn get_storage_ptr_help(elements: *mut T) -> *mut isize { - let ptr = elements as *mut isize; - - unsafe { ptr.offset(-1) } - } - - fn get_storage_ptr(&self) -> *const isize { - Self::get_storage_ptr_help(self.elements) - } - - fn get_storage_ptr_mut(&mut self) -> *mut isize { - self.get_storage_ptr() as *mut isize - } - - fn set_storage_ptr(&mut self, ptr: *const isize) { - self.elements = unsafe { ptr.offset(1) as *mut T }; - } - - fn get_element_ptr(elements: *const T) -> *const T { - let elem_alignment = core::mem::align_of::(); - let ptr = elements as *const usize; - - unsafe { - if elem_alignment <= core::mem::align_of::() { - ptr.add(1) as *const T - } else { - // If elements have an alignment bigger than usize (e.g. an i128), - // we will have necessarily allocated two usize slots worth of - // space for the storage value (with the first usize slot being - // padding for alignment's sake), and we need to skip past both. - ptr.add(2) as *const T - } - } - } - - pub fn from_slice_with_capacity(slice: &[T], capacity: usize) -> Self - where - T: Clone, - { - assert!(capacity > 0); - assert!(slice.len() <= capacity); - - let element_bytes = capacity * core::mem::size_of::(); - - let padding = { - if core::mem::align_of::() <= core::mem::align_of::() { - // aligned on usize (8 bytes on 64-bit systems) - 0 - } else { - // aligned on 2*usize (16 bytes on 64-bit systems) - core::mem::size_of::() - } - }; - - let num_bytes = core::mem::size_of::() + padding + element_bytes; - - let elements = unsafe { - let raw_ptr = roc_alloc(num_bytes, core::mem::size_of::() as u32) as *mut u8; - - // pointer to the first element - let raw_ptr = Self::get_element_ptr(raw_ptr as *mut T) as *mut T; - - // write the refcount - let refcount_ptr = raw_ptr as *mut isize; - *(refcount_ptr.offset(-1)) = isize::MIN; - - // Clone the elements into the new array. - let target_ptr = raw_ptr; - for (i, value) in slice.iter().cloned().enumerate() { - let target_ptr = target_ptr.add(i); - target_ptr.write(value); - } - - raw_ptr - }; - - Self { - length: slice.len(), - elements, - } - } - - pub fn from_slice(slice: &[T]) -> Self - where - T: Clone, - { - // Avoid allocation with empty list. - if slice.is_empty() { - Self::default() - } else { - Self::from_slice_with_capacity(slice, slice.len()) - } - } - - pub fn as_slice(&self) -> &[T] { - unsafe { core::slice::from_raw_parts(self.elements, self.length) } - } - - pub fn as_mut_slice(&mut self) -> &mut [T] { - unsafe { core::slice::from_raw_parts_mut(self.elements, self.length) } - } - - /// Copy the contents of the given slice into the end of this list, - /// reallocating and resizing as necessary. - pub fn append_slice(&mut self, slice: &[T]) { - let new_len = self.len() + slice.len(); - let storage_ptr = self.get_storage_ptr_mut(); - - // First, ensure that there's enough storage space. - unsafe { - let storage_val = *storage_ptr as isize; - - // Check if this is refcounted, readonly, or has a capcacity. - // (Capacity will be positive if it has a capacity.) - if storage_val > 0 { - let capacity = storage_val as usize; - - // We don't have enough capacity, so we need to get some more. - if capacity < new_len { - // Double our capacity using realloc - let new_cap = 2 * capacity; - let new_ptr = roc_realloc( - storage_ptr as *mut c_void, - new_cap, - capacity, - Self::align_of_storage_ptr(), - ) as *mut isize; - - // Write the new capacity into the new memory - *new_ptr = new_cap as isize; - - // Copy all the existing elements into the new allocation. - ptr::copy_nonoverlapping(self.elements, new_ptr as *mut T, self.len()); - - // Update our storage pointer to be the new one - self.set_storage_ptr(new_ptr); - } - } else { - // If this was reference counted, decrement the refcount! - if storage_val < 0 { - let refcount = storage_val; - - // Either deallocate or decrement. - if refcount == REFCOUNT_1 { - roc_dealloc(storage_ptr as *mut c_void, Self::align_of_storage_ptr()); - } else { - *storage_ptr = refcount - 1; - } - } - - // This is either refcounted or readonly; either way, we need - // to clone the elements! - - // Double the capacity we need, in case there are future additions. - let new_cap = new_len * 2; - let new_ptr = roc_alloc(new_cap, Self::align_of_storage_ptr()) as *mut isize; - - // Write the new capacity into the new memory; this list is - // now unique, and gets its own capacity! - *new_ptr = new_cap as isize; - - // Copy all the existing elements into the new allocation. - ptr::copy_nonoverlapping(self.elements, new_ptr as *mut T, self.len()); - - // Update our storage pointer to be the new one - self.set_storage_ptr(new_ptr); - } - - // Since this is an append, we want to start writing new elements - // into the memory immediately after the current last element. - let dest = self.elements.add(self.len()); - - // There's now enough storage to append the contents of the slice - // in-place, so do that! - ptr::copy_nonoverlapping(slice.as_ptr(), dest, self.len()); - } - - self.length = new_len; - } - - /// The alignment we need is either the alignment of T, or else - /// the alignment of usize, whichever is higher. That's because we need - /// to store both T values as well as the refcount/capacity storage slot. - fn align_of_storage_ptr() -> u32 { - mem::align_of::().max(mem::align_of::()) as u32 - } - - unsafe fn drop_pointer_to_first_argument(ptr: *mut T) { - let storage_ptr = Self::get_storage_ptr_help(ptr); - let storage_val = *storage_ptr; - - if storage_val == REFCOUNT_1 || storage_val > 0 { - // If we have no more references, or if this was unique, - // deallocate it. - roc_dealloc(storage_ptr as *mut c_void, Self::align_of_storage_ptr()); - } else if storage_val < 0 { - // If this still has more references, decrement one. - *storage_ptr = storage_val - 1; - } - - // The only remaining option is that this is in readonly memory, - // in which case we shouldn't attempt to do anything to it. - } -} - -impl Deref for RocList { - type Target = [T]; - - fn deref(&self) -> &[T] { - self.as_slice() - } -} - -impl DerefMut for RocList { - fn deref_mut(&mut self) -> &mut [T] { - self.as_mut_slice() - } -} - -impl<'a, T> IntoIterator for &'a RocList { - type Item = &'a T; - - type IntoIter = <&'a [T] as IntoIterator>::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.as_slice().iter() - } -} - -impl IntoIterator for RocList { - type Item = T; - - type IntoIter = IntoIter; - - fn into_iter(self) -> Self::IntoIter { - let remaining = self.len(); - - let buf = unsafe { NonNull::new_unchecked(self.elements as _) }; - let ptr = self.elements; - - IntoIter { - buf, - ptr, - remaining, - } - } -} - -use core::ptr::NonNull; - -pub struct IntoIter { - buf: NonNull, - // pub cap: usize, - ptr: *const T, - remaining: usize, -} - -impl Iterator for IntoIter { - type Item = T; - - fn next(&mut self) -> Option { - next_help(self) - } -} - -fn next_help(this: &mut IntoIter) -> Option { - if this.remaining == 0 { - None - } else if mem::size_of::() == 0 { - // purposefully don't use 'ptr.offset' because for - // vectors with 0-size elements this would return the - // same pointer. - this.remaining -= 1; - - // Make up a value of this ZST. - Some(unsafe { mem::zeroed() }) - } else { - let old = this.ptr; - this.ptr = unsafe { this.ptr.offset(1) }; - this.remaining -= 1; - - Some(unsafe { ptr::read(old) }) - } -} - -impl Drop for IntoIter { - fn drop(&mut self) { - // drop the elements that we have not yet returned. - while let Some(item) = next_help(self) { - drop(item); - } - - // deallocate the whole buffer - unsafe { - RocList::drop_pointer_to_first_argument(self.buf.as_mut()); - } - } -} - -impl Default for RocList { - fn default() -> Self { - Self { - length: 0, - elements: core::ptr::null_mut(), - } - } -} - -impl fmt::Debug for RocList { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // RocList { storage: Refcounted(3), elements: [ 1,2,3,4] } - f.debug_struct("RocList") - .field("storage", &self.storage()) - .field("elements", &self.as_slice()) - .finish() - } -} - -impl PartialEq for RocList { - fn eq(&self, other: &Self) -> bool { - if self.length != other.length { - return false; - } - - for i in 0..self.length { - unsafe { - if *self.elements.add(i) != *other.elements.add(i) { - return false; - } - } - } - - true - } -} - -impl Eq for RocList {} - -impl Drop for RocList { - fn drop(&mut self) { - if !self.is_empty() { - let storage_ptr = self.get_storage_ptr_mut(); - - unsafe { - let storage_val = *storage_ptr; - - if storage_val == REFCOUNT_1 || storage_val > 0 { - // If we have no more references, or if this was unique, - // deallocate it. - roc_dealloc(storage_ptr as *mut c_void, Self::align_of_storage_ptr()); - } else if storage_val < 0 { - // If this still has more references, decrement one. - *storage_ptr = storage_val - 1; - } - - // The only remaining option is that this is in readonly memory, - // in which case we shouldn't attempt to do anything to it. - } - } - } -} - -#[repr(C)] -pub struct RocStr { - elements: *mut u8, - length: usize, -} - -impl RocStr { - pub fn len(&self) -> usize { - if self.is_small_str() { - let bytes = self.length.to_ne_bytes(); - let last_byte = bytes[mem::size_of::() - 1]; - - (last_byte ^ 0b1000_0000) as usize - } else { - self.length - } - } - - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - pub fn is_small_str(&self) -> bool { - (self.length as isize) < 0 - } - - 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 - } - } - - pub fn get_bytes(&self) -> *const u8 { - if self.is_small_str() { - self.get_small_str_ptr() - } else { - self.elements - } - } - - pub fn storage(&self) -> Option { - use core::cmp::Ordering::*; - - if self.is_small_str() { - return None; - } - - unsafe { - let value = *self.get_storage_ptr(); - - // NOTE doesn't work with elements of 16 or more bytes - match isize::cmp(&(value as isize), &0) { - Equal => Some(Storage::ReadOnly), - Less => Some(Storage::Refcounted(value)), - Greater => Some(Storage::Capacity(value as usize)), - } - } - } - - fn get_storage_ptr(&self) -> *const isize { - let ptr = self.elements as *const isize; - - unsafe { ptr.offset(-1) } - } - - fn get_storage_ptr_mut(&mut self) -> *mut isize { - self.get_storage_ptr() as *mut isize - } - - fn get_element_ptr(elements: *const u8) -> *const usize { - let elem_alignment = core::mem::align_of::(); - let ptr = elements as *const usize; - - unsafe { - if elem_alignment <= core::mem::align_of::() { - ptr.add(1) - } else { - // If elements have an alignment bigger than usize (e.g. an i128), - // we will have necessarily allocated two usize slots worth of - // space for the storage value (with the first usize slot being - // padding for alignment's sake), and we need to skip past both. - ptr.add(2) - } - } - } - - fn get_small_str_ptr(&self) -> *const u8 { - (self as *const Self).cast() - } - - fn get_small_str_ptr_mut(&mut self) -> *mut u8 { - (self as *mut Self).cast() - } - - fn from_slice_with_capacity_str(slice: &[u8], capacity: usize) -> Self { - assert!( - slice.len() <= capacity, - "RocStr::from_slice_with_capacity_str length bigger than capacity {} {}", - slice.len(), - capacity - ); - if capacity < core::mem::size_of::() { - let mut rocstr = Self::default(); - let target_ptr = rocstr.get_small_str_ptr_mut(); - let source_ptr = slice.as_ptr() as *const u8; - for index in 0..slice.len() { - unsafe { - *target_ptr.add(index) = *source_ptr.add(index); - } - } - // Write length and small string bit to last byte of length. - let mut bytes = rocstr.length.to_ne_bytes(); - bytes[mem::size_of::() - 1] = capacity as u8 ^ 0b1000_0000; - rocstr.length = usize::from_ne_bytes(bytes); - - rocstr - } else { - let ptr = slice.as_ptr(); - let element_bytes = capacity; - - let num_bytes = core::mem::size_of::() + element_bytes; - - let elements = unsafe { - let raw_ptr = roc_alloc(num_bytes, core::mem::size_of::() as u32) as *mut u8; - // write the capacity - let capacity_ptr = raw_ptr as *mut usize; - *capacity_ptr = capacity; - - let raw_ptr = Self::get_element_ptr(raw_ptr as *mut u8); - - // write the refcount - let refcount_ptr = raw_ptr as *mut isize; - *(refcount_ptr.offset(-1)) = isize::MIN; - - { - // NOTE: using a memcpy here causes weird issues - let target_ptr = raw_ptr as *mut u8; - let source_ptr = ptr as *const u8; - let length = slice.len(); - - for index in 0..length { - *target_ptr.add(index) = *source_ptr.add(index); - } - } - - raw_ptr as *mut u8 - }; - - Self { - length: slice.len(), - elements, - } - } - } - - pub fn from_slice(slice: &[u8]) -> Self { - Self::from_slice_with_capacity_str(slice, slice.len()) - } - - pub fn as_slice(&self) -> &[u8] { - if self.is_empty() { - &[] - } else if self.is_small_str() { - unsafe { core::slice::from_raw_parts(self.get_small_str_ptr(), self.len()) } - } else { - unsafe { core::slice::from_raw_parts(self.elements, self.length) } - } - } - - pub fn as_mut_slice(&mut self) -> &mut [u8] { - if self.is_empty() { - &mut [] - } else if self.is_small_str() { - unsafe { core::slice::from_raw_parts_mut(self.get_small_str_ptr_mut(), self.len()) } - } else { - unsafe { core::slice::from_raw_parts_mut(self.elements, self.length) } - } - } - - pub fn as_str(&self) -> &str { - let slice = self.as_slice(); - - unsafe { core::str::from_utf8_unchecked(slice) } - } - - pub fn as_mut_str(&mut self) -> &mut str { - let slice = self.as_mut_slice(); - - unsafe { core::str::from_utf8_unchecked_mut(slice) } - } - - /// Write a CStr (null-terminated) representation of this RocStr into - /// the given buffer. - /// - /// # 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()); - } - - // null-terminate - *(buf.add(self.len())) = '\0'; - } -} - -impl Deref for RocStr { - type Target = str; - - fn deref(&self) -> &str { - self.as_str() - } -} - -impl DerefMut for RocStr { - fn deref_mut(&mut self) -> &mut str { - self.as_mut_str() - } -} - -impl Default for RocStr { - fn default() -> Self { - Self { - length: isize::MIN as usize, - elements: core::ptr::null_mut(), - } - } -} - -impl From<&str> for RocStr { - fn from(str: &str) -> Self { - Self::from_slice(str.as_bytes()) - } -} - -impl Display for RocStr { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - self.as_str().fmt(f) - } -} - -impl fmt::Debug for RocStr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // RocStr { is_small_str: false, storage: Refcounted(3), elements: [ 1,2,3,4] } - - match core::str::from_utf8(self.as_slice()) { - Ok(string) => f - .debug_struct("RocStr") - .field("is_small_str", &self.is_small_str()) - .field("storage", &self.storage()) - .field("string_contents", &string) - .finish(), - Err(_) => f - .debug_struct("RocStr") - .field("is_small_str", &self.is_small_str()) - .field("storage", &self.storage()) - .field("byte_contents", &self.as_slice()) - .finish(), - } - } -} - -impl PartialEq for RocStr { - fn eq(&self, other: &Self) -> bool { - self.as_slice() == other.as_slice() - } -} - -impl Eq for RocStr {} - -impl Clone for RocStr { - fn clone(&self) -> Self { - if self.is_small_str() { - Self { - elements: self.elements, - length: self.length, - } - } else { - let capacity_size = core::mem::size_of::(); - let copy_length = self.length + capacity_size; - let elements = unsafe { - // We use *mut u8 here even though technically these are - // usize-aligned (due to the refcount slot). - // This avoids any potential edge cases around there somehow - // being unreadable memory after the last byte, which would - // potentially get read when reading bytes at a time. - let raw_ptr = - roc_alloc(copy_length, core::mem::size_of::() as u32) as *mut u8; - let dest_slice = slice::from_raw_parts_mut(raw_ptr, copy_length); - let src_ptr = self.elements.offset(-(capacity_size as isize)) 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 usize) = self.length; - - (raw_ptr as *mut u8).add(capacity_size) - }; - - Self { - elements, - length: self.length, - } - } - } -} - -impl Drop for RocStr { - fn drop(&mut self) { - if !self.is_small_str() { - let storage_ptr = self.get_storage_ptr_mut(); - - unsafe { - let storage_val = *storage_ptr; - - if storage_val == REFCOUNT_1 || storage_val > 0 { - // If we have no more references, or if this was unique, - // deallocate it. - roc_dealloc(storage_ptr as *mut c_void, mem::align_of::() as u32); - } else if storage_val < 0 { - // If this still has more references, decrement one. - *storage_ptr = storage_val - 1; - } - - // The only remaining option is that this is in readonly memory, - // in which case we shouldn't attempt to do anything to it. - } - } - } -} - /// Like a Rust `Result`, but following Roc's ABI instead of Rust's. /// (Using Rust's `Result` instead of this will not work properly with Roc code!) /// diff --git a/roc_std/src/rc.rs b/roc_std/src/rc.rs new file mode 100644 index 0000000000..5e9da2c089 --- /dev/null +++ b/roc_std/src/rc.rs @@ -0,0 +1,100 @@ +/// A type which uses reference counting for it's heap allocated memory. +/// +/// Note that if a type doesn't allocate any heap memory (eg. `i32`), the +/// `increment` and `decrement` methods don't need to do anything. +/// +/// # Safety +/// +/// It must be safe to memcpy this type to a new location after the reference count has been increased. +pub unsafe trait ReferenceCount { + /// Increment the reference count. + fn increment(&self); + /// Decrement the reference count. + /// + /// # Safety + /// + /// The caller must ensure that `ptr` points to a value with a non-zero + /// reference count. + unsafe fn decrement(ptr: *const Self); +} + +macro_rules! impl_reference_count_for_primitive { + ($ty:ty) => { + unsafe impl ReferenceCount for $ty { + fn increment(&self) { + // Do nothing. + } + + unsafe fn decrement(_ptr: *const Self) { + // Do nothing. + } + } + }; +} + +impl_reference_count_for_primitive!(bool); +impl_reference_count_for_primitive!(char); +impl_reference_count_for_primitive!(u8); +impl_reference_count_for_primitive!(i8); +impl_reference_count_for_primitive!(u16); +impl_reference_count_for_primitive!(i16); +impl_reference_count_for_primitive!(u32); +impl_reference_count_for_primitive!(i32); +impl_reference_count_for_primitive!(u64); +impl_reference_count_for_primitive!(i64); +impl_reference_count_for_primitive!(u128); +impl_reference_count_for_primitive!(i128); +impl_reference_count_for_primitive!(f32); +impl_reference_count_for_primitive!(f64); + +macro_rules! impl_reference_count_for_tuple { + ($($ty:ident: $field:tt,)*) => { + unsafe impl<$($ty),*> ReferenceCount for ($($ty,)*) + where + $($ty: ReferenceCount,)* + { + fn increment(&self) { + $(self.$field.increment();)* + } + + #[allow(unused_variables, clippy::unused_unit)] + unsafe fn decrement(ptr: *const Self) { + let ptrs = { + let this = &*ptr; + ($(core::ptr::addr_of!(this.$field),)*) + }; + + $($ty::decrement(ptrs.$field);)* + } + } + }; +} + +impl_reference_count_for_tuple!(); +impl_reference_count_for_tuple!(A: 0,); +impl_reference_count_for_tuple!(A: 0, B: 1,); +impl_reference_count_for_tuple!(A: 0, B: 1, C: 2,); +impl_reference_count_for_tuple!(A: 0, B: 1, C: 2, D: 3,); +impl_reference_count_for_tuple!(A: 0, B: 1, C: 2, D: 3, E: 4,); +impl_reference_count_for_tuple!(A: 0, B: 1, C: 2, D: 3, E: 4, F: 5,); +impl_reference_count_for_tuple!(A: 0, B: 1, C: 2, D: 3, E: 4, F: 5, G: 6,); +impl_reference_count_for_tuple!(A: 0, B: 1, C: 2, D: 3, E: 4, F: 5, G: 6, H: 7,); +impl_reference_count_for_tuple!(A: 0, B: 1, C: 2, D: 3, E: 4, F: 5, G: 6, H: 7, I: 8,); +impl_reference_count_for_tuple!(A: 0, B: 1, C: 2, D: 3, E: 4, F: 5, G: 6, H: 7, I: 8, J: 9,); +impl_reference_count_for_tuple!(A: 0, B: 1, C: 2, D: 3, E: 4, F: 5, G: 6, H: 7, I: 8, J: 9, K: 10,); +impl_reference_count_for_tuple!(A: 0, B: 1, C: 2, D: 3, E: 4, F: 5, G: 6, H: 7, I: 8, J: 9, K: 10, L: 11,); + +unsafe impl ReferenceCount for [T; N] +where + T: ReferenceCount, +{ + fn increment(&self) { + self.iter().for_each(T::increment) + } + + unsafe fn decrement(ptr: *const Self) { + for i in 0..N { + T::decrement(ptr.cast::().add(i)); + } + } +} diff --git a/roc_std/src/roc_list.rs b/roc_std/src/roc_list.rs new file mode 100644 index 0000000000..c8b769204d --- /dev/null +++ b/roc_std/src/roc_list.rs @@ -0,0 +1,336 @@ +#![deny(unsafe_op_in_unsafe_fn)] + +use core::{ + cell::Cell, cmp, fmt::Debug, intrinsics::copy_nonoverlapping, ops::Deref, ptr::NonNull, +}; + +use crate::{rc::ReferenceCount, roc_alloc, roc_dealloc, roc_realloc, storage::Storage}; + +#[repr(C)] +pub struct RocList +where + T: ReferenceCount, +{ + elements: Option>, + length: usize, +} + +impl RocList +where + T: ReferenceCount, +{ + pub fn empty() -> Self { + RocList { + elements: None, + length: 0, + } + } + + pub fn from_slice(slice: &[T]) -> Self { + let mut list = Self::empty(); + list.extend_from_slice(slice); + list + } + + pub fn len(&self) -> usize { + self.length + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn as_slice(&self) -> &[T] { + &*self + } + + pub fn extend_from_slice(&mut self, slice: &[T]) { + // TODO: Can we do better for ZSTs? Alignment might be a problem. + + if slice.is_empty() { + return; + } + + let alignment = cmp::max(core::mem::align_of::(), core::mem::align_of::()); + let elements_offset = alignment; + + let new_size = elements_offset + core::mem::size_of::() * (self.len() + slice.len()); + + let new_ptr = if let Some((elements, storage)) = self.elements_and_storage() { + // Decrement the lists refence count. + let mut copy = storage.get(); + let is_unique = copy.decrease(); + + if is_unique { + // If the memory is not shared, we can reuse the memory. + let old_size = elements_offset + core::mem::size_of::() * self.len(); + unsafe { + let ptr = elements.as_ptr().cast::().sub(alignment).cast(); + roc_realloc(ptr, new_size, old_size, alignment as u32).cast() + } + } else { + if !copy.is_readonly() { + // Write the decremented reference count back. + storage.set(copy); + } + + // Allocate new memory. + let new_ptr = unsafe { roc_alloc(new_size, alignment as u32) }; + let new_elements = unsafe { new_ptr.cast::().add(alignment).cast::() }; + + // Initialize the reference count. + unsafe { + let storage_ptr = new_elements.cast::().sub(1); + storage_ptr.write(Storage::new_reference_counted()); + } + + // Copy the old elements to the new allocation. + unsafe { + copy_nonoverlapping(elements.as_ptr(), new_elements, self.length); + } + + new_ptr + } + } else { + // Allocate new memory. + let new_ptr = unsafe { roc_alloc(new_size, alignment as u32) }; + let new_elements = unsafe { new_ptr.cast::().add(elements_offset).cast::() }; + + // Initialize the reference count. + unsafe { + let storage_ptr = new_elements.cast::().sub(1); + storage_ptr.write(Storage::new_reference_counted()); + } + + new_ptr + }; + + let elements = unsafe { new_ptr.cast::().add(elements_offset).cast::() }; + + let non_null_elements = NonNull::new(elements).unwrap(); + self.elements = Some(non_null_elements); + + let elements = self.elements.unwrap().as_ptr(); + + let append_ptr = unsafe { elements.add(self.len()) }; + for (i, element) in slice.iter().enumerate() { + // Increment the element's reference count. + element.increment(); + + // Write the element into the slot. + unsafe { + let element = core::ptr::read(element); + append_ptr.add(i).write(element); + } + + // It's important that the length is increased one by one, to + // make sure that we don't drop uninitialized elements, even when + // a incrementing the reference count panics. + self.length += 1; + } + } + + fn elements_and_storage(&self) -> Option<(NonNull, &Cell)> { + let elements = self.elements?; + let storage = unsafe { &*elements.as_ptr().cast::>().sub(1) }; + Some((elements, storage)) + } +} + +impl Deref for RocList +where + T: ReferenceCount, +{ + type Target = [T]; + + fn deref(&self) -> &Self::Target { + if let Some(elements) = self.elements { + let elements = core::ptr::slice_from_raw_parts(elements.as_ptr(), self.length); + unsafe { &*elements } + } else { + &[] + } + } +} + +impl Default for RocList +where + T: ReferenceCount, +{ + fn default() -> Self { + Self::empty() + } +} + +impl PartialEq> for RocList +where + T: PartialEq + ReferenceCount, + U: ReferenceCount, +{ + fn eq(&self, other: &RocList) -> bool { + self.deref() == other.deref() + } +} + +impl Eq for RocList where T: Eq + ReferenceCount {} + +impl Debug for RocList +where + T: Debug + ReferenceCount, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.deref().fmt(f) + } +} + +unsafe impl ReferenceCount for RocList +where + T: ReferenceCount, +{ + fn increment(&self) { + // Increment list's the reference count. + if let Some((_, storage)) = self.elements_and_storage() { + let mut copy = storage.get(); + if !copy.is_readonly() { + copy.increment_reference_count(); + storage.set(copy); + } + + // Increment the children's the reference counts. + self.iter().for_each(T::increment); + } + } + + unsafe fn decrement(ptr: *const Self) { + let this = unsafe { &*ptr }; + let (elements, storage) = if let Some((elements, storage)) = this.elements_and_storage() { + (elements, storage) + } else { + return; + }; + + // Decrement the refence counts of the contained values. + for i in 0..this.len() { + unsafe { + T::decrement(elements.as_ptr().add(i)); + } + } + + // Decrease the list's reference count. + let mut copy = storage.get(); + let can_be_released = copy.decrease(); + + if !can_be_released { + if !copy.is_readonly() { + // Write the storage back. + storage.set(copy); + } + return; + } + + // Release the memory. + let alignment = cmp::max(core::mem::align_of::(), core::mem::align_of::()); + unsafe { + roc_dealloc( + elements.as_ptr().cast::().sub(alignment).cast(), + alignment as u32, + ); + } + } +} + +impl Clone for RocList +where + T: ReferenceCount, +{ + fn clone(&self) -> Self { + // Increment the reference counts. + self.increment(); + + // Create a copy. + Self { + elements: self.elements, + length: self.length, + } + } +} + +impl Drop for RocList +where + T: ReferenceCount, +{ + fn drop(&mut self) { + unsafe { + Self::decrement(self); + } + } +} + +impl IntoIterator for RocList +where + T: ReferenceCount, +{ + type Item = T; + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + IntoIter { list: self, idx: 0 } + } +} + +pub struct IntoIter +where + T: ReferenceCount, +{ + list: RocList, + idx: usize, +} + +impl Iterator for IntoIter +where + T: ReferenceCount, +{ + type Item = T; + + fn next(&mut self) -> Option { + if self.list.len() <= self.idx { + return None; + } + + let elements = self.list.elements?; + let element_ptr = unsafe { elements.as_ptr().add(self.idx) }; + self.idx += 1; + + // Return the element. + let element = unsafe { element_ptr.read() }; + Some(element) + } +} + +impl Drop for IntoIter +where + T: ReferenceCount, +{ + fn drop(&mut self) { + // Check if there are any elements left of which we need to decrement + // the refence counts. + let elements = if let Some(elements) = self.list.elements { + elements + } else { + return; + }; + + // Set the list's length to zero to prevent double-frees. + // Note that this leaks if decrementing any of the elements' reference + // counts panics. + let len = core::mem::take(&mut self.list.length); + + // Decrement the reference counts of the elements that haven't been + // returned from the iterator. + for i in self.idx..len { + unsafe { + T::decrement(elements.as_ptr().add(i)); + } + } + } +} diff --git a/roc_std/src/roc_str.rs b/roc_std/src/roc_str.rs new file mode 100644 index 0000000000..b4c3f143fa --- /dev/null +++ b/roc_std/src/roc_str.rs @@ -0,0 +1,221 @@ +#![deny(unsafe_op_in_unsafe_fn)] + +use core::{ + convert::TryFrom, + fmt::Debug, + mem::{size_of, ManuallyDrop}, + ops::{Deref, DerefMut}, +}; + +use crate::{rc::ReferenceCount, RocList}; + +#[repr(transparent)] +pub struct RocStr(RocStrInner); + +impl RocStr { + pub const SIZE: usize = core::mem::size_of::(); + pub const MASK: u8 = 0b1000_0000; + + pub const fn empty() -> Self { + Self(RocStrInner { + small_string: SmallString::empty(), + }) + } + + /// Create a string from bytes. + /// + /// # Safety + /// + /// `slice` must be valid UTF-8. + pub unsafe fn from_slice(slice: &[u8]) -> Self { + if let Some(small_string) = unsafe { SmallString::try_from(slice) } { + Self(RocStrInner { small_string }) + } else { + let heap_allocated = RocList::from_slice(slice); + Self(RocStrInner { + heap_allocated: ManuallyDrop::new(heap_allocated), + }) + } + } + + fn is_small_str(&self) -> bool { + unsafe { self.0.small_string.is_small_str() } + } + + fn as_enum_ref(&self) -> RocStrInnerRef { + if self.is_small_str() { + unsafe { RocStrInnerRef::SmallString(&self.0.small_string) } + } else { + unsafe { RocStrInnerRef::HeapAllocated(&self.0.heap_allocated) } + } + } + + pub fn len(&self) -> usize { + match self.as_enum_ref() { + RocStrInnerRef::HeapAllocated(h) => h.len(), + RocStrInnerRef::SmallString(s) => s.len(), + } + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn as_str(&self) -> &str { + &*self + } +} + +impl Deref for RocStr { + type Target = str; + + fn deref(&self) -> &Self::Target { + match self.as_enum_ref() { + RocStrInnerRef::HeapAllocated(h) => unsafe { core::str::from_utf8_unchecked(&*h) }, + RocStrInnerRef::SmallString(s) => &*s, + } + } +} + +impl Default for RocStr { + fn default() -> Self { + Self::empty() + } +} + +impl From<&str> for RocStr { + fn from(s: &str) -> Self { + unsafe { Self::from_slice(s.as_bytes()) } + } +} + +impl PartialEq for RocStr { + fn eq(&self, other: &Self) -> bool { + self.deref() == other.deref() + } +} + +impl Eq for RocStr {} + +impl Debug for RocStr { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.deref().fmt(f) + } +} + +unsafe impl ReferenceCount for RocStr { + fn increment(&self) { + match self.as_enum_ref() { + RocStrInnerRef::HeapAllocated(h) => h.increment(), + RocStrInnerRef::SmallString(_) => { + // Do nothing. + } + } + } + + unsafe fn decrement(ptr: *const Self) { + let this = unsafe { &*ptr }; + if this.is_small_str() { + // Do nothing. + } else { + unsafe { + RocList::::decrement(ptr.cast()); + } + } + } +} + +impl Clone for RocStr { + fn clone(&self) -> Self { + match self.as_enum_ref() { + RocStrInnerRef::HeapAllocated(h) => Self(RocStrInner { + heap_allocated: ManuallyDrop::new(h.clone()), + }), + RocStrInnerRef::SmallString(s) => Self(RocStrInner { small_string: *s }), + } + } +} + +impl Drop for RocStr { + fn drop(&mut self) { + if self.is_small_str() { + // Do nothing. + } else { + unsafe { + ManuallyDrop::drop(&mut self.0.heap_allocated); + } + } + } +} + +#[repr(C)] +union RocStrInner { + heap_allocated: ManuallyDrop>, + small_string: SmallString, +} + +enum RocStrInnerRef<'a> { + HeapAllocated(&'a RocList), + SmallString(&'a SmallString), +} + +#[derive(Debug, Clone, Copy)] +#[repr(C)] +struct SmallString { + bytes: [u8; Self::CAPACITY], + len: u8, +} + +impl SmallString { + const CAPACITY: usize = size_of::>() - 1; + + const fn empty() -> Self { + Self { + bytes: [0; Self::CAPACITY], + len: RocStr::MASK, + } + } + + /// # Safety + /// + /// `slice` must be valid UTF-8. + unsafe fn try_from(slice: &[u8]) -> Option { + // Check the size of the slice. + let len_as_u8 = u8::try_from(slice.len()).ok()?; + if (len_as_u8 as usize) > Self::CAPACITY { + return None; + } + + // Construct the small string. + let mut bytes = [0; Self::CAPACITY]; + bytes[..slice.len()].copy_from_slice(slice); + Some(Self { + bytes, + len: len_as_u8 | RocStr::MASK, + }) + } + + fn is_small_str(&self) -> bool { + self.len & RocStr::MASK != 0 + } + + fn len(&self) -> usize { + usize::from(self.len & !RocStr::MASK) + } +} + +impl Deref for SmallString { + type Target = str; + + fn deref(&self) -> &Self::Target { + let len = self.len(); + unsafe { core::str::from_utf8_unchecked(self.bytes.get_unchecked(..len)) } + } +} + +impl DerefMut for SmallString { + fn deref_mut(&mut self) -> &mut Self::Target { + let len = self.len(); + unsafe { core::str::from_utf8_unchecked_mut(self.bytes.get_unchecked_mut(..len)) } + } +} diff --git a/roc_std/src/storage.rs b/roc_std/src/storage.rs new file mode 100644 index 0000000000..2be9976fca --- /dev/null +++ b/roc_std/src/storage.rs @@ -0,0 +1,54 @@ +use core::num::NonZeroIsize; + +const REFCOUNT_1: isize = isize::MIN; + +#[derive(Clone, Copy, Debug)] +pub enum Storage { + Readonly, + ReferenceCounted(NonZeroIsize), +} + +impl Storage { + pub fn new_reference_counted() -> Self { + Self::ReferenceCounted(NonZeroIsize::new(REFCOUNT_1).unwrap()) + } + + /// Increment the reference count. + pub fn increment_reference_count(&mut self) { + match self { + Storage::Readonly => { + // Do nothing. + } + Storage::ReferenceCounted(rc) => { + let new_rc = rc.get() + 1; + if let Some(new_rc) = NonZeroIsize::new(new_rc) { + *self = Storage::ReferenceCounted(new_rc); + } else { + *self = Storage::Readonly; + } + } + } + } + + /// Decrease the reference count. + /// + /// Returns `true` once there are no more references left. + pub fn decrease(&mut self) -> bool { + match self { + Storage::Readonly => false, + Storage::ReferenceCounted(rc) => { + let rc_as_isize = rc.get(); + if rc_as_isize == REFCOUNT_1 { + true + } else { + *rc = NonZeroIsize::new(rc_as_isize - 1).unwrap(); + false + } + } + } + } + + pub fn is_readonly(&self) -> bool { + matches!(self, Self::Readonly) + } +} diff --git a/roc_std/tests/test_roc_std.rs b/roc_std/tests/test_roc_std.rs index 4d59fdbd63..af4f299b78 100644 --- a/roc_std/tests/test_roc_std.rs +++ b/roc_std/tests/test_roc_std.rs @@ -5,9 +5,69 @@ extern crate pretty_assertions; extern crate quickcheck; extern crate roc_std; +use core::ffi::c_void; + +#[no_mangle] +pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { + libc::malloc(size) +} + +#[no_mangle] +pub unsafe extern "C" fn roc_realloc( + c_ptr: *mut c_void, + new_size: usize, + _old_size: usize, + _alignment: u32, +) -> *mut c_void { + libc::realloc(c_ptr, new_size) +} + +#[no_mangle] +pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { + libc::free(c_ptr) +} + #[cfg(test)] mod test_roc_std { use roc_std::RocResult; + use roc_std::RocStr; + + fn roc_str_byte_representation(string: &RocStr) -> [u8; RocStr::SIZE] { + unsafe { core::mem::transmute_copy(string) } + } + + #[test] + fn roc_str_empty() { + let actual = roc_str_byte_representation(&RocStr::empty()); + + let mut expected = [0u8; RocStr::SIZE]; + expected[RocStr::SIZE - 1] = RocStr::MASK; + + assert_eq!(actual, expected); + } + + #[test] + fn roc_str_single_char() { + let actual = roc_str_byte_representation(&RocStr::from("a")); + + let mut expected = [0u8; RocStr::SIZE]; + expected[0] = b'a'; + expected[RocStr::SIZE - 1] = RocStr::MASK | 1; + + assert_eq!(actual, expected); + } + + #[test] + fn roc_str_max_small_string() { + let s = str::repeat("a", RocStr::SIZE - 1); + let actual = roc_str_byte_representation(&RocStr::from(s.as_str())); + + let mut expected = [0u8; RocStr::SIZE]; + expected[..RocStr::SIZE - 1].copy_from_slice(s.as_bytes()); + expected[RocStr::SIZE - 1] = RocStr::MASK | s.len() as u8; + + assert_eq!(actual, expected); + } #[test] fn roc_result_to_rust_result() {