mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-26 13:29:12 +00:00
Merge remote-tracking branch 'origin/trunk' into complete-num-add
This commit is contained in:
commit
003408e3ef
147 changed files with 4249 additions and 1589 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -8,6 +8,8 @@ zig-cache
|
|||
*.obj
|
||||
*.tmp
|
||||
*.wasm
|
||||
*.exe
|
||||
*.pdb
|
||||
|
||||
# llvm human-readable output
|
||||
*.ll
|
||||
|
|
2
AUTHORS
2
AUTHORS
|
@ -87,3 +87,5 @@ Patrick Kilgore <git@pck.email>
|
|||
Marten/Qqwy <w-m@wmcode.nl>
|
||||
Christoph Rüßler <christoph.ruessler@mailbox.org>
|
||||
Ralf Engbers <raleng@users.noreply.github.com>
|
||||
Mostly Void <7rat13@gmail.com>
|
||||
Luis F. Gutierrez <luis@gutierrezhiller.com>
|
||||
|
|
101
Cargo.lock
generated
101
Cargo.lock
generated
|
@ -104,7 +104,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "arena-pool"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
|
@ -3268,7 +3268,7 @@ checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157"
|
|||
|
||||
[[package]]
|
||||
name = "repl_test"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"indoc",
|
||||
"lazy_static",
|
||||
|
@ -3316,7 +3316,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_alias_analysis"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"morphic_lib",
|
||||
"roc_collections",
|
||||
|
@ -3355,7 +3355,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_build"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"inkwell 0.1.0",
|
||||
|
@ -3389,7 +3389,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_builtins"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"dunce",
|
||||
"lazy_static",
|
||||
|
@ -3403,7 +3403,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_can"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bitvec 1.0.1",
|
||||
"bumpalo",
|
||||
|
@ -3484,7 +3484,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_collections"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bitvec 1.0.1",
|
||||
"bumpalo",
|
||||
|
@ -3496,7 +3496,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_constrain"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"arrayvec 0.7.2",
|
||||
"roc_can",
|
||||
|
@ -3510,11 +3510,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_debug_flags"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
|
||||
[[package]]
|
||||
name = "roc_derive"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"roc_can",
|
||||
|
@ -3529,7 +3529,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_derive_key"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"roc_can",
|
||||
"roc_collections",
|
||||
|
@ -3541,7 +3541,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_docs"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"peg",
|
||||
|
@ -3565,7 +3565,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_docs_cli"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"clap 3.2.11",
|
||||
"roc_docs",
|
||||
|
@ -3573,7 +3573,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_editor"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"arrayvec 0.7.2",
|
||||
"bumpalo",
|
||||
|
@ -3622,11 +3622,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_error_macros"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
|
||||
[[package]]
|
||||
name = "roc_exhaustive"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"roc_collections",
|
||||
"roc_module",
|
||||
|
@ -3635,7 +3635,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_fmt"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"indoc",
|
||||
|
@ -3650,7 +3650,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_gen_dev"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"capstone",
|
||||
|
@ -3675,7 +3675,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_gen_llvm"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"inkwell 0.1.0",
|
||||
|
@ -3695,7 +3695,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_gen_wasm"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bitvec 1.0.1",
|
||||
"bumpalo",
|
||||
|
@ -3710,7 +3710,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_glue"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"clap 3.2.11",
|
||||
|
@ -3740,7 +3740,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_highlight"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"peg",
|
||||
"roc_code_markup",
|
||||
|
@ -3748,11 +3748,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_ident"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
|
||||
[[package]]
|
||||
name = "roc_late_solve"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"roc_can",
|
||||
|
@ -3767,7 +3767,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_linker"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bumpalo",
|
||||
|
@ -3787,7 +3787,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_load"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"roc_builtins",
|
||||
|
@ -3802,7 +3802,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_load_internal"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"crossbeam",
|
||||
|
@ -3836,7 +3836,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_module"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"lazy_static",
|
||||
|
@ -3850,7 +3850,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_mono"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"hashbrown 0.12.2",
|
||||
|
@ -3859,6 +3859,7 @@ dependencies = [
|
|||
"roc_collections",
|
||||
"roc_debug_flags",
|
||||
"roc_derive",
|
||||
"roc_derive_key",
|
||||
"roc_error_macros",
|
||||
"roc_exhaustive",
|
||||
"roc_late_solve",
|
||||
|
@ -3874,7 +3875,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_parse"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"criterion",
|
||||
|
@ -3891,7 +3892,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_problem"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"roc_collections",
|
||||
"roc_module",
|
||||
|
@ -3902,14 +3903,14 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_region"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_repl_cli"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"const_format",
|
||||
|
@ -3936,7 +3937,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_repl_eval"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"roc_builtins",
|
||||
|
@ -3956,7 +3957,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_repl_expect"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"indoc",
|
||||
|
@ -3986,7 +3987,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_repl_wasm"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"console_error_panic_hook",
|
||||
|
@ -4008,7 +4009,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_reporting"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"distance",
|
||||
|
@ -4038,7 +4039,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_solve"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"arrayvec 0.7.2",
|
||||
"bumpalo",
|
||||
|
@ -4071,7 +4072,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_solve_problem"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"roc_can",
|
||||
"roc_collections",
|
||||
|
@ -4084,7 +4085,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_std"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"arrayvec 0.7.2",
|
||||
"static_assertions",
|
||||
|
@ -4092,7 +4093,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_target"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"strum",
|
||||
"strum_macros",
|
||||
|
@ -4101,7 +4102,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_test_utils"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"pretty_assertions",
|
||||
"remove_dir_all 0.7.0",
|
||||
|
@ -4109,7 +4110,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_types"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"roc_collections",
|
||||
|
@ -4122,7 +4123,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_unify"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"roc_can",
|
||||
|
@ -4136,7 +4137,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "roc_utils"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"snafu",
|
||||
]
|
||||
|
@ -4785,7 +4786,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "test_derive"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"indoc",
|
||||
|
@ -4811,7 +4812,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "test_gen"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"criterion",
|
||||
|
@ -4851,7 +4852,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "test_mono"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"indoc",
|
||||
|
@ -4868,7 +4869,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "test_mono_macros"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -5187,7 +5188,7 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
|||
|
||||
[[package]]
|
||||
name = "wasi_libc_sys"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
|
|
|
@ -89,6 +89,9 @@ test-rust:
|
|||
# gen-wasm has some multithreading problems to do with the wasmer runtime. Run it single-threaded as a separate job
|
||||
RUN --mount=type=cache,target=$SCCACHE_DIR \
|
||||
cargo test --locked --release --package test_gen --no-default-features --features gen-wasm -- --test-threads=1 && sccache --show-stats
|
||||
# run `roc test` on Str builtins
|
||||
RUN --mount=type=cache,target=$SCCACHE_DIR \
|
||||
cargo run --release -- test crates/compiler/builtins/roc/Str.roc && sccache --show-stats
|
||||
# repl_test: build the compiler for wasm target, then run the tests on native target
|
||||
RUN --mount=type=cache,target=$SCCACHE_DIR \
|
||||
crates/repl_test/test_wasm.sh && sccache --show-stats
|
||||
|
|
41
FAQ.md
41
FAQ.md
|
@ -1,5 +1,34 @@
|
|||
# Frequently Asked Questions
|
||||
|
||||
## Where did the name Roc come from?
|
||||
|
||||
<img width="128" alt="The Roc logo, an origami bird" src="https://user-images.githubusercontent.com/1094080/92188927-e61ebd00-ee2b-11ea-97ef-2fc88e0094b0.png">
|
||||
|
||||
The Roc programming language is named after [a mythical bird](<https://en.wikipedia.org/wiki/Roc_(mythology)>).
|
||||
|
||||
That’s why the logo is a bird. It’s specifically an [_origami_ bird](https://youtu.be/9gni1t1k1uY) as an homage
|
||||
to [Elm](https://elm-lang.org/)’s tangram logo.
|
||||
|
||||
Roc is a direct descendant of Elm. The languages are similar, but not the same.
|
||||
[Origami](https://en.wikipedia.org/wiki/Origami) likewise has similarities to [tangrams](https://en.wikipedia.org/wiki/Tangram), although they are not the same.
|
||||
Both involve making a surprising variety of things
|
||||
from simple primitives. [_Folds_](<https://en.wikipedia.org/wiki/Fold_(higher-order_function)>)
|
||||
are also common in functional programming.
|
||||
|
||||
The logo was made by tracing triangles onto a photo of a physical origami bird.
|
||||
It’s made of triangles because triangles are a foundational primitive in
|
||||
computer graphics.
|
||||
|
||||
The name was chosen because it makes for a three-letter file extension, it means something
|
||||
fantastical, and it has incredible potential for puns. Here are some different ways to spell it:
|
||||
|
||||
- **Roc** - traditional
|
||||
- **roc** - low-key
|
||||
- **ROC** - [YELLING](https://package.elm-lang.org/packages/elm/core/latest/String#toUpper)
|
||||
- **Röc** - [metal 🤘](https://en.wikipedia.org/wiki/Metal_umlaut)
|
||||
|
||||
Fun fact: "roc" translates to 鹏 in Chinese, [which means](https://www.mdbg.net/chinese/dictionary?page=worddict&wdrst=0&wdqb=%E9%B9%8F) "a large fabulous bird."
|
||||
|
||||
# Why make a new editor instead of making an LSP plugin for VSCode, Vim or Emacs?
|
||||
|
||||
The Roc editor is one of the key areas where we want to innovate. Constraining ourselves to a plugin for existing editors would severely limit our possibilities for innovation.
|
||||
|
@ -419,6 +448,18 @@ The goal is for Roc's compiler to deliver the best user experience possible. Com
|
|||
|
||||
Roc isn't trying to be the best possible language for high-performance compiler development, but it is trying to have a high-performance compiler. The best tool for that job is a language other than Roc, so that's what we're using!
|
||||
|
||||
## Why does Roc use the license it does?
|
||||
|
||||
The short explanation for why Roc is released under the [Universal Permissive License](https://opensource.org/licenses/UPL):
|
||||
|
||||
- Like [MIT](https://opensource.org/licenses/MIT), it's permissive and concise
|
||||
- Like [Apache2](https://opensource.org/licenses/Apache-2.0), it protects against contributors claiming software patents over contributed code after the fact (MIT and BSD do not include protections against this)
|
||||
- It's compatible with [GPLv2](https://opensource.org/licenses/GPL-2.0) (which [Apache2 is not](https://www.apache.org/licenses/GPL-compatibility.html))
|
||||
- It's one license, unlike "MIT or Apache2, at your choice" (which is how [Rust addressed the problem](https://internals.rust-lang.org/t/rationale-of-apache-dual-licensing/8952/4) of MIT not having patent protections but Apache2 not being GPLv2 compatible)
|
||||
- It's been approved by OSI, FSF, and Oracle's lawyers, so it has been not only vetted by three giants in the world of software licensing, but also three giants with competing interests - and they all approved it.
|
||||
|
||||
There's also [a longer explanation](https://github.com/rtfeldman/roc/issues/1199) with more detail about the motivation and thought process, if you're interested.
|
||||
|
||||
## Why does Roc use both Rust and Zig?
|
||||
|
||||
Roc's compiler has always been written in [Rust](https://www.rust-lang.org/). Roc's standard library was briefly written in Rust, but was soon rewritten in [Zig](https://ziglang.org/).
|
||||
|
|
117
README.md
117
README.md
|
@ -1,118 +1,17 @@
|
|||
# The Roc Programming Language
|
||||
# Work in progress!
|
||||
|
||||
Roc is a language for making delightful software.
|
||||
Roc is not ready for an 0.1 release yet, but we have a [Zulip chat](https://roc.zulipchat.com) where you can learn more about the project.
|
||||
|
||||
The [tutorial](TUTORIAL.md) is the best place to learn about how to use the language - it assumes no prior knowledge of Roc or similar languages. (If you already know [Elm](https://elm-lang.org/), then [Roc for Elm Programmers](https://github.com/rtfeldman/roc/blob/trunk/roc-for-elm-programmers.md) may be of interest.)
|
||||
If you'd like to get involved in contributing to the language, the Zulip chat is also the best place to get help with [good first issues](https://github.com/rtfeldman/roc/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22).
|
||||
|
||||
There's also a folder of [examples](https://github.com/rtfeldman/roc/tree/trunk/examples) - the [CLI form example](https://github.com/rtfeldman/roc/tree/trunk/examples/interactive/form.roc) in particular is a reasonable starting point to build on.
|
||||
# Sponsors
|
||||
|
||||
If you have a specific question, the [FAQ](FAQ.md) might have an answer, although [Roc Zulip chat](https://roc.zulipchat.com) is overall the best place to ask questions and get help! It's also where we discuss [ideas](https://roc.zulipchat.com/#narrow/stream/304641-ideas) for the language. If you want to get involved in contributing to the language, Zulip is also a great place to ask about good first projects.
|
||||
|
||||
## State of Roc
|
||||
|
||||
Roc is not ready for production yet. You are likely to encounter bugs. Publishing packages or documentation is not yet supported.
|
||||
Many programs can however be compiled correctly. Check out [examples](examples) and [examples/benchmarks](examples/benchmarks). There are minimal platforms for Rust, Zig, C, Swift and an HTTP server. We are hard at work to make programming in Roc a delightful experience!
|
||||
|
||||
## Getting started
|
||||
|
||||
- [Linux x86](getting_started/linux_x86.md)
|
||||
- [MacOS Apple Silicon](getting_started/macos_apple_silicon.md)
|
||||
- [MacOS x86](getting_started/macos_x86.md)
|
||||
- [Windows](getting_started/windows.md)
|
||||
- [Other](getting_started/other.md)
|
||||
|
||||
### Examples
|
||||
|
||||
Run examples as follows:
|
||||
```
|
||||
cargo run examples/hello-world/main.roc
|
||||
```
|
||||
Some examples like `examples/benchmarks/NQueens.roc` require input after running.
|
||||
For NQueens, input 10 in the terminal and press enter.
|
||||
|
||||
[examples/benchmarks](examples/benchmarks) contains larger examples.
|
||||
|
||||
**Tip:** when programming in roc, we recommend to execute `./roc check myproject/Foo.roc` before `./roc myproject/Foo.roc` or `./roc build myproject/Foo.roc`. `./roc check` can produce clear error messages in cases where building/running may panic.
|
||||
|
||||
## Sponsors
|
||||
|
||||
We are very grateful for our sponsors [NoRedInk](https://www.noredink.com/) and [rwx](https://www.rwx.com).
|
||||
We are very grateful to our sponsors [NoRedInk](https://www.noredink.com/), [rwx](https://www.rwx.com), and [Tweede golf](https://tweedegolf.nl/en).
|
||||
|
||||
[<img src="https://www.noredink.com/assets/logo-red-black-f6989d7567cf90b349409137595e99c52d036d755b4403d25528e0fd83a3b084.svg" height="60" alt="NoRedInk logo"/>](https://www.noredink.com/)
|
||||
|
||||
[<img src="https://www.rwx.com/build/_assets/rwx_banner_transparent_cropped-RYV7W2KL.svg" height="60" alt="rwx logo"/>](https://www.rwx.com)
|
||||
|
||||
[<img src="https://user-images.githubusercontent.com/1094080/183123052-856815b1-8cc9-410a-83b0-589f03613188.svg" height="60" alt="tweede golf logo"/>](https://tweedegolf.nl/en)
|
||||
|
||||
## Applications and Platforms
|
||||
|
||||
Applications are often built on a *framework.* Typically, both application and framework are written in the same language.
|
||||
* [Rails](https://rubyonrails.org/) applications are written in Ruby, and so is Rails.
|
||||
* [Angular](https://angularjs.org/) applications are written in TypeScript, and so is Angular.
|
||||
* [Phoenix](https://phoenixframework.org/) applications are written in Elixir, and so is Phoenix.
|
||||
|
||||
Some programs support plugins. Often the plugins are written in the same language as the underlying program.
|
||||
* [Webpack](https://webpack.js.org/) plugins are written in JavaScript, and so is Webpack.
|
||||
* [Eclipse](https://www.eclipse.org/ide/) plugins are written in Java, and so is Eclipse.
|
||||
* [Leiningen](https://leiningen.org/) plugins are written in Clojure, and so is Leiningen.
|
||||
|
||||
All of these can be considered examples of a platform/application relationship. There is an underlying platform, and many applications are built on top of it. (Plugins are a type of application in this sense.)
|
||||
|
||||
Sometimes, platforms and their applications are written in different languages.
|
||||
|
||||
* [Neovim](https://neovim.io/) is written in C for performance, and its plugins can be written in languages such as Python, JS, and Ruby.
|
||||
* [NGINX](https://www.nginx.com/) is written in C for performance, and its plugins can be written in a [subset of JavaScript](https://www.nginx.com/blog/introduction-nginscript/).
|
||||
* [Unity](https://unity.com/) is written in C++ for performance, and Unity applications (such as games) can be written in C#, Boo, or a JavaScript dialect called UnityScript.
|
||||
|
||||
Like in the previous examples, application authors building on these platforms get to use high-level languages with automatic memory management. They make no ergonomics sacrifices, and may not even be aware that the underlying platform is written in a lower-level language.
|
||||
|
||||
By using systems-level programming languages like C and C++, platform authors sacrifice development speed, but unlock the highest possible performance characteristics. This is a tradeoff many platform authors are happy to accept, for the sake of having applications built on their platforms run very fast.
|
||||
|
||||
## Roc's Design
|
||||
|
||||
Roc is designed to make the "systems-level platform, higher-level application" experience as nice as possible.
|
||||
|
||||
* **Application** authors code exclusively in Roc. It's a language designed for nice ergonomics. The syntax resembles Ruby or CoffeeScript, and it has a fast compiler with full type inference.
|
||||
* **Platform** authors code almost exclusively in a systems-level language like C, C++, Rust, Swift or [Zig](https://ziglang.org/), except for the thin Roc API they expose to application authors. Roc application code compiles to machine code, and production builds of Roc apps benefit from the same [LLVM](https://llvm.org/) optimizations that C++, Rust, Swift and Zig do. Roc application authors do not need to know this lower-level code exists; all they have to interact with is the platform's API, which is exposed as an ordinary Roc API.
|
||||
|
||||
Every Roc application is built on top of exactly one Roc platform. There is no such thing as a Roc application that runs without a platform, and there is no default platform. You must choose one!
|
||||
|
||||
The core Roc language and standard library include no I/O operations, which gives platform authors complete control over which effects they want to support. Some of the implications of this include:
|
||||
|
||||
* A high-performance build tool (or text editor) written in Rust can be a Roc platform with a strong plugin security model. For example, it could expose only operations allowing plugin authors to modify the contents of certain files, rather than allowing plugins arbitrary read/write access to the entire filesystem.
|
||||
* A VR or [Arduino](https://www.arduino.cc/) platform can expose uncommon I/O operations supported by that hardware, while omitting common I/O operations that are unsupported (such as reading keyboard input from a terminal that doesn't exist).
|
||||
* A high-performance Web server written in Rust can be a Roc platform where all I/O operations are implemented in terms of Streams or Observables rather than a more traditional asynchronous abstraction like Futures or Promises. This would mean all code in that platform's ecosystem would be necessarily built on a common streaming abstraction.
|
||||
|
||||
## Project Goals
|
||||
|
||||
Roc is in relatively early stages of development. It's currently possible to build both platforms and applications (see the [examples](https://github.com/rtfeldman/roc/tree/trunk/examples) folder for some examples that aren't particularly organized at the moment), although [documentation](https://github.com/rtfeldman/roc/tree/trunk/crates/compiler/builtins/roc) is in even earlier stages than the compiler itself.
|
||||
|
||||
Besides the above language design, a separate goal is for Roc to ship with an ambitiously boundary-pushing graphical editor. Not like "an IDE," but rather something that makes people say "I have never seen anything remotely like this outside of Bret Victor demos."
|
||||
|
||||
One of the reasons this editor is coupled with the language itself is to allow package authors to include custom editor tooling inside packages.
|
||||
|
||||
A trivial example: suppose I'm writing a Roc app for an Arduino platform. I install a platform-specific package for displaying text on a grid of LEDs. Because I've installed this package, at the call site where I call the function to specify the color of the text on the LEDs, my Roc editor displays an inline color picker. As I move a slider around to try out different colors, not only does my code change to reflect that value in realtime, but the physical LEDs in my room change color in realtime as well. As the application author, all I did to get that experience was to install the "text on an LED grid" package, nothing else.
|
||||
|
||||
The goal is for this to be one of the most trivial, bare minimum examples of what the editor experience would be like. Hopefully, people in the future will look back on this example and say "that's so embarrassingly basic; why didn't you talk about one of the *actually great* things in the seamless editor plugin ecosystem?"
|
||||
|
||||
Finally, some implementation goals:
|
||||
|
||||
* The web server for the package manager is written in Roc (with an underlying Rust platform for the web server, for example [warp](https://github.com/seanmonstar/warp)).
|
||||
* The editor plugins are written in Roc (with an underlying Rust platform for the editor itself, for example using [gfx-hal](https://github.com/gfx-rs/gfx)).
|
||||
* The CLI (for building Roc projects on CI platforms) has its user interface written in Roc (with an underlying Rust platform for fast compilation and basic CLI interactions).
|
||||
|
||||
It's an ambitious project! It'll take a long time to get where it's going, but hopefully it'll be worth the wait.
|
||||
|
||||
## Getting Involved
|
||||
|
||||
The number of people involved in Roc's development has been steadily increasing
|
||||
over time - which has been great, because it's meant we've been able to onboard
|
||||
people at a nice pace. (Most people who have contributed to Roc had previously
|
||||
never done anything with Rust and also never worked on a compiler, but we've
|
||||
been able to find beginner-friendly projects to get people up to speed gradually.)
|
||||
|
||||
If you're interested in getting involved, check out
|
||||
[CONTRIBUTING.md](https://github.com/rtfeldman/roc/blob/trunk/CONTRIBUTING.md)!
|
||||
|
||||
## Name and Logo
|
||||
|
||||
If you're curious about where the language's name and logo came from,
|
||||
[here's an explanation](https://github.com/rtfeldman/roc/blob/trunk/name-and-logo.md).
|
||||
If you or your employer would like to sponsor Roc's development, please [DM Richard Feldman on Zulip](https://roc.zulipchat.com/#narrow/pm-with/281383-user281383)!
|
||||
|
|
|
@ -1,20 +1,21 @@
|
|||
use bumpalo::Bump;
|
||||
use roc_load::{LoadedModule, Threading};
|
||||
use roc_load::{ExecutionMode, LoadConfig, LoadedModule, Threading};
|
||||
use roc_target::TargetInfo;
|
||||
use std::path::Path;
|
||||
|
||||
pub fn load_module(src_file: &Path, threading: Threading) -> LoadedModule {
|
||||
let subs_by_module = Default::default();
|
||||
|
||||
let arena = Bump::new();
|
||||
let loaded = roc_load::load_and_typecheck(
|
||||
&arena,
|
||||
src_file.to_path_buf(),
|
||||
subs_by_module,
|
||||
TargetInfo::default_x86_64(),
|
||||
roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
let load_config = LoadConfig {
|
||||
target_info: TargetInfo::default_x86_64(), // editor only needs type info, so this is unused
|
||||
render: roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
threading,
|
||||
);
|
||||
exec_mode: ExecutionMode::Check,
|
||||
};
|
||||
|
||||
let arena = Bump::new();
|
||||
let loaded =
|
||||
roc_load::load_and_typecheck(&arena, src_file.to_path_buf(), subs_by_module, load_config);
|
||||
|
||||
match loaded {
|
||||
Ok(x) => x,
|
||||
|
|
6
crates/cli/build.rs
Normal file
6
crates/cli/build.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
fn main() {
|
||||
// workaround for issue https://github.com/NixOS/nixpkgs/issues/166205 . This println can be removed when this issue is fixed. Upgrading to LLVM 14 could also fix this issue.
|
||||
// also see https://github.com/NixOS/nixpkgs/pull/181485
|
||||
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
|
||||
println!("cargo:rustc-link-lib=c++abi")
|
||||
}
|
|
@ -5,7 +5,7 @@ use roc_build::{
|
|||
};
|
||||
use roc_builtins::bitcode;
|
||||
use roc_collections::VecMap;
|
||||
use roc_load::{Expectations, LoadingProblem, Threading};
|
||||
use roc_load::{EntryPoint, ExecutionMode, Expectations, LoadConfig, LoadingProblem, Threading};
|
||||
use roc_module::symbol::{Interns, ModuleId};
|
||||
use roc_mono::ir::OptLevel;
|
||||
use roc_reporting::report::RenderTarget;
|
||||
|
@ -55,14 +55,18 @@ pub fn build_file<'a>(
|
|||
// Step 1: compile the app and generate the .o file
|
||||
let subs_by_module = Default::default();
|
||||
|
||||
let load_config = LoadConfig {
|
||||
target_info,
|
||||
// TODO: expose this from CLI?
|
||||
render: RenderTarget::ColorTerminal,
|
||||
threading,
|
||||
exec_mode: ExecutionMode::Executable,
|
||||
};
|
||||
let loaded = roc_load::load_and_monomorphize(
|
||||
arena,
|
||||
app_module_path.clone(),
|
||||
subs_by_module,
|
||||
target_info,
|
||||
// TODO: expose this from CLI?
|
||||
RenderTarget::ColorTerminal,
|
||||
threading,
|
||||
load_config,
|
||||
)?;
|
||||
|
||||
use target_lexicon::Architecture;
|
||||
|
@ -74,36 +78,37 @@ pub fn build_file<'a>(
|
|||
// > Non-Emscripten WebAssembly hasn't implemented __builtin_return_address
|
||||
//
|
||||
// and zig does not currently emit `.a` webassembly static libraries
|
||||
let host_extension = if emit_wasm {
|
||||
let (host_extension, app_extension, extension) = {
|
||||
use roc_target::OperatingSystem::*;
|
||||
|
||||
match roc_target::OperatingSystem::from(target.operating_system) {
|
||||
Wasi => {
|
||||
if matches!(opt_level, OptLevel::Development) {
|
||||
"wasm"
|
||||
("wasm", "wasm", Some("wasm"))
|
||||
} else {
|
||||
"zig"
|
||||
("zig", "bc", Some("wasm"))
|
||||
}
|
||||
} else {
|
||||
"o"
|
||||
};
|
||||
let app_extension = if emit_wasm {
|
||||
if matches!(opt_level, OptLevel::Development) {
|
||||
"wasm"
|
||||
} else {
|
||||
"bc"
|
||||
}
|
||||
} else {
|
||||
"o"
|
||||
Unix => ("o", "o", None),
|
||||
Windows => ("obj", "obj", Some("exe")),
|
||||
}
|
||||
};
|
||||
|
||||
let cwd = app_module_path.parent().unwrap();
|
||||
let mut binary_path = cwd.join(&*loaded.output_path); // TODO should join ".exe" on Windows
|
||||
let mut binary_path = cwd.join(&*loaded.output_path);
|
||||
|
||||
if emit_wasm {
|
||||
binary_path.set_extension("wasm");
|
||||
if let Some(extension) = extension {
|
||||
binary_path.set_extension(extension);
|
||||
}
|
||||
|
||||
let host_input_path = cwd
|
||||
.join(&*loaded.platform_path)
|
||||
let host_input_path = if let EntryPoint::Executable { platform_path, .. } = &loaded.entry_point
|
||||
{
|
||||
cwd.join(platform_path)
|
||||
.with_file_name("host")
|
||||
.with_extension(host_extension);
|
||||
.with_extension(host_extension)
|
||||
} else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
// TODO this should probably be moved before load_and_monomorphize.
|
||||
// To do this we will need to preprocess files just for their exported symbols.
|
||||
|
@ -441,15 +446,15 @@ pub fn check_file(
|
|||
// Step 1: compile the app and generate the .o file
|
||||
let subs_by_module = Default::default();
|
||||
|
||||
let mut loaded = roc_load::load_and_typecheck(
|
||||
arena,
|
||||
roc_file_path,
|
||||
subs_by_module,
|
||||
let load_config = LoadConfig {
|
||||
target_info,
|
||||
// TODO: expose this from CLI?
|
||||
RenderTarget::ColorTerminal,
|
||||
render: RenderTarget::ColorTerminal,
|
||||
threading,
|
||||
)?;
|
||||
exec_mode: ExecutionMode::Check,
|
||||
};
|
||||
let mut loaded =
|
||||
roc_load::load_and_typecheck(arena, roc_file_path, subs_by_module, load_config)?;
|
||||
|
||||
let buf = &mut String::with_capacity(1024);
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ use roc_error_macros::{internal_error, user_error};
|
|||
use roc_gen_llvm::llvm::build::LlvmBackendMode;
|
||||
use roc_gen_llvm::run_roc::RocCallResult;
|
||||
use roc_gen_llvm::run_roc_dylib;
|
||||
use roc_load::{Expectations, LoadingProblem, Threading};
|
||||
use roc_load::{ExecutionMode, Expectations, LoadConfig, LoadingProblem, Threading};
|
||||
use roc_module::symbol::{Interns, ModuleId};
|
||||
use roc_mono::ir::OptLevel;
|
||||
use roc_repl_expect::run::{expect_mono_module_to_dylib, roc_dev_expect};
|
||||
|
@ -361,15 +361,15 @@ pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
|
|||
// Step 1: compile the app and generate the .o file
|
||||
let subs_by_module = Default::default();
|
||||
|
||||
let loaded = roc_load::load_and_monomorphize(
|
||||
arena,
|
||||
path.to_path_buf(),
|
||||
subs_by_module,
|
||||
let load_config = LoadConfig {
|
||||
target_info,
|
||||
// TODO: expose this from CLI?
|
||||
roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
render: roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
threading,
|
||||
)
|
||||
exec_mode: ExecutionMode::Test,
|
||||
};
|
||||
let loaded =
|
||||
roc_load::load_and_monomorphize(arena, path.to_path_buf(), subs_by_module, load_config)
|
||||
.unwrap();
|
||||
|
||||
let mut loaded = loaded;
|
||||
|
@ -732,14 +732,18 @@ fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
|
|||
// since the process is about to exit anyway.
|
||||
std::mem::forget(arena);
|
||||
|
||||
if cfg!(target_family = "unix") {
|
||||
#[cfg(target_family = "unix")]
|
||||
{
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
run_with_wasmer(
|
||||
generated_filename,
|
||||
args.into_iter().map(|os_str| os_str.as_bytes()),
|
||||
);
|
||||
} else {
|
||||
}
|
||||
|
||||
#[cfg(not(target_family = "unix"))]
|
||||
{
|
||||
run_with_wasmer(
|
||||
generated_filename,
|
||||
args.into_iter().map(|os_str| {
|
||||
|
@ -756,6 +760,7 @@ fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
fn make_argv_envp<'a, I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
|
||||
arena: &'a Bump,
|
||||
executable: &ExecutableFile,
|
||||
|
@ -889,11 +894,28 @@ impl ExecutableFile {
|
|||
let path_cstring = CString::new(path.as_os_str().as_bytes()).unwrap();
|
||||
libc::execve(path_cstring.as_ptr().cast(), argv.as_ptr(), envp.as_ptr())
|
||||
}
|
||||
|
||||
#[cfg(all(target_family = "windows"))]
|
||||
ExecutableFile::OnDisk(_, path) => {
|
||||
use std::process::Command;
|
||||
|
||||
let _ = argv;
|
||||
let _ = envp;
|
||||
|
||||
let mut command = Command::new(path);
|
||||
|
||||
let output = command.output().unwrap();
|
||||
|
||||
println!("{}", String::from_utf8_lossy(&output.stdout));
|
||||
|
||||
std::process::exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// with Expect
|
||||
#[cfg(target_family = "unix")]
|
||||
unsafe fn roc_run_native_debug(
|
||||
executable: ExecutableFile,
|
||||
argv: &[*const c_char],
|
||||
|
@ -1033,35 +1055,75 @@ fn roc_run_executable_file_path(binary_bytes: &mut [u8]) -> std::io::Result<Exec
|
|||
Ok(ExecutableFile::OnDisk(temp_dir, app_path_buf))
|
||||
}
|
||||
|
||||
#[cfg(all(target_family = "windows"))]
|
||||
fn roc_run_executable_file_path(binary_bytes: &mut [u8]) -> std::io::Result<ExecutableFile> {
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
|
||||
let temp_dir = tempfile::tempdir()?;
|
||||
|
||||
// We have not found a way to use a virtual file on non-Linux OSes.
|
||||
// Hence we fall back to just writing the file to the file system, and using that file.
|
||||
let app_path_buf = temp_dir.path().join("roc_app_binary.exe");
|
||||
let mut file = OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
//.mode(0o777) // create the file as executable
|
||||
.open(&app_path_buf)?;
|
||||
|
||||
file.write_all(binary_bytes)?;
|
||||
|
||||
// We store the TempDir in this variant alongside the path to the executable,
|
||||
// so that the TempDir doesn't get dropped until after we're done with the path.
|
||||
// If we didn't do that, then the tempdir would potentially get deleted by the
|
||||
// TempDir's Drop impl before the file had been executed.
|
||||
Ok(ExecutableFile::OnDisk(temp_dir, app_path_buf))
|
||||
}
|
||||
|
||||
/// Run on the native OS (not on wasm)
|
||||
#[cfg(not(target_family = "unix"))]
|
||||
fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
|
||||
_arena: Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it!
|
||||
arena: Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it!
|
||||
opt_level: OptLevel,
|
||||
_args: I,
|
||||
_binary_bytes: &mut [u8],
|
||||
binary_bytes: &mut [u8],
|
||||
_expectations: VecMap<ModuleId, Expectations>,
|
||||
_interns: Interns,
|
||||
) -> io::Result<i32> {
|
||||
todo!("TODO support running roc programs on non-UNIX targets");
|
||||
// let mut cmd = std::process::Command::new(&binary_path);
|
||||
use bumpalo::collections::CollectIn;
|
||||
|
||||
// // Run the compiled app
|
||||
// let exit_status = cmd
|
||||
// .spawn()
|
||||
// .unwrap_or_else(|err| panic!("Failed to run app after building it: {:?}", err))
|
||||
// .wait()
|
||||
// .expect("TODO gracefully handle block_on failing when `roc` spawns a subprocess for the compiled app");
|
||||
unsafe {
|
||||
let executable = roc_run_executable_file_path(binary_bytes)?;
|
||||
|
||||
// // `roc [FILE]` exits with the same status code as the app it ran.
|
||||
// //
|
||||
// // If you want to know whether there were compilation problems
|
||||
// // via status code, use either `roc build` or `roc check` instead!
|
||||
// match exit_status.code() {
|
||||
// Some(code) => Ok(code),
|
||||
// None => {
|
||||
// todo!("TODO gracefully handle the `roc [FILE]` subprocess terminating with a signal.");
|
||||
// }
|
||||
// }
|
||||
// TODO forward the arguments
|
||||
// let (argv_cstrings, envp_cstrings) = make_argv_envp(&arena, &executable, args);
|
||||
let argv_cstrings = bumpalo::vec![ in &arena; CString::default()];
|
||||
let envp_cstrings = bumpalo::vec![ in &arena; CString::default()];
|
||||
|
||||
let argv: bumpalo::collections::Vec<*const c_char> = argv_cstrings
|
||||
.iter()
|
||||
.map(|s| s.as_ptr())
|
||||
.chain([std::ptr::null()])
|
||||
.collect_in(&arena);
|
||||
|
||||
let envp: bumpalo::collections::Vec<*const c_char> = envp_cstrings
|
||||
.iter()
|
||||
.map(|s| s.as_ptr())
|
||||
.chain([std::ptr::null()])
|
||||
.collect_in(&arena);
|
||||
|
||||
match opt_level {
|
||||
OptLevel::Development => {
|
||||
// roc_run_native_debug(executable, &argv, &envp, expectations, interns)
|
||||
todo!()
|
||||
}
|
||||
OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => {
|
||||
roc_run_native_fast(executable, &argv, &envp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(1)
|
||||
}
|
||||
|
||||
#[cfg(feature = "run-wasm32")]
|
||||
|
|
|
@ -3,7 +3,7 @@ authors = ["The Roc Contributors"]
|
|||
edition = "2021"
|
||||
license = "UPL-1.0"
|
||||
name = "roc_alias_analysis"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
|
||||
[dependencies]
|
||||
morphic_lib = {path = "../../vendor/morphic_lib"}
|
||||
|
|
|
@ -131,7 +131,7 @@ fn bytes_as_ascii(bytes: &[u8]) -> String {
|
|||
|
||||
pub fn spec_program<'a, I>(
|
||||
opt_level: OptLevel,
|
||||
entry_point: roc_mono::ir::EntryPoint<'a>,
|
||||
opt_entry_point: Option<roc_mono::ir::EntryPoint<'a>>,
|
||||
procs: I,
|
||||
) -> Result<morphic_lib::Solutions>
|
||||
where
|
||||
|
@ -221,6 +221,7 @@ where
|
|||
m.add_func(func_name, spec)?;
|
||||
}
|
||||
|
||||
if let Some(entry_point) = opt_entry_point {
|
||||
// the entry point wrapper
|
||||
let roc_main_bytes = func_name_bytes_help(
|
||||
entry_point.symbol,
|
||||
|
@ -234,6 +235,7 @@ where
|
|||
build_entry_point(entry_point.layout, roc_main, &host_exposed_functions)?;
|
||||
let entry_point_name = FuncName(ENTRY_POINT_NAME);
|
||||
m.add_func(entry_point_name, entry_point_function)?;
|
||||
}
|
||||
|
||||
for union_layout in type_definitions {
|
||||
let type_name_bytes = recursive_tag_union_name_bytes(&union_layout).as_bytes();
|
||||
|
@ -264,8 +266,10 @@ where
|
|||
let mut p = ProgramBuilder::new();
|
||||
p.add_mod(MOD_APP, main_module)?;
|
||||
|
||||
if opt_entry_point.is_some() {
|
||||
let entry_point_name = FuncName(ENTRY_POINT_NAME);
|
||||
p.add_entry_point(EntryPointName(ENTRY_POINT_NAME), MOD_APP, entry_point_name)?;
|
||||
}
|
||||
|
||||
p.build()?
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "arena-pool"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
repository = "https://github.com/rtfeldman/roc"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_build"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -120,6 +120,7 @@ pub fn build_zig_host_native(
|
|||
.env_clear()
|
||||
.env("PATH", env_path)
|
||||
.env("HOME", env_home);
|
||||
|
||||
if let Some(shared_lib_path) = shared_lib_path {
|
||||
command.args(&[
|
||||
"build-exe",
|
||||
|
@ -130,6 +131,7 @@ pub fn build_zig_host_native(
|
|||
} else {
|
||||
command.args(&["build-obj", "-fPIC"]);
|
||||
}
|
||||
|
||||
command.args(&[
|
||||
zig_host_src,
|
||||
emit_bin,
|
||||
|
@ -160,6 +162,7 @@ pub fn build_zig_host_native(
|
|||
} else if matches!(opt_level, OptLevel::Size) {
|
||||
command.args(&["-O", "ReleaseSmall"]);
|
||||
}
|
||||
|
||||
command.output().unwrap()
|
||||
}
|
||||
|
||||
|
@ -425,7 +428,11 @@ pub fn rebuild_host(
|
|||
host_input_path.with_file_name(if shared_lib_path.is_some() {
|
||||
"dynhost"
|
||||
} else {
|
||||
"host.o"
|
||||
match roc_target::OperatingSystem::from(target.operating_system) {
|
||||
roc_target::OperatingSystem::Windows => "host.obj",
|
||||
roc_target::OperatingSystem::Unix => "host.o",
|
||||
roc_target::OperatingSystem::Wasi => "host.o",
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
|
@ -1095,11 +1102,58 @@ fn link_wasm32(
|
|||
|
||||
fn link_windows(
|
||||
_target: &Triple,
|
||||
_output_path: PathBuf,
|
||||
_input_paths: &[&str],
|
||||
_link_type: LinkType,
|
||||
output_path: PathBuf,
|
||||
input_paths: &[&str],
|
||||
link_type: LinkType,
|
||||
) -> io::Result<(Child, PathBuf)> {
|
||||
todo!("Add windows support to the surgical linker. See issue #2608.")
|
||||
let zig_str_path = find_zig_str_path();
|
||||
|
||||
match link_type {
|
||||
LinkType::Dylib => {
|
||||
let child = Command::new(&zig_executable())
|
||||
.args(&["build-lib"])
|
||||
.args(input_paths)
|
||||
.args([
|
||||
"-lc",
|
||||
&format!("-femit-bin={}", output_path.to_str().unwrap()),
|
||||
"-target",
|
||||
"native",
|
||||
"--pkg-begin",
|
||||
"str",
|
||||
zig_str_path.to_str().unwrap(),
|
||||
"--pkg-end",
|
||||
"--strip",
|
||||
"-O",
|
||||
"Debug",
|
||||
"-dynamic",
|
||||
])
|
||||
.spawn()?;
|
||||
|
||||
Ok((child, output_path))
|
||||
}
|
||||
LinkType::Executable => {
|
||||
let child = Command::new(&zig_executable())
|
||||
.args(&["build-exe"])
|
||||
.args(input_paths)
|
||||
.args([
|
||||
"-lc",
|
||||
&format!("-femit-bin={}", output_path.to_str().unwrap()),
|
||||
"-target",
|
||||
"native",
|
||||
"--pkg-begin",
|
||||
"str",
|
||||
zig_str_path.to_str().unwrap(),
|
||||
"--pkg-end",
|
||||
"--strip",
|
||||
"-O",
|
||||
"Debug",
|
||||
])
|
||||
.spawn()?;
|
||||
|
||||
Ok((child, output_path))
|
||||
}
|
||||
LinkType::None => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn llvm_module_to_dylib(
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
pub use roc_gen_llvm::llvm::build::FunctionIterator;
|
||||
use roc_gen_llvm::llvm::build::{module_from_builtins, LlvmBackendMode};
|
||||
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
|
||||
use roc_load::{LoadedModule, MonomorphizedModule};
|
||||
use roc_load::{EntryPoint, LoadedModule, MonomorphizedModule};
|
||||
use roc_module::symbol::{Interns, ModuleId};
|
||||
use roc_mono::ir::OptLevel;
|
||||
use roc_region::all::LineInfo;
|
||||
|
@ -265,11 +265,18 @@ pub fn gen_from_mono_module_llvm(
|
|||
// expects that would confuse the surgical linker
|
||||
add_default_roc_externs(&env);
|
||||
|
||||
let opt_entry_point = match loaded.entry_point {
|
||||
EntryPoint::Executable { symbol, layout, .. } => {
|
||||
Some(roc_mono::ir::EntryPoint { symbol, layout })
|
||||
}
|
||||
EntryPoint::Test => None,
|
||||
};
|
||||
|
||||
roc_gen_llvm::llvm::build::build_procedures(
|
||||
&env,
|
||||
opt_level,
|
||||
loaded.procedures,
|
||||
loaded.entry_point,
|
||||
opt_entry_point,
|
||||
Some(&app_ll_file),
|
||||
);
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_builtins"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -89,6 +89,7 @@ fn generate_object_file(bitcode_path: &Path, zig_object: &str, object_file_name:
|
|||
&bitcode_path,
|
||||
&zig_executable(),
|
||||
&["build", zig_object, "-Drelease=true"],
|
||||
0,
|
||||
);
|
||||
|
||||
println!("Moving zig object `{}` to: {}", zig_object, dest_obj);
|
||||
|
@ -123,6 +124,7 @@ fn generate_bc_file(bitcode_path: &Path, zig_object: &str, file_name: &str) {
|
|||
&bitcode_path,
|
||||
&zig_executable(),
|
||||
&["build", zig_object, "-Drelease=true"],
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -192,8 +194,12 @@ fn cp_unless_zig_cache(src_dir: &Path, target_dir: &Path) -> io::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn run_command<S, I: Copy, P: AsRef<Path> + Copy>(path: P, command_str: &str, args: I)
|
||||
where
|
||||
fn run_command<S, I: Copy, P: AsRef<Path> + Copy>(
|
||||
path: P,
|
||||
command_str: &str,
|
||||
args: I,
|
||||
flaky_fail_counter: usize,
|
||||
) where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<OsStr>,
|
||||
{
|
||||
|
@ -212,10 +218,14 @@ where
|
|||
};
|
||||
|
||||
// flaky test error that only occurs sometimes inside MacOS ci run
|
||||
if error_str.contains("unable to build stage1 zig object: FileNotFound")
|
||||
if error_str.contains("FileNotFound")
|
||||
|| error_str.contains("unable to save cached ZIR code")
|
||||
{
|
||||
run_command(path, command_str, args)
|
||||
if flaky_fail_counter == 10 {
|
||||
panic!("{} failed 10 times in a row. The following error is unlikely to be a flaky error: {}", command_str, error_str);
|
||||
} else {
|
||||
run_command(path, command_str, args, flaky_fail_counter + 1)
|
||||
}
|
||||
} else {
|
||||
panic!("{} failed: {}", command_str, error_str);
|
||||
}
|
||||
|
|
|
@ -197,7 +197,7 @@ takeFloat = \bytes ->
|
|||
|
||||
when List.get rest 0 is
|
||||
Ok 46 -> # 46 = .
|
||||
{ taken: floatPart, rest: afterAll } = takeDigits rest
|
||||
{ taken: floatPart, rest: afterAll } = takeDigits (List.split rest 1).others
|
||||
builtFloat =
|
||||
List.concat (List.append intPart (asciiByte '.')) floatPart
|
||||
|
||||
|
|
|
@ -330,11 +330,20 @@ splitLast = \haystack, needle ->
|
|||
None ->
|
||||
Err NotFound
|
||||
|
||||
# splitLast when needle isn't in haystack
|
||||
expect Str.splitLast "foo" "z" == Err NotFound
|
||||
|
||||
# splitLast when haystack ends with needle repeated
|
||||
expect Str.splitLast "foo" "o" == Ok { before: "fo", after: "" }
|
||||
|
||||
# splitLast with multi-byte needle
|
||||
expect Str.splitLast "hullabaloo" "ab" == Ok { before: "hull", after: "aloo" }
|
||||
|
||||
lastMatch : Str, Str -> [Some Nat, None]
|
||||
lastMatch = \haystack, needle ->
|
||||
haystackLength = Str.countUtf8Bytes haystack
|
||||
needleLength = Str.countUtf8Bytes needle
|
||||
lastPossibleIndex = Num.subSaturated haystackLength (needleLength + 1)
|
||||
lastPossibleIndex = Num.subSaturated haystackLength needleLength
|
||||
|
||||
lastMatchHelp haystack needle lastPossibleIndex
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ impl FloatWidth {
|
|||
}
|
||||
|
||||
pub const fn alignment_bytes(&self, target_info: TargetInfo) -> u32 {
|
||||
use roc_target::Architecture;
|
||||
use roc_target::Architecture::*;
|
||||
use FloatWidth::*;
|
||||
|
||||
// NOTE: this must never use mem::align_of, because that returns the alignment
|
||||
|
@ -73,8 +73,8 @@ impl FloatWidth {
|
|||
match self {
|
||||
F32 => 4,
|
||||
F64 | F128 => match target_info.architecture {
|
||||
Architecture::X86_64 | Architecture::Aarch64 | Architecture::Wasm32 => 8,
|
||||
Architecture::X86_32 | Architecture::Aarch32 => 4,
|
||||
X86_64 | Aarch64 | Wasm32 => 8,
|
||||
X86_32 | Aarch32 => 4,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_can"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -18,7 +18,7 @@ pub struct Annotation {
|
|||
pub typ: Type,
|
||||
pub introduced_variables: IntroducedVariables,
|
||||
pub references: VecSet<Symbol>,
|
||||
pub aliases: SendMap<Symbol, Alias>,
|
||||
pub aliases: VecMap<Symbol, Alias>,
|
||||
}
|
||||
|
||||
impl Annotation {
|
||||
|
@ -271,7 +271,7 @@ pub fn canonicalize_annotation(
|
|||
) -> Annotation {
|
||||
let mut introduced_variables = IntroducedVariables::default();
|
||||
let mut references = VecSet::default();
|
||||
let mut aliases = SendMap::default();
|
||||
let mut aliases = VecMap::default();
|
||||
|
||||
let (annotation, region) = match annotation {
|
||||
TypeAnnotation::Where(annotation, clauses) => {
|
||||
|
@ -475,7 +475,7 @@ fn can_annotation_help(
|
|||
scope: &mut Scope,
|
||||
var_store: &mut VarStore,
|
||||
introduced_variables: &mut IntroducedVariables,
|
||||
local_aliases: &mut SendMap<Symbol, Alias>,
|
||||
local_aliases: &mut VecMap<Symbol, Alias>,
|
||||
references: &mut VecSet<Symbol>,
|
||||
) -> Type {
|
||||
use roc_parse::ast::TypeAnnotation::*;
|
||||
|
@ -976,7 +976,7 @@ fn can_extension_type<'a>(
|
|||
scope: &mut Scope,
|
||||
var_store: &mut VarStore,
|
||||
introduced_variables: &mut IntroducedVariables,
|
||||
local_aliases: &mut SendMap<Symbol, Alias>,
|
||||
local_aliases: &mut VecMap<Symbol, Alias>,
|
||||
references: &mut VecSet<Symbol>,
|
||||
opt_ext: &Option<&Loc<TypeAnnotation<'a>>>,
|
||||
ext_problem_kind: roc_problem::can::ExtensionTypeKind,
|
||||
|
@ -1169,7 +1169,7 @@ fn can_assigned_fields<'a>(
|
|||
scope: &mut Scope,
|
||||
var_store: &mut VarStore,
|
||||
introduced_variables: &mut IntroducedVariables,
|
||||
local_aliases: &mut SendMap<Symbol, Alias>,
|
||||
local_aliases: &mut VecMap<Symbol, Alias>,
|
||||
references: &mut VecSet<Symbol>,
|
||||
) -> SendMap<Lowercase, RecordField<Type>> {
|
||||
use roc_parse::ast::AssignedField::*;
|
||||
|
@ -1282,7 +1282,7 @@ fn can_tags<'a>(
|
|||
scope: &mut Scope,
|
||||
var_store: &mut VarStore,
|
||||
introduced_variables: &mut IntroducedVariables,
|
||||
local_aliases: &mut SendMap<Symbol, Alias>,
|
||||
local_aliases: &mut VecMap<Symbol, Alias>,
|
||||
references: &mut VecSet<Symbol>,
|
||||
) -> Vec<(TagName, Vec<Type>)> {
|
||||
let mut tag_types = Vec::with_capacity(tags.len());
|
||||
|
|
|
@ -79,7 +79,7 @@ impl Def {
|
|||
pub struct Annotation {
|
||||
pub signature: Type,
|
||||
pub introduced_variables: IntroducedVariables,
|
||||
pub aliases: SendMap<Symbol, Alias>,
|
||||
pub aliases: VecMap<Symbol, Alias>,
|
||||
pub region: Region,
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::def::Def;
|
|||
use crate::expr::{AnnotatedMark, ClosureData, Declarations, Expr, Recursive, WhenBranchPattern};
|
||||
use crate::pattern::Pattern;
|
||||
use crate::scope::Scope;
|
||||
use roc_collections::{SendMap, VecSet};
|
||||
use roc_collections::{SendMap, VecMap, VecSet};
|
||||
use roc_module::called_via::CalledVia;
|
||||
use roc_module::ident::TagName;
|
||||
use roc_module::symbol::Symbol;
|
||||
|
@ -201,7 +201,7 @@ fn build_effect_always(
|
|||
let def_annotation = crate::def::Annotation {
|
||||
signature,
|
||||
introduced_variables,
|
||||
aliases: SendMap::default(),
|
||||
aliases: VecMap::default(),
|
||||
region: Region::zero(),
|
||||
};
|
||||
|
||||
|
@ -393,7 +393,7 @@ fn build_effect_map(
|
|||
let def_annotation = crate::def::Annotation {
|
||||
signature,
|
||||
introduced_variables,
|
||||
aliases: SendMap::default(),
|
||||
aliases: VecMap::default(),
|
||||
region: Region::zero(),
|
||||
};
|
||||
|
||||
|
@ -601,7 +601,7 @@ fn build_effect_after(
|
|||
let def_annotation = crate::def::Annotation {
|
||||
signature,
|
||||
introduced_variables,
|
||||
aliases: SendMap::default(),
|
||||
aliases: VecMap::default(),
|
||||
region: Region::zero(),
|
||||
};
|
||||
|
||||
|
@ -833,7 +833,7 @@ fn build_effect_forever(
|
|||
let def_annotation = crate::def::Annotation {
|
||||
signature,
|
||||
introduced_variables,
|
||||
aliases: SendMap::default(),
|
||||
aliases: VecMap::default(),
|
||||
region: Region::zero(),
|
||||
};
|
||||
|
||||
|
@ -1090,7 +1090,7 @@ fn build_effect_loop(
|
|||
let def_annotation = crate::def::Annotation {
|
||||
signature,
|
||||
introduced_variables,
|
||||
aliases: SendMap::default(),
|
||||
aliases: VecMap::default(),
|
||||
region: Region::zero(),
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_collections"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_constrain"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
[package]
|
||||
name = "roc_debug_flags"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
edition = "2021"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
|
||||
[dependencies]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_derive"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
258
crates/compiler/derive/src/decoding.rs
Normal file
258
crates/compiler/derive/src/decoding.rs
Normal file
|
@ -0,0 +1,258 @@
|
|||
//! Derivers for the `Decoding` ability.
|
||||
|
||||
use roc_can::expr::{AnnotatedMark, ClosureData, Expr, Recursive};
|
||||
use roc_can::pattern::Pattern;
|
||||
use roc_derive_key::decoding::FlatDecodableKey;
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::called_via::CalledVia;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::Loc;
|
||||
use roc_types::subs::{
|
||||
Content, FlatType, GetSubsSlice, LambdaSet, OptVariable, SubsSlice, UnionLambdas, Variable,
|
||||
};
|
||||
use roc_types::types::AliasKind;
|
||||
|
||||
use crate::util::Env;
|
||||
use crate::{synth_var, DerivedBody};
|
||||
|
||||
pub(crate) fn derive_decoder(
|
||||
env: &mut Env<'_>,
|
||||
key: FlatDecodableKey,
|
||||
def_symbol: Symbol,
|
||||
) -> DerivedBody {
|
||||
let (body, body_type) = match key {
|
||||
FlatDecodableKey::List() => decoder_list(env, def_symbol),
|
||||
};
|
||||
|
||||
let specialization_lambda_sets =
|
||||
env.get_specialization_lambda_sets(body_type, Symbol::DECODE_DECODER);
|
||||
|
||||
DerivedBody {
|
||||
body,
|
||||
body_type,
|
||||
specialization_lambda_sets,
|
||||
}
|
||||
}
|
||||
|
||||
fn decoder_list(env: &mut Env<'_>, _def_symbol: Symbol) -> (Expr, Variable) {
|
||||
// Build
|
||||
//
|
||||
// def_symbol : Decoder (List elem) fmt | elem has Decoding, fmt has DecoderFormatting
|
||||
// def_symbol = Decode.custom \bytes, fmt -> Decode.decodeWith bytes (Decode.list Decode.decoder) fmt
|
||||
//
|
||||
// TODO try to reduce to `Decode.list Decode.decoder`
|
||||
|
||||
use Expr::*;
|
||||
|
||||
// Decode.list Decode.decoder : Decoder (List elem) fmt
|
||||
let (decode_list_call, this_decode_list_ret_var) = {
|
||||
// List elem
|
||||
let elem_var = env.subs.fresh_unnamed_flex_var();
|
||||
|
||||
// Decode.decoder : Decoder elem fmt | elem has Decoding, fmt has EncoderFormatting
|
||||
let (elem_decoder, elem_decoder_var) = {
|
||||
// build `Decode.decoder : Decoder elem fmt` type
|
||||
// Decoder val fmt | val has Decoding, fmt has EncoderFormatting
|
||||
let elem_decoder_var = env.import_builtin_symbol_var(Symbol::DECODE_DECODER);
|
||||
|
||||
// set val ~ elem
|
||||
let val_var = match env.subs.get_content_without_compacting(elem_decoder_var) {
|
||||
Content::Alias(Symbol::DECODE_DECODER_OPAQUE, vars, _, AliasKind::Opaque)
|
||||
if vars.type_variables_len == 2 =>
|
||||
{
|
||||
env.subs.get_subs_slice(vars.type_variables())[0]
|
||||
}
|
||||
_ => internal_error!("Decode.decode not an opaque type"),
|
||||
};
|
||||
|
||||
env.unify(val_var, elem_var);
|
||||
|
||||
(
|
||||
AbilityMember(Symbol::DECODE_DECODER, None, elem_decoder_var),
|
||||
elem_decoder_var,
|
||||
)
|
||||
};
|
||||
|
||||
// Build `Decode.list Decode.decoder` type
|
||||
// Decoder val fmt -[uls]-> Decoder (List val) fmt | fmt has DecoderFormatting
|
||||
let decode_list_fn_var = env.import_builtin_symbol_var(Symbol::DECODE_LIST);
|
||||
|
||||
// Decoder elem fmt -a-> b
|
||||
let elem_decoder_var_slice = SubsSlice::insert_into_subs(env.subs, [elem_decoder_var]);
|
||||
let this_decode_list_clos_var = env.subs.fresh_unnamed_flex_var();
|
||||
let this_decode_list_ret_var = env.subs.fresh_unnamed_flex_var();
|
||||
let this_decode_list_fn_var = synth_var(
|
||||
env.subs,
|
||||
Content::Structure(FlatType::Func(
|
||||
elem_decoder_var_slice,
|
||||
this_decode_list_clos_var,
|
||||
this_decode_list_ret_var,
|
||||
)),
|
||||
);
|
||||
|
||||
// Decoder val fmt -[uls]-> Decoder (List val) fmt | fmt has DecoderFormatting
|
||||
// ~ Decoder elem fmt -a -> b
|
||||
env.unify(decode_list_fn_var, this_decode_list_fn_var);
|
||||
|
||||
let decode_list_member = AbilityMember(Symbol::DECODE_LIST, None, this_decode_list_fn_var);
|
||||
let decode_list_fn = Box::new((
|
||||
decode_list_fn_var,
|
||||
Loc::at_zero(decode_list_member),
|
||||
this_decode_list_clos_var,
|
||||
this_decode_list_ret_var,
|
||||
));
|
||||
|
||||
let decode_list_call = Call(
|
||||
decode_list_fn,
|
||||
vec![(elem_decoder_var, Loc::at_zero(elem_decoder))],
|
||||
CalledVia::Space,
|
||||
);
|
||||
|
||||
(decode_list_call, this_decode_list_ret_var)
|
||||
};
|
||||
|
||||
let bytes_sym = env.new_symbol("bytes");
|
||||
let bytes_var = env.subs.fresh_unnamed_flex_var();
|
||||
let fmt_sym = env.new_symbol("fmt");
|
||||
let fmt_var = env.subs.fresh_unnamed_flex_var();
|
||||
|
||||
// Decode.decodeWith bytes (Decode.list Decode.decoder) fmt : DecodeResult (List elem)
|
||||
let (decode_with_call, decode_result_list_elem_var) = {
|
||||
// Decode.decodeWith : List U8, Decoder val fmt, fmt -> DecodeResult val | fmt has DecoderFormatting
|
||||
let decode_with_type = env.import_builtin_symbol_var(Symbol::DECODE_DECODE_WITH);
|
||||
|
||||
// Decode.decodeWith : bytes, Decoder (List elem) fmt, fmt -> DecoderResult (List val)
|
||||
let this_decode_with_var_slice =
|
||||
SubsSlice::insert_into_subs(env.subs, [bytes_var, this_decode_list_ret_var, fmt_var]);
|
||||
let this_decode_with_clos_var = env.subs.fresh_unnamed_flex_var();
|
||||
let this_decode_with_ret_var = env.subs.fresh_unnamed_flex_var();
|
||||
let this_decode_with_fn_var = synth_var(
|
||||
env.subs,
|
||||
Content::Structure(FlatType::Func(
|
||||
this_decode_with_var_slice,
|
||||
this_decode_with_clos_var,
|
||||
this_decode_with_ret_var,
|
||||
)),
|
||||
);
|
||||
|
||||
// List U8, Decoder val fmt, fmt -> DecodeResult val | fmt has DecoderFormatting
|
||||
// ~ bytes, Decoder (List elem) fmt, fmt -> DecoderResult (List val)
|
||||
env.unify(decode_with_type, this_decode_with_fn_var);
|
||||
|
||||
let decode_with_var = Var(Symbol::DECODE_DECODE_WITH);
|
||||
let decode_with_fn = Box::new((
|
||||
this_decode_with_fn_var,
|
||||
Loc::at_zero(decode_with_var),
|
||||
this_decode_with_clos_var,
|
||||
this_decode_with_ret_var,
|
||||
));
|
||||
let decode_with_call = Call(
|
||||
decode_with_fn,
|
||||
vec![
|
||||
// bytes (Decode.list Decode.decoder) fmt
|
||||
(bytes_var, Loc::at_zero(Var(bytes_sym))),
|
||||
(this_decode_list_ret_var, Loc::at_zero(decode_list_call)),
|
||||
(fmt_var, Loc::at_zero(Var(fmt_sym))),
|
||||
],
|
||||
CalledVia::Space,
|
||||
);
|
||||
|
||||
(decode_with_call, this_decode_with_ret_var)
|
||||
};
|
||||
|
||||
// \bytes, fmt -> Decode.decodeWith bytes (Decode.list Decode.decoder) fmt
|
||||
let (custom_lambda, custom_var) = {
|
||||
let fn_name = env.new_symbol("custom");
|
||||
|
||||
// Create fn_var for ambient capture; we fix it up below.
|
||||
let fn_var = synth_var(env.subs, Content::Error);
|
||||
|
||||
// -[[fn_name]]->
|
||||
let fn_name_labels = UnionLambdas::insert_into_subs(env.subs, [(fn_name, vec![])]);
|
||||
let fn_clos_var = synth_var(
|
||||
env.subs,
|
||||
Content::LambdaSet(LambdaSet {
|
||||
solved: fn_name_labels,
|
||||
recursion_var: OptVariable::NONE,
|
||||
unspecialized: SubsSlice::default(),
|
||||
ambient_function: fn_var,
|
||||
}),
|
||||
);
|
||||
|
||||
// bytes, fmt -[[fn_name]]-> DecoderResult (List elem)
|
||||
let args_slice = SubsSlice::insert_into_subs(env.subs, vec![bytes_var, fmt_var]);
|
||||
env.subs.set_content(
|
||||
fn_var,
|
||||
Content::Structure(FlatType::Func(
|
||||
args_slice,
|
||||
fn_clos_var,
|
||||
decode_result_list_elem_var,
|
||||
)),
|
||||
);
|
||||
|
||||
// \bytes, fmt -[[fn_name]]-> Decode.decodeWith bytes (Decode.list Decode.decoder) fmt
|
||||
let clos = Closure(ClosureData {
|
||||
function_type: fn_var,
|
||||
closure_type: fn_clos_var,
|
||||
return_type: decode_result_list_elem_var,
|
||||
name: fn_name,
|
||||
captured_symbols: vec![],
|
||||
recursive: Recursive::NotRecursive,
|
||||
arguments: vec![
|
||||
(
|
||||
bytes_var,
|
||||
AnnotatedMark::known_exhaustive(),
|
||||
Loc::at_zero(Pattern::Identifier(bytes_sym)),
|
||||
),
|
||||
(
|
||||
fmt_var,
|
||||
AnnotatedMark::known_exhaustive(),
|
||||
Loc::at_zero(Pattern::Identifier(fmt_sym)),
|
||||
),
|
||||
],
|
||||
loc_body: Box::new(Loc::at_zero(decode_with_call)),
|
||||
});
|
||||
|
||||
(clos, fn_var)
|
||||
};
|
||||
|
||||
// Decode.custom \bytes, fmt -> Decode.decodeWith bytes (Decode.list Decode.decoder) fmt
|
||||
let (decode_custom_call, decoder_var) = {
|
||||
// (List U8, fmt -> DecodeResult val) -> Decoder val fmt | fmt has DecoderFormatting
|
||||
let decode_custom_type = env.import_builtin_symbol_var(Symbol::DECODE_CUSTOM);
|
||||
|
||||
// (List U8, fmt -> DecodeResult (List elem)) -> Decoder (List elem) fmt
|
||||
let this_decode_custom_args = SubsSlice::insert_into_subs(env.subs, [custom_var]);
|
||||
let this_decode_custom_clos_var = env.subs.fresh_unnamed_flex_var();
|
||||
let this_decode_custom_ret_var = env.subs.fresh_unnamed_flex_var();
|
||||
let this_decode_custom_fn_var = synth_var(
|
||||
env.subs,
|
||||
Content::Structure(FlatType::Func(
|
||||
this_decode_custom_args,
|
||||
this_decode_custom_clos_var,
|
||||
this_decode_custom_ret_var,
|
||||
)),
|
||||
);
|
||||
|
||||
// (List U8, fmt -> DecodeResult val) -> Decoder val fmt | fmt has DecoderFormatting
|
||||
// ~ (List U8, fmt -> DecodeResult (List elem)) -> Decoder (List elem) fmt
|
||||
env.unify(decode_custom_type, this_decode_custom_fn_var);
|
||||
|
||||
let decode_custom_var = Var(Symbol::DECODE_CUSTOM);
|
||||
let decode_custom_fn = Box::new((
|
||||
this_decode_custom_fn_var,
|
||||
Loc::at_zero(decode_custom_var),
|
||||
this_decode_custom_clos_var,
|
||||
this_decode_custom_ret_var,
|
||||
));
|
||||
let decode_custom_call = Call(
|
||||
decode_custom_fn,
|
||||
vec![(custom_var, Loc::at_zero(custom_lambda))],
|
||||
CalledVia::Space,
|
||||
);
|
||||
|
||||
(decode_custom_call, this_decode_custom_ret_var)
|
||||
};
|
||||
|
||||
(decode_custom_call, decoder_var)
|
||||
}
|
|
@ -2,173 +2,24 @@
|
|||
|
||||
use std::iter::once;
|
||||
|
||||
use roc_can::abilities::SpecializationLambdaSets;
|
||||
use roc_can::expr::{
|
||||
AnnotatedMark, ClosureData, Expr, Field, Recursive, WhenBranch, WhenBranchPattern,
|
||||
};
|
||||
use roc_can::module::ExposedByModule;
|
||||
use roc_can::pattern::Pattern;
|
||||
use roc_collections::SendMap;
|
||||
use roc_derive_key::encoding::FlatEncodableKey;
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::called_via::CalledVia;
|
||||
use roc_module::ident::Lowercase;
|
||||
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::{
|
||||
instantiate_rigids, Content, ExhaustiveMark, FlatType, GetSubsSlice, LambdaSet, OptVariable,
|
||||
RecordFields, RedundantMark, Subs, SubsSlice, UnionLambdas, UnionTags, Variable,
|
||||
VariableSubsSlice,
|
||||
Content, ExhaustiveMark, FlatType, GetSubsSlice, LambdaSet, OptVariable, RecordFields,
|
||||
RedundantMark, SubsSlice, UnionLambdas, UnionTags, Variable, VariableSubsSlice,
|
||||
};
|
||||
use roc_types::types::RecordField;
|
||||
|
||||
use crate::{synth_var, DerivedBody, DERIVED_SYNTH};
|
||||
|
||||
pub(crate) struct Env<'a> {
|
||||
/// NB: This **must** be subs for the derive module!
|
||||
pub subs: &'a mut Subs,
|
||||
pub exposed_types: &'a ExposedByModule,
|
||||
pub derived_ident_ids: &'a mut IdentIds,
|
||||
}
|
||||
|
||||
impl Env<'_> {
|
||||
fn new_symbol(&mut self, name_hint: &str) -> Symbol {
|
||||
if cfg!(any(
|
||||
debug_assertions,
|
||||
test,
|
||||
feature = "debug-derived-symbols"
|
||||
)) {
|
||||
let mut i = 0;
|
||||
let debug_name = loop {
|
||||
i += 1;
|
||||
let name = if i == 1 {
|
||||
name_hint.to_owned()
|
||||
} else {
|
||||
format!("{}{}", name_hint, i)
|
||||
};
|
||||
if self.derived_ident_ids.get_id(&name).is_none() {
|
||||
break name;
|
||||
}
|
||||
};
|
||||
|
||||
let ident_id = self.derived_ident_ids.get_or_insert(&debug_name);
|
||||
|
||||
Symbol::new(DERIVED_SYNTH, ident_id)
|
||||
} else {
|
||||
self.unique_symbol()
|
||||
}
|
||||
}
|
||||
|
||||
fn unique_symbol(&mut self) -> Symbol {
|
||||
let ident_id = self.derived_ident_ids.gen_unique();
|
||||
Symbol::new(DERIVED_SYNTH, ident_id)
|
||||
}
|
||||
|
||||
fn import_encode_symbol(&mut self, symbol: Symbol) -> Variable {
|
||||
debug_assert_eq!(symbol.module_id(), ModuleId::ENCODE);
|
||||
|
||||
let encode_types = &self
|
||||
.exposed_types
|
||||
.get(&ModuleId::ENCODE)
|
||||
.unwrap()
|
||||
.exposed_types_storage_subs;
|
||||
let storage_var = encode_types.stored_vars_by_symbol.get(&symbol).unwrap();
|
||||
let imported = encode_types
|
||||
.storage_subs
|
||||
.export_variable_to_directly_to_use_site(self.subs, *storage_var);
|
||||
|
||||
instantiate_rigids(self.subs, imported.variable);
|
||||
|
||||
imported.variable
|
||||
}
|
||||
|
||||
fn unify(&mut self, left: Variable, right: Variable) {
|
||||
use roc_unify::unify::{unify, Env, Mode, Unified};
|
||||
|
||||
let unified = unify(&mut Env::new(self.subs), left, right, Mode::EQ);
|
||||
|
||||
match unified {
|
||||
Unified::Success {
|
||||
vars: _,
|
||||
must_implement_ability: _,
|
||||
lambda_sets_to_specialize,
|
||||
extra_metadata: _,
|
||||
} => {
|
||||
if !lambda_sets_to_specialize.is_empty() {
|
||||
internal_error!("Did not expect derivers to need to specialize unspecialized lambda sets, but we got some: {:?}", lambda_sets_to_specialize)
|
||||
}
|
||||
}
|
||||
Unified::Failure(..) | Unified::BadType(..) => {
|
||||
internal_error!("Unification failed in deriver - that's a deriver bug!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_specialization_lambda_sets(
|
||||
&mut self,
|
||||
specialization_type: Variable,
|
||||
ability_member: Symbol,
|
||||
) -> SpecializationLambdaSets {
|
||||
use roc_unify::unify::{unify_introduced_ability_specialization, Env, Mode, Unified};
|
||||
|
||||
let member_signature = self.import_encode_symbol(ability_member);
|
||||
|
||||
let unified = unify_introduced_ability_specialization(
|
||||
&mut Env::new(self.subs),
|
||||
member_signature,
|
||||
specialization_type,
|
||||
Mode::EQ,
|
||||
);
|
||||
|
||||
match unified {
|
||||
Unified::Success {
|
||||
vars: _,
|
||||
must_implement_ability: _,
|
||||
lambda_sets_to_specialize: _lambda_sets_to_specialize,
|
||||
extra_metadata: specialization_lsets,
|
||||
} => {
|
||||
let specialization_lsets: SpecializationLambdaSets = specialization_lsets
|
||||
.0
|
||||
.into_iter()
|
||||
.map(|((spec_member, region), var)| {
|
||||
debug_assert_eq!(spec_member, ability_member);
|
||||
(region, var)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Since we're doing `{foo} ~ a | a has Encoding`, we may see "lambda sets to
|
||||
// specialize" for e.g. `{foo}:toEncoder:1`, but these are actually just the
|
||||
// specialization lambda sets, so we don't need to do any extra work!
|
||||
//
|
||||
// If there are other lambda sets to specialize in here, that's unexpected, because
|
||||
// that means we would have been deriving something like `toEncoder {foo: bar}`,
|
||||
// and now seen that we needed `toEncoder bar` where `bar` is a concrete type. But
|
||||
// we only expect `bar` to polymorphic at this stage!
|
||||
//
|
||||
// TODO: it would be better if `unify` could prune these for us. See also
|
||||
// https://github.com/rtfeldman/roc/issues/3207; that is a blocker for this TODO.
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
for (spec_var, lambda_sets) in _lambda_sets_to_specialize.drain() {
|
||||
for lambda_set in lambda_sets {
|
||||
let belongs_to_specialized_lambda_sets =
|
||||
specialization_lsets.iter().any(|(_, var)| {
|
||||
self.subs.get_root_key_without_compacting(*var)
|
||||
== self.subs.get_root_key_without_compacting(lambda_set)
|
||||
});
|
||||
debug_assert!(belongs_to_specialized_lambda_sets,
|
||||
"Did not expect derivers to need to specialize unspecialized lambda sets, but we got one: {:?} for {:?}", lambda_set, spec_var)
|
||||
}
|
||||
}
|
||||
}
|
||||
specialization_lsets
|
||||
}
|
||||
Unified::Failure(..) | Unified::BadType(..) => {
|
||||
internal_error!("Unification failed in deriver - that's a deriver bug!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
use crate::util::Env;
|
||||
use crate::{synth_var, DerivedBody};
|
||||
|
||||
pub(crate) fn derive_to_encoder(
|
||||
env: &mut Env<'_>,
|
||||
|
@ -253,7 +104,7 @@ fn to_encoder_list(env: &mut Env<'_>, fn_name: Symbol) -> (Expr, Variable) {
|
|||
|
||||
// build `toEncoder elem` type
|
||||
// val -[uls]-> Encoder fmt | fmt has EncoderFormatting
|
||||
let to_encoder_fn_var = env.import_encode_symbol(Symbol::ENCODE_TO_ENCODER);
|
||||
let to_encoder_fn_var = env.import_builtin_symbol_var(Symbol::ENCODE_TO_ENCODER);
|
||||
|
||||
// elem -[clos]-> t1
|
||||
let to_encoder_clos_var = env.subs.fresh_unnamed_flex_var(); // clos
|
||||
|
@ -333,7 +184,7 @@ fn to_encoder_list(env: &mut Env<'_>, fn_name: Symbol) -> (Expr, Variable) {
|
|||
|
||||
// build `Encode.list lst (\elem -> Encode.toEncoder elem)` type
|
||||
// List e, (e -> Encoder fmt) -[uls]-> Encoder fmt | fmt has EncoderFormatting
|
||||
let encode_list_fn_var = env.import_encode_symbol(Symbol::ENCODE_LIST);
|
||||
let encode_list_fn_var = env.import_builtin_symbol_var(Symbol::ENCODE_LIST);
|
||||
|
||||
// List elem, to_elem_encoder_fn_var -[clos]-> t1
|
||||
let this_encode_list_args_slice =
|
||||
|
@ -469,7 +320,7 @@ fn to_encoder_record(
|
|||
|
||||
// build `toEncoder rcd.a` type
|
||||
// val -[uls]-> Encoder fmt | fmt has EncoderFormatting
|
||||
let to_encoder_fn_var = env.import_encode_symbol(Symbol::ENCODE_TO_ENCODER);
|
||||
let to_encoder_fn_var = env.import_builtin_symbol_var(Symbol::ENCODE_TO_ENCODER);
|
||||
|
||||
// (typeof rcd.a) -[clos]-> t1
|
||||
let to_encoder_clos_var = env.subs.fresh_unnamed_flex_var(); // clos
|
||||
|
@ -549,7 +400,7 @@ fn to_encoder_record(
|
|||
|
||||
// build `Encode.record [ { key: .., value: ..}, .. ]` type
|
||||
// List { key : Str, value : Encoder fmt } -[uls]-> Encoder fmt | fmt has EncoderFormatting
|
||||
let encode_record_fn_var = env.import_encode_symbol(Symbol::ENCODE_RECORD);
|
||||
let encode_record_fn_var = env.import_builtin_symbol_var(Symbol::ENCODE_RECORD);
|
||||
|
||||
// fields_list_var -[clos]-> t1
|
||||
let fields_list_var_slice =
|
||||
|
@ -687,7 +538,8 @@ fn to_encoder_tag_union(
|
|||
.map(|(&sym, &sym_var)| {
|
||||
// build `toEncoder v1` type
|
||||
// expected: val -[uls]-> Encoder fmt | fmt has EncoderFormatting
|
||||
let to_encoder_fn_var = env.import_encode_symbol(Symbol::ENCODE_TO_ENCODER);
|
||||
let to_encoder_fn_var =
|
||||
env.import_builtin_symbol_var(Symbol::ENCODE_TO_ENCODER);
|
||||
|
||||
// wanted: t1 -[clos]-> t'
|
||||
let var_slice_of_sym_var =
|
||||
|
@ -747,7 +599,7 @@ fn to_encoder_tag_union(
|
|||
|
||||
// build `Encode.tag "A" [ ... ]` type
|
||||
// expected: Str, List (Encoder fmt) -[uls]-> Encoder fmt | fmt has EncoderFormatting
|
||||
let encode_tag_fn_var = env.import_encode_symbol(Symbol::ENCODE_TAG);
|
||||
let encode_tag_fn_var = env.import_builtin_symbol_var(Symbol::ENCODE_TAG);
|
||||
|
||||
// wanted: Str, List whole_encoders_var -[clos]-> t'
|
||||
let this_encode_tag_args_var_slice = VariableSubsSlice::insert_into_subs(
|
||||
|
@ -904,7 +756,7 @@ fn wrap_in_encode_custom(
|
|||
|
||||
// build `Encode.appendWith bytes encoder fmt` type
|
||||
// expected: Encode.appendWith : List U8, Encoder fmt, fmt -[appendWith]-> List U8 | fmt has EncoderFormatting
|
||||
let append_with_fn_var = env.import_encode_symbol(Symbol::ENCODE_APPEND_WITH);
|
||||
let append_with_fn_var = env.import_builtin_symbol_var(Symbol::ENCODE_APPEND_WITH);
|
||||
|
||||
// wanted: Encode.appendWith : List U8, encoder_var, fmt -[clos]-> List U8 | fmt has EncoderFormatting
|
||||
let this_append_with_args_var_slice =
|
||||
|
@ -995,7 +847,7 @@ fn wrap_in_encode_custom(
|
|||
// Encode.custom \bytes, fmt -> Encode.appendWith bytes encoder fmt
|
||||
//
|
||||
// expected: Encode.custom : (List U8, fmt -> List U8) -> Encoder fmt | fmt has EncoderFormatting
|
||||
let custom_fn_var = env.import_encode_symbol(Symbol::ENCODE_CUSTOM);
|
||||
let custom_fn_var = env.import_builtin_symbol_var(Symbol::ENCODE_CUSTOM);
|
||||
|
||||
// wanted: Encode.custom : fn_var -[clos]-> t'
|
||||
let this_custom_args_var_slice = VariableSubsSlice::insert_into_subs(env.subs, [fn_var]);
|
||||
|
|
|
@ -14,9 +14,13 @@ use roc_region::all::Loc;
|
|||
use roc_types::subs::{
|
||||
copy_import_to, Content, Descriptor, Mark, OptVariable, Rank, Subs, Variable,
|
||||
};
|
||||
use util::Env;
|
||||
|
||||
mod decoding;
|
||||
mod encoding;
|
||||
|
||||
mod util;
|
||||
|
||||
pub(crate) const DERIVED_SYNTH: ModuleId = ModuleId::DERIVED_SYNTH;
|
||||
|
||||
pub fn synth_var(subs: &mut Subs, content: Content) -> Variable {
|
||||
|
@ -56,20 +60,23 @@ fn build_derived_body(
|
|||
derived_symbol: Symbol,
|
||||
derive_key: DeriveKey,
|
||||
) -> (Def, SpecializationLambdaSets) {
|
||||
let mut env = Env {
|
||||
subs: derived_subs,
|
||||
exposed_types: exposed_by_module,
|
||||
derived_ident_ids,
|
||||
};
|
||||
|
||||
let DerivedBody {
|
||||
body,
|
||||
body_type,
|
||||
specialization_lambda_sets,
|
||||
} = match derive_key {
|
||||
DeriveKey::ToEncoder(to_encoder_key) => {
|
||||
let mut env = encoding::Env {
|
||||
subs: derived_subs,
|
||||
exposed_types: exposed_by_module,
|
||||
derived_ident_ids,
|
||||
};
|
||||
encoding::derive_to_encoder(&mut env, to_encoder_key, derived_symbol)
|
||||
}
|
||||
DeriveKey::Decoding => todo!(),
|
||||
DeriveKey::Decoder(decoder_key) => {
|
||||
decoding::derive_decoder(&mut env, decoder_key, derived_symbol)
|
||||
}
|
||||
};
|
||||
|
||||
let def = Def {
|
||||
|
@ -174,18 +181,18 @@ impl DerivedModule {
|
|||
&mut self,
|
||||
gen_subs: &mut Subs,
|
||||
should_load_def: impl Fn(Symbol) -> bool,
|
||||
) -> VecMap<Symbol, Expr> {
|
||||
) -> VecMap<Symbol, (Expr, Variable)> {
|
||||
self.map
|
||||
.values()
|
||||
.filter_map(|(symbol, def, _)| {
|
||||
if should_load_def(*symbol) {
|
||||
let (_new_expr_var, new_expr) = roc_can::copy::deep_copy_expr_across_subs(
|
||||
let (new_expr_var, new_expr) = roc_can::copy::deep_copy_expr_across_subs(
|
||||
&mut self.subs,
|
||||
gen_subs,
|
||||
def.expr_var,
|
||||
&def.loc_expr.value,
|
||||
);
|
||||
Some((*symbol, new_expr))
|
||||
Some((*symbol, (new_expr, new_expr_var)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
155
crates/compiler/derive/src/util.rs
Normal file
155
crates/compiler/derive/src/util.rs
Normal file
|
@ -0,0 +1,155 @@
|
|||
use roc_can::{abilities::SpecializationLambdaSets, module::ExposedByModule};
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::symbol::{IdentIds, Symbol};
|
||||
use roc_types::subs::{instantiate_rigids, Subs, Variable};
|
||||
|
||||
use crate::DERIVED_SYNTH;
|
||||
|
||||
/// An environment representing the Derived_synth module, for use in building derived
|
||||
/// implementations.
|
||||
pub(crate) struct Env<'a> {
|
||||
/// NB: This **must** be subs for the derive module!
|
||||
pub subs: &'a mut Subs,
|
||||
pub exposed_types: &'a ExposedByModule,
|
||||
pub derived_ident_ids: &'a mut IdentIds,
|
||||
}
|
||||
|
||||
impl Env<'_> {
|
||||
pub fn new_symbol(&mut self, name_hint: &str) -> Symbol {
|
||||
if cfg!(any(
|
||||
debug_assertions,
|
||||
test,
|
||||
feature = "debug-derived-symbols"
|
||||
)) {
|
||||
let mut i = 0;
|
||||
let debug_name = loop {
|
||||
i += 1;
|
||||
let name = if i == 1 {
|
||||
name_hint.to_owned()
|
||||
} else {
|
||||
format!("{}{}", name_hint, i)
|
||||
};
|
||||
if self.derived_ident_ids.get_id(&name).is_none() {
|
||||
break name;
|
||||
}
|
||||
};
|
||||
|
||||
let ident_id = self.derived_ident_ids.get_or_insert(&debug_name);
|
||||
|
||||
Symbol::new(DERIVED_SYNTH, ident_id)
|
||||
} else {
|
||||
self.unique_symbol()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unique_symbol(&mut self) -> Symbol {
|
||||
let ident_id = self.derived_ident_ids.gen_unique();
|
||||
Symbol::new(DERIVED_SYNTH, ident_id)
|
||||
}
|
||||
|
||||
pub fn import_builtin_symbol_var(&mut self, symbol: Symbol) -> Variable {
|
||||
let module_id = symbol.module_id();
|
||||
debug_assert!(module_id.is_builtin());
|
||||
|
||||
let module_types = &self
|
||||
.exposed_types
|
||||
.get(&module_id)
|
||||
.unwrap()
|
||||
.exposed_types_storage_subs;
|
||||
let storage_var = module_types.stored_vars_by_symbol.get(&symbol).unwrap();
|
||||
let imported = module_types
|
||||
.storage_subs
|
||||
.export_variable_to_directly_to_use_site(self.subs, *storage_var);
|
||||
|
||||
instantiate_rigids(self.subs, imported.variable);
|
||||
|
||||
imported.variable
|
||||
}
|
||||
|
||||
pub fn unify(&mut self, left: Variable, right: Variable) {
|
||||
use roc_unify::unify::{unify, Env, Mode, Unified};
|
||||
|
||||
let unified = unify(&mut Env::new(self.subs), left, right, Mode::EQ);
|
||||
|
||||
match unified {
|
||||
Unified::Success {
|
||||
vars: _,
|
||||
must_implement_ability: _,
|
||||
lambda_sets_to_specialize,
|
||||
extra_metadata: _,
|
||||
} => {
|
||||
if !lambda_sets_to_specialize.is_empty() {
|
||||
internal_error!("Did not expect derivers to need to specialize unspecialized lambda sets, but we got some: {:?}", lambda_sets_to_specialize)
|
||||
}
|
||||
}
|
||||
Unified::Failure(..) | Unified::BadType(..) => {
|
||||
internal_error!("Unification failed in deriver - that's a deriver bug!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_specialization_lambda_sets(
|
||||
&mut self,
|
||||
specialization_type: Variable,
|
||||
ability_member: Symbol,
|
||||
) -> SpecializationLambdaSets {
|
||||
use roc_unify::unify::{unify_introduced_ability_specialization, Env, Mode, Unified};
|
||||
|
||||
let member_signature = self.import_builtin_symbol_var(ability_member);
|
||||
|
||||
let unified = unify_introduced_ability_specialization(
|
||||
&mut Env::new(self.subs),
|
||||
member_signature,
|
||||
specialization_type,
|
||||
Mode::EQ,
|
||||
);
|
||||
|
||||
match unified {
|
||||
Unified::Success {
|
||||
vars: _,
|
||||
must_implement_ability: _,
|
||||
lambda_sets_to_specialize: _lambda_sets_to_specialize,
|
||||
extra_metadata: specialization_lsets,
|
||||
} => {
|
||||
let specialization_lsets: SpecializationLambdaSets = specialization_lsets
|
||||
.0
|
||||
.into_iter()
|
||||
.map(|((spec_member, region), var)| {
|
||||
debug_assert_eq!(spec_member, ability_member);
|
||||
(region, var)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Since we're doing `{foo} ~ a | a has Encoding`, we may see "lambda sets to
|
||||
// specialize" for e.g. `{foo}:toEncoder:1`, but these are actually just the
|
||||
// specialization lambda sets, so we don't need to do any extra work!
|
||||
//
|
||||
// If there are other lambda sets to specialize in here, that's unexpected, because
|
||||
// that means we would have been deriving something like `toEncoder {foo: bar}`,
|
||||
// and now seen that we needed `toEncoder bar` where `bar` is a concrete type. But
|
||||
// we only expect `bar` to polymorphic at this stage!
|
||||
//
|
||||
// TODO: it would be better if `unify` could prune these for us. See also
|
||||
// https://github.com/rtfeldman/roc/issues/3207; that is a blocker for this TODO.
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
for (spec_var, lambda_sets) in _lambda_sets_to_specialize.drain() {
|
||||
for lambda_set in lambda_sets {
|
||||
let belongs_to_specialized_lambda_sets =
|
||||
specialization_lsets.iter().any(|(_, var)| {
|
||||
self.subs.get_root_key_without_compacting(*var)
|
||||
== self.subs.get_root_key_without_compacting(lambda_set)
|
||||
});
|
||||
debug_assert!(belongs_to_specialized_lambda_sets,
|
||||
"Did not expect derivers to need to specialize unspecialized lambda sets, but we got one: {:?} for {:?}", lambda_set, spec_var)
|
||||
}
|
||||
}
|
||||
}
|
||||
specialization_lsets
|
||||
}
|
||||
Unified::Failure(..) | Unified::BadType(..) => {
|
||||
internal_error!("Unification failed in deriver - that's a deriver bug!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_derive_key"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
84
crates/compiler/derive_key/src/decoding.rs
Normal file
84
crates/compiler/derive_key/src/decoding.rs
Normal file
|
@ -0,0 +1,84 @@
|
|||
use roc_module::symbol::Symbol;
|
||||
use roc_types::subs::{Content, FlatType, Subs, Variable};
|
||||
|
||||
use crate::DeriveError;
|
||||
|
||||
#[derive(Hash)]
|
||||
pub enum FlatDecodable {
|
||||
Immediate(Symbol),
|
||||
Key(FlatDecodableKey),
|
||||
}
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, Debug, Clone)]
|
||||
pub enum FlatDecodableKey {
|
||||
List(/* takes one variable */),
|
||||
}
|
||||
|
||||
impl FlatDecodableKey {
|
||||
pub(crate) fn debug_name(&self) -> String {
|
||||
match self {
|
||||
FlatDecodableKey::List() => "list".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FlatDecodable {
|
||||
pub(crate) fn from_var(subs: &Subs, var: Variable) -> Result<FlatDecodable, DeriveError> {
|
||||
use DeriveError::*;
|
||||
use FlatDecodable::*;
|
||||
match *subs.get_content_without_compacting(var) {
|
||||
Content::Structure(flat_type) => match flat_type {
|
||||
FlatType::Apply(sym, _) => match sym {
|
||||
Symbol::LIST_LIST => Ok(Key(FlatDecodableKey::List())),
|
||||
Symbol::STR_STR => Ok(Immediate(Symbol::DECODE_STRING)),
|
||||
_ => Err(Underivable),
|
||||
},
|
||||
FlatType::Record(_fields, _ext) => {
|
||||
Err(Underivable) // yet
|
||||
}
|
||||
FlatType::TagUnion(_tags, _ext) | FlatType::RecursiveTagUnion(_, _tags, _ext) => {
|
||||
Err(Underivable) // yet
|
||||
}
|
||||
FlatType::FunctionOrTagUnion(_name_index, _, _) => {
|
||||
Err(Underivable) // yet
|
||||
}
|
||||
FlatType::EmptyRecord => {
|
||||
Err(Underivable) // yet
|
||||
}
|
||||
FlatType::EmptyTagUnion => {
|
||||
Err(Underivable) // yet
|
||||
}
|
||||
//
|
||||
FlatType::Erroneous(_) => Err(Underivable),
|
||||
FlatType::Func(..) => Err(Underivable),
|
||||
},
|
||||
Content::Alias(sym, _, real_var, _) => match sym {
|
||||
Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 => Ok(Immediate(Symbol::DECODE_U8)),
|
||||
Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16 => Ok(Immediate(Symbol::DECODE_U16)),
|
||||
Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32 => Ok(Immediate(Symbol::DECODE_U32)),
|
||||
Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64 => Ok(Immediate(Symbol::DECODE_U64)),
|
||||
Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128 => Ok(Immediate(Symbol::DECODE_U128)),
|
||||
Symbol::NUM_I8 | Symbol::NUM_SIGNED8 => Ok(Immediate(Symbol::DECODE_I8)),
|
||||
Symbol::NUM_I16 | Symbol::NUM_SIGNED16 => Ok(Immediate(Symbol::DECODE_I16)),
|
||||
Symbol::NUM_I32 | Symbol::NUM_SIGNED32 => Ok(Immediate(Symbol::DECODE_I32)),
|
||||
Symbol::NUM_I64 | Symbol::NUM_SIGNED64 => Ok(Immediate(Symbol::DECODE_I64)),
|
||||
Symbol::NUM_I128 | Symbol::NUM_SIGNED128 => Ok(Immediate(Symbol::DECODE_I128)),
|
||||
Symbol::NUM_DEC | Symbol::NUM_DECIMAL => Ok(Immediate(Symbol::DECODE_DEC)),
|
||||
Symbol::NUM_F32 | Symbol::NUM_BINARY32 => Ok(Immediate(Symbol::DECODE_F32)),
|
||||
Symbol::NUM_F64 | Symbol::NUM_BINARY64 => Ok(Immediate(Symbol::DECODE_F64)),
|
||||
// NB: I believe it is okay to unwrap opaques here because derivers are only used
|
||||
// by the backend, and the backend treats opaques like structural aliases.
|
||||
_ => Self::from_var(subs, real_var),
|
||||
},
|
||||
Content::RangedNumber(_) => Err(Underivable),
|
||||
//
|
||||
Content::RecursionVar { .. } => Err(Underivable),
|
||||
Content::Error => Err(Underivable),
|
||||
Content::FlexVar(_)
|
||||
| Content::RigidVar(_)
|
||||
| Content::FlexAbleVar(_, _)
|
||||
| Content::RigidAbleVar(_, _) => Err(UnboundVar),
|
||||
Content::LambdaSet(_) => Err(Underivable),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,8 +13,10 @@
|
|||
//! For these reasons the content keying is based on a strategy as well, which are the variants of
|
||||
//! [`DeriveKey`].
|
||||
|
||||
pub mod decoding;
|
||||
pub mod encoding;
|
||||
|
||||
use decoding::{FlatDecodable, FlatDecodableKey};
|
||||
use encoding::{FlatEncodable, FlatEncodableKey};
|
||||
|
||||
use roc_module::symbol::Symbol;
|
||||
|
@ -33,15 +35,14 @@ pub enum DeriveError {
|
|||
#[repr(u8)]
|
||||
pub enum DeriveKey {
|
||||
ToEncoder(FlatEncodableKey),
|
||||
#[allow(unused)]
|
||||
Decoding,
|
||||
Decoder(FlatDecodableKey),
|
||||
}
|
||||
|
||||
impl DeriveKey {
|
||||
pub fn debug_name(&self) -> String {
|
||||
match self {
|
||||
DeriveKey::ToEncoder(key) => format!("toEncoder_{}", key.debug_name()),
|
||||
DeriveKey::Decoding => todo!(),
|
||||
DeriveKey::Decoder(key) => format!("decoder_{}", key.debug_name()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,11 +58,40 @@ pub enum Derived {
|
|||
Key(DeriveKey),
|
||||
}
|
||||
|
||||
impl Derived {
|
||||
pub fn encoding(subs: &Subs, var: Variable) -> Result<Self, DeriveError> {
|
||||
match encoding::FlatEncodable::from_var(subs, var)? {
|
||||
FlatEncodable::Immediate(imm) => Ok(Derived::Immediate(imm)),
|
||||
FlatEncodable::Key(repr) => Ok(Derived::Key(DeriveKey::ToEncoder(repr))),
|
||||
/// The builtin ability member to derive.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum DeriveBuiltin {
|
||||
ToEncoder,
|
||||
Decoder,
|
||||
}
|
||||
|
||||
impl TryFrom<Symbol> for DeriveBuiltin {
|
||||
type Error = Symbol;
|
||||
|
||||
fn try_from(value: Symbol) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Symbol::ENCODE_TO_ENCODER => Ok(DeriveBuiltin::ToEncoder),
|
||||
Symbol::DECODE_DECODER => Ok(DeriveBuiltin::Decoder),
|
||||
_ => Err(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Derived {
|
||||
pub fn builtin(
|
||||
builtin: DeriveBuiltin,
|
||||
subs: &Subs,
|
||||
var: Variable,
|
||||
) -> Result<Self, DeriveError> {
|
||||
match builtin {
|
||||
DeriveBuiltin::ToEncoder => match encoding::FlatEncodable::from_var(subs, var)? {
|
||||
FlatEncodable::Immediate(imm) => Ok(Derived::Immediate(imm)),
|
||||
FlatEncodable::Key(repr) => Ok(Derived::Key(DeriveKey::ToEncoder(repr))),
|
||||
},
|
||||
DeriveBuiltin::Decoder => match decoding::FlatDecodable::from_var(subs, var)? {
|
||||
FlatDecodable::Immediate(imm) => Ok(Derived::Immediate(imm)),
|
||||
FlatDecodable::Key(repr) => Ok(Derived::Key(DeriveKey::Decoder(repr))),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_exhaustive"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_fmt"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -288,13 +288,18 @@ fn fmt_expect<'a, 'buf>(
|
|||
is_multiline: bool,
|
||||
indent: u16,
|
||||
) {
|
||||
buf.ensure_ends_with_newline();
|
||||
buf.indent(indent);
|
||||
buf.push_str("expect");
|
||||
|
||||
let return_indent = if is_multiline {
|
||||
buf.newline();
|
||||
indent + INDENT
|
||||
} else {
|
||||
buf.spaces(1);
|
||||
indent
|
||||
};
|
||||
|
||||
buf.push_str("expect");
|
||||
condition.format(buf, return_indent);
|
||||
}
|
||||
|
||||
|
|
|
@ -815,16 +815,24 @@ fn fmt_expect<'a, 'buf>(
|
|||
is_multiline: bool,
|
||||
indent: u16,
|
||||
) {
|
||||
buf.ensure_ends_with_newline();
|
||||
buf.indent(indent);
|
||||
buf.push_str("expect");
|
||||
|
||||
let return_indent = if is_multiline {
|
||||
buf.newline();
|
||||
indent + INDENT
|
||||
} else {
|
||||
buf.spaces(1);
|
||||
indent
|
||||
};
|
||||
|
||||
buf.push_str("expect");
|
||||
condition.format(buf, return_indent);
|
||||
buf.push('\n');
|
||||
continuation.format(buf, return_indent);
|
||||
|
||||
// Always put a blank line after the `expect` line(s)
|
||||
buf.ensure_ends_with_blank_line();
|
||||
|
||||
continuation.format(buf, indent);
|
||||
}
|
||||
|
||||
fn fmt_if<'a, 'buf>(
|
||||
|
|
|
@ -5399,6 +5399,64 @@ mod test_fmt {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expect_single_line() {
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
x = 5
|
||||
|
||||
expect x == y
|
||||
|
||||
expect y == z
|
||||
|
||||
42
|
||||
"#
|
||||
));
|
||||
|
||||
module_formats_same(indoc!(
|
||||
r#"
|
||||
interface Foo exposes [] imports []
|
||||
|
||||
expect x == y
|
||||
|
||||
expect y == z
|
||||
|
||||
foo = bar
|
||||
"#
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expect_multiline() {
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
x = 5
|
||||
|
||||
expect
|
||||
foo bar
|
||||
|> baz
|
||||
|
||||
42
|
||||
"#
|
||||
));
|
||||
|
||||
module_formats_same(indoc!(
|
||||
r#"
|
||||
interface Foo exposes [] imports []
|
||||
|
||||
expect
|
||||
foo bar
|
||||
|> baz
|
||||
|
||||
expect
|
||||
blah
|
||||
etc
|
||||
|
||||
foo = bar
|
||||
"#
|
||||
));
|
||||
}
|
||||
|
||||
// this is a parse error atm
|
||||
// #[test]
|
||||
// fn multiline_apply() {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "roc_gen_dev"
|
||||
description = "The development backend for the Roc compiler"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "roc_gen_llvm"
|
||||
description = "The LLVM backend for the Roc compiler"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -4220,10 +4220,16 @@ pub fn build_procedures<'a, 'ctx, 'env>(
|
|||
env: &Env<'a, 'ctx, 'env>,
|
||||
opt_level: OptLevel,
|
||||
procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>,
|
||||
entry_point: EntryPoint<'a>,
|
||||
opt_entry_point: Option<EntryPoint<'a>>,
|
||||
debug_output_file: Option<&Path>,
|
||||
) {
|
||||
build_procedures_help(env, opt_level, procedures, entry_point, debug_output_file);
|
||||
build_procedures_help(
|
||||
env,
|
||||
opt_level,
|
||||
procedures,
|
||||
opt_entry_point,
|
||||
debug_output_file,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn build_wasm_test_wrapper<'a, 'ctx, 'env>(
|
||||
|
@ -4236,7 +4242,7 @@ pub fn build_wasm_test_wrapper<'a, 'ctx, 'env>(
|
|||
env,
|
||||
opt_level,
|
||||
procedures,
|
||||
entry_point,
|
||||
Some(entry_point),
|
||||
Some(Path::new("/tmp/test.ll")),
|
||||
);
|
||||
|
||||
|
@ -4253,7 +4259,7 @@ pub fn build_procedures_return_main<'a, 'ctx, 'env>(
|
|||
env,
|
||||
opt_level,
|
||||
procedures,
|
||||
entry_point,
|
||||
Some(entry_point),
|
||||
Some(Path::new("/tmp/test.ll")),
|
||||
);
|
||||
|
||||
|
@ -4265,13 +4271,13 @@ pub fn build_procedures_expose_expects<'a, 'ctx, 'env>(
|
|||
opt_level: OptLevel,
|
||||
expects: &[Symbol],
|
||||
procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>,
|
||||
entry_point: EntryPoint<'a>,
|
||||
opt_entry_point: Option<EntryPoint<'a>>,
|
||||
) -> Vec<'a, &'a str> {
|
||||
let mod_solutions = build_procedures_help(
|
||||
env,
|
||||
opt_level,
|
||||
procedures,
|
||||
entry_point,
|
||||
opt_entry_point,
|
||||
Some(Path::new("/tmp/test.ll")),
|
||||
);
|
||||
|
||||
|
@ -4333,7 +4339,7 @@ fn build_procedures_help<'a, 'ctx, 'env>(
|
|||
env: &Env<'a, 'ctx, 'env>,
|
||||
opt_level: OptLevel,
|
||||
procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>,
|
||||
entry_point: EntryPoint<'a>,
|
||||
opt_entry_point: Option<EntryPoint<'a>>,
|
||||
debug_output_file: Option<&Path>,
|
||||
) -> &'a ModSolutions {
|
||||
let mut layout_ids = roc_mono::layout::LayoutIds::default();
|
||||
|
@ -4341,7 +4347,7 @@ fn build_procedures_help<'a, 'ctx, 'env>(
|
|||
|
||||
let it = procedures.iter().map(|x| x.1);
|
||||
|
||||
let solutions = match roc_alias_analysis::spec_program(opt_level, entry_point, it) {
|
||||
let solutions = match roc_alias_analysis::spec_program(opt_level, opt_entry_point, it) {
|
||||
Err(e) => panic!("Error in alias analysis: {}", e),
|
||||
Ok(solutions) => solutions,
|
||||
};
|
||||
|
@ -6951,21 +6957,30 @@ fn build_int_binop<'a, 'ctx, 'env>(
|
|||
// but llvm normalizes to the above ordering in -O3
|
||||
let zero = rhs.get_type().const_zero();
|
||||
let neg_1 = rhs.get_type().const_int(-1i64 as u64, false);
|
||||
let is_signed = int_width.is_signed();
|
||||
|
||||
let special_block = env.context.append_basic_block(parent, "special_block");
|
||||
let default_block = env.context.append_basic_block(parent, "default_block");
|
||||
let cont_block = env.context.append_basic_block(parent, "branchcont");
|
||||
|
||||
if is_signed {
|
||||
bd.build_switch(
|
||||
rhs,
|
||||
default_block,
|
||||
&[(zero, special_block), (neg_1, special_block)],
|
||||
);
|
||||
)
|
||||
} else {
|
||||
bd.build_switch(rhs, default_block, &[(zero, special_block)])
|
||||
};
|
||||
|
||||
let condition_rem = {
|
||||
bd.position_at_end(default_block);
|
||||
|
||||
let rem = bd.build_int_signed_rem(lhs, rhs, "int_rem");
|
||||
let rem = if is_signed {
|
||||
bd.build_int_signed_rem(lhs, rhs, "int_rem")
|
||||
} else {
|
||||
bd.build_int_unsigned_rem(lhs, rhs, "uint_rem")
|
||||
};
|
||||
let result = bd.build_int_compare(IntPredicate::EQ, rem, zero, "is_zero_rem");
|
||||
|
||||
bd.build_unconditional_branch(cont_block);
|
||||
|
@ -6976,10 +6991,15 @@ fn build_int_binop<'a, 'ctx, 'env>(
|
|||
bd.position_at_end(special_block);
|
||||
|
||||
let is_zero = bd.build_int_compare(IntPredicate::EQ, lhs, zero, "is_zero_lhs");
|
||||
|
||||
let result = if is_signed {
|
||||
let is_neg_one =
|
||||
bd.build_int_compare(IntPredicate::EQ, rhs, neg_1, "is_neg_one_rhs");
|
||||
|
||||
let result = bd.build_or(is_neg_one, is_zero, "cond");
|
||||
bd.build_or(is_neg_one, is_zero, "cond")
|
||||
} else {
|
||||
is_zero
|
||||
};
|
||||
|
||||
bd.build_unconditional_branch(cont_block);
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::debug_info_init;
|
||||
use crate::llvm::bitcode::call_str_bitcode_fn;
|
||||
use crate::llvm::build::{get_tag_id, store_roc_value, Env};
|
||||
use crate::llvm::build::{get_tag_id, store_roc_value, tag_pointer_clear_tag_id, Env};
|
||||
use crate::llvm::build_list::{self, incrementing_elem_loop};
|
||||
use crate::llvm::convert::{basic_type_from_layout, RocUnion};
|
||||
use inkwell::builder::Builder;
|
||||
|
@ -503,6 +503,9 @@ fn build_clone_tag_help<'a, 'ctx, 'env>(
|
|||
.unwrap();
|
||||
|
||||
let layout = Layout::struct_no_name_order(field_layouts);
|
||||
let layout = Layout::struct_no_name_order(
|
||||
env.arena.alloc([layout, union_layout.tag_id_layout()]),
|
||||
);
|
||||
let basic_type = basic_type_from_layout(env, &layout);
|
||||
|
||||
let data_ptr = env.builder.build_pointer_cast(
|
||||
|
@ -533,7 +536,304 @@ fn build_clone_tag_help<'a, 'ctx, 'env>(
|
|||
}
|
||||
}
|
||||
}
|
||||
_ => todo!(),
|
||||
Recursive(tags) => {
|
||||
let id = get_tag_id(env, parent, &union_layout, tag_value);
|
||||
|
||||
let switch_block = env.context.append_basic_block(parent, "switch_block");
|
||||
env.builder.build_unconditional_branch(switch_block);
|
||||
|
||||
let mut cases = Vec::with_capacity_in(tags.len(), env.arena);
|
||||
|
||||
for (tag_id, field_layouts) in tags.iter().enumerate() {
|
||||
let block = env.context.append_basic_block(parent, "tag_id_modify");
|
||||
env.builder.position_at_end(block);
|
||||
|
||||
// write the "pointer" of the current offset
|
||||
write_pointer_with_tag_id(env, ptr, offset, extra_offset, union_layout, tag_id);
|
||||
|
||||
let tag_value = tag_pointer_clear_tag_id(env, tag_value.into_pointer_value());
|
||||
|
||||
let raw_data_ptr = env
|
||||
.builder
|
||||
.build_struct_gep(tag_value, RocUnion::TAG_DATA_INDEX, "tag_data")
|
||||
.unwrap();
|
||||
|
||||
let layout = Layout::struct_no_name_order(field_layouts);
|
||||
let layout = if union_layout.stores_tag_id_in_pointer(env.target_info) {
|
||||
layout
|
||||
} else {
|
||||
Layout::struct_no_name_order(
|
||||
env.arena.alloc([layout, union_layout.tag_id_layout()]),
|
||||
)
|
||||
};
|
||||
let basic_type = basic_type_from_layout(env, &layout);
|
||||
|
||||
let data_ptr = env.builder.build_pointer_cast(
|
||||
raw_data_ptr,
|
||||
basic_type.ptr_type(AddressSpace::Generic),
|
||||
"data_ptr",
|
||||
);
|
||||
|
||||
let data = env.builder.build_load(data_ptr, "load_data");
|
||||
|
||||
let (width, _) = union_layout.data_size_and_alignment(env.target_info);
|
||||
|
||||
let cursors = Cursors {
|
||||
offset: extra_offset,
|
||||
extra_offset: env.builder.build_int_add(
|
||||
extra_offset,
|
||||
env.ptr_int().const_int(width as _, false),
|
||||
"new_offset",
|
||||
),
|
||||
};
|
||||
|
||||
let when_recursive = WhenRecursive::Loop(union_layout);
|
||||
let answer =
|
||||
build_clone(env, layout_ids, ptr, cursors, data, layout, when_recursive);
|
||||
|
||||
env.builder.build_return(Some(&answer));
|
||||
|
||||
cases.push((id.get_type().const_int(tag_id as u64, false), block));
|
||||
}
|
||||
|
||||
env.builder.position_at_end(switch_block);
|
||||
|
||||
match cases.pop() {
|
||||
Some((_, default)) => {
|
||||
env.builder.build_switch(id, default, &cases);
|
||||
}
|
||||
None => {
|
||||
// we're serializing an empty tag union; this code is effectively unreachable
|
||||
env.builder.build_unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
NonNullableUnwrapped(fields) => {
|
||||
//
|
||||
|
||||
let tag_value = tag_value.into_pointer_value();
|
||||
|
||||
build_copy(env, ptr, offset, extra_offset.into());
|
||||
|
||||
let layout = Layout::struct_no_name_order(fields);
|
||||
let basic_type = basic_type_from_layout(env, &layout);
|
||||
|
||||
let (width, _) = union_layout.data_size_and_alignment(env.target_info);
|
||||
|
||||
let cursors = Cursors {
|
||||
offset: extra_offset,
|
||||
extra_offset: env.builder.build_int_add(
|
||||
extra_offset,
|
||||
env.ptr_int().const_int(width as _, false),
|
||||
"new_offset",
|
||||
),
|
||||
};
|
||||
|
||||
let raw_data_ptr = env
|
||||
.builder
|
||||
.build_struct_gep(tag_value, RocUnion::TAG_DATA_INDEX, "tag_data")
|
||||
.unwrap();
|
||||
|
||||
let data_ptr = env.builder.build_pointer_cast(
|
||||
raw_data_ptr,
|
||||
basic_type.ptr_type(AddressSpace::Generic),
|
||||
"data_ptr",
|
||||
);
|
||||
|
||||
let data = env.builder.build_load(data_ptr, "load_data");
|
||||
|
||||
let when_recursive = WhenRecursive::Loop(union_layout);
|
||||
let answer = build_clone(env, layout_ids, ptr, cursors, data, layout, when_recursive);
|
||||
|
||||
env.builder.build_return(Some(&answer));
|
||||
}
|
||||
NullableWrapped {
|
||||
nullable_id,
|
||||
other_tags,
|
||||
} => {
|
||||
let switch_block = env.context.append_basic_block(parent, "switch_block");
|
||||
let null_block = env.context.append_basic_block(parent, "null_block");
|
||||
|
||||
let id = get_tag_id(env, parent, &union_layout, tag_value);
|
||||
|
||||
let comparison = env
|
||||
.builder
|
||||
.build_is_null(tag_value.into_pointer_value(), "is_null");
|
||||
|
||||
env.builder
|
||||
.build_conditional_branch(comparison, null_block, switch_block);
|
||||
|
||||
{
|
||||
let mut cases = Vec::with_capacity_in(other_tags.len(), env.arena);
|
||||
|
||||
for i in 0..other_tags.len() + 1 {
|
||||
if i == nullable_id as _ {
|
||||
continue;
|
||||
}
|
||||
|
||||
let block = env.context.append_basic_block(parent, "tag_id_modify");
|
||||
env.builder.position_at_end(block);
|
||||
|
||||
// write the "pointer" of the current offset
|
||||
write_pointer_with_tag_id(env, ptr, offset, extra_offset, union_layout, i);
|
||||
|
||||
let fields = if i >= nullable_id as _ {
|
||||
other_tags[i - 1]
|
||||
} else {
|
||||
other_tags[i]
|
||||
};
|
||||
|
||||
let layout = Layout::struct_no_name_order(fields);
|
||||
let basic_type = basic_type_from_layout(env, &layout);
|
||||
|
||||
let (width, _) = union_layout.data_size_and_alignment(env.target_info);
|
||||
|
||||
let cursors = Cursors {
|
||||
offset: extra_offset,
|
||||
extra_offset: env.builder.build_int_add(
|
||||
extra_offset,
|
||||
env.ptr_int().const_int(width as _, false),
|
||||
"new_offset",
|
||||
),
|
||||
};
|
||||
|
||||
let tag_value = tag_pointer_clear_tag_id(env, tag_value.into_pointer_value());
|
||||
|
||||
let raw_data_ptr = env
|
||||
.builder
|
||||
.build_struct_gep(tag_value, RocUnion::TAG_DATA_INDEX, "tag_data")
|
||||
.unwrap();
|
||||
|
||||
let data_ptr = env.builder.build_pointer_cast(
|
||||
raw_data_ptr,
|
||||
basic_type.ptr_type(AddressSpace::Generic),
|
||||
"data_ptr",
|
||||
);
|
||||
|
||||
let data = env.builder.build_load(data_ptr, "load_data");
|
||||
|
||||
let when_recursive = WhenRecursive::Loop(union_layout);
|
||||
let answer =
|
||||
build_clone(env, layout_ids, ptr, cursors, data, layout, when_recursive);
|
||||
|
||||
env.builder.build_return(Some(&answer));
|
||||
|
||||
cases.push((id.get_type().const_int(i as u64, false), block));
|
||||
}
|
||||
|
||||
env.builder.position_at_end(switch_block);
|
||||
|
||||
match cases.pop() {
|
||||
Some((_, default)) => {
|
||||
env.builder.build_switch(id, default, &cases);
|
||||
}
|
||||
None => {
|
||||
// we're serializing an empty tag union; this code is effectively unreachable
|
||||
env.builder.build_unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
env.builder.position_at_end(null_block);
|
||||
|
||||
let value = env.ptr_int().const_zero();
|
||||
build_copy(env, ptr, offset, value.into());
|
||||
|
||||
env.builder.build_return(Some(&extra_offset));
|
||||
}
|
||||
}
|
||||
NullableUnwrapped { other_fields, .. } => {
|
||||
let other_block = env.context.append_basic_block(parent, "other_block");
|
||||
let null_block = env.context.append_basic_block(parent, "null_block");
|
||||
|
||||
let comparison = env
|
||||
.builder
|
||||
.build_is_null(tag_value.into_pointer_value(), "is_null");
|
||||
|
||||
env.builder
|
||||
.build_conditional_branch(comparison, null_block, other_block);
|
||||
|
||||
{
|
||||
env.builder.position_at_end(null_block);
|
||||
|
||||
let value = env.ptr_int().const_zero();
|
||||
build_copy(env, ptr, offset, value.into());
|
||||
|
||||
env.builder.build_return(Some(&extra_offset));
|
||||
}
|
||||
|
||||
{
|
||||
env.builder.position_at_end(other_block);
|
||||
|
||||
// write the "pointer" af the current offset
|
||||
build_copy(env, ptr, offset, extra_offset.into());
|
||||
|
||||
let layout = Layout::struct_no_name_order(other_fields);
|
||||
let basic_type = basic_type_from_layout(env, &layout);
|
||||
|
||||
let cursors = Cursors {
|
||||
offset: extra_offset,
|
||||
extra_offset: env.builder.build_int_add(
|
||||
extra_offset,
|
||||
env.ptr_int()
|
||||
.const_int(layout.stack_size(env.target_info) as _, false),
|
||||
"new_offset",
|
||||
),
|
||||
};
|
||||
|
||||
let raw_data_ptr = env
|
||||
.builder
|
||||
.build_struct_gep(
|
||||
tag_value.into_pointer_value(),
|
||||
RocUnion::TAG_DATA_INDEX,
|
||||
"tag_data",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let data_ptr = env.builder.build_pointer_cast(
|
||||
raw_data_ptr,
|
||||
basic_type.ptr_type(AddressSpace::Generic),
|
||||
"data_ptr",
|
||||
);
|
||||
|
||||
let data = env.builder.build_load(data_ptr, "load_data");
|
||||
|
||||
let when_recursive = WhenRecursive::Loop(union_layout);
|
||||
let answer =
|
||||
build_clone(env, layout_ids, ptr, cursors, data, layout, when_recursive);
|
||||
|
||||
env.builder.build_return(Some(&answer));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_pointer_with_tag_id<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
ptr: PointerValue<'ctx>,
|
||||
offset: IntValue<'ctx>,
|
||||
extra_offset: IntValue<'ctx>,
|
||||
union_layout: UnionLayout<'a>,
|
||||
tag_id: usize,
|
||||
) {
|
||||
if union_layout.stores_tag_id_in_pointer(env.target_info) {
|
||||
// first, store tag id as u32
|
||||
let tag_id_intval = env.context.i32_type().const_int(tag_id as _, false);
|
||||
build_copy(env, ptr, offset, tag_id_intval.into());
|
||||
|
||||
// increment offset by 4
|
||||
let four = env.ptr_int().const_int(4, false);
|
||||
let offset = env.builder.build_int_add(offset, four, "");
|
||||
|
||||
// cast to u32
|
||||
let extra_offset = env
|
||||
.builder
|
||||
.build_int_cast(extra_offset, env.context.i32_type(), "");
|
||||
|
||||
build_copy(env, ptr, offset, extra_offset.into());
|
||||
} else {
|
||||
build_copy(env, ptr, offset, extra_offset.into());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
[package]
|
||||
name = "roc_gen_wasm"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
|
||||
[dependencies]
|
||||
bitvec = "1"
|
||||
|
|
|
@ -715,7 +715,7 @@ impl<'a> WasmBackend<'a> {
|
|||
let mut current_stmt = stmt;
|
||||
while let Stmt::Let(sym, expr, layout, following) = current_stmt {
|
||||
if DEBUG_SETTINGS.let_stmt_ir {
|
||||
println!("let {:?} = {}", sym, expr.to_pretty(200)); // ignore `following`! Too confusing otherwise.
|
||||
print!("\nlet {:?} = {}", sym, expr.to_pretty(200));
|
||||
}
|
||||
|
||||
let kind = match following {
|
||||
|
@ -1479,7 +1479,14 @@ impl<'a> WasmBackend<'a> {
|
|||
// length of the list
|
||||
self.code_builder.get_local(stack_local_id);
|
||||
self.code_builder.i32_const(elems.len() as i32);
|
||||
self.code_builder.i32_store(Align::Bytes4, stack_offset + 4);
|
||||
self.code_builder
|
||||
.i32_store(Align::Bytes4, stack_offset + 4 * Builtin::WRAPPER_LEN);
|
||||
|
||||
// capacity of the list
|
||||
self.code_builder.get_local(stack_local_id);
|
||||
self.code_builder.i32_const(elems.len() as i32);
|
||||
self.code_builder
|
||||
.i32_store(Align::Bytes4, stack_offset + 4 * Builtin::WRAPPER_CAPACITY);
|
||||
|
||||
let mut elem_offset = 0;
|
||||
|
||||
|
@ -1521,12 +1528,14 @@ impl<'a> WasmBackend<'a> {
|
|||
if let StoredValue::StackMemory { location, .. } = storage {
|
||||
let (local_id, offset) = location.local_and_offset(self.storage.stack_frame_pointer);
|
||||
|
||||
// This is a minor cheat.
|
||||
// What we want to write to stack memory is { elements: null, length: 0 }
|
||||
// But instead of two 32-bit stores, we can do a single 64-bit store.
|
||||
// Store 12 bytes of zeros { elements: null, length: 0, capacity: 0 }
|
||||
debug_assert_eq!(Builtin::LIST_WORDS, 3);
|
||||
self.code_builder.get_local(local_id);
|
||||
self.code_builder.i64_const(0);
|
||||
self.code_builder.i64_store(Align::Bytes4, offset);
|
||||
self.code_builder.get_local(local_id);
|
||||
self.code_builder.i32_const(0);
|
||||
self.code_builder.i32_store(Align::Bytes4, offset + 8);
|
||||
} else {
|
||||
internal_error!("Unexpected storage for {:?}", sym)
|
||||
}
|
||||
|
@ -1566,13 +1575,24 @@ impl<'a> WasmBackend<'a> {
|
|||
StoredValue::Local { local_id, .. } => {
|
||||
// Tag is stored as a heap pointer.
|
||||
if let Some(reused) = maybe_reused {
|
||||
// Reuse an existing heap allocation
|
||||
// Reuse an existing heap allocation, if one is available (not NULL at runtime)
|
||||
self.storage.load_symbols(&mut self.code_builder, &[reused]);
|
||||
self.code_builder.if_();
|
||||
{
|
||||
self.storage.load_symbols(&mut self.code_builder, &[reused]);
|
||||
self.code_builder.set_local(local_id);
|
||||
}
|
||||
self.code_builder.else_();
|
||||
{
|
||||
self.allocate_with_refcount(Some(data_size), data_alignment, 1);
|
||||
self.code_builder.set_local(local_id);
|
||||
}
|
||||
self.code_builder.end();
|
||||
} else {
|
||||
// Call the allocator to get a memory address.
|
||||
self.allocate_with_refcount(Some(data_size), data_alignment, 1);
|
||||
}
|
||||
self.code_builder.set_local(local_id);
|
||||
}
|
||||
(local_id, 0)
|
||||
}
|
||||
StoredValue::VirtualMachineStack { .. } => {
|
||||
|
@ -1618,7 +1638,7 @@ impl<'a> WasmBackend<'a> {
|
|||
self.code_builder.i64_store(id_align, id_offset);
|
||||
}
|
||||
}
|
||||
} else if stores_tag_id_in_pointer {
|
||||
} else if stores_tag_id_in_pointer && tag_id != 0 {
|
||||
self.code_builder.get_local(local_id);
|
||||
self.code_builder.i32_const(tag_id as i32);
|
||||
self.code_builder.i32_or();
|
||||
|
|
|
@ -256,7 +256,7 @@ pub struct WasmDebugSettings {
|
|||
|
||||
pub const DEBUG_SETTINGS: WasmDebugSettings = WasmDebugSettings {
|
||||
proc_start_end: false && cfg!(debug_assertions),
|
||||
user_procs_ir: false && cfg!(debug_assertions), // Note: we also have `ROC_PRINT_IR_AFTER_SPECIALIZATION=1 cargo test-gen-wasm`
|
||||
user_procs_ir: false && cfg!(debug_assertions), // Note: we also have `ROC_PRINT_IR_AFTER_REFCOUNT=1 cargo test-gen-wasm`
|
||||
helper_procs_ir: false && cfg!(debug_assertions),
|
||||
let_stmt_ir: false && cfg!(debug_assertions),
|
||||
instructions: false && cfg!(debug_assertions),
|
||||
|
|
|
@ -13,7 +13,7 @@ use crate::backend::{ProcLookupData, ProcSource, WasmBackend};
|
|||
use crate::layout::{CallConv, StackMemoryFormat, WasmLayout};
|
||||
use crate::storage::{AddressValue, StackMemoryLocation, StoredValue};
|
||||
use crate::wasm_module::{Align, LocalId, ValueType};
|
||||
use crate::TARGET_INFO;
|
||||
use crate::{PTR_TYPE, TARGET_INFO};
|
||||
|
||||
/// Number types used for Wasm code gen
|
||||
/// Unlike other enums, this contains no details about layout or storage.
|
||||
|
@ -368,25 +368,27 @@ impl<'a> LowLevelCall<'a> {
|
|||
location.local_and_offset(backend.storage.stack_frame_pointer);
|
||||
backend.code_builder.get_local(fp);
|
||||
backend.code_builder.i32_load(Align::Bytes4, offset);
|
||||
} else {
|
||||
internal_error!("Lists are always stored in stack memory");
|
||||
}
|
||||
|
||||
// Target element heap pointer
|
||||
// Get pointer to target element and save it to a local var
|
||||
backend.code_builder.i32_add(); // base + index*size
|
||||
let elem_local = backend.storage.create_anonymous_local(PTR_TYPE);
|
||||
backend.code_builder.set_local(elem_local);
|
||||
|
||||
// Copy to stack
|
||||
// Copy element value from heap to stack
|
||||
backend.storage.copy_value_from_memory(
|
||||
&mut backend.code_builder,
|
||||
self.ret_symbol,
|
||||
AddressValue::Loaded,
|
||||
AddressValue::NotLoaded(elem_local),
|
||||
0,
|
||||
);
|
||||
|
||||
// Increment refcount
|
||||
if self.ret_layout.is_refcounted() {
|
||||
let inc_fn = backend.get_refcount_fn_index(self.ret_layout, HelperOp::Inc);
|
||||
backend
|
||||
.storage
|
||||
.load_symbols(&mut backend.code_builder, &[self.ret_symbol]);
|
||||
backend.code_builder.get_local(elem_local);
|
||||
backend.code_builder.i32_const(1);
|
||||
backend.code_builder.call(inc_fn, 2, false);
|
||||
}
|
||||
|
@ -2350,7 +2352,13 @@ fn list_map_n<'a>(
|
|||
// If we have lists of different lengths, we may need to decrement
|
||||
let num_wasm_args = if arg_elem_layouts.len() > 1 {
|
||||
for el in arg_elem_layouts.iter() {
|
||||
let idx = backend.get_refcount_fn_index(*el, HelperOp::Dec);
|
||||
// The dec function will be passed a pointer to the element within the list, not the element itself!
|
||||
// Here we wrap the layout in a Struct to ensure we get the right code gen
|
||||
let el_ptr = Layout::Struct {
|
||||
field_order_hash: FieldOrderHash::from_ordered_fields(&[]),
|
||||
field_layouts: backend.env.arena.alloc([*el]),
|
||||
};
|
||||
let idx = backend.get_refcount_fn_index(el_ptr, HelperOp::Dec);
|
||||
let ptr = backend.get_fn_ptr(idx);
|
||||
backend.code_builder.i32_const(ptr);
|
||||
}
|
||||
|
|
|
@ -621,7 +621,7 @@ impl<'a> Storage<'a> {
|
|||
to_symbol: Symbol,
|
||||
from_addr: AddressValue,
|
||||
from_offset: u32,
|
||||
) -> u32 {
|
||||
) {
|
||||
let to_storage = self.get(&to_symbol).to_owned();
|
||||
match to_storage {
|
||||
StoredValue::StackMemory {
|
||||
|
@ -656,7 +656,6 @@ impl<'a> Storage<'a> {
|
|||
alignment_bytes,
|
||||
},
|
||||
);
|
||||
size
|
||||
}
|
||||
|
||||
StoredValue::VirtualMachineStack {
|
||||
|
@ -690,8 +689,6 @@ impl<'a> Storage<'a> {
|
|||
if let StoredValue::Local { local_id, .. } = to_storage {
|
||||
code_builder.set_local(local_id);
|
||||
}
|
||||
|
||||
size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_ident"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_late_solve"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_load"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
pub use roc_load_internal::file::Threading;
|
||||
|
||||
use bumpalo::Bump;
|
||||
use roc_can::module::ExposedByModule;
|
||||
use roc_collections::all::MutMap;
|
||||
|
@ -11,7 +9,8 @@ use std::path::PathBuf;
|
|||
|
||||
pub use roc_load_internal::docs;
|
||||
pub use roc_load_internal::file::{
|
||||
Expectations, LoadResult, LoadStart, LoadedModule, LoadingProblem, MonomorphizedModule, Phase,
|
||||
EntryPoint, ExecutionMode, Expectations, LoadConfig, LoadResult, LoadStart, LoadedModule,
|
||||
LoadingProblem, MonomorphizedModule, Phase, Threading,
|
||||
};
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
|
@ -19,23 +18,11 @@ fn load<'a>(
|
|||
arena: &'a Bump,
|
||||
load_start: LoadStart<'a>,
|
||||
exposed_types: ExposedByModule,
|
||||
goal_phase: Phase,
|
||||
target_info: TargetInfo,
|
||||
render: RenderTarget,
|
||||
threading: Threading,
|
||||
load_config: LoadConfig,
|
||||
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
|
||||
let cached_subs = read_cached_subs();
|
||||
|
||||
roc_load_internal::file::load(
|
||||
arena,
|
||||
load_start,
|
||||
exposed_types,
|
||||
goal_phase,
|
||||
target_info,
|
||||
cached_subs,
|
||||
render,
|
||||
threading,
|
||||
)
|
||||
roc_load_internal::file::load(arena, load_start, exposed_types, cached_subs, load_config)
|
||||
}
|
||||
|
||||
/// Load using only a single thread; used when compiling to webassembly
|
||||
|
@ -43,9 +30,9 @@ pub fn load_single_threaded<'a>(
|
|||
arena: &'a Bump,
|
||||
load_start: LoadStart<'a>,
|
||||
exposed_types: ExposedByModule,
|
||||
goal_phase: Phase,
|
||||
target_info: TargetInfo,
|
||||
render: RenderTarget,
|
||||
exec_mode: ExecutionMode,
|
||||
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
|
||||
let cached_subs = read_cached_subs();
|
||||
|
||||
|
@ -53,10 +40,10 @@ pub fn load_single_threaded<'a>(
|
|||
arena,
|
||||
load_start,
|
||||
exposed_types,
|
||||
goal_phase,
|
||||
target_info,
|
||||
cached_subs,
|
||||
render,
|
||||
exec_mode,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -67,23 +54,13 @@ pub fn load_and_monomorphize_from_str<'a>(
|
|||
src: &'a str,
|
||||
src_dir: PathBuf,
|
||||
exposed_types: ExposedByModule,
|
||||
target_info: TargetInfo,
|
||||
render: RenderTarget,
|
||||
threading: Threading,
|
||||
load_config: LoadConfig,
|
||||
) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>> {
|
||||
use LoadResult::*;
|
||||
|
||||
let load_start = LoadStart::from_str(arena, filename, src, src_dir)?;
|
||||
|
||||
match load(
|
||||
arena,
|
||||
load_start,
|
||||
exposed_types,
|
||||
Phase::MakeSpecializations,
|
||||
target_info,
|
||||
render,
|
||||
threading,
|
||||
)? {
|
||||
match load(arena, load_start, exposed_types, load_config)? {
|
||||
Monomorphized(module) => Ok(module),
|
||||
TypeChecked(_) => unreachable!(""),
|
||||
}
|
||||
|
@ -93,23 +70,13 @@ pub fn load_and_monomorphize(
|
|||
arena: &Bump,
|
||||
filename: PathBuf,
|
||||
exposed_types: ExposedByModule,
|
||||
target_info: TargetInfo,
|
||||
render: RenderTarget,
|
||||
threading: Threading,
|
||||
load_config: LoadConfig,
|
||||
) -> Result<MonomorphizedModule<'_>, LoadingProblem<'_>> {
|
||||
use LoadResult::*;
|
||||
|
||||
let load_start = LoadStart::from_path(arena, filename, render)?;
|
||||
let load_start = LoadStart::from_path(arena, filename, load_config.render)?;
|
||||
|
||||
match load(
|
||||
arena,
|
||||
load_start,
|
||||
exposed_types,
|
||||
Phase::MakeSpecializations,
|
||||
target_info,
|
||||
render,
|
||||
threading,
|
||||
)? {
|
||||
match load(arena, load_start, exposed_types, load_config)? {
|
||||
Monomorphized(module) => Ok(module),
|
||||
TypeChecked(_) => unreachable!(""),
|
||||
}
|
||||
|
@ -119,23 +86,13 @@ pub fn load_and_typecheck(
|
|||
arena: &Bump,
|
||||
filename: PathBuf,
|
||||
exposed_types: ExposedByModule,
|
||||
target_info: TargetInfo,
|
||||
render: RenderTarget,
|
||||
threading: Threading,
|
||||
load_config: LoadConfig,
|
||||
) -> Result<LoadedModule, LoadingProblem<'_>> {
|
||||
use LoadResult::*;
|
||||
|
||||
let load_start = LoadStart::from_path(arena, filename, render)?;
|
||||
let load_start = LoadStart::from_path(arena, filename, load_config.render)?;
|
||||
|
||||
match load(
|
||||
arena,
|
||||
load_start,
|
||||
exposed_types,
|
||||
Phase::SolveTypes,
|
||||
target_info,
|
||||
render,
|
||||
threading,
|
||||
)? {
|
||||
match load(arena, load_start, exposed_types, load_config)? {
|
||||
Monomorphized(_) => unreachable!(""),
|
||||
TypeChecked(module) => Ok(module),
|
||||
}
|
||||
|
@ -161,9 +118,9 @@ pub fn load_and_typecheck_str<'a>(
|
|||
arena,
|
||||
load_start,
|
||||
exposed_types,
|
||||
Phase::SolveTypes,
|
||||
target_info,
|
||||
render,
|
||||
ExecutionMode::Check,
|
||||
)? {
|
||||
Monomorphized(_) => unreachable!(""),
|
||||
TypeChecked(module) => Ok(module),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_load_internal"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -30,8 +30,8 @@ use roc_module::symbol::{
|
|||
PackageQualified, Symbol,
|
||||
};
|
||||
use roc_mono::ir::{
|
||||
CapturedSymbols, EntryPoint, ExternalSpecializations, PartialProc, Proc, ProcLayout, Procs,
|
||||
ProcsBase, UpdateModeIds,
|
||||
CapturedSymbols, ExternalSpecializations, PartialProc, Proc, ProcLayout, Procs, ProcsBase,
|
||||
UpdateModeIds,
|
||||
};
|
||||
use roc_mono::layout::{CapturesNiche, LambdaName, Layout, LayoutCache, LayoutProblem};
|
||||
use roc_parse::ast::{self, Defs, ExtractSpaces, Spaced, StrLiteral, TypeAnnotation};
|
||||
|
@ -117,6 +117,30 @@ macro_rules! log {
|
|||
($($arg:tt)*) => (dbg_do!(ROC_PRINT_LOAD_LOG, println!($($arg)*)))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LoadConfig {
|
||||
pub target_info: TargetInfo,
|
||||
pub render: RenderTarget,
|
||||
pub threading: Threading,
|
||||
pub exec_mode: ExecutionMode,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum ExecutionMode {
|
||||
Test,
|
||||
Check,
|
||||
Executable,
|
||||
}
|
||||
|
||||
impl ExecutionMode {
|
||||
fn goal_phase(&self) -> Phase {
|
||||
match self {
|
||||
ExecutionMode::Test | ExecutionMode::Executable => Phase::MakeSpecializations,
|
||||
ExecutionMode::Check => Phase::SolveTypes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct storing various intermediate stages by their ModuleId
|
||||
#[derive(Debug)]
|
||||
struct ModuleCache<'a> {
|
||||
|
@ -421,6 +445,7 @@ fn start_phase<'a>(
|
|||
|
||||
BuildTask::BuildPendingSpecializations {
|
||||
layout_cache,
|
||||
execution_mode: state.exec_mode,
|
||||
module_id,
|
||||
module_timing,
|
||||
solved_subs,
|
||||
|
@ -670,7 +695,6 @@ pub struct MonomorphizedModule<'a> {
|
|||
pub interns: Interns,
|
||||
pub subs: Subs,
|
||||
pub output_path: Box<Path>,
|
||||
pub platform_path: Box<Path>,
|
||||
pub can_problems: MutMap<ModuleId, Vec<roc_problem::can::Problem>>,
|
||||
pub type_problems: MutMap<ModuleId, Vec<TypeError>>,
|
||||
pub procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||
|
@ -682,6 +706,16 @@ pub struct MonomorphizedModule<'a> {
|
|||
pub expectations: VecMap<ModuleId, Expectations>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EntryPoint<'a> {
|
||||
Executable {
|
||||
symbol: Symbol,
|
||||
layout: ProcLayout<'a>,
|
||||
platform_path: Box<Path>,
|
||||
},
|
||||
Test,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Expectations {
|
||||
pub subs: roc_types::subs::Subs,
|
||||
|
@ -848,7 +882,6 @@ struct State<'a> {
|
|||
pub root_id: ModuleId,
|
||||
pub root_subs: Option<Subs>,
|
||||
pub platform_data: Option<PlatformData>,
|
||||
pub goal_phase: Phase,
|
||||
pub exposed_types: ExposedByModule,
|
||||
pub output_path: Option<&'a str>,
|
||||
pub platform_path: PlatformPath<'a>,
|
||||
|
@ -859,6 +892,7 @@ struct State<'a> {
|
|||
pub procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||
pub toplevel_expects: VecMap<Symbol, Region>,
|
||||
pub exposed_to_host: ExposedToHost,
|
||||
pub goal_phase: Phase,
|
||||
|
||||
/// This is the "final" list of IdentIds, after canonicalization and constraint gen
|
||||
/// have completed for a given module.
|
||||
|
@ -886,6 +920,7 @@ struct State<'a> {
|
|||
pub layout_caches: std::vec::Vec<LayoutCache<'a>>,
|
||||
|
||||
pub render: RenderTarget,
|
||||
pub exec_mode: ExecutionMode,
|
||||
|
||||
/// All abilities across all modules.
|
||||
pub world_abilities: WorldAbilities,
|
||||
|
@ -903,16 +938,17 @@ impl<'a> State<'a> {
|
|||
fn new(
|
||||
root_id: ModuleId,
|
||||
target_info: TargetInfo,
|
||||
goal_phase: Phase,
|
||||
exposed_types: ExposedByModule,
|
||||
arc_modules: Arc<Mutex<PackageModuleIds<'a>>>,
|
||||
ident_ids_by_module: SharedIdentIdsByModule,
|
||||
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
|
||||
render: RenderTarget,
|
||||
number_of_workers: usize,
|
||||
exec_mode: ExecutionMode,
|
||||
) -> Self {
|
||||
let arc_shorthands = Arc::new(Mutex::new(MutMap::default()));
|
||||
|
||||
let goal_phase = exec_mode.goal_phase();
|
||||
let dependencies = Dependencies::new(goal_phase);
|
||||
|
||||
Self {
|
||||
|
@ -940,6 +976,7 @@ impl<'a> State<'a> {
|
|||
layout_caches: std::vec::Vec::with_capacity(number_of_workers),
|
||||
cached_subs: Arc::new(Mutex::new(cached_subs)),
|
||||
render,
|
||||
exec_mode,
|
||||
make_specializations_pass: MakeSpecializationsPass::Pass(1),
|
||||
world_abilities: Default::default(),
|
||||
}
|
||||
|
@ -1054,6 +1091,7 @@ enum BuildTask<'a> {
|
|||
},
|
||||
BuildPendingSpecializations {
|
||||
module_timing: ModuleTiming,
|
||||
execution_mode: ExecutionMode,
|
||||
layout_cache: LayoutCache<'a>,
|
||||
solved_subs: Solved<Subs>,
|
||||
imported_module_thunks: &'a [Symbol],
|
||||
|
@ -1146,16 +1184,14 @@ pub fn load_and_typecheck_str<'a>(
|
|||
// where we want to regenerate the cached data
|
||||
let cached_subs = MutMap::default();
|
||||
|
||||
match load(
|
||||
arena,
|
||||
load_start,
|
||||
exposed_types,
|
||||
Phase::SolveTypes,
|
||||
let load_config = LoadConfig {
|
||||
target_info,
|
||||
cached_subs,
|
||||
render,
|
||||
threading,
|
||||
)? {
|
||||
exec_mode: ExecutionMode::Check,
|
||||
};
|
||||
|
||||
match load(arena, load_start, exposed_types, cached_subs, load_config)? {
|
||||
Monomorphized(_) => unreachable!(""),
|
||||
TypeChecked(module) => Ok(module),
|
||||
}
|
||||
|
@ -1364,11 +1400,8 @@ pub fn load<'a>(
|
|||
arena: &'a Bump,
|
||||
load_start: LoadStart<'a>,
|
||||
exposed_types: ExposedByModule,
|
||||
goal_phase: Phase,
|
||||
target_info: TargetInfo,
|
||||
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
|
||||
render: RenderTarget,
|
||||
threading: Threading,
|
||||
load_config: LoadConfig,
|
||||
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
|
||||
enum Threads {
|
||||
Single,
|
||||
|
@ -1385,7 +1418,7 @@ pub fn load<'a>(
|
|||
Err(_) => Threads::Single,
|
||||
Ok(0) => unreachable!("NonZeroUsize"),
|
||||
Ok(1) => Threads::Single,
|
||||
Ok(reported) => match threading {
|
||||
Ok(reported) => match load_config.threading {
|
||||
Threading::Single => Threads::Single,
|
||||
Threading::AllAvailable => Threads::Many(reported),
|
||||
Threading::AtMost(at_most) => Threads::Many(Ord::min(reported, at_most)),
|
||||
|
@ -1399,20 +1432,20 @@ pub fn load<'a>(
|
|||
arena,
|
||||
load_start,
|
||||
exposed_types,
|
||||
goal_phase,
|
||||
target_info,
|
||||
load_config.target_info,
|
||||
cached_subs,
|
||||
render,
|
||||
load_config.render,
|
||||
load_config.exec_mode,
|
||||
),
|
||||
Threads::Many(threads) => load_multi_threaded(
|
||||
arena,
|
||||
load_start,
|
||||
exposed_types,
|
||||
goal_phase,
|
||||
target_info,
|
||||
load_config.target_info,
|
||||
cached_subs,
|
||||
render,
|
||||
load_config.render,
|
||||
threads,
|
||||
load_config.exec_mode,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -1423,10 +1456,10 @@ pub fn load_single_threaded<'a>(
|
|||
arena: &'a Bump,
|
||||
load_start: LoadStart<'a>,
|
||||
exposed_types: ExposedByModule,
|
||||
goal_phase: Phase,
|
||||
target_info: TargetInfo,
|
||||
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
|
||||
render: RenderTarget,
|
||||
exec_mode: ExecutionMode,
|
||||
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
|
||||
let LoadStart {
|
||||
arc_modules,
|
||||
|
@ -1447,13 +1480,13 @@ pub fn load_single_threaded<'a>(
|
|||
let mut state = State::new(
|
||||
root_id,
|
||||
target_info,
|
||||
goal_phase,
|
||||
exposed_types,
|
||||
arc_modules,
|
||||
ident_ids_by_module,
|
||||
cached_subs,
|
||||
render,
|
||||
number_of_workers,
|
||||
exec_mode,
|
||||
);
|
||||
|
||||
// We'll add tasks to this, and then worker threads will take tasks from it.
|
||||
|
@ -1624,11 +1657,11 @@ fn load_multi_threaded<'a>(
|
|||
arena: &'a Bump,
|
||||
load_start: LoadStart<'a>,
|
||||
exposed_types: ExposedByModule,
|
||||
goal_phase: Phase,
|
||||
target_info: TargetInfo,
|
||||
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
|
||||
render: RenderTarget,
|
||||
available_threads: usize,
|
||||
exec_mode: ExecutionMode,
|
||||
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
|
||||
let LoadStart {
|
||||
arc_modules,
|
||||
|
@ -1664,13 +1697,13 @@ fn load_multi_threaded<'a>(
|
|||
let mut state = State::new(
|
||||
root_id,
|
||||
target_info,
|
||||
goal_phase,
|
||||
exposed_types,
|
||||
arc_modules,
|
||||
ident_ids_by_module,
|
||||
cached_subs,
|
||||
render,
|
||||
num_workers,
|
||||
exec_mode,
|
||||
);
|
||||
|
||||
// an arena for every worker, stored in an arena-allocated bumpalo vec to make the lifetimes work
|
||||
|
@ -2749,6 +2782,7 @@ fn finish_specialization(
|
|||
output_path,
|
||||
platform_path,
|
||||
platform_data,
|
||||
exec_mode,
|
||||
..
|
||||
} = state;
|
||||
|
||||
|
@ -2765,6 +2799,10 @@ fn finish_specialization(
|
|||
.map(|(id, (path, src))| (id, (path, src.into())))
|
||||
.collect();
|
||||
|
||||
let entry_point = {
|
||||
match exec_mode {
|
||||
ExecutionMode::Test => EntryPoint::Test,
|
||||
ExecutionMode::Executable => {
|
||||
let path_to_platform = {
|
||||
use PlatformPath::*;
|
||||
let package_name = match platform_path {
|
||||
|
@ -2785,8 +2823,6 @@ fn finish_specialization(
|
|||
};
|
||||
|
||||
let platform_path = Path::new(path_to_platform).into();
|
||||
|
||||
let entry_point = {
|
||||
let symbol = match platform_data {
|
||||
None => {
|
||||
debug_assert_eq!(exposed_to_host.values.len(), 1);
|
||||
|
@ -2796,23 +2832,28 @@ fn finish_specialization(
|
|||
};
|
||||
|
||||
match procedures.keys().find(|(s, _)| *s == symbol) {
|
||||
Some((_, layout)) => EntryPoint {
|
||||
Some((_, layout)) => EntryPoint::Executable {
|
||||
layout: *layout,
|
||||
symbol,
|
||||
platform_path,
|
||||
},
|
||||
None => {
|
||||
// the entry point is not specialized. This can happen if the repl output
|
||||
// is a function value
|
||||
EntryPoint {
|
||||
EntryPoint::Executable {
|
||||
layout: roc_mono::ir::ProcLayout {
|
||||
arguments: &[],
|
||||
result: Layout::struct_no_name_order(&[]),
|
||||
captures_niche: CapturesNiche::no_niche(),
|
||||
},
|
||||
symbol,
|
||||
platform_path,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ExecutionMode::Check => unreachable!(),
|
||||
}
|
||||
};
|
||||
|
||||
let output_path = match output_path {
|
||||
|
@ -2824,7 +2865,7 @@ fn finish_specialization(
|
|||
can_problems,
|
||||
type_problems,
|
||||
output_path,
|
||||
platform_path,
|
||||
expectations,
|
||||
exposed_to_host,
|
||||
module_id: state.root_id,
|
||||
subs,
|
||||
|
@ -2834,7 +2875,6 @@ fn finish_specialization(
|
|||
sources,
|
||||
timings: state.timings,
|
||||
toplevel_expects,
|
||||
expectations,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -4479,7 +4519,7 @@ fn canonicalize_and_constrain<'a>(
|
|||
Vacant(vacant) => {
|
||||
let should_include_builtin = matches!(
|
||||
name.module_id(),
|
||||
ModuleId::ENCODE | ModuleId::DICT | ModuleId::SET
|
||||
ModuleId::ENCODE | ModuleId::DECODE | ModuleId::DICT | ModuleId::SET
|
||||
);
|
||||
|
||||
if !name.is_builtin() || should_include_builtin {
|
||||
|
@ -4698,6 +4738,7 @@ fn make_specializations<'a>(
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
fn build_pending_specializations<'a>(
|
||||
arena: &'a Bump,
|
||||
execution_mode: ExecutionMode,
|
||||
solved_subs: Solved<Subs>,
|
||||
imported_module_thunks: &'a [Symbol],
|
||||
home: ModuleId,
|
||||
|
@ -4955,6 +4996,12 @@ fn build_pending_specializations<'a>(
|
|||
// the declarations of this group will be treaded individually by later iterations
|
||||
}
|
||||
Expectation => {
|
||||
// skip expectations if we're not going to run them
|
||||
match execution_mode {
|
||||
ExecutionMode::Test => { /* fall through */ }
|
||||
ExecutionMode::Check | ExecutionMode::Executable => continue,
|
||||
}
|
||||
|
||||
// mark this symbol as a top-level thunk before any other work on the procs
|
||||
module_thunks.push(symbol);
|
||||
|
||||
|
@ -5078,7 +5125,7 @@ fn load_derived_partial_procs<'a>(
|
|||
|
||||
// TODO: we can be even lazier here if we move `add_def_to_module` to happen in mono. Also, the
|
||||
// timings would be more accurate.
|
||||
for (derived_symbol, derived_expr) in derives_to_add.into_iter() {
|
||||
for (derived_symbol, (derived_expr, derived_expr_var)) in derives_to_add.into_iter() {
|
||||
let mut mono_env = roc_mono::ir::Env {
|
||||
arena,
|
||||
subs,
|
||||
|
@ -5117,7 +5164,22 @@ fn load_derived_partial_procs<'a>(
|
|||
return_type,
|
||||
)
|
||||
}
|
||||
_ => internal_error!("Expected only functions to be derived"),
|
||||
_ => {
|
||||
// mark this symbols as a top-level thunk before any other work on the procs
|
||||
new_module_thunks.push(derived_symbol);
|
||||
|
||||
PartialProc {
|
||||
annotation: derived_expr_var,
|
||||
// This is a 0-arity thunk, so it has no arguments.
|
||||
pattern_symbols: &[],
|
||||
// This is a top-level definition, so it cannot capture anything
|
||||
captured_symbols: CapturedSymbols::None,
|
||||
body: derived_expr,
|
||||
body_var: derived_expr_var,
|
||||
// This is a 0-arity thunk, so it cannot be recursive
|
||||
is_self_recursive: false,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
procs_base
|
||||
|
@ -5212,6 +5274,7 @@ fn run_task<'a>(
|
|||
)),
|
||||
BuildPendingSpecializations {
|
||||
module_id,
|
||||
execution_mode,
|
||||
ident_ids,
|
||||
decls,
|
||||
module_timing,
|
||||
|
@ -5224,6 +5287,7 @@ fn run_task<'a>(
|
|||
derived_module,
|
||||
} => Ok(build_pending_specializations(
|
||||
arena,
|
||||
execution_mode,
|
||||
solved_subs,
|
||||
imported_module_thunks,
|
||||
module_id,
|
||||
|
|
|
@ -17,8 +17,8 @@ mod helpers;
|
|||
use crate::helpers::fixtures_dir;
|
||||
use bumpalo::Bump;
|
||||
use roc_can::module::ExposedByModule;
|
||||
use roc_load_internal::file::Threading;
|
||||
use roc_load_internal::file::{LoadResult, LoadStart, LoadedModule, LoadingProblem, Phase};
|
||||
use roc_load_internal::file::{ExecutionMode, LoadConfig, Threading};
|
||||
use roc_load_internal::file::{LoadResult, LoadStart, LoadedModule, LoadingProblem};
|
||||
use roc_module::ident::ModuleName;
|
||||
use roc_module::symbol::{Interns, ModuleId};
|
||||
use roc_problem::can::Problem;
|
||||
|
@ -41,16 +41,19 @@ fn load_and_typecheck(
|
|||
use LoadResult::*;
|
||||
|
||||
let load_start = LoadStart::from_path(arena, filename, RenderTarget::Generic)?;
|
||||
let load_config = LoadConfig {
|
||||
target_info,
|
||||
render: RenderTarget::Generic,
|
||||
threading: Threading::Single,
|
||||
exec_mode: ExecutionMode::Check,
|
||||
};
|
||||
|
||||
match roc_load_internal::file::load(
|
||||
arena,
|
||||
load_start,
|
||||
exposed_types,
|
||||
Phase::SolveTypes,
|
||||
target_info,
|
||||
Default::default(), // these tests will re-compile the builtins
|
||||
RenderTarget::Generic,
|
||||
Threading::Single,
|
||||
load_config,
|
||||
)? {
|
||||
Monomorphized(_) => unreachable!(""),
|
||||
TypeChecked(module) => Ok(module),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_module"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
edition = "2021"
|
||||
license = "UPL-1.0"
|
||||
|
|
|
@ -47,8 +47,10 @@ const SYMBOL_HAS_NICHE: () =
|
|||
#[cfg(debug_assertions)]
|
||||
const PRETTY_PRINT_DEBUG_SYMBOLS: bool = true;
|
||||
|
||||
pub const DERIVABLE_ABILITIES: &[(Symbol, &[Symbol])] =
|
||||
&[(Symbol::ENCODE_ENCODING, &[Symbol::ENCODE_TO_ENCODER])];
|
||||
pub const DERIVABLE_ABILITIES: &[(Symbol, &[Symbol])] = &[
|
||||
(Symbol::ENCODE_ENCODING, &[Symbol::ENCODE_TO_ENCODER]),
|
||||
(Symbol::DECODE_DECODING, &[Symbol::DECODE_DECODER]),
|
||||
];
|
||||
|
||||
/// In Debug builds only, Symbol has a name() method that lets
|
||||
/// you look up its name in a global intern table. This table is
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_mono"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
@ -12,6 +12,7 @@ roc_region = { path = "../region" }
|
|||
roc_module = { path = "../module" }
|
||||
roc_types = { path = "../types" }
|
||||
roc_can = { path = "../can" }
|
||||
roc_derive_key = { path = "../derive_key" }
|
||||
roc_derive = { path = "../derive" }
|
||||
roc_late_solve = { path = "../late_solve" }
|
||||
roc_std = { path = "../../roc_std", default-features = false }
|
||||
|
|
|
@ -144,6 +144,7 @@ pub fn refcount_reset_proc_body<'a>(
|
|||
let rc = root.create_symbol(ident_ids, "rc");
|
||||
let refcount_1 = root.create_symbol(ident_ids, "refcount_1");
|
||||
let is_unique = root.create_symbol(ident_ids, "is_unique");
|
||||
let addr = root.create_symbol(ident_ids, "addr");
|
||||
|
||||
let union_layout = match layout {
|
||||
Layout::Union(u) => u,
|
||||
|
@ -201,6 +202,39 @@ pub fn refcount_reset_proc_body<'a>(
|
|||
)
|
||||
};
|
||||
|
||||
let alloc_addr_stmt = {
|
||||
let alignment = root.create_symbol(ident_ids, "alignment");
|
||||
let alignment_expr = Expr::Literal(Literal::Int(
|
||||
(layout.alignment_bytes(root.target_info) as i128).to_ne_bytes(),
|
||||
));
|
||||
let alloc_addr = root.create_symbol(ident_ids, "alloc_addr");
|
||||
let alloc_addr_expr = Expr::Call(Call {
|
||||
call_type: CallType::LowLevel {
|
||||
op: LowLevel::NumSubWrap,
|
||||
update_mode: UpdateModeId::BACKEND_DUMMY,
|
||||
},
|
||||
arguments: root.arena.alloc([addr, alignment]),
|
||||
});
|
||||
|
||||
Stmt::Let(
|
||||
alignment,
|
||||
alignment_expr,
|
||||
root.layout_isize,
|
||||
root.arena.alloc(
|
||||
//
|
||||
Stmt::Let(
|
||||
alloc_addr,
|
||||
alloc_addr_expr,
|
||||
root.layout_isize,
|
||||
root.arena.alloc(
|
||||
//
|
||||
Stmt::Ret(alloc_addr),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
let rc_contents_stmt = refcount_union_contents(
|
||||
root,
|
||||
ident_ids,
|
||||
|
@ -211,7 +245,7 @@ pub fn refcount_reset_proc_body<'a>(
|
|||
structure,
|
||||
tag_id_sym,
|
||||
tag_id_layout,
|
||||
Stmt::Ret(structure),
|
||||
alloc_addr_stmt,
|
||||
);
|
||||
|
||||
tag_id_stmt(root.arena.alloc(
|
||||
|
@ -300,13 +334,14 @@ pub fn refcount_reset_proc_body<'a>(
|
|||
|
||||
// Refcount pointer
|
||||
let rc_ptr_stmt = {
|
||||
rc_ptr_from_data_ptr(
|
||||
rc_ptr_from_data_ptr_help(
|
||||
root,
|
||||
ident_ids,
|
||||
structure,
|
||||
rc_ptr,
|
||||
union_layout.stores_tag_id_in_pointer(root.target_info),
|
||||
root.arena.alloc(rc_stmt),
|
||||
addr,
|
||||
)
|
||||
};
|
||||
|
||||
|
@ -380,20 +415,45 @@ pub fn rc_ptr_from_data_ptr<'a>(
|
|||
rc_ptr_sym: Symbol,
|
||||
mask_lower_bits: bool,
|
||||
following: &'a Stmt<'a>,
|
||||
) -> Stmt<'a> {
|
||||
let addr_sym = root.create_symbol(ident_ids, "addr");
|
||||
rc_ptr_from_data_ptr_help(
|
||||
root,
|
||||
ident_ids,
|
||||
structure,
|
||||
rc_ptr_sym,
|
||||
mask_lower_bits,
|
||||
following,
|
||||
addr_sym,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn rc_ptr_from_data_ptr_help<'a>(
|
||||
root: &CodeGenHelp<'a>,
|
||||
ident_ids: &mut IdentIds,
|
||||
structure: Symbol,
|
||||
rc_ptr_sym: Symbol,
|
||||
mask_lower_bits: bool,
|
||||
following: &'a Stmt<'a>,
|
||||
addr_sym: Symbol,
|
||||
) -> Stmt<'a> {
|
||||
use std::ops::Neg;
|
||||
|
||||
// Typecast the structure pointer to an integer
|
||||
// Backends expect a number Layout to choose the right "subtract" instruction
|
||||
let addr_sym = root.create_symbol(ident_ids, "addr");
|
||||
let addr_expr = Expr::Call(Call {
|
||||
let as_int_sym = if mask_lower_bits {
|
||||
root.create_symbol(ident_ids, "as_int")
|
||||
} else {
|
||||
addr_sym
|
||||
};
|
||||
let as_int_expr = Expr::Call(Call {
|
||||
call_type: CallType::LowLevel {
|
||||
op: LowLevel::PtrCast,
|
||||
update_mode: UpdateModeId::BACKEND_DUMMY,
|
||||
},
|
||||
arguments: root.arena.alloc([structure]),
|
||||
});
|
||||
let addr_stmt = |next| Stmt::Let(addr_sym, addr_expr, root.layout_isize, next);
|
||||
let as_int_stmt = |next| Stmt::Let(as_int_sym, as_int_expr, root.layout_isize, next);
|
||||
|
||||
// Mask for lower bits (for tag union id)
|
||||
let mask_sym = root.create_symbol(ident_ids, "mask");
|
||||
|
@ -402,15 +462,14 @@ pub fn rc_ptr_from_data_ptr<'a>(
|
|||
));
|
||||
let mask_stmt = |next| Stmt::Let(mask_sym, mask_expr, root.layout_isize, next);
|
||||
|
||||
let masked_sym = root.create_symbol(ident_ids, "masked");
|
||||
let and_expr = Expr::Call(Call {
|
||||
call_type: CallType::LowLevel {
|
||||
op: LowLevel::And,
|
||||
update_mode: UpdateModeId::BACKEND_DUMMY,
|
||||
},
|
||||
arguments: root.arena.alloc([addr_sym, mask_sym]),
|
||||
arguments: root.arena.alloc([as_int_sym, mask_sym]),
|
||||
});
|
||||
let and_stmt = |next| Stmt::Let(masked_sym, and_expr, root.layout_isize, next);
|
||||
let and_stmt = |next| Stmt::Let(addr_sym, and_expr, root.layout_isize, next);
|
||||
|
||||
// Pointer size constant
|
||||
let ptr_size_sym = root.create_symbol(ident_ids, "ptr_size");
|
||||
|
@ -426,14 +485,7 @@ pub fn rc_ptr_from_data_ptr<'a>(
|
|||
op: LowLevel::NumSub,
|
||||
update_mode: UpdateModeId::BACKEND_DUMMY,
|
||||
},
|
||||
arguments: root.arena.alloc([
|
||||
if mask_lower_bits {
|
||||
masked_sym
|
||||
} else {
|
||||
addr_sym
|
||||
},
|
||||
ptr_size_sym,
|
||||
]),
|
||||
arguments: root.arena.alloc([addr_sym, ptr_size_sym]),
|
||||
});
|
||||
let sub_stmt = |next| Stmt::Let(rc_addr_sym, sub_expr, root.layout_isize, next);
|
||||
|
||||
|
@ -448,7 +500,7 @@ pub fn rc_ptr_from_data_ptr<'a>(
|
|||
let cast_stmt = |next| Stmt::Let(rc_ptr_sym, cast_expr, LAYOUT_PTR, next);
|
||||
|
||||
if mask_lower_bits {
|
||||
addr_stmt(root.arena.alloc(
|
||||
as_int_stmt(root.arena.alloc(
|
||||
//
|
||||
mask_stmt(root.arena.alloc(
|
||||
//
|
||||
|
@ -468,7 +520,7 @@ pub fn rc_ptr_from_data_ptr<'a>(
|
|||
)),
|
||||
))
|
||||
} else {
|
||||
addr_stmt(root.arena.alloc(
|
||||
as_int_stmt(root.arena.alloc(
|
||||
//
|
||||
ptr_size_stmt(root.arena.alloc(
|
||||
//
|
||||
|
|
|
@ -4921,7 +4921,7 @@ pub fn with_hole<'a>(
|
|||
|
||||
let resolved_proc = match resolved_proc {
|
||||
Resolved::Specialization(symbol) => symbol,
|
||||
Resolved::NeedsGenerated => {
|
||||
Resolved::NeedsGenerated(_) => {
|
||||
todo_abilities!("Generate impls for structural types")
|
||||
}
|
||||
};
|
||||
|
@ -5236,8 +5236,30 @@ fn late_resolve_ability_specialization<'a>(
|
|||
|
||||
match specialization {
|
||||
Resolved::Specialization(symbol) => symbol,
|
||||
Resolved::NeedsGenerated => {
|
||||
todo_abilities!("Generate impls for structural types")
|
||||
Resolved::NeedsGenerated(var) => {
|
||||
let derive_key = roc_derive_key::Derived::builtin(
|
||||
member.try_into().expect("derived symbols must be builtins"),
|
||||
env.subs,
|
||||
var,
|
||||
)
|
||||
.expect("specialization var not derivable!");
|
||||
|
||||
match derive_key {
|
||||
roc_derive_key::Derived::Immediate(imm) => {
|
||||
// The immediate is an ability member itself, so it must be resolved!
|
||||
late_resolve_ability_specialization(env, imm, None, specialization_var)
|
||||
}
|
||||
roc_derive_key::Derived::Key(derive_key) => {
|
||||
let mut derived_module = env
|
||||
.derived_module
|
||||
.lock()
|
||||
.expect("derived module unavailable");
|
||||
|
||||
derived_module
|
||||
.get_or_insert(env.exposed_by_module, derive_key)
|
||||
.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -472,10 +472,13 @@ impl<'a> UnionLayout<'a> {
|
|||
tags.len() < target_info.ptr_width() as usize
|
||||
}
|
||||
|
||||
pub const POINTER_MASK_32BIT: usize = 0b0000_0111;
|
||||
pub const POINTER_MASK_64BIT: usize = 0b0000_0011;
|
||||
|
||||
pub fn tag_id_pointer_bits_and_mask(target_info: TargetInfo) -> (usize, usize) {
|
||||
match target_info.ptr_width() {
|
||||
PtrWidth::Bytes8 => (3, 0b0000_0111),
|
||||
PtrWidth::Bytes4 => (2, 0b0000_0011),
|
||||
PtrWidth::Bytes8 => (3, Self::POINTER_MASK_64BIT),
|
||||
PtrWidth::Bytes4 => (2, Self::POINTER_MASK_32BIT),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_parse"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -866,7 +866,7 @@ where
|
|||
// the next character should not be an identifier character
|
||||
// to prevent treating `whence` or `iffy` as keywords
|
||||
match state.bytes().get(width) {
|
||||
Some(next) if *next == b' ' || *next == b'#' || *next == b'\n' => {
|
||||
Some(next) if *next == b' ' || *next == b'#' || *next == b'\n' || *next == b'\r' => {
|
||||
state = state.advance(width);
|
||||
Ok((MadeProgress, (), state))
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_problem"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_region"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_target"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -4,9 +4,31 @@
|
|||
|
||||
use strum_macros::{EnumCount, EnumIter};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum OperatingSystem {
|
||||
Windows,
|
||||
Unix,
|
||||
Wasi,
|
||||
}
|
||||
|
||||
impl From<target_lexicon::OperatingSystem> for OperatingSystem {
|
||||
fn from(target: target_lexicon::OperatingSystem) -> Self {
|
||||
match target {
|
||||
target_lexicon::OperatingSystem::Windows => OperatingSystem::Windows,
|
||||
target_lexicon::OperatingSystem::Wasi => OperatingSystem::Wasi,
|
||||
target_lexicon::OperatingSystem::Linux => OperatingSystem::Unix,
|
||||
target_lexicon::OperatingSystem::MacOSX { .. } => OperatingSystem::Unix,
|
||||
target_lexicon::OperatingSystem::Darwin => OperatingSystem::Unix,
|
||||
target_lexicon::OperatingSystem::Unknown => OperatingSystem::Unix,
|
||||
other => unreachable!("unsupported operating system {:?}", other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct TargetInfo {
|
||||
pub architecture: Architecture,
|
||||
pub operating_system: OperatingSystem,
|
||||
}
|
||||
|
||||
impl TargetInfo {
|
||||
|
@ -28,18 +50,21 @@ impl TargetInfo {
|
|||
pub const fn default_aarch64() -> Self {
|
||||
TargetInfo {
|
||||
architecture: Architecture::Aarch64,
|
||||
operating_system: OperatingSystem::Unix,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn default_x86_64() -> Self {
|
||||
TargetInfo {
|
||||
architecture: Architecture::X86_64,
|
||||
operating_system: OperatingSystem::Unix,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn default_wasm32() -> Self {
|
||||
TargetInfo {
|
||||
architecture: Architecture::Wasm32,
|
||||
operating_system: OperatingSystem::Wasi,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -47,14 +72,12 @@ impl TargetInfo {
|
|||
impl From<&target_lexicon::Triple> for TargetInfo {
|
||||
fn from(triple: &target_lexicon::Triple) -> Self {
|
||||
let architecture = Architecture::from(triple.architecture);
|
||||
let operating_system = OperatingSystem::from(triple.operating_system);
|
||||
|
||||
Self { architecture }
|
||||
Self {
|
||||
architecture,
|
||||
operating_system,
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Architecture> for TargetInfo {
|
||||
fn from(architecture: Architecture) -> Self {
|
||||
Self { architecture }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_solve"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use roc_can::abilities::AbilitiesStore;
|
||||
use roc_can::expr::PendingDerives;
|
||||
use roc_collections::{VecMap, VecSet};
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_error_macros::{internal_error, todo_abilities};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_solve_problem::{TypeError, UnderivableReason, Unfulfilled};
|
||||
use roc_types::num::NumericRange;
|
||||
use roc_types::subs::{instantiate_rigids, Content, FlatType, GetSubsSlice, Rank, Subs, Variable};
|
||||
use roc_types::types::{AliasKind, Category, MemberImpl, PatternCategory};
|
||||
use roc_unify::unify::{Env, MustImplementConstraints};
|
||||
|
@ -253,7 +254,20 @@ impl ObligationCache {
|
|||
// independent queries.
|
||||
|
||||
let opt_can_derive_builtin = match ability {
|
||||
Symbol::ENCODE_ENCODING => Some(self.can_derive_encoding(subs, abilities_store, var)),
|
||||
Symbol::ENCODE_ENCODING => Some(DeriveEncoding::is_derivable(
|
||||
self,
|
||||
abilities_store,
|
||||
subs,
|
||||
var,
|
||||
)),
|
||||
|
||||
Symbol::DECODE_DECODING => Some(DeriveDecoding::is_derivable(
|
||||
self,
|
||||
abilities_store,
|
||||
subs,
|
||||
var,
|
||||
)),
|
||||
|
||||
_ => None,
|
||||
};
|
||||
|
||||
|
@ -262,7 +276,7 @@ impl ObligationCache {
|
|||
// can derive!
|
||||
None
|
||||
}
|
||||
Some(Err(failure_var)) => Some(if failure_var == var {
|
||||
Some(Err(DerivableError::NotDerivable(failure_var))) => Some(if failure_var == var {
|
||||
UnderivableReason::SurfaceNotDerivable
|
||||
} else {
|
||||
let (error_type, _skeletons) = subs.var_to_error_type(failure_var);
|
||||
|
@ -391,16 +405,133 @@ impl ObligationCache {
|
|||
let check_has_fake = self.derive_cache.insert(derive_key, root_result);
|
||||
debug_assert_eq!(check_has_fake, Some(fake_fulfilled));
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a lot of these, consider using a visitor.
|
||||
// It will be very similar for most types (can't derive functions, can't derive unbound type
|
||||
// variables, can only derive opaques if they have an impl, etc).
|
||||
fn can_derive_encoding(
|
||||
&mut self,
|
||||
subs: &mut Subs,
|
||||
#[inline(always)]
|
||||
#[rustfmt::skip]
|
||||
fn is_builtin_number_alias(symbol: Symbol) -> bool {
|
||||
matches!(symbol,
|
||||
Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8
|
||||
| Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16
|
||||
| Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32
|
||||
| Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64
|
||||
| Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128
|
||||
| Symbol::NUM_I8 | Symbol::NUM_SIGNED8
|
||||
| Symbol::NUM_I16 | Symbol::NUM_SIGNED16
|
||||
| Symbol::NUM_I32 | Symbol::NUM_SIGNED32
|
||||
| Symbol::NUM_I64 | Symbol::NUM_SIGNED64
|
||||
| Symbol::NUM_I128 | Symbol::NUM_SIGNED128
|
||||
| Symbol::NUM_NAT | Symbol::NUM_NATURAL
|
||||
| Symbol::NUM_F32 | Symbol::NUM_BINARY32
|
||||
| Symbol::NUM_F64 | Symbol::NUM_BINARY64
|
||||
| Symbol::NUM_DEC | Symbol::NUM_DECIMAL,
|
||||
)
|
||||
}
|
||||
|
||||
enum DerivableError {
|
||||
NotDerivable(Variable),
|
||||
}
|
||||
|
||||
struct Descend(bool);
|
||||
|
||||
trait DerivableVisitor {
|
||||
const ABILITY: Symbol;
|
||||
|
||||
#[inline(always)]
|
||||
fn is_derivable_builtin_opaque(_symbol: Symbol) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_flex(var: Variable) -> Result<(), DerivableError> {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_rigid(var: Variable) -> Result<(), DerivableError> {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_flex_able(var: Variable, ability: Symbol) -> Result<(), DerivableError> {
|
||||
if ability != Self::ABILITY {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_rigid_able(var: Variable, ability: Symbol) -> Result<(), DerivableError> {
|
||||
if ability != Self::ABILITY {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_recursion(var: Variable) -> Result<Descend, DerivableError> {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_apply(var: Variable, _symbol: Symbol) -> Result<Descend, DerivableError> {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_func(var: Variable) -> Result<Descend, DerivableError> {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_record(var: Variable) -> Result<Descend, DerivableError> {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_tag_union(var: Variable) -> Result<Descend, DerivableError> {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_recursive_tag_union(var: Variable) -> Result<Descend, DerivableError> {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_function_or_tag_union(var: Variable) -> Result<Descend, DerivableError> {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_empty_record(var: Variable) -> Result<(), DerivableError> {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_empty_tag_union(var: Variable) -> Result<(), DerivableError> {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_alias(var: Variable, _symbol: Symbol) -> Result<Descend, DerivableError> {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_ranged_number(var: Variable, _range: NumericRange) -> Result<(), DerivableError> {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_derivable(
|
||||
obligation_cache: &mut ObligationCache,
|
||||
abilities_store: &AbilitiesStore,
|
||||
subs: &Subs,
|
||||
var: Variable,
|
||||
) -> Result<(), Variable> {
|
||||
) -> Result<(), DerivableError> {
|
||||
let mut stack = vec![var];
|
||||
let mut seen_recursion_vars = vec![];
|
||||
|
||||
|
@ -418,102 +549,103 @@ impl ObligationCache {
|
|||
let content = subs.get_content_without_compacting(var);
|
||||
|
||||
use Content::*;
|
||||
use DerivableError::*;
|
||||
use FlatType::*;
|
||||
match content {
|
||||
FlexVar(_) | RigidVar(_) => return Err(var),
|
||||
FlexAbleVar(_, ability) | RigidAbleVar(_, ability) => {
|
||||
if *ability != Symbol::ENCODE_ENCODING {
|
||||
return Err(var);
|
||||
}
|
||||
// Any concrete type this variables is instantiated with will also gain a "does
|
||||
// implement" check so this is okay.
|
||||
}
|
||||
match *content {
|
||||
FlexVar(_) => Self::visit_flex(var)?,
|
||||
RigidVar(_) => Self::visit_rigid(var)?,
|
||||
FlexAbleVar(_, ability) => Self::visit_flex_able(var, ability)?,
|
||||
RigidAbleVar(_, ability) => Self::visit_rigid_able(var, ability)?,
|
||||
RecursionVar {
|
||||
structure,
|
||||
opt_name: _,
|
||||
} => {
|
||||
let descend = Self::visit_recursion(var)?;
|
||||
if descend.0 {
|
||||
seen_recursion_vars.push(var);
|
||||
stack.push(*structure);
|
||||
stack.push(structure);
|
||||
}
|
||||
}
|
||||
Structure(flat_type) => match flat_type {
|
||||
Apply(
|
||||
Symbol::LIST_LIST | Symbol::SET_SET | Symbol::DICT_DICT | Symbol::STR_STR,
|
||||
vars,
|
||||
) => push_var_slice!(*vars),
|
||||
Apply(..) => return Err(var),
|
||||
Func(..) => {
|
||||
return Err(var);
|
||||
Apply(symbol, vars) => {
|
||||
let descend = Self::visit_apply(var, symbol)?;
|
||||
if descend.0 {
|
||||
push_var_slice!(vars)
|
||||
}
|
||||
Record(fields, var) => {
|
||||
}
|
||||
Func(args, _clos, ret) => {
|
||||
let descend = Self::visit_func(var)?;
|
||||
if descend.0 {
|
||||
push_var_slice!(args);
|
||||
stack.push(ret);
|
||||
}
|
||||
}
|
||||
Record(fields, ext) => {
|
||||
let descend = Self::visit_record(var)?;
|
||||
if descend.0 {
|
||||
push_var_slice!(fields.variables());
|
||||
stack.push(*var);
|
||||
stack.push(ext);
|
||||
}
|
||||
TagUnion(tags, ext_var) => {
|
||||
}
|
||||
TagUnion(tags, ext) => {
|
||||
let descend = Self::visit_tag_union(var)?;
|
||||
if descend.0 {
|
||||
for i in tags.variables() {
|
||||
push_var_slice!(subs[i]);
|
||||
}
|
||||
stack.push(*ext_var);
|
||||
stack.push(ext);
|
||||
}
|
||||
FunctionOrTagUnion(_, _, var) => stack.push(*var),
|
||||
RecursiveTagUnion(rec_var, tags, ext_var) => {
|
||||
seen_recursion_vars.push(*rec_var);
|
||||
}
|
||||
FunctionOrTagUnion(_tag_name, _fn_name, ext) => {
|
||||
let descend = Self::visit_function_or_tag_union(var)?;
|
||||
if descend.0 {
|
||||
stack.push(ext);
|
||||
}
|
||||
}
|
||||
RecursiveTagUnion(rec, tags, ext) => {
|
||||
let descend = Self::visit_recursive_tag_union(var)?;
|
||||
if descend.0 {
|
||||
seen_recursion_vars.push(rec);
|
||||
for i in tags.variables() {
|
||||
push_var_slice!(subs[i]);
|
||||
}
|
||||
stack.push(*ext_var);
|
||||
stack.push(ext);
|
||||
}
|
||||
EmptyRecord | EmptyTagUnion => {
|
||||
// yes
|
||||
}
|
||||
Erroneous(_) => return Err(var),
|
||||
EmptyRecord => Self::visit_empty_record(var)?,
|
||||
EmptyTagUnion => Self::visit_empty_tag_union(var)?,
|
||||
|
||||
Erroneous(_) => return Err(NotDerivable(var)),
|
||||
},
|
||||
#[rustfmt::skip]
|
||||
Alias(
|
||||
Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8
|
||||
| Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16
|
||||
| Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32
|
||||
| Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64
|
||||
| Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128
|
||||
| Symbol::NUM_I8 | Symbol::NUM_SIGNED8
|
||||
| Symbol::NUM_I16 | Symbol::NUM_SIGNED16
|
||||
| Symbol::NUM_I32 | Symbol::NUM_SIGNED32
|
||||
| Symbol::NUM_I64 | Symbol::NUM_SIGNED64
|
||||
| Symbol::NUM_I128 | Symbol::NUM_SIGNED128
|
||||
| Symbol::NUM_NAT | Symbol::NUM_NATURAL
|
||||
| Symbol::NUM_F32 | Symbol::NUM_BINARY32
|
||||
| Symbol::NUM_F64 | Symbol::NUM_BINARY64
|
||||
| Symbol::NUM_DEC | Symbol::NUM_DECIMAL,
|
||||
_,
|
||||
_,
|
||||
_,
|
||||
) => {
|
||||
// yes
|
||||
}
|
||||
Alias(
|
||||
Symbol::NUM_NUM | Symbol::NUM_INTEGER | Symbol::NUM_FLOATINGPOINT,
|
||||
_,
|
||||
_alias_variables,
|
||||
real_var,
|
||||
_,
|
||||
) => stack.push(*real_var),
|
||||
Alias(name, _, _, AliasKind::Opaque) => {
|
||||
let opaque = *name;
|
||||
if self
|
||||
.check_opaque_and_read(abilities_store, opaque, Symbol::ENCODE_ENCODING)
|
||||
AliasKind::Opaque,
|
||||
) => {
|
||||
// Numbers: always decay until a ground is hit.
|
||||
stack.push(real_var);
|
||||
}
|
||||
Alias(opaque, _alias_variables, _real_var, AliasKind::Opaque) => {
|
||||
if obligation_cache
|
||||
.check_opaque_and_read(abilities_store, opaque, Self::ABILITY)
|
||||
.is_err()
|
||||
&& !Self::is_derivable_builtin_opaque(opaque)
|
||||
{
|
||||
return Err(var);
|
||||
return Err(NotDerivable(var));
|
||||
}
|
||||
}
|
||||
Alias(_, arguments, real_type_var, _) => {
|
||||
push_var_slice!(arguments.all_variables());
|
||||
stack.push(*real_type_var);
|
||||
Alias(symbol, _alias_variables, real_var, AliasKind::Structural) => {
|
||||
let descend = Self::visit_alias(var, symbol)?;
|
||||
if descend.0 {
|
||||
stack.push(real_var);
|
||||
}
|
||||
RangedNumber(..) => {
|
||||
// yes, all numbers can
|
||||
}
|
||||
LambdaSet(..) => return Err(var),
|
||||
RangedNumber(range) => Self::visit_ranged_number(var, range)?,
|
||||
|
||||
LambdaSet(..) => return Err(NotDerivable(var)),
|
||||
Error => {
|
||||
return Err(var);
|
||||
return Err(NotDerivable(var));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -522,6 +654,148 @@ impl ObligationCache {
|
|||
}
|
||||
}
|
||||
|
||||
struct DeriveEncoding;
|
||||
impl DerivableVisitor for DeriveEncoding {
|
||||
const ABILITY: Symbol = Symbol::ENCODE_ENCODING;
|
||||
|
||||
#[inline(always)]
|
||||
fn is_derivable_builtin_opaque(symbol: Symbol) -> bool {
|
||||
is_builtin_number_alias(symbol)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_recursion(_var: Variable) -> Result<Descend, DerivableError> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_apply(var: Variable, symbol: Symbol) -> Result<Descend, DerivableError> {
|
||||
if matches!(
|
||||
symbol,
|
||||
Symbol::LIST_LIST | Symbol::SET_SET | Symbol::DICT_DICT | Symbol::STR_STR,
|
||||
) {
|
||||
Ok(Descend(true))
|
||||
} else {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_record(_var: Variable) -> Result<Descend, DerivableError> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_tag_union(_var: Variable) -> Result<Descend, DerivableError> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_recursive_tag_union(_var: Variable) -> Result<Descend, DerivableError> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_function_or_tag_union(_var: Variable) -> Result<Descend, DerivableError> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_empty_record(_var: Variable) -> Result<(), DerivableError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_empty_tag_union(_var: Variable) -> Result<(), DerivableError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_alias(_var: Variable, symbol: Symbol) -> Result<Descend, DerivableError> {
|
||||
if is_builtin_number_alias(symbol) {
|
||||
Ok(Descend(false))
|
||||
} else {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_ranged_number(_var: Variable, _range: NumericRange) -> Result<(), DerivableError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct DeriveDecoding;
|
||||
impl DerivableVisitor for DeriveDecoding {
|
||||
const ABILITY: Symbol = Symbol::DECODE_DECODING;
|
||||
|
||||
#[inline(always)]
|
||||
fn is_derivable_builtin_opaque(symbol: Symbol) -> bool {
|
||||
is_builtin_number_alias(symbol)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_recursion(_var: Variable) -> Result<Descend, DerivableError> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_apply(var: Variable, symbol: Symbol) -> Result<Descend, DerivableError> {
|
||||
if matches!(
|
||||
symbol,
|
||||
Symbol::LIST_LIST | Symbol::SET_SET | Symbol::DICT_DICT | Symbol::STR_STR,
|
||||
) {
|
||||
Ok(Descend(true))
|
||||
} else {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_record(_var: Variable) -> Result<Descend, DerivableError> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_tag_union(_var: Variable) -> Result<Descend, DerivableError> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_recursive_tag_union(_var: Variable) -> Result<Descend, DerivableError> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_function_or_tag_union(_var: Variable) -> Result<Descend, DerivableError> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_empty_record(_var: Variable) -> Result<(), DerivableError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_empty_tag_union(_var: Variable) -> Result<(), DerivableError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_alias(_var: Variable, symbol: Symbol) -> Result<Descend, DerivableError> {
|
||||
if is_builtin_number_alias(symbol) {
|
||||
Ok(Descend(false))
|
||||
} else {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_ranged_number(_var: Variable, _range: NumericRange) -> Result<(), DerivableError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines what type implements an ability member of a specialized signature, given the
|
||||
/// [MustImplementAbility] constraints of the signature.
|
||||
pub fn type_implementing_specialization(
|
||||
|
@ -551,8 +825,8 @@ pub fn type_implementing_specialization(
|
|||
pub enum Resolved {
|
||||
/// A user-defined specialization should be used.
|
||||
Specialization(Symbol),
|
||||
/// A specialization must be generated.
|
||||
NeedsGenerated,
|
||||
/// A specialization must be generated for the given type variable.
|
||||
NeedsGenerated(Variable),
|
||||
}
|
||||
|
||||
/// An [`AbilityResolver`] is a shell of an abilities store that answers questions needed for
|
||||
|
@ -636,15 +910,17 @@ pub fn resolve_ability_specialization<R: AbilityResolver>(
|
|||
roc_types::types::MemberImpl::Impl(spec_symbol) => {
|
||||
Resolved::Specialization(spec_symbol)
|
||||
}
|
||||
roc_types::types::MemberImpl::Derived => Resolved::NeedsGenerated,
|
||||
roc_types::types::MemberImpl::Derived => {
|
||||
todo_abilities!("get type from obligated opaque")
|
||||
}
|
||||
// TODO this is not correct. We can replace `Resolved` with `MemberImpl` entirely,
|
||||
// which will make this simpler.
|
||||
roc_types::types::MemberImpl::Error => Resolved::Specialization(Symbol::UNDERSCORE),
|
||||
}
|
||||
}
|
||||
Obligated::Adhoc(_) => {
|
||||
Obligated::Adhoc(variable) => {
|
||||
// TODO: more rules need to be validated here, like is this a builtin ability?
|
||||
Resolved::NeedsGenerated
|
||||
Resolved::NeedsGenerated(variable)
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -655,13 +655,16 @@ fn make_specialization_decision<P: Phase>(
|
|||
}
|
||||
}
|
||||
Structure(_) | Alias(_, _, _, _) => {
|
||||
// This is a structural type, find the name of the derived ability function it
|
||||
// should use.
|
||||
match roc_derive_key::Derived::encoding(subs, var) {
|
||||
let builtin = match ability_member.try_into() {
|
||||
Ok(builtin) => builtin,
|
||||
Err(_) => return SpecializeDecision::Drop,
|
||||
};
|
||||
|
||||
// This is a structural type, find the derived ability function it should use.
|
||||
match roc_derive_key::Derived::builtin(builtin, subs, var) {
|
||||
Ok(derived) => match derived {
|
||||
roc_derive_key::Derived::Immediate(imm) => {
|
||||
SpecializeDecision::Specialize(Immediate(imm))
|
||||
// todo!("deal with lambda set extraction from immediates")
|
||||
}
|
||||
roc_derive_key::Derived::Key(derive_key) => {
|
||||
SpecializeDecision::Specialize(Derived(derive_key))
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_solve_problem"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_str"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "test_derive"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
51
crates/compiler/test_derive/src/decoding.rs
Normal file
51
crates/compiler/test_derive/src/decoding.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
#![cfg(test)]
|
||||
// Even with #[allow(non_snake_case)] on individual idents, rust-analyzer issues diagnostics.
|
||||
// See https://github.com/rust-lang/rust-analyzer/issues/6541.
|
||||
// For the `v!` macro we use uppercase variables when constructing tag unions.
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::{
|
||||
util::{check_immediate, derive_test},
|
||||
v,
|
||||
};
|
||||
use insta::assert_snapshot;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_types::subs::Variable;
|
||||
|
||||
use roc_derive_key::DeriveBuiltin::Decoder;
|
||||
|
||||
#[test]
|
||||
fn immediates() {
|
||||
check_immediate(Decoder, v!(U8), Symbol::DECODE_U8);
|
||||
check_immediate(Decoder, v!(U16), Symbol::DECODE_U16);
|
||||
check_immediate(Decoder, v!(U32), Symbol::DECODE_U32);
|
||||
check_immediate(Decoder, v!(U64), Symbol::DECODE_U64);
|
||||
check_immediate(Decoder, v!(U128), Symbol::DECODE_U128);
|
||||
check_immediate(Decoder, v!(I8), Symbol::DECODE_I8);
|
||||
check_immediate(Decoder, v!(I16), Symbol::DECODE_I16);
|
||||
check_immediate(Decoder, v!(I32), Symbol::DECODE_I32);
|
||||
check_immediate(Decoder, v!(I64), Symbol::DECODE_I64);
|
||||
check_immediate(Decoder, v!(I128), Symbol::DECODE_I128);
|
||||
check_immediate(Decoder, v!(DEC), Symbol::DECODE_DEC);
|
||||
check_immediate(Decoder, v!(F32), Symbol::DECODE_F32);
|
||||
check_immediate(Decoder, v!(F64), Symbol::DECODE_F64);
|
||||
check_immediate(Decoder, v!(STR), Symbol::DECODE_STRING);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list() {
|
||||
derive_test(Decoder, v!(Symbol::LIST_LIST v!(STR)), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for List Str
|
||||
# Decoder (List val) fmt | fmt has DecoderFormatting, val has Decoding
|
||||
# List U8, fmt -[[custom(3)]]-> { rest : List U8, result : [Err [TooShort], Ok (List val)] } | fmt has DecoderFormatting, val has Decoding
|
||||
# Specialization lambda sets:
|
||||
# @<1>: [[custom(3)]]
|
||||
#Derived.decoder_list =
|
||||
Decode.custom
|
||||
\#Derived.bytes, #Derived.fmt ->
|
||||
Decode.decodeWith #Derived.bytes (Decode.list Decode.decoder) #Derived.fmt
|
||||
"###
|
||||
)
|
||||
})
|
||||
}
|
|
@ -4,427 +4,22 @@
|
|||
// For the `v!` macro we use uppercase variables when constructing tag unions.
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use bumpalo::Bump;
|
||||
use insta::assert_snapshot;
|
||||
use pretty_assertions::assert_eq;
|
||||
use ven_pretty::DocAllocator;
|
||||
|
||||
use crate::pretty_print::{pretty_print_def, Ctx};
|
||||
use roc_can::{
|
||||
abilities::{AbilitiesStore, SpecializationLambdaSets},
|
||||
constraint::Constraints,
|
||||
def::Def,
|
||||
expr::Declarations,
|
||||
module::{
|
||||
ExposedByModule, ExposedForModule, ExposedModuleTypes, ResolvedImplementations,
|
||||
RigidVariables,
|
||||
},
|
||||
use crate::{
|
||||
test_hash_eq, test_hash_neq,
|
||||
util::{check_immediate, derive_test},
|
||||
v,
|
||||
};
|
||||
use roc_collections::VecSet;
|
||||
use roc_constrain::expr::constrain_decls;
|
||||
use roc_debug_flags::dbg_do;
|
||||
use roc_derive::{synth_var, DerivedModule};
|
||||
use roc_derive_key::{DeriveKey, Derived};
|
||||
use roc_load_internal::file::{add_imports, default_aliases, LoadedModule, Threading};
|
||||
use roc_module::{
|
||||
ident::TagName,
|
||||
symbol::{IdentIds, Interns, ModuleId, Symbol},
|
||||
};
|
||||
use roc_region::all::LineInfo;
|
||||
use roc_reporting::report::{type_problem, RocDocAllocator};
|
||||
use roc_types::{
|
||||
pretty_print::{name_and_print_var, DebugPrint},
|
||||
subs::{
|
||||
AliasVariables, Content, ExposedTypesStorageSubs, FlatType, RecordFields, Subs, SubsIndex,
|
||||
SubsSlice, UnionTags, Variable,
|
||||
},
|
||||
types::{AliasKind, RecordField},
|
||||
};
|
||||
|
||||
const DERIVED_MODULE: ModuleId = ModuleId::DERIVED_SYNTH;
|
||||
|
||||
fn encode_path() -> PathBuf {
|
||||
let repo_root = std::env::var("ROC_WORKSPACE_DIR").expect("are you running with `cargo test`?");
|
||||
PathBuf::from(repo_root)
|
||||
.join("compiler")
|
||||
.join("builtins")
|
||||
.join("roc")
|
||||
.join("Encode.roc")
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn assemble_derived_golden(
|
||||
subs: &mut Subs,
|
||||
test_module: ModuleId,
|
||||
interns: &Interns,
|
||||
source_var: Variable,
|
||||
derived_source: &str,
|
||||
typ: Variable,
|
||||
specialization_lsets: SpecializationLambdaSets,
|
||||
) -> String {
|
||||
let mut print_var = |var: Variable, print_only_under_alias| {
|
||||
let snapshot = subs.snapshot();
|
||||
let pretty_type = name_and_print_var(
|
||||
var,
|
||||
subs,
|
||||
test_module,
|
||||
interns,
|
||||
DebugPrint {
|
||||
print_lambda_sets: true,
|
||||
print_only_under_alias,
|
||||
},
|
||||
);
|
||||
subs.rollback_to(snapshot);
|
||||
pretty_type
|
||||
};
|
||||
|
||||
let mut pretty_buf = String::new();
|
||||
|
||||
pretty_buf.push_str(&format!("# derived for {}\n", print_var(source_var, false)));
|
||||
|
||||
let pretty_type = print_var(typ, false);
|
||||
pretty_buf.push_str(&format!("# {}\n", &pretty_type));
|
||||
|
||||
let pretty_type_under_aliases = print_var(typ, true);
|
||||
pretty_buf.push_str(&format!("# {}\n", &pretty_type_under_aliases));
|
||||
|
||||
pretty_buf.push_str("# Specialization lambda sets:\n");
|
||||
let mut specialization_lsets = specialization_lsets.into_iter().collect::<Vec<_>>();
|
||||
specialization_lsets.sort_by_key(|(region, _)| *region);
|
||||
for (region, var) in specialization_lsets {
|
||||
let pretty_lset = print_var(var, false);
|
||||
pretty_buf.push_str(&format!("# @<{}>: {}\n", region, pretty_lset));
|
||||
}
|
||||
|
||||
pretty_buf.push_str(derived_source);
|
||||
|
||||
pretty_buf
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn check_derived_typechecks_and_golden(
|
||||
derived_def: Def,
|
||||
test_module: ModuleId,
|
||||
mut test_subs: Subs,
|
||||
interns: &Interns,
|
||||
exposed_encode_types: ExposedTypesStorageSubs,
|
||||
encode_abilities_store: AbilitiesStore,
|
||||
source_var: Variable,
|
||||
derived_program: &str,
|
||||
specialization_lsets: SpecializationLambdaSets,
|
||||
check_golden: impl Fn(&str),
|
||||
) {
|
||||
// constrain the derived
|
||||
let mut constraints = Constraints::new();
|
||||
let def_var = derived_def.expr_var;
|
||||
let mut decls = Declarations::new();
|
||||
decls.push_def(derived_def);
|
||||
let constr = constrain_decls(&mut constraints, test_module, &decls);
|
||||
|
||||
// the derived depends on stuff from Encode, so
|
||||
// - we need to add those dependencies as imported on the constraint
|
||||
// - we need to add Encode ability info to a local abilities store
|
||||
let encode_values_to_import = exposed_encode_types
|
||||
.stored_vars_by_symbol
|
||||
.keys()
|
||||
.copied()
|
||||
.collect::<VecSet<_>>();
|
||||
let pending_abilities = encode_abilities_store.closure_from_imported(&encode_values_to_import);
|
||||
let mut exposed_by_module = ExposedByModule::default();
|
||||
exposed_by_module.insert(
|
||||
ModuleId::ENCODE,
|
||||
ExposedModuleTypes {
|
||||
exposed_types_storage_subs: exposed_encode_types,
|
||||
resolved_implementations: ResolvedImplementations::default(),
|
||||
},
|
||||
);
|
||||
let exposed_for_module =
|
||||
ExposedForModule::new(encode_values_to_import.iter(), exposed_by_module);
|
||||
let mut def_types = Default::default();
|
||||
let mut rigid_vars = Default::default();
|
||||
let (import_variables, abilities_store) = add_imports(
|
||||
test_module,
|
||||
&mut test_subs,
|
||||
pending_abilities,
|
||||
&exposed_for_module,
|
||||
&mut def_types,
|
||||
&mut rigid_vars,
|
||||
);
|
||||
let constr =
|
||||
constraints.let_import_constraint(rigid_vars, def_types, constr, &import_variables);
|
||||
|
||||
// run the solver, print and fail if we have errors
|
||||
dbg_do!(
|
||||
roc_debug_flags::ROC_PRINT_UNIFICATIONS_DERIVED,
|
||||
std::env::set_var(roc_debug_flags::ROC_PRINT_UNIFICATIONS_DERIVED, "1")
|
||||
);
|
||||
let (mut solved_subs, _, problems, _) = roc_solve::module::run_solve(
|
||||
test_module,
|
||||
&constraints,
|
||||
constr,
|
||||
RigidVariables::default(),
|
||||
test_subs,
|
||||
default_aliases(),
|
||||
abilities_store,
|
||||
Default::default(),
|
||||
&exposed_for_module.exposed_by_module,
|
||||
Default::default(),
|
||||
);
|
||||
let subs = solved_subs.inner_mut();
|
||||
|
||||
if !problems.is_empty() {
|
||||
let filename = PathBuf::from("Test.roc");
|
||||
let lines = LineInfo::new(" ");
|
||||
let src_lines = vec![" "];
|
||||
let mut reports = Vec::new();
|
||||
let alloc = RocDocAllocator::new(&src_lines, test_module, interns);
|
||||
|
||||
for problem in problems.into_iter() {
|
||||
if let Some(report) = type_problem(&alloc, &lines, filename.clone(), problem.clone()) {
|
||||
reports.push(report);
|
||||
}
|
||||
}
|
||||
|
||||
let has_reports = !reports.is_empty();
|
||||
|
||||
let doc = alloc
|
||||
.stack(reports.into_iter().map(|v| v.pretty(&alloc)))
|
||||
.append(if has_reports {
|
||||
alloc.line()
|
||||
} else {
|
||||
alloc.nil()
|
||||
});
|
||||
|
||||
let mut buf = String::new();
|
||||
doc.1
|
||||
.render_raw(80, &mut roc_reporting::report::CiWrite::new(&mut buf))
|
||||
.unwrap();
|
||||
|
||||
panic!(
|
||||
"Derived does not typecheck:\n{}\nDerived def:\n{}",
|
||||
buf, derived_program
|
||||
);
|
||||
}
|
||||
|
||||
let golden = assemble_derived_golden(
|
||||
subs,
|
||||
test_module,
|
||||
interns,
|
||||
source_var,
|
||||
derived_program,
|
||||
def_var,
|
||||
specialization_lsets,
|
||||
);
|
||||
|
||||
check_golden(&golden)
|
||||
}
|
||||
|
||||
fn derive_test<S>(synth_input: S, check_golden: impl Fn(&str))
|
||||
where
|
||||
S: FnOnce(&mut Subs) -> Variable,
|
||||
{
|
||||
let arena = Bump::new();
|
||||
let source = roc_builtins::roc::module_source(ModuleId::ENCODE);
|
||||
let target_info = roc_target::TargetInfo::default_x86_64();
|
||||
|
||||
let LoadedModule {
|
||||
mut interns,
|
||||
exposed_types_storage: exposed_encode_types,
|
||||
abilities_store,
|
||||
resolved_implementations,
|
||||
..
|
||||
} = roc_load_internal::file::load_and_typecheck_str(
|
||||
&arena,
|
||||
encode_path().file_name().unwrap().into(),
|
||||
source,
|
||||
encode_path().parent().unwrap().to_path_buf(),
|
||||
Default::default(),
|
||||
target_info,
|
||||
roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
Threading::AllAvailable,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut subs = Subs::new();
|
||||
let ident_ids = IdentIds::default();
|
||||
let source_var = synth_input(&mut subs);
|
||||
let key = get_key(&subs, source_var);
|
||||
|
||||
let mut derived_module = unsafe { DerivedModule::from_components(subs, ident_ids) };
|
||||
|
||||
let mut exposed_by_module = ExposedByModule::default();
|
||||
exposed_by_module.insert(
|
||||
ModuleId::ENCODE,
|
||||
ExposedModuleTypes {
|
||||
exposed_types_storage_subs: exposed_encode_types.clone(),
|
||||
resolved_implementations,
|
||||
},
|
||||
);
|
||||
|
||||
let (_derived_symbol, derived_def, specialization_lsets) =
|
||||
derived_module.get_or_insert(&exposed_by_module, key);
|
||||
let specialization_lsets = specialization_lsets.clone();
|
||||
let derived_def = derived_def.clone();
|
||||
|
||||
let (subs, ident_ids) = derived_module.decompose();
|
||||
|
||||
interns.all_ident_ids.insert(DERIVED_MODULE, ident_ids);
|
||||
DERIVED_MODULE.register_debug_idents(interns.all_ident_ids.get(&DERIVED_MODULE).unwrap());
|
||||
|
||||
let ctx = Ctx { interns: &interns };
|
||||
let derived_program = pretty_print_def(&ctx, &derived_def);
|
||||
|
||||
check_derived_typechecks_and_golden(
|
||||
derived_def,
|
||||
DERIVED_MODULE,
|
||||
subs,
|
||||
&interns,
|
||||
exposed_encode_types,
|
||||
abilities_store,
|
||||
source_var,
|
||||
&derived_program,
|
||||
specialization_lsets,
|
||||
check_golden,
|
||||
);
|
||||
}
|
||||
|
||||
fn get_key(subs: &Subs, var: Variable) -> DeriveKey {
|
||||
match Derived::encoding(subs, var) {
|
||||
Ok(Derived::Key(key)) => key,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_key<S1, S2>(eq: bool, synth1: S1, synth2: S2)
|
||||
where
|
||||
S1: FnOnce(&mut Subs) -> Variable,
|
||||
S2: FnOnce(&mut Subs) -> Variable,
|
||||
{
|
||||
let mut subs = Subs::new();
|
||||
let var1 = synth1(&mut subs);
|
||||
let var2 = synth2(&mut subs);
|
||||
|
||||
let key1 = Derived::encoding(&subs, var1);
|
||||
let key2 = Derived::encoding(&subs, var2);
|
||||
|
||||
if eq {
|
||||
assert_eq!(key1, key2);
|
||||
} else {
|
||||
assert_ne!(key1, key2);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_immediate<S>(synth: S, immediate: Symbol)
|
||||
where
|
||||
S: FnOnce(&mut Subs) -> Variable,
|
||||
{
|
||||
let mut subs = Subs::new();
|
||||
let var = synth(&mut subs);
|
||||
|
||||
let key = Derived::encoding(&subs, var);
|
||||
|
||||
assert_eq!(key, Ok(Derived::Immediate(immediate)));
|
||||
}
|
||||
|
||||
// Writing out the types into content is terrible, so let's use a DSL at least for testing
|
||||
macro_rules! v {
|
||||
({ $($field:ident: $make_v:expr,)* $(?$opt_field:ident : $make_opt_v:expr,)* }) => {
|
||||
|subs: &mut Subs| {
|
||||
$(let $field = $make_v(subs);)*
|
||||
$(let $opt_field = $make_opt_v(subs);)*
|
||||
let fields = vec![
|
||||
$( (stringify!($field).into(), RecordField::Required($field)) ,)*
|
||||
$( (stringify!($opt_field).into(), RecordField::Required($opt_field)) ,)*
|
||||
];
|
||||
let fields = RecordFields::insert_into_subs(subs, fields);
|
||||
synth_var(subs, Content::Structure(FlatType::Record(fields, Variable::EMPTY_RECORD)))
|
||||
}
|
||||
};
|
||||
([ $($tag:ident $($payload:expr)*),* ]) => {
|
||||
|subs: &mut Subs| {
|
||||
$(
|
||||
let $tag = vec![ $( $payload(subs), )* ];
|
||||
)*
|
||||
let tags = UnionTags::insert_into_subs::<_, Vec<Variable>>(subs, vec![ $( (TagName(stringify!($tag).into()), $tag) ,)* ]);
|
||||
synth_var(subs, Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)))
|
||||
}
|
||||
};
|
||||
([ $($tag:ident $($payload:expr)*),* ] as $rec_var:ident) => {
|
||||
|subs: &mut Subs| {
|
||||
let $rec_var = subs.fresh_unnamed_flex_var();
|
||||
let rec_name_index =
|
||||
SubsIndex::push_new(&mut subs.field_names, stringify!($rec).into());
|
||||
|
||||
$(
|
||||
let $tag = vec![ $( $payload(subs), )* ];
|
||||
)*
|
||||
let tags = UnionTags::insert_into_subs::<_, Vec<Variable>>(subs, vec![ $( (TagName(stringify!($tag).into()), $tag) ,)* ]);
|
||||
let tag_union_var = synth_var(subs, Content::Structure(FlatType::RecursiveTagUnion($rec_var, tags, Variable::EMPTY_TAG_UNION)));
|
||||
|
||||
subs.set_content(
|
||||
$rec_var,
|
||||
Content::RecursionVar {
|
||||
structure: tag_union_var,
|
||||
opt_name: Some(rec_name_index),
|
||||
},
|
||||
);
|
||||
tag_union_var
|
||||
}
|
||||
};
|
||||
(Symbol::$sym:ident $($arg:expr)*) => {
|
||||
|subs: &mut Subs| {
|
||||
let $sym = vec![ $( $arg(subs) ,)* ];
|
||||
let var_slice = SubsSlice::insert_into_subs(subs, $sym);
|
||||
synth_var(subs, Content::Structure(FlatType::Apply(Symbol::$sym, var_slice)))
|
||||
}
|
||||
};
|
||||
(Symbol::$alias:ident $($arg:expr)* => $real_var:expr) => {
|
||||
|subs: &mut Subs| {
|
||||
let args = vec![$( $arg(subs) )*];
|
||||
let alias_variables = AliasVariables::insert_into_subs::<Vec<_>, Vec<_>>(subs, args, vec![]);
|
||||
let real_var = $real_var(subs);
|
||||
synth_var(subs, Content::Alias(Symbol::$alias, alias_variables, real_var, AliasKind::Structural))
|
||||
}
|
||||
};
|
||||
(@Symbol::$alias:ident $($arg:expr)* => $real_var:expr) => {
|
||||
|subs: &mut Subs| {
|
||||
let args = vec![$( $arg(subs) )*];
|
||||
let alias_variables = AliasVariables::insert_into_subs::<Vec<_>, Vec<_>>(subs, args, vec![]);
|
||||
let real_var = $real_var(subs);
|
||||
synth_var(subs, Content::Alias(Symbol::$alias, alias_variables, real_var, AliasKind::Opaque))
|
||||
}
|
||||
};
|
||||
(*$rec_var:ident) => {
|
||||
|_: &mut Subs| { $rec_var }
|
||||
};
|
||||
($var:ident) => {
|
||||
|_: &mut Subs| { Variable::$var }
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! test_hash_eq {
|
||||
($($name:ident: $synth1:expr, $synth2:expr)*) => {$(
|
||||
#[test]
|
||||
fn $name() {
|
||||
check_key(true, $synth1, $synth2)
|
||||
}
|
||||
)*};
|
||||
}
|
||||
|
||||
macro_rules! test_hash_neq {
|
||||
($($name:ident: $synth1:expr, $synth2:expr)*) => {$(
|
||||
#[test]
|
||||
fn $name() {
|
||||
check_key(false, $synth1, $synth2)
|
||||
}
|
||||
)*};
|
||||
}
|
||||
use roc_derive_key::DeriveBuiltin::ToEncoder;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_types::subs::Variable;
|
||||
|
||||
// {{{ hash tests
|
||||
|
||||
test_hash_eq! {
|
||||
ToEncoder,
|
||||
|
||||
same_record:
|
||||
v!({ a: v!(U8), }), v!({ a: v!(U8), })
|
||||
same_record_fields_diff_types:
|
||||
|
@ -448,9 +43,9 @@ test_hash_eq! {
|
|||
v!(EMPTY_TAG_UNION), v!([])
|
||||
|
||||
same_recursive_tag_union:
|
||||
v!([ Nil, Cons v!(*lst)] as lst), v!([ Nil, Cons v!(*lst)] as lst)
|
||||
v!([ Nil, Cons v!(^lst)] as lst), v!([ Nil, Cons v!(^lst)] as lst)
|
||||
same_tag_union_and_recursive_tag_union_fields:
|
||||
v!([ Nil, Cons v!(STR)]), v!([ Nil, Cons v!(*lst)] as lst)
|
||||
v!([ Nil, Cons v!(STR)]), v!([ Nil, Cons v!(^lst)] as lst)
|
||||
|
||||
list_list_diff_types:
|
||||
v!(Symbol::LIST_LIST v!(STR)), v!(Symbol::LIST_LIST v!(U8))
|
||||
|
@ -476,6 +71,8 @@ test_hash_eq! {
|
|||
}
|
||||
|
||||
test_hash_neq! {
|
||||
ToEncoder,
|
||||
|
||||
different_record_fields:
|
||||
v!({ a: v!(U8), }), v!({ b: v!(U8), })
|
||||
record_empty_vs_nonempty:
|
||||
|
@ -486,7 +83,7 @@ test_hash_neq! {
|
|||
tag_union_empty_vs_nonempty:
|
||||
v!(EMPTY_TAG_UNION), v!([ B v!(U8) ])
|
||||
different_recursive_tag_union_tags:
|
||||
v!([ Nil, Cons v!(*lst) ] as lst), v!([ Nil, Next v!(*lst) ] as lst)
|
||||
v!([ Nil, Cons v!(^lst) ] as lst), v!([ Nil, Next v!(^lst) ] as lst)
|
||||
|
||||
same_alias_diff_real_type:
|
||||
v!(Symbol::BOOL_BOOL => v!([ True, False ])), v!(Symbol::BOOL_BOOL => v!([ False, True, Maybe ]))
|
||||
|
@ -505,25 +102,25 @@ test_hash_neq! {
|
|||
|
||||
#[test]
|
||||
fn immediates() {
|
||||
check_immediate(v!(U8), Symbol::ENCODE_U8);
|
||||
check_immediate(v!(U16), Symbol::ENCODE_U16);
|
||||
check_immediate(v!(U32), Symbol::ENCODE_U32);
|
||||
check_immediate(v!(U64), Symbol::ENCODE_U64);
|
||||
check_immediate(v!(U128), Symbol::ENCODE_U128);
|
||||
check_immediate(v!(I8), Symbol::ENCODE_I8);
|
||||
check_immediate(v!(I16), Symbol::ENCODE_I16);
|
||||
check_immediate(v!(I32), Symbol::ENCODE_I32);
|
||||
check_immediate(v!(I64), Symbol::ENCODE_I64);
|
||||
check_immediate(v!(I128), Symbol::ENCODE_I128);
|
||||
check_immediate(v!(DEC), Symbol::ENCODE_DEC);
|
||||
check_immediate(v!(F32), Symbol::ENCODE_F32);
|
||||
check_immediate(v!(F64), Symbol::ENCODE_F64);
|
||||
check_immediate(v!(STR), Symbol::ENCODE_STRING);
|
||||
check_immediate(ToEncoder, v!(U8), Symbol::ENCODE_U8);
|
||||
check_immediate(ToEncoder, v!(U16), Symbol::ENCODE_U16);
|
||||
check_immediate(ToEncoder, v!(U32), Symbol::ENCODE_U32);
|
||||
check_immediate(ToEncoder, v!(U64), Symbol::ENCODE_U64);
|
||||
check_immediate(ToEncoder, v!(U128), Symbol::ENCODE_U128);
|
||||
check_immediate(ToEncoder, v!(I8), Symbol::ENCODE_I8);
|
||||
check_immediate(ToEncoder, v!(I16), Symbol::ENCODE_I16);
|
||||
check_immediate(ToEncoder, v!(I32), Symbol::ENCODE_I32);
|
||||
check_immediate(ToEncoder, v!(I64), Symbol::ENCODE_I64);
|
||||
check_immediate(ToEncoder, v!(I128), Symbol::ENCODE_I128);
|
||||
check_immediate(ToEncoder, v!(DEC), Symbol::ENCODE_DEC);
|
||||
check_immediate(ToEncoder, v!(F32), Symbol::ENCODE_F32);
|
||||
check_immediate(ToEncoder, v!(F64), Symbol::ENCODE_F64);
|
||||
check_immediate(ToEncoder, v!(STR), Symbol::ENCODE_STRING);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_record() {
|
||||
derive_test(v!(EMPTY_RECORD), |golden| {
|
||||
derive_test(ToEncoder, v!(EMPTY_RECORD), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for {}
|
||||
# {} -[[toEncoder_{}(0)]]-> Encoder fmt | fmt has EncoderFormatting
|
||||
|
@ -543,7 +140,7 @@ fn empty_record() {
|
|||
|
||||
#[test]
|
||||
fn zero_field_record() {
|
||||
derive_test(v!({}), |golden| {
|
||||
derive_test(ToEncoder, v!({}), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for {}
|
||||
# {} -[[toEncoder_{}(0)]]-> Encoder fmt | fmt has EncoderFormatting
|
||||
|
@ -563,7 +160,7 @@ fn zero_field_record() {
|
|||
|
||||
#[test]
|
||||
fn one_field_record() {
|
||||
derive_test(v!({ a: v!(U8), }), |golden| {
|
||||
derive_test(ToEncoder, v!({ a: v!(U8), }), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for { a : U8 }
|
||||
# { a : val } -[[toEncoder_{a}(0)]]-> Encoder fmt | fmt has EncoderFormatting, val has Encoding
|
||||
|
@ -588,23 +185,27 @@ fn one_field_record() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "TODO #3421 unification of unspecialized variables in lambda sets currently causes this to be derived incorrectly"]
|
||||
fn two_field_record() {
|
||||
derive_test(v!({ a: v!(U8), b: v!(STR), }), |golden| {
|
||||
derive_test(ToEncoder, v!({ a: v!(U8), b: v!(STR), }), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for { a : U8, b : Str }
|
||||
# { a : val, b : a } -[[toEncoder_{a,b}(0)]]-> Encoder fmt | a has Encoding, fmt has EncoderFormatting, val has Encoding
|
||||
# { a : val, b : a } -[[toEncoder_{a,b}(0)]]-> (List U8, fmt -[[custom(2) { a : val, b : a }]]-> List U8) | a has Encoding, fmt has EncoderFormatting, val has Encoding
|
||||
# { a : val, b : val1 } -[[toEncoder_{a,b}(0)]]-> Encoder fmt | fmt has EncoderFormatting, val has Encoding, val1 has Encoding
|
||||
# { a : val, b : val1 } -[[toEncoder_{a,b}(0)]]-> (List U8, fmt -[[custom(2) { a : val, b : val1 }]]-> List U8) | fmt has EncoderFormatting, val has Encoding, val1 has Encoding
|
||||
# Specialization lambda sets:
|
||||
# @<1>: [[toEncoder_{a,b}(0)]]
|
||||
# @<2>: [[custom(2) { a : val, b : a }]] | a has Encoding, val has Encoding
|
||||
# @<2>: [[custom(2) { a : val, b : val1 }]] | val has Encoding, val1 has Encoding
|
||||
#Derived.toEncoder_{a,b} =
|
||||
\#Derived.rcd ->
|
||||
Encode.custom \#Derived.bytes, #Derived.fmt ->
|
||||
Encode.appendWith #Derived.bytes (Encode.record [
|
||||
Encode.custom
|
||||
\#Derived.bytes, #Derived.fmt ->
|
||||
Encode.appendWith
|
||||
#Derived.bytes
|
||||
(Encode.record
|
||||
[
|
||||
{ value: Encode.toEncoder #Derived.rcd.a, key: "a", },
|
||||
{ value: Encode.toEncoder #Derived.rcd.b, key: "b", },
|
||||
]) #Derived.fmt
|
||||
])
|
||||
#Derived.fmt
|
||||
"###
|
||||
)
|
||||
})
|
||||
|
@ -614,7 +215,7 @@ fn two_field_record() {
|
|||
#[ignore = "NOTE: this would never actually happen, because [] is uninhabited, and hence toEncoder can never be called with a value of []!
|
||||
Rightfully it induces broken assertions in other parts of the compiler, so we ignore it."]
|
||||
fn empty_tag_union() {
|
||||
derive_test(v!(EMPTY_TAG_UNION), |golden| {
|
||||
derive_test(ToEncoder, v!(EMPTY_TAG_UNION), |golden| {
|
||||
assert_snapshot!(
|
||||
golden,
|
||||
@r#"
|
||||
|
@ -625,7 +226,7 @@ fn empty_tag_union() {
|
|||
|
||||
#[test]
|
||||
fn tag_one_label_zero_args() {
|
||||
derive_test(v!([A]), |golden| {
|
||||
derive_test(ToEncoder, v!([A]), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for [A]
|
||||
# [A] -[[toEncoder_[A 0](0)]]-> Encoder fmt | fmt has EncoderFormatting
|
||||
|
@ -647,47 +248,59 @@ fn tag_one_label_zero_args() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "TODO #3421 unification of unspecialized variables in lambda sets currently causes this to be derived incorrectly"]
|
||||
fn tag_one_label_two_args() {
|
||||
derive_test(v!([A v!(U8) v!(STR)]), |golden| {
|
||||
derive_test(ToEncoder, v!([A v!(U8) v!(STR)]), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for [A U8 Str]
|
||||
# [A val a] -[[toEncoder_[A 2](0)]]-> Encoder fmt | a has Encoding, fmt has EncoderFormatting, val has Encoding
|
||||
# [A val a] -[[toEncoder_[A 2](0)]]-> (List U8, fmt -[[custom(4) [A val a]]]-> List U8) | a has Encoding, fmt has EncoderFormatting, val has Encoding
|
||||
# [A val val1] -[[toEncoder_[A 2](0)]]-> Encoder fmt | fmt has EncoderFormatting, val has Encoding, val1 has Encoding
|
||||
# [A val val1] -[[toEncoder_[A 2](0)]]-> (List U8, fmt -[[custom(4) [A val val1]]]-> List U8) | fmt has EncoderFormatting, val has Encoding, val1 has Encoding
|
||||
# Specialization lambda sets:
|
||||
# @<1>: [[toEncoder_[A 2](0)]]
|
||||
# @<2>: [[custom(4) [A val a]]] | a has Encoding, val has Encoding
|
||||
# @<2>: [[custom(4) [A val val1]]] | val has Encoding, val1 has Encoding
|
||||
#Derived.toEncoder_[A 2] =
|
||||
\#Derived.tag ->
|
||||
Encode.custom \#Derived.bytes, #Derived.fmt ->
|
||||
Encode.appendWith #Derived.bytes (when #Derived.tag is
|
||||
Encode.custom
|
||||
\#Derived.bytes, #Derived.fmt ->
|
||||
Encode.appendWith
|
||||
#Derived.bytes
|
||||
(when #Derived.tag is
|
||||
A #Derived.2 #Derived.3 ->
|
||||
Encode.tag "A" [
|
||||
Encode.tag
|
||||
"A"
|
||||
[
|
||||
Encode.toEncoder #Derived.2,
|
||||
Encode.toEncoder #Derived.3,
|
||||
]) #Derived.fmt
|
||||
])
|
||||
#Derived.fmt
|
||||
"###
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "TODO #3421 unification of unspecialized variables in lambda sets currently causes this to be derived incorrectly"]
|
||||
fn tag_two_labels() {
|
||||
derive_test(v!([A v!(U8) v!(STR) v!(U16), B v!(STR)]), |golden| {
|
||||
derive_test(
|
||||
ToEncoder,
|
||||
v!([A v!(U8) v!(STR) v!(U16), B v!(STR)]),
|
||||
|golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for [A U8 Str U16, B Str]
|
||||
# [A val a b, B c] -[[toEncoder_[A 3,B 1](0)]]-> Encoder fmt | a has Encoding, b has Encoding, c has Encoding, fmt has EncoderFormatting, val has Encoding
|
||||
# [A val a b, B c] -[[toEncoder_[A 3,B 1](0)]]-> (List U8, fmt -[[custom(6) [A val a b, B c]]]-> List U8) | a has Encoding, b has Encoding, c has Encoding, fmt has EncoderFormatting, val has Encoding
|
||||
# [A val val1 val1, B val1] -[[toEncoder_[A 3,B 1](0)]]-> Encoder fmt | fmt has EncoderFormatting, val has Encoding, val1 has Encoding
|
||||
# [A val val1 val1, B val1] -[[toEncoder_[A 3,B 1](0)]]-> (List U8, fmt -[[custom(6) [A val val1 val1, B val1]]]-> List U8) | fmt has EncoderFormatting, val has Encoding, val1 has Encoding
|
||||
# Specialization lambda sets:
|
||||
# @<1>: [[toEncoder_[A 3,B 1](0)]]
|
||||
# @<2>: [[custom(6) [A val a b, B c]]] | a has Encoding, b has Encoding, c has Encoding, val has Encoding
|
||||
# @<2>: [[custom(6) [A val val1 val1, B val1]]] | val has Encoding, val1 has Encoding
|
||||
#Derived.toEncoder_[A 3,B 1] =
|
||||
\#Derived.tag ->
|
||||
Encode.custom \#Derived.bytes, #Derived.fmt ->
|
||||
Encode.appendWith #Derived.bytes (when #Derived.tag is
|
||||
Encode.custom
|
||||
\#Derived.bytes, #Derived.fmt ->
|
||||
Encode.appendWith
|
||||
#Derived.bytes
|
||||
(when #Derived.tag is
|
||||
A #Derived.2 #Derived.3 #Derived.4 ->
|
||||
Encode.tag "A" [
|
||||
Encode.tag
|
||||
"A"
|
||||
[
|
||||
Encode.toEncoder #Derived.2,
|
||||
Encode.toEncoder #Derived.3,
|
||||
Encode.toEncoder #Derived.4,
|
||||
|
@ -696,38 +309,48 @@ fn tag_two_labels() {
|
|||
#Derived.fmt
|
||||
"###
|
||||
)
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "TODO #3421 unification of unspecialized variables in lambda sets currently causes this to be derived incorrectly"]
|
||||
fn recursive_tag_union() {
|
||||
derive_test(v!([Nil, Cons v!(U8) v!(*lst) ] as lst), |golden| {
|
||||
derive_test(
|
||||
ToEncoder,
|
||||
v!([Nil, Cons v!(U8) v!(^lst) ] as lst),
|
||||
|golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for [Cons U8 $rec, Nil] as $rec
|
||||
# [Cons val a, Nil] -[[toEncoder_[Cons 2,Nil 0](0)]]-> Encoder fmt | a has Encoding, fmt has EncoderFormatting, val has Encoding
|
||||
# [Cons val a, Nil] -[[toEncoder_[Cons 2,Nil 0](0)]]-> (List U8, fmt -[[custom(4) [Cons val a, Nil]]]-> List U8) | a has Encoding, fmt has EncoderFormatting, val has Encoding
|
||||
# [Cons val val1, Nil] -[[toEncoder_[Cons 2,Nil 0](0)]]-> Encoder fmt | fmt has EncoderFormatting, val has Encoding, val1 has Encoding
|
||||
# [Cons val val1, Nil] -[[toEncoder_[Cons 2,Nil 0](0)]]-> (List U8, fmt -[[custom(4) [Cons val val1, Nil]]]-> List U8) | fmt has EncoderFormatting, val has Encoding, val1 has Encoding
|
||||
# Specialization lambda sets:
|
||||
# @<1>: [[toEncoder_[Cons 2,Nil 0](0)]]
|
||||
# @<2>: [[custom(4) [Cons val a, Nil]]] | a has Encoding, val has Encoding
|
||||
# @<2>: [[custom(4) [Cons val val1, Nil]]] | val has Encoding, val1 has Encoding
|
||||
#Derived.toEncoder_[Cons 2,Nil 0] =
|
||||
\#Derived.tag ->
|
||||
Encode.custom \#Derived.bytes, #Derived.fmt ->
|
||||
Encode.appendWith #Derived.bytes (when #Derived.tag is
|
||||
Encode.custom
|
||||
\#Derived.bytes, #Derived.fmt ->
|
||||
Encode.appendWith
|
||||
#Derived.bytes
|
||||
(when #Derived.tag is
|
||||
Cons #Derived.2 #Derived.3 ->
|
||||
Encode.tag "Cons" [
|
||||
Encode.tag
|
||||
"Cons"
|
||||
[
|
||||
Encode.toEncoder #Derived.2,
|
||||
Encode.toEncoder #Derived.3,
|
||||
]
|
||||
Nil -> Encode.tag "Nil" []) #Derived.fmt
|
||||
Nil -> Encode.tag "Nil" [])
|
||||
#Derived.fmt
|
||||
"###
|
||||
)
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list() {
|
||||
derive_test(v!(Symbol::LIST_LIST v!(STR)), |golden| {
|
||||
derive_test(ToEncoder, v!(Symbol::LIST_LIST v!(STR)), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for List Str
|
||||
# List val -[[toEncoder_list(0)]]-> Encoder fmt | fmt has EncoderFormatting, val has Encoding
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#![cfg(test)]
|
||||
|
||||
mod decoding;
|
||||
mod encoding;
|
||||
|
||||
mod pretty_print;
|
||||
mod util;
|
||||
|
|
467
crates/compiler/test_derive/src/util.rs
Normal file
467
crates/compiler/test_derive/src/util.rs
Normal file
|
@ -0,0 +1,467 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use bumpalo::Bump;
|
||||
use ven_pretty::DocAllocator;
|
||||
|
||||
use crate::pretty_print::{pretty_print_def, Ctx};
|
||||
use roc_can::{
|
||||
abilities::{AbilitiesStore, SpecializationLambdaSets},
|
||||
constraint::Constraints,
|
||||
def::Def,
|
||||
expr::Declarations,
|
||||
module::{
|
||||
ExposedByModule, ExposedForModule, ExposedModuleTypes, ResolvedImplementations,
|
||||
RigidVariables,
|
||||
},
|
||||
};
|
||||
use roc_collections::VecSet;
|
||||
use roc_constrain::expr::constrain_decls;
|
||||
use roc_debug_flags::dbg_do;
|
||||
use roc_derive::DerivedModule;
|
||||
use roc_derive_key::{DeriveBuiltin, DeriveKey, Derived};
|
||||
use roc_load_internal::file::{add_imports, default_aliases, LoadedModule, Threading};
|
||||
use roc_module::symbol::{IdentIds, Interns, ModuleId, Symbol};
|
||||
use roc_region::all::LineInfo;
|
||||
use roc_reporting::report::{type_problem, RocDocAllocator};
|
||||
use roc_types::{
|
||||
pretty_print::{name_and_print_var, DebugPrint},
|
||||
subs::{ExposedTypesStorageSubs, Subs, Variable},
|
||||
};
|
||||
|
||||
const DERIVED_MODULE: ModuleId = ModuleId::DERIVED_SYNTH;
|
||||
|
||||
fn module_source_and_path(builtin: DeriveBuiltin) -> (ModuleId, &'static str, PathBuf) {
|
||||
use roc_builtins::roc::module_source;
|
||||
|
||||
let repo_root = std::env::var("ROC_WORKSPACE_DIR").expect("are you running with `cargo test`?");
|
||||
let builtins_path = PathBuf::from(repo_root)
|
||||
.join("compiler")
|
||||
.join("builtins")
|
||||
.join("roc");
|
||||
|
||||
match builtin {
|
||||
DeriveBuiltin::ToEncoder => (
|
||||
ModuleId::ENCODE,
|
||||
module_source(ModuleId::ENCODE),
|
||||
builtins_path.join("Encode.roc"),
|
||||
),
|
||||
DeriveBuiltin::Decoder => (
|
||||
ModuleId::DECODE,
|
||||
module_source(ModuleId::DECODE),
|
||||
builtins_path.join("Decode.roc"),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// DSL for creating [`Content`][crate::subs::Content].
|
||||
#[macro_export]
|
||||
macro_rules! v {
|
||||
({ $($field:ident: $make_v:expr,)* $(?$opt_field:ident : $make_opt_v:expr,)* }) => {{
|
||||
#[allow(unused)]
|
||||
use roc_types::types::RecordField;
|
||||
use roc_types::subs::{Subs, RecordFields, Content, FlatType, Variable};
|
||||
|subs: &mut Subs| {
|
||||
$(let $field = $make_v(subs);)*
|
||||
$(let $opt_field = $make_opt_v(subs);)*
|
||||
let fields = vec![
|
||||
$( (stringify!($field).into(), RecordField::Required($field)) ,)*
|
||||
$( (stringify!($opt_field).into(), RecordField::Required($opt_field)) ,)*
|
||||
];
|
||||
let fields = RecordFields::insert_into_subs(subs, fields);
|
||||
roc_derive::synth_var(subs, Content::Structure(FlatType::Record(fields, Variable::EMPTY_RECORD)))
|
||||
}
|
||||
}};
|
||||
([ $($tag:ident $($payload:expr)*),* ]$( $ext:tt )?) => {{
|
||||
#[allow(unused)]
|
||||
use roc_types::subs::{Subs, UnionTags, Content, FlatType, Variable};
|
||||
#[allow(unused)]
|
||||
use roc_module::ident::TagName;
|
||||
|subs: &mut Subs| {
|
||||
$(
|
||||
let $tag = vec![ $( $payload(subs), )* ];
|
||||
)*
|
||||
let tags = UnionTags::insert_into_subs::<_, Vec<Variable>>(subs, vec![ $( (TagName(stringify!($tag).into()), $tag) ,)* ]);
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut ext = Variable::EMPTY_TAG_UNION;
|
||||
$( ext = $crate::v!($ext)(subs); )?
|
||||
|
||||
roc_derive::synth_var(subs, Content::Structure(FlatType::TagUnion(tags, ext)))
|
||||
}
|
||||
}};
|
||||
([ $($tag:ident $($payload:expr)*),* ] as $rec_var:ident) => {{
|
||||
use roc_types::subs::{Subs, SubsIndex, Variable, Content, FlatType, UnionTags};
|
||||
use roc_module::ident::TagName;
|
||||
|subs: &mut Subs| {
|
||||
let $rec_var = subs.fresh_unnamed_flex_var();
|
||||
let rec_name_index =
|
||||
SubsIndex::push_new(&mut subs.field_names, stringify!($rec).into());
|
||||
|
||||
$(
|
||||
let $tag = vec![ $( $payload(subs), )* ];
|
||||
)*
|
||||
let tags = UnionTags::insert_into_subs::<_, Vec<Variable>>(subs, vec![ $( (TagName(stringify!($tag).into()), $tag) ,)* ]);
|
||||
let tag_union_var = roc_derive::synth_var(subs, Content::Structure(FlatType::RecursiveTagUnion($rec_var, tags, Variable::EMPTY_TAG_UNION)));
|
||||
|
||||
subs.set_content(
|
||||
$rec_var,
|
||||
Content::RecursionVar {
|
||||
structure: tag_union_var,
|
||||
opt_name: Some(rec_name_index),
|
||||
},
|
||||
);
|
||||
tag_union_var
|
||||
}
|
||||
}};
|
||||
(Symbol::$sym:ident $($arg:expr)*) => {{
|
||||
use roc_types::subs::{Subs, SubsSlice, Content, FlatType};
|
||||
use roc_module::symbol::Symbol;
|
||||
|subs: &mut Subs| {
|
||||
let $sym = vec![ $( $arg(subs) ,)* ];
|
||||
let var_slice = SubsSlice::insert_into_subs(subs, $sym);
|
||||
roc_derive::synth_var(subs, Content::Structure(FlatType::Apply(Symbol::$sym, var_slice)))
|
||||
}
|
||||
}};
|
||||
(Symbol::$alias:ident $($arg:expr)* => $real_var:expr) => {{
|
||||
use roc_types::subs::{Subs, AliasVariables, Content};
|
||||
use roc_types::types::AliasKind;
|
||||
use roc_module::symbol::Symbol;
|
||||
|subs: &mut Subs| {
|
||||
let args = vec![$( $arg(subs) )*];
|
||||
let alias_variables = AliasVariables::insert_into_subs::<Vec<_>, Vec<_>>(subs, args, vec![]);
|
||||
let real_var = $real_var(subs);
|
||||
roc_derive::synth_var(subs, Content::Alias(Symbol::$alias, alias_variables, real_var, AliasKind::Structural))
|
||||
}
|
||||
}};
|
||||
(@Symbol::$alias:ident $($arg:expr)* => $real_var:expr) => {{
|
||||
use roc_types::subs::{Subs, AliasVariables, Content};
|
||||
use roc_types::types::AliasKind;
|
||||
use roc_module::symbol::Symbol;
|
||||
|subs: &mut Subs| {
|
||||
let args = vec![$( $arg(subs) )*];
|
||||
let alias_variables = AliasVariables::insert_into_subs::<Vec<_>, Vec<_>>(subs, args, vec![]);
|
||||
let real_var = $real_var(subs);
|
||||
roc_derive::synth_var(subs, Content::Alias(Symbol::$alias, alias_variables, real_var, AliasKind::Opaque))
|
||||
}
|
||||
}};
|
||||
(*) => {{
|
||||
use roc_types::subs::{Subs, Content};
|
||||
|subs: &mut Subs| { roc_derive::synth_var(subs, Content::FlexVar(None)) }
|
||||
}};
|
||||
(^$rec_var:ident) => {{
|
||||
use roc_types::subs::{Subs};
|
||||
|_: &mut Subs| { $rec_var }
|
||||
}};
|
||||
($var:ident) => {{
|
||||
use roc_types::subs::{Subs};
|
||||
|_: &mut Subs| { Variable::$var }
|
||||
}};
|
||||
}
|
||||
|
||||
pub(crate) fn check_key<S1, S2>(builtin: DeriveBuiltin, eq: bool, synth1: S1, synth2: S2)
|
||||
where
|
||||
S1: FnOnce(&mut Subs) -> Variable,
|
||||
S2: FnOnce(&mut Subs) -> Variable,
|
||||
{
|
||||
let mut subs = Subs::new();
|
||||
let var1 = synth1(&mut subs);
|
||||
let var2 = synth2(&mut subs);
|
||||
|
||||
let key1 = Derived::builtin(builtin, &subs, var1);
|
||||
let key2 = Derived::builtin(builtin, &subs, var2);
|
||||
|
||||
if eq {
|
||||
assert_eq!(key1, key2);
|
||||
} else {
|
||||
assert_ne!(key1, key2);
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! test_hash_eq {
|
||||
($builtin:expr, $($name:ident: $synth1:expr, $synth2:expr)*) => {$(
|
||||
#[test]
|
||||
fn $name() {
|
||||
$crate::util::check_key($builtin,true, $synth1, $synth2)
|
||||
}
|
||||
)*};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! test_hash_neq {
|
||||
($builtin:expr, $($name:ident: $synth1:expr, $synth2:expr)*) => {$(
|
||||
#[test]
|
||||
fn $name() {
|
||||
$crate::util::check_key($builtin, false, $synth1, $synth2)
|
||||
}
|
||||
)*};
|
||||
}
|
||||
|
||||
pub(crate) fn check_immediate<S>(builtin: DeriveBuiltin, synth: S, immediate: Symbol)
|
||||
where
|
||||
S: FnOnce(&mut Subs) -> Variable,
|
||||
{
|
||||
let mut subs = Subs::new();
|
||||
let var = synth(&mut subs);
|
||||
|
||||
let key = Derived::builtin(builtin, &subs, var);
|
||||
|
||||
assert_eq!(key, Ok(Derived::Immediate(immediate)));
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn assemble_derived_golden(
|
||||
subs: &mut Subs,
|
||||
test_module: ModuleId,
|
||||
interns: &Interns,
|
||||
source_var: Variable,
|
||||
derived_source: &str,
|
||||
typ: Variable,
|
||||
specialization_lsets: SpecializationLambdaSets,
|
||||
) -> String {
|
||||
let mut print_var = |var: Variable, print_only_under_alias| {
|
||||
let snapshot = subs.snapshot();
|
||||
let pretty_type = name_and_print_var(
|
||||
var,
|
||||
subs,
|
||||
test_module,
|
||||
interns,
|
||||
DebugPrint {
|
||||
print_lambda_sets: true,
|
||||
print_only_under_alias,
|
||||
},
|
||||
);
|
||||
subs.rollback_to(snapshot);
|
||||
pretty_type
|
||||
};
|
||||
|
||||
let mut pretty_buf = String::new();
|
||||
|
||||
pretty_buf.push_str(&format!("# derived for {}\n", print_var(source_var, false)));
|
||||
|
||||
let pretty_type = print_var(typ, false);
|
||||
pretty_buf.push_str(&format!("# {}\n", &pretty_type));
|
||||
|
||||
let pretty_type_under_aliases = print_var(typ, true);
|
||||
pretty_buf.push_str(&format!("# {}\n", &pretty_type_under_aliases));
|
||||
|
||||
pretty_buf.push_str("# Specialization lambda sets:\n");
|
||||
let mut specialization_lsets = specialization_lsets.into_iter().collect::<Vec<_>>();
|
||||
specialization_lsets.sort_by_key(|(region, _)| *region);
|
||||
for (region, var) in specialization_lsets {
|
||||
let pretty_lset = print_var(var, false);
|
||||
pretty_buf.push_str(&format!("# @<{}>: {}\n", region, pretty_lset));
|
||||
}
|
||||
|
||||
pretty_buf.push_str(derived_source);
|
||||
|
||||
pretty_buf
|
||||
}
|
||||
|
||||
/// The environment of the module containing the builtin ability we're deriving for a type.
|
||||
struct DeriveBuiltinEnv {
|
||||
module_id: ModuleId,
|
||||
exposed_types: ExposedTypesStorageSubs,
|
||||
abilities_store: AbilitiesStore,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn check_derived_typechecks_and_golden(
|
||||
derived_def: Def,
|
||||
test_module: ModuleId,
|
||||
mut test_subs: Subs,
|
||||
interns: &Interns,
|
||||
derive_builtin_env: DeriveBuiltinEnv,
|
||||
source_var: Variable,
|
||||
derived_program: &str,
|
||||
specialization_lsets: SpecializationLambdaSets,
|
||||
check_golden: impl Fn(&str),
|
||||
) {
|
||||
// constrain the derived
|
||||
let mut constraints = Constraints::new();
|
||||
let def_var = derived_def.expr_var;
|
||||
let mut decls = Declarations::new();
|
||||
decls.push_def(derived_def);
|
||||
let constr = constrain_decls(&mut constraints, test_module, &decls);
|
||||
|
||||
// the derived implementation on stuff from the builtin module, so
|
||||
// - we need to add those dependencies as imported on the constraint
|
||||
// - we need to add the builtin ability info to a local abilities store
|
||||
let values_to_import_from_builtin_module = derive_builtin_env
|
||||
.exposed_types
|
||||
.stored_vars_by_symbol
|
||||
.keys()
|
||||
.copied()
|
||||
.collect::<VecSet<_>>();
|
||||
let pending_abilities = derive_builtin_env
|
||||
.abilities_store
|
||||
.closure_from_imported(&values_to_import_from_builtin_module);
|
||||
let mut exposed_by_module = ExposedByModule::default();
|
||||
exposed_by_module.insert(
|
||||
derive_builtin_env.module_id,
|
||||
ExposedModuleTypes {
|
||||
exposed_types_storage_subs: derive_builtin_env.exposed_types,
|
||||
resolved_implementations: ResolvedImplementations::default(),
|
||||
},
|
||||
);
|
||||
let exposed_for_module = ExposedForModule::new(
|
||||
values_to_import_from_builtin_module.iter(),
|
||||
exposed_by_module,
|
||||
);
|
||||
let mut def_types = Default::default();
|
||||
let mut rigid_vars = Default::default();
|
||||
let (import_variables, abilities_store) = add_imports(
|
||||
test_module,
|
||||
&mut test_subs,
|
||||
pending_abilities,
|
||||
&exposed_for_module,
|
||||
&mut def_types,
|
||||
&mut rigid_vars,
|
||||
);
|
||||
let constr =
|
||||
constraints.let_import_constraint(rigid_vars, def_types, constr, &import_variables);
|
||||
|
||||
// run the solver, print and fail if we have errors
|
||||
dbg_do!(
|
||||
roc_debug_flags::ROC_PRINT_UNIFICATIONS_DERIVED,
|
||||
std::env::set_var(roc_debug_flags::ROC_PRINT_UNIFICATIONS_DERIVED, "1")
|
||||
);
|
||||
let (mut solved_subs, _, problems, _) = roc_solve::module::run_solve(
|
||||
test_module,
|
||||
&constraints,
|
||||
constr,
|
||||
RigidVariables::default(),
|
||||
test_subs,
|
||||
default_aliases(),
|
||||
abilities_store,
|
||||
Default::default(),
|
||||
&exposed_for_module.exposed_by_module,
|
||||
Default::default(),
|
||||
);
|
||||
let subs = solved_subs.inner_mut();
|
||||
|
||||
if !problems.is_empty() {
|
||||
let filename = PathBuf::from("Test.roc");
|
||||
let lines = LineInfo::new(" ");
|
||||
let src_lines = vec![" "];
|
||||
let mut reports = Vec::new();
|
||||
let alloc = RocDocAllocator::new(&src_lines, test_module, interns);
|
||||
|
||||
for problem in problems.into_iter() {
|
||||
if let Some(report) = type_problem(&alloc, &lines, filename.clone(), problem.clone()) {
|
||||
reports.push(report);
|
||||
}
|
||||
}
|
||||
|
||||
let has_reports = !reports.is_empty();
|
||||
|
||||
let doc = alloc
|
||||
.stack(reports.into_iter().map(|v| v.pretty(&alloc)))
|
||||
.append(if has_reports {
|
||||
alloc.line()
|
||||
} else {
|
||||
alloc.nil()
|
||||
});
|
||||
|
||||
let mut buf = String::new();
|
||||
doc.1
|
||||
.render_raw(80, &mut roc_reporting::report::CiWrite::new(&mut buf))
|
||||
.unwrap();
|
||||
|
||||
panic!(
|
||||
"Derived does not typecheck:\n{}\nDerived def:\n{}",
|
||||
buf, derived_program
|
||||
);
|
||||
}
|
||||
|
||||
let golden = assemble_derived_golden(
|
||||
subs,
|
||||
test_module,
|
||||
interns,
|
||||
source_var,
|
||||
derived_program,
|
||||
def_var,
|
||||
specialization_lsets,
|
||||
);
|
||||
|
||||
check_golden(&golden)
|
||||
}
|
||||
|
||||
fn get_key(builtin: DeriveBuiltin, subs: &Subs, var: Variable) -> DeriveKey {
|
||||
match Derived::builtin(builtin, subs, var) {
|
||||
Ok(Derived::Key(key)) => key,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn derive_test<S>(builtin: DeriveBuiltin, synth_input: S, check_golden: impl Fn(&str))
|
||||
where
|
||||
S: FnOnce(&mut Subs) -> Variable,
|
||||
{
|
||||
let arena = Bump::new();
|
||||
let (builtin_module, source, path) = module_source_and_path(builtin);
|
||||
let target_info = roc_target::TargetInfo::default_x86_64();
|
||||
|
||||
let LoadedModule {
|
||||
mut interns,
|
||||
exposed_types_storage,
|
||||
abilities_store,
|
||||
resolved_implementations,
|
||||
..
|
||||
} = roc_load_internal::file::load_and_typecheck_str(
|
||||
&arena,
|
||||
path.file_name().unwrap().into(),
|
||||
source,
|
||||
path.parent().unwrap().to_path_buf(),
|
||||
Default::default(),
|
||||
target_info,
|
||||
roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
Threading::AllAvailable,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut subs = Subs::new();
|
||||
let ident_ids = IdentIds::default();
|
||||
let source_var = synth_input(&mut subs);
|
||||
let key = get_key(builtin, &subs, source_var);
|
||||
|
||||
let mut derived_module = unsafe { DerivedModule::from_components(subs, ident_ids) };
|
||||
|
||||
let mut exposed_by_module = ExposedByModule::default();
|
||||
exposed_by_module.insert(
|
||||
builtin_module,
|
||||
ExposedModuleTypes {
|
||||
exposed_types_storage_subs: exposed_types_storage.clone(),
|
||||
resolved_implementations,
|
||||
},
|
||||
);
|
||||
|
||||
let (_derived_symbol, derived_def, specialization_lsets) =
|
||||
derived_module.get_or_insert(&exposed_by_module, key);
|
||||
let specialization_lsets = specialization_lsets.clone();
|
||||
let derived_def = derived_def.clone();
|
||||
|
||||
let (subs, ident_ids) = derived_module.decompose();
|
||||
|
||||
interns.all_ident_ids.insert(DERIVED_MODULE, ident_ids);
|
||||
DERIVED_MODULE.register_debug_idents(interns.all_ident_ids.get(&DERIVED_MODULE).unwrap());
|
||||
|
||||
let ctx = Ctx { interns: &interns };
|
||||
let derived_program = pretty_print_def(&ctx, &derived_def);
|
||||
|
||||
check_derived_typechecks_and_golden(
|
||||
derived_def,
|
||||
DERIVED_MODULE,
|
||||
subs,
|
||||
&interns,
|
||||
DeriveBuiltinEnv {
|
||||
module_id: builtin_module,
|
||||
exposed_types: exposed_types_storage,
|
||||
abilities_store,
|
||||
},
|
||||
source_var,
|
||||
&derived_program,
|
||||
specialization_lsets,
|
||||
check_golden,
|
||||
);
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "test_gen"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -437,7 +437,7 @@ mod encode_immediate {
|
|||
macro_rules! num_immediate {
|
||||
($($num:expr, $typ:ident)*) => {$(
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn $typ() {
|
||||
assert_evals_to!(
|
||||
&format!(indoc!(
|
||||
|
@ -689,6 +689,31 @@ fn encode_derived_list_of_records() {
|
|||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[ignore = "#3696: Currently hits some weird panic in borrow checking, not sure if it's directly related to abilities."]
|
||||
fn encode_derived_list_of_lists_of_strings() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test"
|
||||
imports [Encode.{ toEncoder }, Json]
|
||||
provides [main] to "./platform"
|
||||
|
||||
main =
|
||||
lst = [["a", "b"], ["c", "d", "e"], ["f"]]
|
||||
encoded = Encode.toBytes lst Json.toUtf8
|
||||
result = Str.fromUtf8 encoded
|
||||
when result is
|
||||
Ok s -> s
|
||||
_ -> "<bad>"
|
||||
"#
|
||||
),
|
||||
RocStr::from(r#"[["a","b"],["c","d","e"],["f"]]"#),
|
||||
RocStr
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(
|
||||
any(feature = "gen-llvm", feature = "gen-wasm"),
|
||||
|
@ -780,3 +805,156 @@ fn decode_use_stdlib_json_list() {
|
|||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
mod decode_immediate {
|
||||
#[cfg(feature = "gen-llvm")]
|
||||
use crate::helpers::llvm::assert_evals_to;
|
||||
|
||||
#[cfg(feature = "gen-wasm")]
|
||||
use crate::helpers::wasm::assert_evals_to;
|
||||
|
||||
#[cfg(all(test, any(feature = "gen-llvm", feature = "gen-wasm")))]
|
||||
use indoc::indoc;
|
||||
|
||||
#[cfg(all(test, any(feature = "gen-llvm", feature = "gen-wasm")))]
|
||||
use roc_std::RocStr;
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn string() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" imports [Decode, Json] provides [main] to "./platform"
|
||||
|
||||
main =
|
||||
when Str.toUtf8 "\"foo\"" |> Decode.fromBytes Json.fromUtf8 is
|
||||
Ok s -> s
|
||||
_ -> "<bad>"
|
||||
"#
|
||||
),
|
||||
RocStr::from("foo"),
|
||||
RocStr
|
||||
)
|
||||
}
|
||||
|
||||
macro_rules! num_immediate {
|
||||
($($num:expr, $typ:ident)*) => {$(
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn $typ() {
|
||||
assert_evals_to!(
|
||||
&format!(indoc!(
|
||||
r#"
|
||||
app "test" imports [Decode, Json] provides [main] to "./platform"
|
||||
|
||||
main =
|
||||
when Num.toStr {}{} |> Str.toUtf8 |> Decode.fromBytes Json.fromUtf8 is
|
||||
Ok n -> n
|
||||
_ -> 101{}
|
||||
"#
|
||||
), $num, stringify!($typ), stringify!($typ)),
|
||||
$num,
|
||||
$typ
|
||||
)
|
||||
}
|
||||
)*}
|
||||
}
|
||||
|
||||
num_immediate! {
|
||||
17, i8
|
||||
17, i16
|
||||
17, i32
|
||||
17, i64
|
||||
17, i128
|
||||
17, u8
|
||||
17, u16
|
||||
17, u32
|
||||
17, u64
|
||||
17, u128
|
||||
17.23, f32
|
||||
17.23, f64
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn dec() {
|
||||
use roc_std::RocDec;
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" imports [Decode, Json] provides [main] to "./platform"
|
||||
|
||||
main =
|
||||
when Num.toStr 17.23dec |> Str.toUtf8 |> Decode.fromBytes Json.fromUtf8 is
|
||||
Ok n -> n
|
||||
_ -> 101dec
|
||||
"#
|
||||
),
|
||||
RocDec::from_str("17.23").unwrap(),
|
||||
RocDec
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn decode_list_of_strings() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" imports [Decode, Json] provides [main] to "./platform"
|
||||
|
||||
main =
|
||||
when Str.toUtf8 "[\"a\",\"b\",\"c\"]" |> Decode.fromBytes Json.fromUtf8 is
|
||||
Ok l -> Str.joinWith l ","
|
||||
_ -> "<bad>"
|
||||
"#
|
||||
),
|
||||
RocStr::from("a,b,c"),
|
||||
RocStr
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(
|
||||
any(feature = "gen-llvm"), // currently fails on gen-wasm
|
||||
not(feature = "gen-llvm-wasm") // hits a stack limit in wasm3
|
||||
))]
|
||||
fn encode_then_decode_list_of_strings() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" imports [Encode, Decode, Json] provides [main] to "./platform"
|
||||
|
||||
main =
|
||||
when Encode.toBytes ["a", "b", "c"] Json.fromUtf8 |> Decode.fromBytes Json.fromUtf8 is
|
||||
Ok l -> Str.joinWith l ","
|
||||
_ -> "something went wrong"
|
||||
"#
|
||||
),
|
||||
RocStr::from("a,b,c"),
|
||||
RocStr
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[ignore = "#3696: Currently hits some weird panic in borrow checking, not sure if it's directly related to abilities."]
|
||||
fn encode_then_decode_list_of_lists_of_strings() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" imports [Encode, Decode, Json] provides [main] to "./platform"
|
||||
|
||||
main =
|
||||
when Encode.toBytes [["a", "b"], ["c", "d", "e"], ["f"]] Json.fromUtf8 |> Decode.fromBytes Json.fromUtf8 is
|
||||
Ok list -> (List.map list \inner -> Str.joinWith inner ",") |> Str.joinWith l ";"
|
||||
_ -> "something went wrong"
|
||||
"#
|
||||
),
|
||||
RocStr::from("a,b;c,d,e;f"),
|
||||
RocStr
|
||||
)
|
||||
}
|
||||
|
|
|
@ -15,8 +15,6 @@ use indoc::indoc;
|
|||
#[allow(unused_imports)]
|
||||
use roc_std::{RocList, RocResult, RocStr};
|
||||
|
||||
use core::convert::Infallible;
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn roc_list_construction() {
|
||||
|
@ -287,6 +285,8 @@ fn list_map_try_ok() {
|
|||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn list_map_try_err() {
|
||||
use core::convert::Infallible;
|
||||
|
||||
assert_evals_to!(
|
||||
r#"
|
||||
List.mapTry [1, 2, 3] \_ -> Err -1
|
||||
|
@ -3354,3 +3354,18 @@ fn issue_3571_lowlevel_call_function_with_bool_lambda_set() {
|
|||
RocStr
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn issue_3530_uninitialized_capacity_in_list_literal() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
[11,22,33]
|
||||
"#
|
||||
),
|
||||
3,
|
||||
(usize, usize, usize),
|
||||
|(_, _, cap)| cap
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2741,7 +2741,7 @@ fn is_multiple_of_signed() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn is_multiple_of_unsigned() {
|
||||
// true
|
||||
assert_evals_to!("Num.isMultipleOf 5u8 1", true, bool);
|
||||
|
|
|
@ -944,7 +944,7 @@ fn when_peano() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[should_panic(expected = "Roc failed with message: ")]
|
||||
fn overflow_frees_list() {
|
||||
assert_evals_to!(
|
||||
|
@ -963,8 +963,8 @@ fn overflow_frees_list() {
|
|||
List.get myList index
|
||||
"#
|
||||
),
|
||||
3,
|
||||
i64
|
||||
(3, 0),
|
||||
(i64, i8)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1187,7 +1187,7 @@ fn return_wrapped_function_pointer() {
|
|||
"#
|
||||
),
|
||||
1,
|
||||
i64,
|
||||
usize,
|
||||
|_| 1
|
||||
);
|
||||
}
|
||||
|
@ -1209,7 +1209,7 @@ fn return_wrapped_function_pointer_b() {
|
|||
"#
|
||||
),
|
||||
1,
|
||||
i64,
|
||||
usize,
|
||||
|_| 1
|
||||
);
|
||||
}
|
||||
|
@ -1235,7 +1235,7 @@ fn return_wrapped_closure() {
|
|||
"#
|
||||
),
|
||||
1,
|
||||
i64,
|
||||
usize,
|
||||
|_| 1
|
||||
);
|
||||
}
|
||||
|
@ -1343,7 +1343,7 @@ fn linked_list_is_empty_2() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn linked_list_singleton() {
|
||||
// verifies only that valid llvm is produced
|
||||
assert_evals_to!(
|
||||
|
@ -1479,7 +1479,10 @@ fn rbtree_insert() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(any(feature = "gen-llvm"), not(feature = "gen-llvm-wasm")))]
|
||||
#[cfg(all(
|
||||
any(feature = "gen-llvm", feature = "gen-wasm"),
|
||||
not(feature = "gen-llvm-wasm")
|
||||
))]
|
||||
fn rbtree_balance_3() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -1498,8 +1501,8 @@ fn rbtree_balance_3() {
|
|||
"#
|
||||
),
|
||||
false,
|
||||
*const i64,
|
||||
|x: *const i64| x.is_null()
|
||||
usize,
|
||||
|x: usize| x == 0
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1880,7 +1883,7 @@ fn task_always_twice() {
|
|||
"#
|
||||
),
|
||||
0,
|
||||
i64,
|
||||
usize,
|
||||
|_| 0
|
||||
);
|
||||
}
|
||||
|
@ -1910,7 +1913,7 @@ fn wildcard_rigid() {
|
|||
"#
|
||||
),
|
||||
0,
|
||||
i64,
|
||||
usize,
|
||||
|_| 0
|
||||
);
|
||||
}
|
||||
|
@ -1939,7 +1942,7 @@ fn alias_of_alias_with_type_arguments() {
|
|||
"#
|
||||
),
|
||||
0,
|
||||
i64,
|
||||
usize,
|
||||
|_| 0
|
||||
);
|
||||
}
|
||||
|
@ -2202,9 +2205,10 @@ fn nullable_eval_cfold() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn nested_switch() {
|
||||
// exposed bug with passing the right symbol/layout down into switch branch generation
|
||||
// This is also the only test_gen test that exercises Reset/Reuse (as of Aug 2022)
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
|
@ -3420,7 +3424,7 @@ fn polymorphic_lambda_set_multiple_specializations() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn list_map2_conslist() {
|
||||
// this had an RC problem, https://github.com/rtfeldman/roc/issues/2968
|
||||
assert_evals_to!(
|
||||
|
@ -3614,7 +3618,7 @@ fn lambda_capture_niches_have_captured_function_in_closure() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn recursive_call_capturing_function() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -3651,7 +3655,7 @@ fn shared_pattern_variable_in_when_branches() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn symbol_not_bound_in_all_patterns_runs_when_no_bound_symbol_used() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -3662,8 +3666,8 @@ fn symbol_not_bound_in_all_patterns_runs_when_no_bound_symbol_used() {
|
|||
{a: f (A 15u8), b: f (B 15u8)}
|
||||
"#
|
||||
),
|
||||
31u8,
|
||||
u8,
|
||||
(31u8, 31u8),
|
||||
(u8, u8),
|
||||
|x| x,
|
||||
true // allow errors
|
||||
);
|
||||
|
|
|
@ -400,6 +400,28 @@ fn union_linked_list_dec() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-wasm"))]
|
||||
fn union_linked_list_nil_dec() {
|
||||
let no_refcounts: &[crate::helpers::RefCount] = &[];
|
||||
assert_refcounts!(
|
||||
indoc!(
|
||||
r#"
|
||||
LinkedList a : [Nil, Cons a (LinkedList a)]
|
||||
|
||||
linked : LinkedList Str
|
||||
linked = Nil
|
||||
|
||||
when linked is
|
||||
Cons x _ -> x
|
||||
Nil -> ""
|
||||
"#
|
||||
),
|
||||
RocStr,
|
||||
no_refcounts
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-wasm"))]
|
||||
fn union_linked_list_long_dec() {
|
||||
|
|
|
@ -477,7 +477,7 @@ fn nested_pattern_match() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn if_guard_vanilla() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -1363,7 +1363,7 @@ fn issue_2365_monomorphize_tag_with_non_empty_ext_var() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn issue_2365_monomorphize_tag_with_non_empty_ext_var_wrapped() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -1392,7 +1392,7 @@ fn issue_2365_monomorphize_tag_with_non_empty_ext_var_wrapped() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn issue_2365_monomorphize_tag_with_non_empty_ext_var_wrapped_nested() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
|
|
@ -31,6 +31,10 @@
|
|||
font-weight: bold;
|
||||
padding: 4px 12px;
|
||||
}
|
||||
select {
|
||||
font-size: 18px;
|
||||
padding: 4px 12px;
|
||||
}
|
||||
small {
|
||||
font-style: italic;
|
||||
}
|
||||
|
@ -69,7 +73,7 @@
|
|||
In <code>gen_wasm/src/lib.rs</code>, set
|
||||
<code>DEBUG_LOG_SETTINGS.keep_test_binary = true</code>
|
||||
</li>
|
||||
<li>Run <code>cargo test-gen-wasm -- my_test --nocapture</code></li>
|
||||
<li>Run <code>cargo test-gen-wasm my_test_function_name</code></li>
|
||||
<li>
|
||||
Look for the path written to the console for
|
||||
<code>final.wasm</code> and select it in the file picker below
|
||||
|
@ -79,45 +83,62 @@
|
|||
<small> Control+Shift+I or Command+Option+I or F12 </small>
|
||||
</li>
|
||||
<li>
|
||||
Click one of the buttons below, depending on what kind of test it is.
|
||||
<br />
|
||||
<small>
|
||||
Only one of them will work. The other will probably crash or
|
||||
something.
|
||||
</small>
|
||||
If your test is from <code>gen_refcount.rs</code>
|
||||
</li>
|
||||
<li>
|
||||
The debugger should pause just before entering the first Wasm call.
|
||||
Step into a couple of Wasm calls until you reach your test code in
|
||||
<code>$#UserApp_main_1</code>
|
||||
<code>$main</code>
|
||||
</li>
|
||||
<li>
|
||||
Chrome DevTools now has a Memory Inspector panel! In the debugger,
|
||||
Chrome DevTools has a handy Memory Inspector panel. In the debugger,
|
||||
find <code>Module -> memories -> $memory</code>. Right click and
|
||||
select "Reveal in Memory Inspector"
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="controls">
|
||||
<div class="row">
|
||||
<label>
|
||||
<input type="checkbox" id="refcount-test">
|
||||
Check box if test is from <code>gen_refcount.rs</code>
|
||||
</label>
|
||||
</div>
|
||||
<div class="row row-file">
|
||||
<label for="wasm-file">Select final.wasm</label>
|
||||
<input id="wasm-file" type="file" />
|
||||
</div>
|
||||
<div id="error" class="row"></div>
|
||||
<div class="row">
|
||||
<button id="button-expression">Run as Roc expression test</button>
|
||||
<button id="button-refcount">Run as reference counting test</button>
|
||||
<button id="button-run">RUN</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<script>
|
||||
const file_input = document.getElementById("wasm-file");
|
||||
const button_expression = document.getElementById("button-expression");
|
||||
const button_refcount = document.getElementById("button-refcount");
|
||||
const refcount_checkbox = document.getElementById("refcount-test");
|
||||
const button = document.getElementById("button-run");
|
||||
const error_box = document.getElementById("error");
|
||||
|
||||
button_expression.onclick = runExpressionTest;
|
||||
button_refcount.onclick = runRefcountTest;
|
||||
if (localStorage.getItem("refcount-test")) {
|
||||
refcount_checkbox.checked = true;
|
||||
}
|
||||
refcount_checkbox.onchange = function (ev) {
|
||||
if (ev.target.checked) {
|
||||
localStorage.setItem("refcount-test", "true");
|
||||
} else {
|
||||
localStorage.removeItem("refcount-test");
|
||||
}
|
||||
}
|
||||
|
||||
button.onclick = function () {
|
||||
if (refcount_checkbox.checked) {
|
||||
runRefcountTest();
|
||||
} else {
|
||||
runExpressionTest();
|
||||
}
|
||||
}
|
||||
|
||||
file_input.onchange = function () {
|
||||
error_box.innerHTML = "";
|
||||
};
|
||||
|
@ -126,7 +147,7 @@
|
|||
const file = getFile();
|
||||
const instance = await compileFileToInstance(file);
|
||||
|
||||
debugger; // Next call is Wasm! Step into test_wrapper, then $#UserApp_main_1
|
||||
debugger; // Next call is Wasm! Step into test_wrapper, then $main
|
||||
instance.exports.test_wrapper();
|
||||
}
|
||||
|
||||
|
@ -137,7 +158,7 @@
|
|||
const refcount_vector_addr =
|
||||
instance.exports.init_refcount_test(MAX_ALLOCATIONS);
|
||||
|
||||
debugger; // Next call is Wasm! Step into test_wrapper, then $#UserApp_main_1
|
||||
debugger; // Next call is Wasm! Step into test_wrapper, then $main
|
||||
instance.exports.test_wrapper();
|
||||
|
||||
const words = new Uint32Array(instance.exports.memory.buffer);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use libloading::Library;
|
||||
use roc_build::link::{link, LinkType};
|
||||
use roc_builtins::bitcode;
|
||||
use roc_load::Threading;
|
||||
use roc_load::{EntryPoint, ExecutionMode, LoadConfig, Threading};
|
||||
use roc_region::all::LineInfo;
|
||||
use tempfile::tempdir;
|
||||
|
||||
|
@ -50,15 +50,19 @@ pub fn helper(
|
|||
module_src = &temp;
|
||||
}
|
||||
|
||||
let load_config = LoadConfig {
|
||||
target_info: roc_target::TargetInfo::default_x86_64(),
|
||||
render: roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
threading: Threading::Single,
|
||||
exec_mode: ExecutionMode::Executable,
|
||||
};
|
||||
let loaded = roc_load::load_and_monomorphize_from_str(
|
||||
arena,
|
||||
filename,
|
||||
module_src,
|
||||
src_dir,
|
||||
Default::default(),
|
||||
roc_target::TargetInfo::default_x86_64(),
|
||||
roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
Threading::Single,
|
||||
load_config,
|
||||
);
|
||||
|
||||
let mut loaded = loaded.expect("failed to load module");
|
||||
|
@ -96,8 +100,16 @@ pub fn helper(
|
|||
}
|
||||
|
||||
debug_assert_eq!(exposed_to_host.values.len(), 1);
|
||||
let main_fn_symbol = loaded.entry_point.symbol;
|
||||
let main_fn_layout = loaded.entry_point.layout;
|
||||
let entry_point = match loaded.entry_point {
|
||||
EntryPoint::Executable { symbol, layout, .. } => {
|
||||
roc_mono::ir::EntryPoint { symbol, layout }
|
||||
}
|
||||
EntryPoint::Test => {
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
let main_fn_symbol = entry_point.symbol;
|
||||
let main_fn_layout = entry_point.layout;
|
||||
|
||||
let mut layout_ids = roc_mono::layout::LayoutIds::default();
|
||||
let main_fn_name = layout_ids
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use roc_error_macros::internal_error;
|
||||
use roc_gen_wasm::{round_up_to_alignment, wasm32_sized::Wasm32Sized};
|
||||
use roc_mono::layout::Builtin;
|
||||
use roc_std::{RocDec, RocList, RocOrder, RocResult, RocStr, I128, U128};
|
||||
use std::convert::TryInto;
|
||||
|
||||
|
@ -57,8 +58,9 @@ impl FromWasm32Memory for RocStr {
|
|||
|
||||
let str_words: &[u32; 3] = unsafe { std::mem::transmute(&str_bytes) };
|
||||
|
||||
let big_elem_ptr = str_words[0] as usize;
|
||||
let big_length = str_words[1] as usize;
|
||||
let big_elem_ptr = str_words[Builtin::WRAPPER_PTR as usize] as usize;
|
||||
let big_length = str_words[Builtin::WRAPPER_LEN as usize] as usize;
|
||||
let big_capacity = str_words[Builtin::WRAPPER_CAPACITY as usize] as usize;
|
||||
|
||||
let last_byte = str_bytes[11];
|
||||
let is_small_str = last_byte >= 0x80;
|
||||
|
@ -70,16 +72,20 @@ impl FromWasm32Memory for RocStr {
|
|||
&memory_bytes[big_elem_ptr..][..big_length]
|
||||
};
|
||||
|
||||
unsafe { RocStr::from_slice_unchecked(slice) }
|
||||
let mut roc_str = unsafe { RocStr::from_slice_unchecked(slice) };
|
||||
if !is_small_str {
|
||||
roc_str.reserve(big_capacity - big_length)
|
||||
}
|
||||
roc_str
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromWasm32Memory + Clone> FromWasm32Memory for RocList<T> {
|
||||
fn decode(memory: &[u8], offset: u32) -> Self {
|
||||
let bytes = <u64 as FromWasm32Memory>::decode(memory, offset);
|
||||
|
||||
let length = (bytes >> 32) as u32;
|
||||
let elements = bytes as u32;
|
||||
let elements = <u32 as FromWasm32Memory>::decode(memory, offset + 4 * Builtin::WRAPPER_PTR);
|
||||
let length = <u32 as FromWasm32Memory>::decode(memory, offset + 4 * Builtin::WRAPPER_LEN);
|
||||
let capacity =
|
||||
<u32 as FromWasm32Memory>::decode(memory, offset + 4 * Builtin::WRAPPER_CAPACITY);
|
||||
|
||||
let mut items = Vec::with_capacity(length as usize);
|
||||
|
||||
|
@ -91,7 +97,9 @@ impl<T: FromWasm32Memory + Clone> FromWasm32Memory for RocList<T> {
|
|||
items.push(item);
|
||||
}
|
||||
|
||||
RocList::from_slice(&items)
|
||||
let mut list = RocList::with_capacity(capacity as usize);
|
||||
list.extend_from_slice(&items);
|
||||
list
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ use roc_build::program::FunctionIterator;
|
|||
use roc_collections::all::MutSet;
|
||||
use roc_gen_llvm::llvm::build::LlvmBackendMode;
|
||||
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
|
||||
use roc_load::Threading;
|
||||
use roc_load::{EntryPoint, ExecutionMode, LoadConfig, Threading};
|
||||
use roc_mono::ir::OptLevel;
|
||||
use roc_region::all::LineInfo;
|
||||
use roc_reporting::report::RenderTarget;
|
||||
|
@ -66,15 +66,19 @@ fn create_llvm_module<'a>(
|
|||
module_src = &temp;
|
||||
}
|
||||
|
||||
let load_config = LoadConfig {
|
||||
target_info,
|
||||
render: RenderTarget::ColorTerminal,
|
||||
threading: Threading::Single,
|
||||
exec_mode: ExecutionMode::Executable,
|
||||
};
|
||||
let loaded = roc_load::load_and_monomorphize_from_str(
|
||||
arena,
|
||||
filename,
|
||||
module_src,
|
||||
src_dir,
|
||||
Default::default(),
|
||||
target_info,
|
||||
RenderTarget::ColorTerminal,
|
||||
Threading::Single,
|
||||
load_config,
|
||||
);
|
||||
|
||||
let mut loaded = match loaded {
|
||||
|
@ -226,6 +230,14 @@ fn create_llvm_module<'a>(
|
|||
// platform to provide them.
|
||||
add_default_roc_externs(&env);
|
||||
|
||||
let entry_point = match entry_point {
|
||||
EntryPoint::Executable { symbol, layout, .. } => {
|
||||
roc_mono::ir::EntryPoint { symbol, layout }
|
||||
}
|
||||
EntryPoint::Test => {
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
let (main_fn_name, main_fn) = match config.mode {
|
||||
LlvmBackendMode::Binary => unreachable!(),
|
||||
LlvmBackendMode::CliTest => unreachable!(),
|
||||
|
|
|
@ -4,7 +4,7 @@ use roc_collections::all::MutSet;
|
|||
use roc_gen_wasm::wasm32_result::Wasm32Result;
|
||||
use roc_gen_wasm::wasm_module::{Export, ExportType};
|
||||
use roc_gen_wasm::DEBUG_SETTINGS;
|
||||
use roc_load::Threading;
|
||||
use roc_load::{ExecutionMode, LoadConfig, Threading};
|
||||
use std::marker::PhantomData;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
|
@ -35,9 +35,9 @@ fn promote_expr_to_module(src: &str) -> String {
|
|||
}
|
||||
|
||||
fn write_final_wasm() -> bool {
|
||||
use roc_debug_flags::{dbg_do, ROC_WRITE_FINAL_WASM};
|
||||
use roc_debug_flags::dbg_do;
|
||||
|
||||
dbg_do!(ROC_WRITE_FINAL_WASM, {
|
||||
dbg_do!(roc_debug_flags::ROC_WRITE_FINAL_WASM, {
|
||||
return true;
|
||||
});
|
||||
|
||||
|
@ -84,15 +84,19 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32Result>(
|
|||
module_src = &temp;
|
||||
}
|
||||
|
||||
let load_config = LoadConfig {
|
||||
target_info: roc_target::TargetInfo::default_wasm32(),
|
||||
render: roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
threading: Threading::Single,
|
||||
exec_mode: ExecutionMode::Executable,
|
||||
};
|
||||
let loaded = roc_load::load_and_monomorphize_from_str(
|
||||
arena,
|
||||
filename,
|
||||
module_src,
|
||||
src_dir,
|
||||
Default::default(),
|
||||
roc_target::TargetInfo::default_wasm32(),
|
||||
roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
Threading::Single,
|
||||
load_config,
|
||||
);
|
||||
|
||||
let loaded = loaded.expect("failed to load module");
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "test_mono"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
26
crates/compiler/test_mono/generated/issue_3669.txt
Normal file
26
crates/compiler/test_mono/generated/issue_3669.txt
Normal file
|
@ -0,0 +1,26 @@
|
|||
procedure Bool.7 (#Attr.2, #Attr.3):
|
||||
let Bool.9 : Int1 = lowlevel Eq #Attr.2 #Attr.3;
|
||||
ret Bool.9;
|
||||
|
||||
procedure Test.2 (Test.19):
|
||||
joinpoint Test.13 Test.7:
|
||||
let Test.16 : U8 = 1i64;
|
||||
let Test.17 : U8 = GetTagId Test.7;
|
||||
let Test.18 : Int1 = lowlevel Eq Test.16 Test.17;
|
||||
if Test.18 then
|
||||
let Test.14 : {} = Struct {};
|
||||
ret Test.14;
|
||||
else
|
||||
let Test.5 : [<rnu><null>, C *self] = UnionAtIndex (Id 0) (Index 0) Test.7;
|
||||
jump Test.13 Test.5;
|
||||
in
|
||||
jump Test.13 Test.19;
|
||||
|
||||
procedure Test.0 ():
|
||||
let Test.12 : [<rnu><null>, C *self] = TagId(1) ;
|
||||
let Test.10 : {} = CallByName Test.2 Test.12;
|
||||
dec Test.12;
|
||||
let Test.11 : {} = Struct {};
|
||||
let Test.8 : Int1 = CallByName Bool.7 Test.10 Test.11;
|
||||
let Test.9 : Str = "";
|
||||
ret Test.9;
|
|
@ -13,6 +13,8 @@ extern crate indoc;
|
|||
#[allow(dead_code)]
|
||||
const EXPANDED_STACK_SIZE: usize = 8 * 1024 * 1024;
|
||||
|
||||
use roc_load::ExecutionMode;
|
||||
use roc_load::LoadConfig;
|
||||
use test_mono_macros::*;
|
||||
|
||||
use roc_collections::all::MutMap;
|
||||
|
@ -91,15 +93,19 @@ fn compiles_to_ir(test_name: &str, src: &str) {
|
|||
module_src = &temp;
|
||||
}
|
||||
|
||||
let load_config = LoadConfig {
|
||||
target_info: TARGET_INFO,
|
||||
threading: Threading::Single,
|
||||
render: roc_reporting::report::RenderTarget::Generic,
|
||||
exec_mode: ExecutionMode::Executable,
|
||||
};
|
||||
let loaded = roc_load::load_and_monomorphize_from_str(
|
||||
arena,
|
||||
filename,
|
||||
module_src,
|
||||
src_dir,
|
||||
Default::default(),
|
||||
TARGET_INFO,
|
||||
roc_reporting::report::RenderTarget::Generic,
|
||||
Threading::Single,
|
||||
load_config,
|
||||
);
|
||||
|
||||
let mut loaded = match loaded {
|
||||
|
@ -1895,3 +1901,24 @@ fn issue_3560_nested_tag_constructor_is_newtype() {
|
|||
"#
|
||||
)
|
||||
}
|
||||
|
||||
#[mono_test]
|
||||
fn issue_3669() {
|
||||
indoc!(
|
||||
r#"
|
||||
Peano a := [
|
||||
Zero,
|
||||
Successor (Peano a)
|
||||
]
|
||||
|
||||
unwrap : Peano a -> {}
|
||||
unwrap = \@Peano p ->
|
||||
when p is
|
||||
Zero -> {}
|
||||
Successor inner -> unwrap inner
|
||||
|
||||
when unwrap (@Peano Zero) == {} is
|
||||
_ -> ""
|
||||
"#
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "test_mono_macros"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_types"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -2366,10 +2366,6 @@ impl AliasVariables {
|
|||
|
||||
let all_variables_len = (subs.variables.len() as u32 - variables_start) as u16;
|
||||
|
||||
if type_variables_len == 3 {
|
||||
panic!();
|
||||
}
|
||||
|
||||
Self {
|
||||
variables_start,
|
||||
type_variables_len,
|
||||
|
|
|
@ -3,7 +3,7 @@ authors = ["The Roc Contributors"]
|
|||
edition = "2021"
|
||||
license = "UPL-1.0"
|
||||
name = "roc_unify"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "1.3.2"
|
||||
|
|
|
@ -680,25 +680,49 @@ fn unify_two_aliases<M: MetaCollector>(
|
|||
env: &mut Env,
|
||||
pool: &mut Pool,
|
||||
ctx: &Context,
|
||||
// _symbol has an underscore because it's unused in --release builds
|
||||
_symbol: Symbol,
|
||||
kind: AliasKind,
|
||||
symbol: Symbol,
|
||||
args: AliasVariables,
|
||||
real_var: Variable,
|
||||
other_args: AliasVariables,
|
||||
other_real_var: Variable,
|
||||
other_content: &Content,
|
||||
) -> Outcome<M> {
|
||||
if args.len() == other_args.len() {
|
||||
let mut outcome = Outcome::default();
|
||||
let it = args
|
||||
.all_variables()
|
||||
.into_iter()
|
||||
.zip(other_args.all_variables().into_iter());
|
||||
|
||||
for (l, r) in it {
|
||||
let args_it = args
|
||||
.type_variables()
|
||||
.into_iter()
|
||||
.zip(other_args.type_variables().into_iter());
|
||||
|
||||
let lambda_set_it = args
|
||||
.lambda_set_variables()
|
||||
.into_iter()
|
||||
.zip(other_args.lambda_set_variables().into_iter());
|
||||
|
||||
let mut merged_args = Vec::with_capacity(args.type_variables().len());
|
||||
let mut merged_lambda_set_args = Vec::with_capacity(args.lambda_set_variables().len());
|
||||
debug_assert_eq!(
|
||||
merged_args.capacity() + merged_lambda_set_args.capacity(),
|
||||
args.all_variables_len as _
|
||||
);
|
||||
|
||||
for (l, r) in args_it {
|
||||
let l_var = env.subs[l];
|
||||
let r_var = env.subs[r];
|
||||
outcome.union(unify_pool(env, pool, l_var, r_var, ctx.mode));
|
||||
|
||||
let merged_var = choose_merged_var(env.subs, l_var, r_var);
|
||||
merged_args.push(merged_var);
|
||||
}
|
||||
|
||||
for (l, r) in lambda_set_it {
|
||||
let l_var = env.subs[l];
|
||||
let r_var = env.subs[r];
|
||||
outcome.union(unify_pool(env, pool, l_var, r_var, ctx.mode));
|
||||
|
||||
let merged_var = choose_merged_var(env.subs, l_var, r_var);
|
||||
merged_lambda_set_args.push(merged_var);
|
||||
}
|
||||
|
||||
if outcome.mismatches.is_empty() {
|
||||
|
@ -730,12 +754,21 @@ fn unify_two_aliases<M: MetaCollector>(
|
|||
let _ = real_var_outcome.mismatches.drain(..);
|
||||
outcome.union(real_var_outcome);
|
||||
|
||||
outcome.union(merge(env, ctx, *other_content));
|
||||
let merged_real_var = choose_merged_var(env.subs, real_var, other_real_var);
|
||||
|
||||
// POSSIBLE OPT: choose_merged_var chooses the left when the choice is arbitrary. If
|
||||
// the merged vars are all left, avoid re-insertion. Is checking for argument slice
|
||||
// equality faster than re-inserting?
|
||||
let merged_variables =
|
||||
AliasVariables::insert_into_subs(env.subs, merged_args, merged_lambda_set_args);
|
||||
let merged_content = Content::Alias(symbol, merged_variables, merged_real_var, kind);
|
||||
|
||||
outcome.union(merge(env, ctx, merged_content));
|
||||
}
|
||||
|
||||
outcome
|
||||
} else {
|
||||
mismatch!("{:?}", _symbol)
|
||||
mismatch!("{:?}", symbol)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -769,12 +802,12 @@ fn unify_alias<M: MetaCollector>(
|
|||
env,
|
||||
pool,
|
||||
ctx,
|
||||
AliasKind::Structural,
|
||||
symbol,
|
||||
args,
|
||||
real_var,
|
||||
*other_args,
|
||||
*other_real_var,
|
||||
other_content,
|
||||
)
|
||||
} else {
|
||||
unify_pool(env, pool, real_var, *other_real_var, ctx.mode)
|
||||
|
@ -838,12 +871,12 @@ fn unify_opaque<M: MetaCollector>(
|
|||
env,
|
||||
pool,
|
||||
ctx,
|
||||
AliasKind::Opaque,
|
||||
symbol,
|
||||
args,
|
||||
real_var,
|
||||
*other_args,
|
||||
*other_real_var,
|
||||
other_content,
|
||||
)
|
||||
} else {
|
||||
mismatch!("{:?}", symbol)
|
||||
|
@ -2208,6 +2241,46 @@ fn maybe_mark_union_recursive(env: &mut Env, union_var: Variable) {
|
|||
}
|
||||
}
|
||||
|
||||
fn choose_merged_var(subs: &Subs, var1: Variable, var2: Variable) -> Variable {
|
||||
// If one of the variables is a recursion var, keep that one, so that we avoid inlining
|
||||
// a recursive tag union type content where we should have a recursion var instead.
|
||||
//
|
||||
// When might this happen? For example, in the code
|
||||
//
|
||||
// Indirect : [Indirect ConsList]
|
||||
//
|
||||
// ConsList : [Nil, Cons Indirect]
|
||||
//
|
||||
// l : ConsList
|
||||
// l = Cons (Indirect (Cons (Indirect Nil)))
|
||||
// # ^^^^^^^^^^^^^^^~~~~~~~~~~~~~~~~~~~~~^ region-a
|
||||
// # ~~~~~~~~~~~~~~~~~~~~~ region-b
|
||||
// l
|
||||
//
|
||||
// Suppose `ConsList` has the expanded type `[Nil, Cons [Indirect <rec>]] as <rec>`.
|
||||
// After unifying the tag application annotated "region-b" with the recursion variable `<rec>`,
|
||||
// we might have that e.g. `actual` is `<rec>` and `expected` is `[Cons (Indirect ...)]`.
|
||||
//
|
||||
// Now, we need to be careful to set the type we choose to represent the merged type
|
||||
// here to be `<rec>`, not the tag union content of `expected`! Otherwise, we will
|
||||
// have lost a recursion variable in the recursive tag union.
|
||||
//
|
||||
// This would not be incorrect from a type perspective, but causes problems later on for e.g.
|
||||
// layout generation, which expects recursion variables to be placed correctly. Attempting to detect
|
||||
// this during layout generation does not work so well because it may be that there *are* recursive
|
||||
// tag unions that should be inlined, and not pass through recursion variables. So instead, resolve
|
||||
// these cases here.
|
||||
//
|
||||
// See tests labeled "issue_2810" for more examples.
|
||||
match (
|
||||
(var1, subs.get_content_unchecked(var1)),
|
||||
(var2, subs.get_content_unchecked(var2)),
|
||||
) {
|
||||
((var, Content::RecursionVar { .. }), _) | (_, (var, Content::RecursionVar { .. })) => var,
|
||||
_ => var1,
|
||||
}
|
||||
}
|
||||
|
||||
fn unify_shared_tags_new<M: MetaCollector>(
|
||||
env: &mut Env,
|
||||
pool: &mut Pool,
|
||||
|
@ -2268,44 +2341,7 @@ fn unify_shared_tags_new<M: MetaCollector>(
|
|||
outcome.union(unify_pool(env, pool, actual, expected, ctx.mode));
|
||||
|
||||
if outcome.mismatches.is_empty() {
|
||||
// If one of the variables is a recursion var, keep that one, so that we avoid inlining
|
||||
// a recursive tag union type content where we should have a recursion var instead.
|
||||
//
|
||||
// When might this happen? For example, in the code
|
||||
//
|
||||
// Indirect : [Indirect ConsList]
|
||||
//
|
||||
// ConsList : [Nil, Cons Indirect]
|
||||
//
|
||||
// l : ConsList
|
||||
// l = Cons (Indirect (Cons (Indirect Nil)))
|
||||
// # ^^^^^^^^^^^^^^^~~~~~~~~~~~~~~~~~~~~~^ region-a
|
||||
// # ~~~~~~~~~~~~~~~~~~~~~ region-b
|
||||
// l
|
||||
//
|
||||
// Suppose `ConsList` has the expanded type `[Nil, Cons [Indirect <rec>]] as <rec>`.
|
||||
// After unifying the tag application annotated "region-b" with the recursion variable `<rec>`,
|
||||
// we might have that e.g. `actual` is `<rec>` and `expected` is `[Cons (Indirect ...)]`.
|
||||
//
|
||||
// Now, we need to be careful to set the type we choose to represent the merged type
|
||||
// here to be `<rec>`, not the tag union content of `expected`! Otherwise, we will
|
||||
// have lost a recursion variable in the recursive tag union.
|
||||
//
|
||||
// This would not be incorrect from a type perspective, but causes problems later on for e.g.
|
||||
// layout generation, which expects recursion variables to be placed correctly. Attempting to detect
|
||||
// this during layout generation does not work so well because it may be that there *are* recursive
|
||||
// tag unions that should be inlined, and not pass through recursion variables. So instead, resolve
|
||||
// these cases here.
|
||||
//
|
||||
// See tests labeled "issue_2810" for more examples.
|
||||
let merged_var = match (
|
||||
(actual, env.subs.get_content_unchecked(actual)),
|
||||
(expected, env.subs.get_content_unchecked(expected)),
|
||||
) {
|
||||
((var, Content::RecursionVar { .. }), _)
|
||||
| (_, (var, Content::RecursionVar { .. })) => var,
|
||||
_ => actual,
|
||||
};
|
||||
let merged_var = choose_merged_var(env.subs, actual, expected);
|
||||
|
||||
matching_vars.push(merged_var);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_docs"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
license = "UPL-1.0"
|
||||
authors = ["The Roc Contributors"]
|
||||
edition = "2021"
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue