mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-28 22:34:45 +00:00
Merge remote-tracking branch 'origin/trunk' into builtins-in-roc-delayed-alias
This commit is contained in:
commit
4e1197165b
181 changed files with 9495 additions and 2273 deletions
|
@ -2,3 +2,9 @@
|
|||
test-gen-llvm = "test -p test_gen"
|
||||
test-gen-dev = "test -p roc_gen_dev -p test_gen --no-default-features --features gen-dev"
|
||||
test-gen-wasm = "test -p roc_gen_wasm -p test_gen --no-default-features --features gen-wasm"
|
||||
|
||||
[target.wasm32-unknown-unknown]
|
||||
# Rust compiler flags for minimum-sized .wasm binary in the web REPL
|
||||
# opt-level=s Optimizations should focus more on size than speed
|
||||
# lto=fat Spend extra effort on link-time optimization across crates
|
||||
rustflags = ["-Copt-level=s", "-Clto=fat"]
|
||||
|
|
58
Cargo.lock
generated
58
Cargo.lock
generated
|
@ -564,6 +564,16 @@ dependencies = [
|
|||
"serde_yaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "console_error_panic_hook"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const_format"
|
||||
version = "0.2.22"
|
||||
|
@ -1770,13 +1780,13 @@ dependencies = [
|
|||
name = "inkwell"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"inkwell 0.1.0 (git+https://github.com/rtfeldman/inkwell?tag=llvm13-0.release1)",
|
||||
"inkwell 0.1.0 (git+https://github.com/rtfeldman/inkwell?branch=master)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inkwell"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/rtfeldman/inkwell?tag=llvm13-0.release1#e15d665227b2acad4ca949820d80048e09f3f4e5"
|
||||
source = "git+https://github.com/rtfeldman/inkwell?branch=master#b3fb82c653ffe754e078ac778d7fd9a619a26c0c"
|
||||
dependencies = [
|
||||
"either",
|
||||
"inkwell_internals",
|
||||
|
@ -1789,7 +1799,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "inkwell_internals"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/rtfeldman/inkwell?tag=llvm13-0.release1#e15d665227b2acad4ca949820d80048e09f3f4e5"
|
||||
source = "git+https://github.com/rtfeldman/inkwell?branch=master#b3fb82c653ffe754e078ac778d7fd9a619a26c0c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -2716,6 +2726,33 @@ version = "0.1.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
|
||||
|
||||
[[package]]
|
||||
name = "peg"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af728fe826811af3b38c37e93de6d104485953ea373d656eebae53d6987fcd2c"
|
||||
dependencies = [
|
||||
"peg-macros",
|
||||
"peg-runtime",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peg-macros"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4536be147b770b824895cbad934fccce8e49f14b4c4946eaa46a6e4a12fcdc16"
|
||||
dependencies = [
|
||||
"peg-runtime",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peg-runtime"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9b0efd3ba03c3a409d44d60425f279ec442bcf0b9e63ff4e410da31c8b0f69f"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.1.0"
|
||||
|
@ -3335,6 +3372,7 @@ name = "roc_builtins"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dunce",
|
||||
"lazy_static",
|
||||
"roc_collections",
|
||||
"roc_module",
|
||||
"roc_region",
|
||||
|
@ -3444,6 +3482,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"bumpalo",
|
||||
"indoc",
|
||||
"peg",
|
||||
"pretty_assertions",
|
||||
"pulldown-cmark",
|
||||
"roc_ast",
|
||||
|
@ -3451,6 +3490,7 @@ dependencies = [
|
|||
"roc_can",
|
||||
"roc_code_markup",
|
||||
"roc_collections",
|
||||
"roc_highlight",
|
||||
"roc_load",
|
||||
"roc_module",
|
||||
"roc_parse",
|
||||
|
@ -3596,6 +3636,14 @@ dependencies = [
|
|||
"roc_target",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_highlight"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"peg",
|
||||
"roc_code_markup",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_ident"
|
||||
version = "0.1.0"
|
||||
|
@ -3714,6 +3762,7 @@ dependencies = [
|
|||
"roc_module",
|
||||
"roc_parse",
|
||||
"roc_region",
|
||||
"roc_types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3739,6 +3788,7 @@ dependencies = [
|
|||
"roc_mono",
|
||||
"roc_parse",
|
||||
"roc_repl_eval",
|
||||
"roc_reporting",
|
||||
"roc_std",
|
||||
"roc_target",
|
||||
"roc_types",
|
||||
|
@ -3772,6 +3822,7 @@ name = "roc_repl_wasm"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"console_error_panic_hook",
|
||||
"futures",
|
||||
"js-sys",
|
||||
"roc_builtins",
|
||||
|
@ -3780,6 +3831,7 @@ dependencies = [
|
|||
"roc_load",
|
||||
"roc_parse",
|
||||
"roc_repl_eval",
|
||||
"roc_reporting",
|
||||
"roc_target",
|
||||
"roc_types",
|
||||
"wasm-bindgen",
|
||||
|
|
|
@ -33,6 +33,7 @@ members = [
|
|||
"ast",
|
||||
"cli",
|
||||
"code_markup",
|
||||
"highlight",
|
||||
"error_macros",
|
||||
"reporting",
|
||||
"repl_cli",
|
||||
|
|
|
@ -50,7 +50,7 @@ install-zig-llvm-valgrind-clippy-rustfmt:
|
|||
|
||||
copy-dirs:
|
||||
FROM +install-zig-llvm-valgrind-clippy-rustfmt
|
||||
COPY --dir cli cli_utils compiler docs editor ast code_markup error_macros utils test_utils reporting repl_cli repl_eval repl_test repl_wasm roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./
|
||||
COPY --dir cli cli_utils compiler docs editor ast code_markup error_macros highlight utils test_utils reporting repl_cli repl_eval repl_test repl_wasm roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./
|
||||
|
||||
test-zig:
|
||||
FROM +install-zig-llvm-valgrind-clippy-rustfmt
|
||||
|
@ -70,7 +70,7 @@ check-rustfmt:
|
|||
|
||||
check-typos:
|
||||
RUN cargo install typos-cli --version 1.0.11 # version set to prevent confusion if the version is updated automatically
|
||||
COPY --dir .github ci cli cli_utils compiler docs editor examples ast code_markup utils linker nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix version.txt ./
|
||||
COPY --dir .github ci cli cli_utils compiler docs editor examples ast code_markup highlight utils linker nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix version.txt ./
|
||||
RUN typos
|
||||
|
||||
test-rust:
|
||||
|
|
91
FAQ.md
91
FAQ.md
|
@ -39,6 +39,90 @@ whether the feature should be in the language at all. In the case of this featur
|
|||
language doesn't have it; that way nobody has to learn (or spend time spreading the word) about the
|
||||
performance-boosting advice not to use it.
|
||||
|
||||
## Why can't functions be compared for equality using the `==` operator?
|
||||
|
||||
Function equality has been proven to be undecidable in the general case because of the [halting problem](https://en.wikipedia.org/wiki/Halting_problem).
|
||||
So while we as humans might be able to look at `\x -> x + 1` and `\x -> 1 + x` and know that they're equivalent,
|
||||
in the general case it's not possible for a computer to do this reliably.
|
||||
|
||||
There are some other potential ways to define function equality, but they all have problems.
|
||||
|
||||
One way would be to have two functions be considered equal if their source code is equivalent. (Perhaps disregarding
|
||||
comments and spaces.) This sounds reasonable, but it means that now revising a function to do
|
||||
exactly the same thing as before (say, changing `\x -> x + 1` to `\x -> 1 + x`) can cause a bug in a
|
||||
distant part of the code base. Defining function equality this way means that revising a function's internals
|
||||
is no longer a safe, local operation - even if it gives all the same outputs for all the same inputs.
|
||||
|
||||
Another option would be to define it using "reference equality." This is what JavaScript does, for example.
|
||||
However, Roc does not use reference equality anywhere else in the language, and it would mean that (for example)
|
||||
passing `\x -> x + 1` to a function compared to defining `fn = \x -> x + 1` elsewhere and then passing `fn` into
|
||||
the function might give different answers.
|
||||
|
||||
Both of these would make revising code riskier across the entire language, which is very undesirable.
|
||||
|
||||
Another option would be to define that function equality always returns `False`. So both of these would evaluate
|
||||
to `False`:
|
||||
|
||||
* `(\x -> x + 1) == (\x -> 1 + x)`
|
||||
* `(\x -> x + 1) == (\x -> x + 1)`
|
||||
|
||||
This makes function equality effectively useless, while still technically allowing it. It has some other downsides:
|
||||
* Now if you put a function inside a record, using `==` on that record will still type-check, but it will then return `False`. This could lead to bugs if you didn't realize you had accidentally put a function in there - for example, because you were actually storing a different type (e.g. an opaque type) and didn't realize it had a function inside it.
|
||||
* If you put a function (or a value containing a function) into a `Dict` or `Set`, you'll never be able to get it out again. This is a common problem with [NaN](https://en.wikipedia.org/wiki/NaN), which is also defined not to be equal to itself.
|
||||
|
||||
The first of these problems could be addressed by having function equality always return `True` instead of `False` (since that way it would not affect other fields' equality checks in a record), but that design has its own problems:
|
||||
* Although function equality is still useless, `(\x -> x + 1) == (\x -> x)` returns `True`. Even if it didn't lead to bugs in practice, this would certainly be surprising and confusing to beginners.
|
||||
* Now if you put several different functions into a `Dict` or `Set`, only one of them will be kept; the others will be discarded or overwritten. This could cause bugs if a value stored a function internally, and then other functions relied on that internal function for correctness.
|
||||
|
||||
Each of these designs makes Roc a language that's some combination of more error-prone, more confusing, and more
|
||||
brittle to change. Disallowing function equality at compile time eliminates all of these drawbacks.
|
||||
|
||||
## Why doesn't Roc have a `Maybe` or `Option` or `Optional` type, or `null` or `nil` or `undefined`?
|
||||
|
||||
It's common for programming languages to have a [null reference](https://en.wikipedia.org/wiki/Null_pointer)
|
||||
(e.g. `null` in C, `nil` in Ruby, `None` in Python, or `undefined` in JavaScript).
|
||||
The inventor of the null reference refers to it as his "[billion dollar mistake](https://en.wikipedia.org/wiki/Null_pointer#History)" because it "has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years."
|
||||
|
||||
For this and other reasons, many languages do not include a null reference, but instead have a standard library
|
||||
data type which can be used in situations where a null reference would otherwise be used. Common names for this
|
||||
null reference alternative type include `Maybe` (like in Haskell or Elm), `Option` (like in OCaml or Rust),
|
||||
and `Optional` (like in Java).
|
||||
|
||||
By design, Roc does not have one of these. There are several reasons for this.
|
||||
|
||||
First, if a function returns a potential error, Roc has the convention to use `Result` with an error type that
|
||||
has a single tag describing what went wrong. (For example, `List.first : List a -> Result a [ ListWasEmpty ]*`
|
||||
instead of `List.first : List a -> Maybe a`.) This is not only more self-descriptive, it also composes better with
|
||||
other operations that can fail; there's no need to have functions like `Result.toMaybe` or `Maybe.toResult`,
|
||||
because in Roc, the convention is that operations that can fail always use `Result`.
|
||||
|
||||
Second, optional record fields can be handled using Roc's Optional Record Field language feature, so using a type like `Maybe` there would be less ergonomic.
|
||||
|
||||
To describe something that's neither an optional field nor an operation that can fail, an explicit tag union can be
|
||||
more descriptive than something like `Maybe`. For example, if a record type has an `artist` field, but the artist
|
||||
information may not be available, compare these three alternative ways to represent that:
|
||||
|
||||
* `artist : Maybe Artist`
|
||||
* `artist : [ Loading, Loaded Artist ]`
|
||||
* `artist : [ Unspecified, Specified Artist ]`
|
||||
|
||||
All three versions tell us that we might not have access to an `Artist`. However, the `Maybe` version doesn't
|
||||
tell us why that might be. The `Loading`/`Loaded` version tells us we don't have one *yet*, because we're
|
||||
still loading it, whereas the `Unspecified`/`Specified` version tells us we don't have one and shouldn't expect
|
||||
to have one later if we wait, because it wasn't specified.
|
||||
|
||||
Naming aside, using explicit tag unions also makes it easier to transition to richer data models. For example,
|
||||
after using `[ Loading, Loaded Artist ]` for awhile, we might realize that there's another possible state: loading
|
||||
failed due to an error. If we modify this to be `[ Loading, Loaded Artist, Errored LoadingErr ]`, all
|
||||
of our code for the `Loading` and `Loaded` states will still work.
|
||||
|
||||
In contrast, if we'd had `Maybe Artist` and were using helper functions like `Maybe.isNone` (a common argument
|
||||
for using `Maybe` even when it's less self-descriptive), we'd have to rewrite all the code which used those
|
||||
helper functions. As such, a subtle downside of these helper functions is that they discourage any change to
|
||||
the data model that would break their call sites, even if that change would improve the data model overall.
|
||||
|
||||
On a historical note, `Maybe` may have been thought of as a substitute for null references—as opposed to something that emerged organically based on specific motivating use cases after `Result` already existed. That said, in languages that do not have an equivalent of Roc's tag unions, it's much less ergonomic to write something like `Result a [ ListWasEmpty ]*`, so that design would not fit those languages as well as it fits Roc.
|
||||
|
||||
## Why doesn't Roc have higher-kinded polymorphism or arbitrary-rank types?
|
||||
|
||||
_Since this is a FAQ answer, I'm going to assume familiarity with higher-kinded types and higher-rank types instead of including a primer on them._
|
||||
|
@ -159,12 +243,7 @@ Roc also has a different standard library from Elm. Some of the differences come
|
|||
* No `Char`. What most people think of as a "character" is a rendered glyph. However, rendered glyphs are comprised of [grapheme clusters](https://stackoverflow.com/a/27331885), which are a variable number of Unicode code points - and there's no upper bound on how many code points there can be in a single cluster. In a world of emoji, I think this makes `Char` error-prone and it's better to have `Str` be the only first-class unit. For convenience when working with unicode code points (e.g. for performance-critical tasks like parsing), the single-quote syntax is sugar for the corresponding `U32` code point - for example, writing `'鹏'` is exactly the same as writing `40527`. Like Rust, you get a compiler error if you put something in single quotes that's not a valid [Unicode scalar value](http://www.unicode.org/glossary/#unicode_scalar_value).
|
||||
* No `Debug.log` - the editor can do a better job at this, or you can write `expect x != x` to see what `x` is when the expectation fails. Using the editor means your code doesn't change, and using `expect` gives a natural reminder to remove the debugging code before shipping: the build will fail.
|
||||
* No `Debug.todo` - instead you can write a type annotation with no implementation below it; the type checker will treat it normally, but attempting to use the value will cause a runtime exception. This is a feature I've often wanted in Elm, because I like prototyping APIs by writing out the types only, but then when I want the compiler to type-check them for me, I end up having to add `Debug.todo` in various places.
|
||||
* No `Maybe`. There are several reasons for this:
|
||||
* If a function returns a potential error, I prefer `Result` with an error type that uses a no-payload tag to describe what went wrong. (For example, `List.first : List a -> Result a [ ListWasEmpty ]*` instead of `List.first : List a -> Maybe a`.) This is not only more self-descriptive, it also composes better with operations that have multiple ways to fail.
|
||||
* Optional record fields can be handled using the explicit Optional Record Field language feature.
|
||||
* To describe something that's neither an operation that can fail nor an optional field, I prefer using a more descriptive tag - e.g. for a nullable JSON decoder, instead of `nullable : Decoder a -> Decoder (Maybe a)`, making a self-documenting API like `nullable : Decoder a -> Decoder [ Null, NonNull a ]`.
|
||||
* It's surprisingly easy to misuse - especially by overusing it when a different language feature (especially a custom tag union) would lead to nicer code. Joël's legendary [talk about Maybe](https://youtu.be/43eM4kNbb6c) is great, but the fact that a whole talk about such a simple type can be so useful speaks to how easy the type is to misuse. Imagine a 20-minute talk about `Result` - could it be anywhere near as hepful?
|
||||
* On a historical note, it's conceivable that the creation of `Maybe` predated `Result`, and `Maybe` might have been thought of as a substitute for null pointers—as opposed to something that emerged organically based on specific motivating use cases after `Result` already existed.
|
||||
* No `Maybe`. See the "Why doesn't Roc have a `Maybe`/`Option`/`Optional` type" FAQ question
|
||||
|
||||
## Why aren't Roc functions curried by default?
|
||||
|
||||
|
|
|
@ -203,7 +203,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
|||
|
||||
* Zig - https://ziglang.org
|
||||
|
||||
This source code can be found in compiler/builtins/bitcode/src/hash.zig and is licensed under the following terms:
|
||||
This source code can be found in compiler/builtins/bitcode/src/hash.zig, highlight/tests/peg_grammar.rs and highlight/src/highlight_parser.rs and is licensed under the following terms:
|
||||
|
||||
|
||||
The MIT License (Expat)
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
// use crate::pattern::{bindings_from_patterns, canonicalize_pattern, Pattern};
|
||||
// use crate::procedure::References;
|
||||
use roc_collections::all::{default_hasher, ImMap, MutMap, MutSet, SendMap};
|
||||
use roc_error_macros::todo_opaques;
|
||||
use roc_error_macros::{todo_abilities, todo_opaques};
|
||||
use roc_module::ident::Lowercase;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_parse::ast::{self, TypeHeader};
|
||||
|
@ -262,6 +262,7 @@ fn to_pending_def<'a>(
|
|||
}
|
||||
|
||||
Opaque { .. } => todo_opaques!(),
|
||||
Ability { .. } => todo_abilities!(),
|
||||
|
||||
Expect(_) => todo!(),
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#![allow(unused_imports)]
|
||||
// use roc_can::expr::Output;
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_error_macros::todo_abilities;
|
||||
use roc_module::ident::{Ident, Lowercase, TagName};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::{Loc, Region};
|
||||
|
@ -570,6 +571,7 @@ pub fn to_type2<'a>(
|
|||
// }
|
||||
Type2::AsAlias(symbol, vars, alias.actual)
|
||||
}
|
||||
Where { .. } => todo_abilities!(),
|
||||
SpaceBefore(nested, _) | SpaceAfter(nested, _) => {
|
||||
to_type2(env, scope, references, nested, region)
|
||||
}
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
use std::path::Path;
|
||||
|
||||
use bumpalo::Bump;
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_load::file::LoadedModule;
|
||||
use roc_target::TargetInfo;
|
||||
|
||||
pub fn load_module(src_file: &Path) -> LoadedModule {
|
||||
let subs_by_module = MutMap::default();
|
||||
let subs_by_module = Default::default();
|
||||
|
||||
let arena = Bump::new();
|
||||
let loaded = roc_load::file::load_and_typecheck(
|
||||
|
|
|
@ -1390,7 +1390,7 @@ fn adjust_rank_content(
|
|||
Alias(_, args, real_var, _) => {
|
||||
let mut rank = Rank::toplevel();
|
||||
|
||||
for var_index in args.variables() {
|
||||
for var_index in args.all_variables() {
|
||||
let var = subs[var_index];
|
||||
rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var));
|
||||
}
|
||||
|
@ -1548,7 +1548,7 @@ fn instantiate_rigids_help(
|
|||
}
|
||||
|
||||
Alias(_, args, real_type_var, _) => {
|
||||
for var_index in args.variables() {
|
||||
for var_index in args.all_variables() {
|
||||
let var = subs[var_index];
|
||||
instantiate_rigids_help(subs, max_rank, pools, var);
|
||||
}
|
||||
|
@ -1798,9 +1798,9 @@ fn deep_copy_var_help(
|
|||
}
|
||||
|
||||
Alias(symbol, mut args, real_type_var, kind) => {
|
||||
let mut new_args = Vec::with_capacity(args.variables().len());
|
||||
let mut new_args = Vec::with_capacity(args.all_variables().len());
|
||||
|
||||
for var_index in args.variables() {
|
||||
for var_index in args.all_variables() {
|
||||
let var = subs[var_index];
|
||||
let new_var = deep_copy_var_help(subs, max_rank, pools, var);
|
||||
new_args.push(new_var);
|
||||
|
|
|
@ -4,7 +4,6 @@ use roc_build::{
|
|||
program,
|
||||
};
|
||||
use roc_builtins::bitcode;
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_load::file::LoadingProblem;
|
||||
use roc_mono::ir::OptLevel;
|
||||
use roc_target::TargetInfo;
|
||||
|
@ -61,7 +60,7 @@ pub fn build_file<'a>(
|
|||
let target_info = TargetInfo::from(target);
|
||||
|
||||
// Step 1: compile the app and generate the .o file
|
||||
let subs_by_module = MutMap::default();
|
||||
let subs_by_module = Default::default();
|
||||
|
||||
// Release builds use uniqueness optimizations
|
||||
let stdlib = arena.alloc(roc_builtins::std::standard_stdlib());
|
||||
|
@ -366,7 +365,7 @@ pub fn check_file(
|
|||
let target_info = TargetInfo::default_x86_64();
|
||||
|
||||
// Step 1: compile the app and generate the .o file
|
||||
let subs_by_module = MutMap::default();
|
||||
let subs_by_module = Default::default();
|
||||
|
||||
// Release builds use uniqueness optimizations
|
||||
let stdlib = arena.alloc(roc_builtins::std::standard_stdlib());
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use std::path::PathBuf;
|
||||
use std::ffi::OsStr;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::FormatMode;
|
||||
use bumpalo::collections::Vec;
|
||||
|
@ -9,8 +10,8 @@ use roc_fmt::module::fmt_module;
|
|||
use roc_fmt::Buf;
|
||||
use roc_module::called_via::{BinOp, UnaryOp};
|
||||
use roc_parse::ast::{
|
||||
AssignedField, Collection, Expr, Pattern, Spaced, StrLiteral, StrSegment, Tag, TypeAnnotation,
|
||||
TypeHeader, WhenBranch,
|
||||
AbilityDemand, AssignedField, Collection, Expr, Has, HasClause, Pattern, Spaced, StrLiteral,
|
||||
StrSegment, Tag, TypeAnnotation, TypeHeader, WhenBranch,
|
||||
};
|
||||
use roc_parse::header::{
|
||||
AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
|
||||
|
@ -39,7 +40,7 @@ fn flatten_directories(files: std::vec::Vec<PathBuf>) -> std::vec::Vec<PathBuf>
|
|||
let file_path = file.path();
|
||||
if file_path.is_dir() {
|
||||
to_flatten.push(file_path);
|
||||
} else if file_path.ends_with(".roc") {
|
||||
} else if is_roc_file(&file_path) {
|
||||
files.push(file_path);
|
||||
}
|
||||
}
|
||||
|
@ -57,14 +58,19 @@ fn flatten_directories(files: std::vec::Vec<PathBuf>) -> std::vec::Vec<PathBuf>
|
|||
error
|
||||
),
|
||||
}
|
||||
} else {
|
||||
files.push(path)
|
||||
} else if is_roc_file(&path) {
|
||||
files.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
files
|
||||
}
|
||||
|
||||
fn is_roc_file(path: &Path) -> bool {
|
||||
let ext = path.extension().and_then(OsStr::to_str);
|
||||
return matches!(ext, Some("roc"));
|
||||
}
|
||||
|
||||
pub fn format(files: std::vec::Vec<PathBuf>, mode: FormatMode) -> Result<(), String> {
|
||||
let files = flatten_directories(files);
|
||||
|
||||
|
@ -482,6 +488,18 @@ impl<'a> RemoveSpaces<'a> for Def<'a> {
|
|||
body_pattern: arena.alloc(body_pattern.remove_spaces(arena)),
|
||||
body_expr: arena.alloc(body_expr.remove_spaces(arena)),
|
||||
},
|
||||
Def::Ability {
|
||||
header: TypeHeader { name, vars },
|
||||
loc_has,
|
||||
demands,
|
||||
} => Def::Ability {
|
||||
header: TypeHeader {
|
||||
name: name.remove_spaces(arena),
|
||||
vars: vars.remove_spaces(arena),
|
||||
},
|
||||
loc_has: loc_has.remove_spaces(arena),
|
||||
demands: demands.remove_spaces(arena),
|
||||
},
|
||||
Def::Expect(a) => Def::Expect(arena.alloc(a.remove_spaces(arena))),
|
||||
Def::NotYetImplemented(a) => Def::NotYetImplemented(a),
|
||||
Def::SpaceBefore(a, _) | Def::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
|
@ -489,6 +507,21 @@ impl<'a> RemoveSpaces<'a> for Def<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Has<'a> {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
Has::Has
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for AbilityDemand<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
AbilityDemand {
|
||||
name: self.name.remove_spaces(arena),
|
||||
typ: self.typ.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for WhenBranch<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
WhenBranch {
|
||||
|
@ -679,12 +712,26 @@ impl<'a> RemoveSpaces<'a> for TypeAnnotation<'a> {
|
|||
},
|
||||
TypeAnnotation::Inferred => TypeAnnotation::Inferred,
|
||||
TypeAnnotation::Wildcard => TypeAnnotation::Wildcard,
|
||||
TypeAnnotation::Where(annot, has_clauses) => TypeAnnotation::Where(
|
||||
arena.alloc(annot.remove_spaces(arena)),
|
||||
arena.alloc(has_clauses.remove_spaces(arena)),
|
||||
),
|
||||
TypeAnnotation::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
TypeAnnotation::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
TypeAnnotation::Malformed(a) => TypeAnnotation::Malformed(a),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for HasClause<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
HasClause {
|
||||
var: self.var.remove_spaces(arena),
|
||||
ability: self.ability.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Tag<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
|
|
|
@ -16,6 +16,7 @@ mod cli_run {
|
|||
known_bad_file, run_cmd, run_roc, run_with_valgrind, Out, ValgrindError,
|
||||
ValgrindErrorXWhat,
|
||||
};
|
||||
use indoc::indoc;
|
||||
use roc_test_utils::assert_multiline_str_eq;
|
||||
use serial_test::serial;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
@ -50,17 +51,17 @@ mod cli_run {
|
|||
}
|
||||
|
||||
fn strip_colors(str: &str) -> String {
|
||||
use roc_reporting::report::*;
|
||||
str.replace(RED_CODE, "")
|
||||
.replace(WHITE_CODE, "")
|
||||
.replace(BLUE_CODE, "")
|
||||
.replace(YELLOW_CODE, "")
|
||||
.replace(GREEN_CODE, "")
|
||||
.replace(CYAN_CODE, "")
|
||||
.replace(MAGENTA_CODE, "")
|
||||
.replace(RESET_CODE, "")
|
||||
.replace(BOLD_CODE, "")
|
||||
.replace(UNDERLINE_CODE, "")
|
||||
use roc_reporting::report::ANSI_STYLE_CODES;
|
||||
str.replace(ANSI_STYLE_CODES.red, "")
|
||||
.replace(ANSI_STYLE_CODES.green, "")
|
||||
.replace(ANSI_STYLE_CODES.yellow, "")
|
||||
.replace(ANSI_STYLE_CODES.blue, "")
|
||||
.replace(ANSI_STYLE_CODES.magenta, "")
|
||||
.replace(ANSI_STYLE_CODES.cyan, "")
|
||||
.replace(ANSI_STYLE_CODES.white, "")
|
||||
.replace(ANSI_STYLE_CODES.bold, "")
|
||||
.replace(ANSI_STYLE_CODES.underline, "")
|
||||
.replace(ANSI_STYLE_CODES.reset, "")
|
||||
}
|
||||
|
||||
fn check_compile_error(file: &Path, flags: &[&str], expected: &str) {
|
||||
|
@ -73,7 +74,6 @@ mod cli_run {
|
|||
fn check_format_check_as_expected(file: &Path, expects_success_exit_code: bool) {
|
||||
let flags = &["--check"];
|
||||
let out = run_roc(&[&["format", file.to_str().unwrap()], &flags[..]].concat());
|
||||
|
||||
if expects_success_exit_code {
|
||||
assert!(out.status.success());
|
||||
} else {
|
||||
|
@ -940,7 +940,7 @@ mod cli_run {
|
|||
// This fails, because "NotFormatted.roc" is present in this folder
|
||||
check_format_check_as_expected(&fixtures_dir("format"), false);
|
||||
|
||||
// This doesn't fail, since only "Formatted.roc" is present in this folder
|
||||
// This doesn't fail, since only "Formatted.roc" and non-roc files are present in this folder
|
||||
check_format_check_as_expected(&fixtures_dir("format/formatted_directory"), true);
|
||||
}
|
||||
}
|
||||
|
|
1
cli/tests/fixtures/format/NotARocFile.txt
vendored
Normal file
1
cli/tests/fixtures/format/NotARocFile.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
This is not a .roc file, and should be ignored by the formatter.
|
1
cli/tests/fixtures/format/formatted_directory/NestedNotARocFile.txt
vendored
Normal file
1
cli/tests/fixtures/format/formatted_directory/NestedNotARocFile.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
This is not a .roc file, and should be ignored by the formatter.
|
3
cli/tests/fixtures/format/formatted_directory/NotARocFile
vendored
Normal file
3
cli/tests/fixtures/format/formatted_directory/NotARocFile
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
This is not a .roc file, and should be ignored by the formatter.
|
||||
|
||||
This file does not have an extension, to ensure the formatter does not simply test with `ends_with(".roc")`
|
1
cli/tests/fixtures/format/formatted_directory/ignored/NotARocFile.txt
vendored
Normal file
1
cli/tests/fixtures/format/formatted_directory/ignored/NotARocFile.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
This is not a .roc file, and should be ignored by the formatter.
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
|||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2018"
|
||||
description = "Our own markup language for Roc code. Used by the editor and (soon) the docs."
|
||||
description = "Our own markup language for Roc code. Used by the editor and the docs."
|
||||
|
||||
[dependencies]
|
||||
roc_ast = { path = "../ast" }
|
||||
|
|
|
@ -1,164 +1,167 @@
|
|||
use roc_ast::lang::core::{ast::ASTNodeId, expr::expr2::ExprId};
|
||||
use crate::{
|
||||
slow_pool::{MarkNodeId, SlowPool},
|
||||
syntax_highlight::HighlightStyle,
|
||||
};
|
||||
|
||||
use crate::{slow_pool::MarkNodeId, syntax_highlight::HighlightStyle};
|
||||
use super::{
|
||||
attribute::Attributes,
|
||||
nodes::MarkupNode,
|
||||
nodes::{self, make_nested_mn},
|
||||
};
|
||||
|
||||
use super::{attribute::Attributes, nodes, nodes::MarkupNode};
|
||||
|
||||
pub fn new_equals_mn(ast_node_id: ASTNodeId, parent_id_opt: Option<MarkNodeId>) -> MarkupNode {
|
||||
MarkupNode::Text {
|
||||
content: nodes::EQUALS.to_owned(),
|
||||
ast_node_id,
|
||||
syn_high_style: HighlightStyle::Operator,
|
||||
attributes: Attributes::default(),
|
||||
parent_id_opt,
|
||||
newlines_at_end: 0,
|
||||
}
|
||||
pub fn new_equals_mn() -> MarkupNode {
|
||||
common_text_node(nodes::EQUALS.to_owned(), HighlightStyle::Operator, 0)
|
||||
}
|
||||
|
||||
pub fn new_comma_mn(expr_id: ExprId, parent_id_opt: Option<MarkNodeId>) -> MarkupNode {
|
||||
new_comma_mn_ast(ASTNodeId::AExprId(expr_id), parent_id_opt)
|
||||
pub fn new_comma_mn() -> MarkupNode {
|
||||
common_text_node(nodes::COMMA.to_owned(), HighlightStyle::Operator, 0)
|
||||
}
|
||||
|
||||
pub fn new_comma_mn_ast(ast_node_id: ASTNodeId, parent_id_opt: Option<MarkNodeId>) -> MarkupNode {
|
||||
MarkupNode::Text {
|
||||
content: nodes::COMMA.to_owned(),
|
||||
ast_node_id,
|
||||
syn_high_style: HighlightStyle::Comma,
|
||||
attributes: Attributes::default(),
|
||||
parent_id_opt,
|
||||
newlines_at_end: 0,
|
||||
}
|
||||
pub fn new_dot_mn() -> MarkupNode {
|
||||
common_text_node(nodes::DOT.to_owned(), HighlightStyle::Operator, 0)
|
||||
}
|
||||
|
||||
pub fn new_blank_mn(ast_node_id: ASTNodeId, parent_id_opt: Option<MarkNodeId>) -> MarkupNode {
|
||||
pub fn new_blank_mn() -> MarkupNode {
|
||||
MarkupNode::Blank {
|
||||
ast_node_id,
|
||||
attributes: Attributes::default(),
|
||||
parent_id_opt,
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_blank_mn_w_nls(
|
||||
ast_node_id: ASTNodeId,
|
||||
parent_id_opt: Option<MarkNodeId>,
|
||||
nr_of_newlines: usize,
|
||||
) -> MarkupNode {
|
||||
pub fn new_blank_mn_w_nls(nr_of_newlines: usize) -> MarkupNode {
|
||||
MarkupNode::Blank {
|
||||
ast_node_id,
|
||||
attributes: Attributes::default(),
|
||||
parent_id_opt,
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: nr_of_newlines,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_colon_mn(expr_id: ExprId, parent_id_opt: Option<MarkNodeId>) -> MarkupNode {
|
||||
new_operator_mn(nodes::COLON.to_owned(), expr_id, parent_id_opt)
|
||||
pub fn new_colon_mn() -> MarkupNode {
|
||||
new_operator_mn(nodes::COLON.to_owned())
|
||||
}
|
||||
|
||||
pub fn new_operator_mn(
|
||||
content: String,
|
||||
expr_id: ExprId,
|
||||
parent_id_opt: Option<MarkNodeId>,
|
||||
) -> MarkupNode {
|
||||
MarkupNode::Text {
|
||||
content,
|
||||
ast_node_id: ASTNodeId::AExprId(expr_id),
|
||||
syn_high_style: HighlightStyle::Operator,
|
||||
attributes: Attributes::default(),
|
||||
parent_id_opt,
|
||||
newlines_at_end: 0,
|
||||
}
|
||||
pub fn new_operator_mn(content: String) -> MarkupNode {
|
||||
common_text_node(content, HighlightStyle::Operator, 0)
|
||||
}
|
||||
|
||||
pub fn new_left_accolade_mn(expr_id: ExprId, parent_id_opt: Option<MarkNodeId>) -> MarkupNode {
|
||||
MarkupNode::Text {
|
||||
content: nodes::LEFT_ACCOLADE.to_owned(),
|
||||
ast_node_id: ASTNodeId::AExprId(expr_id),
|
||||
syn_high_style: HighlightStyle::Bracket,
|
||||
attributes: Attributes::default(),
|
||||
parent_id_opt,
|
||||
newlines_at_end: 0,
|
||||
}
|
||||
pub fn new_left_accolade_mn() -> MarkupNode {
|
||||
common_text_node(nodes::LEFT_ACCOLADE.to_owned(), HighlightStyle::Bracket, 0)
|
||||
}
|
||||
|
||||
pub fn new_right_accolade_mn(expr_id: ExprId, parent_id_opt: Option<MarkNodeId>) -> MarkupNode {
|
||||
MarkupNode::Text {
|
||||
content: nodes::RIGHT_ACCOLADE.to_owned(),
|
||||
ast_node_id: ASTNodeId::AExprId(expr_id),
|
||||
syn_high_style: HighlightStyle::Bracket,
|
||||
attributes: Attributes::default(),
|
||||
parent_id_opt,
|
||||
newlines_at_end: 0,
|
||||
}
|
||||
pub fn new_right_accolade_mn() -> MarkupNode {
|
||||
common_text_node(nodes::RIGHT_ACCOLADE.to_owned(), HighlightStyle::Bracket, 0)
|
||||
}
|
||||
|
||||
pub fn new_left_square_mn(expr_id: ExprId, parent_id_opt: Option<MarkNodeId>) -> MarkupNode {
|
||||
MarkupNode::Text {
|
||||
content: nodes::LEFT_SQUARE_BR.to_owned(),
|
||||
ast_node_id: ASTNodeId::AExprId(expr_id),
|
||||
syn_high_style: HighlightStyle::Bracket,
|
||||
attributes: Attributes::default(),
|
||||
parent_id_opt,
|
||||
newlines_at_end: 0,
|
||||
}
|
||||
pub fn new_left_square_mn() -> MarkupNode {
|
||||
common_text_node(nodes::LEFT_SQUARE_BR.to_owned(), HighlightStyle::Bracket, 0)
|
||||
}
|
||||
|
||||
pub fn new_right_square_mn(expr_id: ExprId, parent_id_opt: Option<MarkNodeId>) -> MarkupNode {
|
||||
MarkupNode::Text {
|
||||
content: nodes::RIGHT_SQUARE_BR.to_owned(),
|
||||
ast_node_id: ASTNodeId::AExprId(expr_id),
|
||||
syn_high_style: HighlightStyle::Bracket,
|
||||
attributes: Attributes::default(),
|
||||
parent_id_opt,
|
||||
newlines_at_end: 0,
|
||||
}
|
||||
pub fn new_right_square_mn() -> MarkupNode {
|
||||
common_text_node(
|
||||
nodes::RIGHT_SQUARE_BR.to_owned(),
|
||||
HighlightStyle::Bracket,
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_func_name_mn(content: String, expr_id: ExprId) -> MarkupNode {
|
||||
MarkupNode::Text {
|
||||
content,
|
||||
ast_node_id: ASTNodeId::AExprId(expr_id),
|
||||
syn_high_style: HighlightStyle::FunctionName,
|
||||
attributes: Attributes::default(),
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 0,
|
||||
}
|
||||
pub fn new_func_name_mn(content: String) -> MarkupNode {
|
||||
common_text_node(content, HighlightStyle::FunctionName, 0)
|
||||
}
|
||||
|
||||
pub fn new_arg_name_mn(content: String, expr_id: ExprId) -> MarkupNode {
|
||||
MarkupNode::Text {
|
||||
content,
|
||||
ast_node_id: ASTNodeId::AExprId(expr_id),
|
||||
syn_high_style: HighlightStyle::FunctionArgName,
|
||||
attributes: Attributes::default(),
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 0,
|
||||
}
|
||||
pub fn new_arg_name_mn(content: String) -> MarkupNode {
|
||||
common_text_node(content, HighlightStyle::FunctionArgName, 0)
|
||||
}
|
||||
|
||||
pub fn new_arrow_mn(ast_node_id: ASTNodeId, newlines_at_end: usize) -> MarkupNode {
|
||||
MarkupNode::Text {
|
||||
content: nodes::ARROW.to_owned(),
|
||||
ast_node_id,
|
||||
syn_high_style: HighlightStyle::Operator,
|
||||
attributes: Attributes::default(),
|
||||
parent_id_opt: None,
|
||||
pub fn new_arrow_mn(newlines_at_end: usize) -> MarkupNode {
|
||||
common_text_node(
|
||||
nodes::ARROW.to_owned(),
|
||||
HighlightStyle::Operator,
|
||||
newlines_at_end,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_comments_mn(
|
||||
comments: String,
|
||||
ast_node_id: ASTNodeId,
|
||||
pub fn new_comments_mn(comment: String, newlines_at_end: usize) -> MarkupNode {
|
||||
common_text_node(comment, HighlightStyle::Comment, newlines_at_end)
|
||||
}
|
||||
|
||||
fn common_text_node(
|
||||
content: String,
|
||||
highlight_style: HighlightStyle,
|
||||
newlines_at_end: usize,
|
||||
) -> MarkupNode {
|
||||
MarkupNode::Text {
|
||||
content: comments,
|
||||
ast_node_id,
|
||||
syn_high_style: HighlightStyle::Comment,
|
||||
content,
|
||||
syn_high_style: highlight_style,
|
||||
attributes: Attributes::default(),
|
||||
parent_id_opt: None,
|
||||
newlines_at_end,
|
||||
}
|
||||
}
|
||||
|
||||
pub const NEW_LINES_AFTER_DEF: usize = 2;
|
||||
|
||||
pub fn new_assign_mn(
|
||||
val_name_mn_id: MarkNodeId,
|
||||
equals_mn_id: MarkNodeId,
|
||||
expr_mark_node_id: MarkNodeId,
|
||||
) -> MarkupNode {
|
||||
make_nested_mn(
|
||||
vec![val_name_mn_id, equals_mn_id, expr_mark_node_id],
|
||||
NEW_LINES_AFTER_DEF,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_module_name_mn_id(mn_ids: Vec<MarkNodeId>, mark_node_pool: &mut SlowPool) -> MarkNodeId {
|
||||
if mn_ids.len() == 1 {
|
||||
*mn_ids.get(0).unwrap() // safe because we checked the length before
|
||||
} else {
|
||||
let nested_node = make_nested_mn(mn_ids, 0);
|
||||
mark_node_pool.add(nested_node)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_module_var_mn(
|
||||
module_name_id: MarkNodeId,
|
||||
dot_id: MarkNodeId,
|
||||
ident_id: MarkNodeId,
|
||||
) -> MarkupNode {
|
||||
make_nested_mn(vec![module_name_id, dot_id, ident_id], 0)
|
||||
}
|
||||
|
||||
pub fn if_mn() -> MarkupNode {
|
||||
keyword_mn("if ")
|
||||
}
|
||||
|
||||
pub fn then_mn() -> MarkupNode {
|
||||
keyword_mn(" then ")
|
||||
}
|
||||
|
||||
pub fn else_mn() -> MarkupNode {
|
||||
keyword_mn(" else ")
|
||||
}
|
||||
|
||||
fn keyword_mn(keyword: &str) -> MarkupNode {
|
||||
common_text_node(keyword.to_owned(), HighlightStyle::Keyword, 0)
|
||||
}
|
||||
|
||||
pub fn new_if_expr_mn(
|
||||
if_mn_id: MarkNodeId,
|
||||
cond_expr_mn_id: MarkNodeId,
|
||||
then_mn_id: MarkNodeId,
|
||||
then_expr_mn_id: MarkNodeId,
|
||||
else_mn_id: MarkNodeId,
|
||||
else_expr_mn_id: MarkNodeId,
|
||||
) -> MarkupNode {
|
||||
make_nested_mn(
|
||||
vec![
|
||||
if_mn_id,
|
||||
cond_expr_mn_id,
|
||||
then_mn_id,
|
||||
then_expr_mn_id,
|
||||
else_mn_id,
|
||||
else_expr_mn_id,
|
||||
],
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ use roc_module::symbol::Interns;
|
|||
use crate::{
|
||||
markup::{
|
||||
convert::{from_def2::def2_to_markup, from_header::header_to_markup},
|
||||
mark_id_ast_id_map::MarkIdAstIdMap,
|
||||
nodes::set_parent_for_all,
|
||||
},
|
||||
slow_pool::{MarkNodeId, SlowPool},
|
||||
|
@ -17,8 +18,13 @@ pub fn ast_to_mark_nodes<'a>(
|
|||
ast: &AST,
|
||||
mark_node_pool: &mut SlowPool,
|
||||
interns: &Interns,
|
||||
) -> ASTResult<Vec<MarkNodeId>> {
|
||||
let mut all_mark_node_ids = vec![header_to_markup(&ast.header, mark_node_pool)];
|
||||
) -> ASTResult<(Vec<MarkNodeId>, MarkIdAstIdMap)> {
|
||||
let mut mark_id_ast_id_map = MarkIdAstIdMap::default();
|
||||
let mut all_mark_node_ids = vec![header_to_markup(
|
||||
&ast.header,
|
||||
mark_node_pool,
|
||||
&mut mark_id_ast_id_map,
|
||||
)];
|
||||
|
||||
for &def_id in ast.def_ids.iter() {
|
||||
// for debugging
|
||||
|
@ -26,12 +32,19 @@ pub fn ast_to_mark_nodes<'a>(
|
|||
|
||||
let def2 = env.pool.get(def_id);
|
||||
|
||||
let expr2_markup_id = def2_to_markup(env, def2, def_id, mark_node_pool, interns)?;
|
||||
let expr2_markup_id = def2_to_markup(
|
||||
env,
|
||||
def2,
|
||||
def_id,
|
||||
mark_node_pool,
|
||||
&mut mark_id_ast_id_map,
|
||||
interns,
|
||||
)?;
|
||||
|
||||
set_parent_for_all(expr2_markup_id, mark_node_pool);
|
||||
|
||||
all_mark_node_ids.push(expr2_markup_id);
|
||||
}
|
||||
|
||||
Ok(all_mark_node_ids)
|
||||
Ok((all_mark_node_ids, mark_id_ast_id_map))
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use crate::{
|
||||
markup::{
|
||||
common_nodes::new_blank_mn_w_nls,
|
||||
top_level_def::{tld_mark_node, tld_w_comments_mark_node},
|
||||
mark_id_ast_id_map::MarkIdAstIdMap,
|
||||
nodes::MarkupNode,
|
||||
top_level_def::{assignment_mark_node, tld_w_comments_mark_node},
|
||||
},
|
||||
slow_pool::{MarkNodeId, SlowPool},
|
||||
};
|
||||
|
@ -20,11 +22,25 @@ use roc_ast::{
|
|||
};
|
||||
use roc_module::symbol::Interns;
|
||||
|
||||
pub fn add_node(
|
||||
mark_node: MarkupNode,
|
||||
ast_node_id: ASTNodeId,
|
||||
mark_node_pool: &mut SlowPool,
|
||||
mark_id_ast_id_map: &mut MarkIdAstIdMap,
|
||||
) -> MarkNodeId {
|
||||
let mark_node_id = mark_node_pool.add(mark_node);
|
||||
|
||||
mark_id_ast_id_map.insert(mark_node_id, ast_node_id);
|
||||
|
||||
mark_node_id
|
||||
}
|
||||
|
||||
pub fn def2_to_markup<'a>(
|
||||
env: &mut Env<'a>,
|
||||
def2: &Def2,
|
||||
def2_node_id: DefId,
|
||||
mark_node_pool: &mut SlowPool,
|
||||
mark_id_ast_id_map: &mut MarkIdAstIdMap,
|
||||
interns: &Interns,
|
||||
) -> ASTResult<MarkNodeId> {
|
||||
let ast_node_id = ASTNodeId::ADefId(def2_node_id);
|
||||
|
@ -39,45 +55,81 @@ pub fn def2_to_markup<'a>(
|
|||
env.pool.get(*expr_id),
|
||||
*expr_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
interns,
|
||||
0,
|
||||
)?;
|
||||
|
||||
let tld_mn =
|
||||
tld_mark_node(*identifier_id, expr_mn_id, ast_node_id, mark_node_pool, env)?;
|
||||
let tld_mn = assignment_mark_node(
|
||||
*identifier_id,
|
||||
expr_mn_id,
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
env,
|
||||
)?;
|
||||
|
||||
mark_node_pool.add(tld_mn)
|
||||
add_node(tld_mn, ast_node_id, mark_node_pool, mark_id_ast_id_map)
|
||||
}
|
||||
Def2::Blank => mark_node_pool.add(new_blank_mn_w_nls(ast_node_id, None, 2)),
|
||||
Def2::Blank => add_node(
|
||||
new_blank_mn_w_nls(2),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
),
|
||||
Def2::CommentsBefore { comments, def_id } => {
|
||||
let inner_def = env.pool.get(*def_id);
|
||||
let inner_def_mark_node_id =
|
||||
def2_to_markup(env, inner_def, *def_id, mark_node_pool, interns)?;
|
||||
let inner_def_mark_node_id = def2_to_markup(
|
||||
env,
|
||||
inner_def,
|
||||
*def_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
interns,
|
||||
)?;
|
||||
|
||||
let full_mark_node = tld_w_comments_mark_node(
|
||||
comments.clone(),
|
||||
inner_def_mark_node_id,
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
true,
|
||||
)?;
|
||||
|
||||
mark_node_pool.add(full_mark_node)
|
||||
add_node(
|
||||
full_mark_node,
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
)
|
||||
}
|
||||
Def2::CommentsAfter { def_id, comments } => {
|
||||
let inner_def = env.pool.get(*def_id);
|
||||
let inner_def_mark_node_id =
|
||||
def2_to_markup(env, inner_def, *def_id, mark_node_pool, interns)?;
|
||||
let inner_def_mark_node_id = def2_to_markup(
|
||||
env,
|
||||
inner_def,
|
||||
*def_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
interns,
|
||||
)?;
|
||||
|
||||
let full_mark_node = tld_w_comments_mark_node(
|
||||
comments.clone(),
|
||||
inner_def_mark_node_id,
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
false,
|
||||
)?;
|
||||
|
||||
mark_node_pool.add(full_mark_node)
|
||||
add_node(
|
||||
full_mark_node,
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ use crate::{
|
|||
new_left_accolade_mn, new_left_square_mn, new_operator_mn, new_right_accolade_mn,
|
||||
new_right_square_mn,
|
||||
},
|
||||
mark_id_ast_id_map::MarkIdAstIdMap,
|
||||
nodes::{
|
||||
get_string, join_mark_nodes_commas, join_mark_nodes_spaces, new_markup_node, MarkupNode,
|
||||
},
|
||||
|
@ -32,12 +33,15 @@ use roc_ast::{
|
|||
};
|
||||
use roc_module::{module_err::ModuleResult, symbol::Interns};
|
||||
|
||||
use super::from_def2::add_node;
|
||||
|
||||
// make Markup Nodes: generate String representation, assign Highlighting Style
|
||||
pub fn expr2_to_markup<'a>(
|
||||
env: &Env<'a>,
|
||||
expr2: &Expr2,
|
||||
expr2_node_id: ExprId,
|
||||
mark_node_pool: &mut SlowPool,
|
||||
mark_id_ast_id_map: &mut MarkIdAstIdMap,
|
||||
interns: &Interns,
|
||||
indent_level: usize,
|
||||
) -> ASTResult<MarkNodeId> {
|
||||
|
@ -58,30 +62,51 @@ pub fn expr2_to_markup<'a>(
|
|||
ast_node_id,
|
||||
HighlightStyle::Number,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
indent_level,
|
||||
)
|
||||
}
|
||||
Expr2::Str(text) => {
|
||||
let content = format!("\"{}\"", text.as_str(env.pool));
|
||||
|
||||
string_mark_node(&content, indent_level, ast_node_id, mark_node_pool)
|
||||
string_mark_node(
|
||||
&content,
|
||||
indent_level,
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
)
|
||||
}
|
||||
Expr2::SmallStr(array_str) => {
|
||||
let content = format!("\"{}\"", array_str.as_str());
|
||||
|
||||
string_mark_node(&content, indent_level, ast_node_id, mark_node_pool)
|
||||
string_mark_node(
|
||||
&content,
|
||||
indent_level,
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
)
|
||||
}
|
||||
Expr2::GlobalTag { name, .. } => new_markup_node(
|
||||
with_indent(indent_level, &get_string(env, name)),
|
||||
ast_node_id,
|
||||
HighlightStyle::Type,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
indent_level,
|
||||
),
|
||||
Expr2::Call { args, expr_id, .. } => {
|
||||
let expr = env.pool.get(*expr_id);
|
||||
let fun_call_mark_id =
|
||||
expr2_to_markup(env, expr, *expr_id, mark_node_pool, interns, indent_level)?;
|
||||
let fun_call_mark_id = expr2_to_markup(
|
||||
env,
|
||||
expr,
|
||||
*expr_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
interns,
|
||||
indent_level,
|
||||
)?;
|
||||
|
||||
let arg_expr_ids: Vec<ExprId> =
|
||||
args.iter(env.pool).map(|(_, arg_id)| *arg_id).collect();
|
||||
|
@ -91,24 +116,31 @@ pub fn expr2_to_markup<'a>(
|
|||
.map(|arg_id| {
|
||||
let arg_expr = env.pool.get(*arg_id);
|
||||
|
||||
expr2_to_markup(env, arg_expr, *arg_id, mark_node_pool, interns, 0)
|
||||
expr2_to_markup(
|
||||
env,
|
||||
arg_expr,
|
||||
*arg_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
interns,
|
||||
0,
|
||||
)
|
||||
})
|
||||
.collect::<ASTResult<Vec<MarkNodeId>>>()?;
|
||||
|
||||
let mut args_with_sapces =
|
||||
join_mark_nodes_spaces(arg_call_mark_ids, true, ast_node_id, mark_node_pool);
|
||||
join_mark_nodes_spaces(arg_call_mark_ids, true, mark_node_pool);
|
||||
|
||||
let mut children_ids = vec![fun_call_mark_id];
|
||||
children_ids.append(&mut args_with_sapces);
|
||||
|
||||
let call_node = MarkupNode::Nested {
|
||||
ast_node_id,
|
||||
children_ids,
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 0,
|
||||
};
|
||||
|
||||
mark_node_pool.add(call_node)
|
||||
add_node(call_node, ast_node_id, mark_node_pool, mark_id_ast_id_map)
|
||||
}
|
||||
Expr2::Var(symbol) => {
|
||||
let text = symbol.fully_qualified(interns, env.home);
|
||||
|
@ -118,12 +150,17 @@ pub fn expr2_to_markup<'a>(
|
|||
ast_node_id,
|
||||
HighlightStyle::Value,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
indent_level,
|
||||
)
|
||||
}
|
||||
Expr2::List { elems, .. } => {
|
||||
let mut children_ids =
|
||||
vec![mark_node_pool.add(new_left_square_mn(expr2_node_id, None))];
|
||||
let mut children_ids = vec![add_node(
|
||||
new_left_square_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
)];
|
||||
|
||||
let indexed_node_ids: Vec<(usize, ExprId)> =
|
||||
elems.iter(env.pool).copied().enumerate().collect();
|
||||
|
@ -136,43 +173,66 @@ pub fn expr2_to_markup<'a>(
|
|||
sub_expr2,
|
||||
*node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
interns,
|
||||
indent_level,
|
||||
)?);
|
||||
|
||||
if idx + 1 < elems.len() {
|
||||
children_ids.push(mark_node_pool.add(new_comma_mn(expr2_node_id, None)));
|
||||
}
|
||||
}
|
||||
children_ids.push(mark_node_pool.add(new_right_square_mn(expr2_node_id, None)));
|
||||
|
||||
let list_node = MarkupNode::Nested {
|
||||
children_ids.push(add_node(
|
||||
new_comma_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
));
|
||||
}
|
||||
}
|
||||
children_ids.push(add_node(
|
||||
new_right_square_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
));
|
||||
|
||||
let list_mn = MarkupNode::Nested {
|
||||
children_ids,
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 0,
|
||||
};
|
||||
|
||||
mark_node_pool.add(list_node)
|
||||
add_node(list_mn, ast_node_id, mark_node_pool, mark_id_ast_id_map)
|
||||
}
|
||||
Expr2::EmptyRecord => {
|
||||
let children_ids = vec![
|
||||
mark_node_pool.add(new_left_accolade_mn(expr2_node_id, None)),
|
||||
mark_node_pool.add(new_right_accolade_mn(expr2_node_id, None)),
|
||||
add_node(
|
||||
new_left_accolade_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
),
|
||||
add_node(
|
||||
new_right_accolade_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
),
|
||||
];
|
||||
|
||||
let record_node = MarkupNode::Nested {
|
||||
ast_node_id,
|
||||
let record_mn = MarkupNode::Nested {
|
||||
children_ids,
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 0,
|
||||
};
|
||||
|
||||
mark_node_pool.add(record_node)
|
||||
add_node(record_mn, ast_node_id, mark_node_pool, mark_id_ast_id_map)
|
||||
}
|
||||
Expr2::Record { fields, .. } => {
|
||||
let mut children_ids =
|
||||
vec![mark_node_pool.add(new_left_accolade_mn(expr2_node_id, None))];
|
||||
let mut children_ids = vec![add_node(
|
||||
new_left_accolade_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
)];
|
||||
|
||||
for (idx, field_node_id) in fields.iter_node_ids().enumerate() {
|
||||
let record_field = env.pool.get(field_node_id);
|
||||
|
@ -184,6 +244,7 @@ pub fn expr2_to_markup<'a>(
|
|||
ast_node_id,
|
||||
HighlightStyle::RecordField,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
indent_level,
|
||||
));
|
||||
|
||||
|
@ -191,7 +252,12 @@ pub fn expr2_to_markup<'a>(
|
|||
RecordField::InvalidLabelOnly(_, _) => (),
|
||||
RecordField::LabelOnly(_, _, _) => (),
|
||||
RecordField::LabeledValue(_, _, sub_expr2_node_id) => {
|
||||
children_ids.push(mark_node_pool.add(new_colon_mn(expr2_node_id, None)));
|
||||
children_ids.push(add_node(
|
||||
new_colon_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
));
|
||||
|
||||
let sub_expr2 = env.pool.get(*sub_expr2_node_id);
|
||||
children_ids.push(expr2_to_markup(
|
||||
|
@ -199,6 +265,7 @@ pub fn expr2_to_markup<'a>(
|
|||
sub_expr2,
|
||||
*sub_expr2_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
interns,
|
||||
indent_level,
|
||||
)?);
|
||||
|
@ -206,22 +273,36 @@ pub fn expr2_to_markup<'a>(
|
|||
}
|
||||
|
||||
if idx + 1 < fields.len() {
|
||||
children_ids.push(mark_node_pool.add(new_comma_mn(expr2_node_id, None)));
|
||||
}
|
||||
}
|
||||
|
||||
children_ids.push(mark_node_pool.add(new_right_accolade_mn(expr2_node_id, None)));
|
||||
|
||||
let record_node = MarkupNode::Nested {
|
||||
children_ids.push(add_node(
|
||||
new_comma_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
children_ids.push(add_node(
|
||||
new_right_accolade_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
));
|
||||
|
||||
let record_mn = MarkupNode::Nested {
|
||||
children_ids,
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 0,
|
||||
};
|
||||
|
||||
mark_node_pool.add(record_node)
|
||||
add_node(record_mn, ast_node_id, mark_node_pool, mark_id_ast_id_map)
|
||||
}
|
||||
Expr2::Blank => mark_node_pool.add(new_blank_mn(ast_node_id, None)),
|
||||
Expr2::Blank => add_node(
|
||||
new_blank_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
),
|
||||
Expr2::LetValue {
|
||||
def_id,
|
||||
body_id: _,
|
||||
|
@ -235,16 +316,21 @@ pub fn expr2_to_markup<'a>(
|
|||
|
||||
let val_name_mn = MarkupNode::Text {
|
||||
content: val_name,
|
||||
ast_node_id,
|
||||
syn_high_style: HighlightStyle::Value,
|
||||
attributes: Attributes::default(),
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 0,
|
||||
};
|
||||
|
||||
let val_name_mn_id = mark_node_pool.add(val_name_mn);
|
||||
let val_name_mn_id =
|
||||
add_node(val_name_mn, ast_node_id, mark_node_pool, mark_id_ast_id_map);
|
||||
|
||||
let equals_mn_id = mark_node_pool.add(new_equals_mn(ast_node_id, None));
|
||||
let equals_mn_id = add_node(
|
||||
new_equals_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let value_def = env.pool.get(*def_id);
|
||||
|
||||
|
@ -259,6 +345,7 @@ pub fn expr2_to_markup<'a>(
|
|||
env.pool.get(*expr_id),
|
||||
*expr_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
interns,
|
||||
indent_level,
|
||||
)?;
|
||||
|
@ -266,14 +353,13 @@ pub fn expr2_to_markup<'a>(
|
|||
let body_mn = mark_node_pool.get_mut(body_mn_id);
|
||||
body_mn.add_newline_at_end();
|
||||
|
||||
let full_let_node = MarkupNode::Nested {
|
||||
ast_node_id,
|
||||
let full_let_mn = MarkupNode::Nested {
|
||||
children_ids: vec![val_name_mn_id, equals_mn_id, body_mn_id],
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 1,
|
||||
};
|
||||
|
||||
mark_node_pool.add(full_let_node)
|
||||
add_node(full_let_mn, ast_node_id, mark_node_pool, mark_id_ast_id_map)
|
||||
}
|
||||
other => {
|
||||
unimplemented!(
|
||||
|
@ -291,8 +377,13 @@ pub fn expr2_to_markup<'a>(
|
|||
body_id,
|
||||
extra: _,
|
||||
} => {
|
||||
let backslash_mn = new_operator_mn("\\".to_string(), expr2_node_id, None);
|
||||
let backslash_mn_id = mark_node_pool.add(backslash_mn);
|
||||
let backslash_mn = new_operator_mn("\\".to_string());
|
||||
let backslash_mn_id = add_node(
|
||||
backslash_mn,
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let arg_names: Vec<&str> = args
|
||||
.iter(env.pool)
|
||||
|
@ -320,31 +411,31 @@ pub fn expr2_to_markup<'a>(
|
|||
|
||||
let arg_mark_nodes = arg_names
|
||||
.iter()
|
||||
.map(|arg_name| new_arg_name_mn(arg_name.to_string(), expr2_node_id))
|
||||
.map(|arg_name| new_arg_name_mn(arg_name.to_string()))
|
||||
.collect_vec();
|
||||
|
||||
let args_with_commas: Vec<MarkupNode> =
|
||||
join_mark_nodes_commas(arg_mark_nodes, ASTNodeId::AExprId(expr2_node_id));
|
||||
let args_with_commas: Vec<MarkupNode> = join_mark_nodes_commas(arg_mark_nodes);
|
||||
|
||||
let mut args_with_commas_ids: Vec<MarkNodeId> = args_with_commas
|
||||
.into_iter()
|
||||
.map(|mark_node| mark_node_pool.add(mark_node))
|
||||
.map(|mark_node| {
|
||||
add_node(mark_node, ast_node_id, mark_node_pool, mark_id_ast_id_map)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let arrow_mn = new_arrow_mn(ASTNodeId::AExprId(expr2_node_id), 1);
|
||||
let arrow_mn_id = mark_node_pool.add(arrow_mn);
|
||||
let arrow_mn = new_arrow_mn(1);
|
||||
let arrow_mn_id = add_node(arrow_mn, ast_node_id, mark_node_pool, mark_id_ast_id_map);
|
||||
|
||||
let mut children_ids = vec![backslash_mn_id];
|
||||
children_ids.append(&mut args_with_commas_ids);
|
||||
children_ids.push(arrow_mn_id);
|
||||
|
||||
let args_mn = MarkupNode::Nested {
|
||||
ast_node_id: ASTNodeId::AExprId(expr2_node_id),
|
||||
children_ids,
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 0,
|
||||
};
|
||||
let args_mn_id = mark_node_pool.add(args_mn);
|
||||
let args_mn_id = add_node(args_mn, ast_node_id, mark_node_pool, mark_id_ast_id_map);
|
||||
|
||||
let body_expr = env.pool.get(*body_id);
|
||||
let body_mn_id = expr2_to_markup(
|
||||
|
@ -352,24 +443,25 @@ pub fn expr2_to_markup<'a>(
|
|||
body_expr,
|
||||
*body_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
interns,
|
||||
indent_level + 1,
|
||||
)?;
|
||||
|
||||
let function_node = MarkupNode::Nested {
|
||||
ast_node_id,
|
||||
let function_mn = MarkupNode::Nested {
|
||||
children_ids: vec![args_mn_id, body_mn_id],
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 0,
|
||||
};
|
||||
|
||||
mark_node_pool.add(function_node)
|
||||
add_node(function_mn, ast_node_id, mark_node_pool, mark_id_ast_id_map)
|
||||
}
|
||||
Expr2::RuntimeError() => new_markup_node(
|
||||
"RunTimeError".to_string(),
|
||||
ast_node_id,
|
||||
HighlightStyle::Blank,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
indent_level,
|
||||
),
|
||||
rest => todo!("implement expr2_to_markup for {:?}", rest),
|
||||
|
@ -392,12 +484,14 @@ fn string_mark_node(
|
|||
indent_level: usize,
|
||||
ast_node_id: ASTNodeId,
|
||||
mark_node_pool: &mut SlowPool,
|
||||
mark_id_ast_id_map: &mut MarkIdAstIdMap,
|
||||
) -> MarkNodeId {
|
||||
new_markup_node(
|
||||
with_indent(indent_level, content),
|
||||
ast_node_id,
|
||||
HighlightStyle::String,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
indent_level,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use roc_ast::lang::core::{ast::ASTNodeId, expr::expr2::ExprId, header::AppHeader};
|
||||
use roc_ast::lang::core::{ast::ASTNodeId, header::AppHeader};
|
||||
|
||||
use crate::{
|
||||
markup::{
|
||||
|
@ -7,54 +7,82 @@ use crate::{
|
|||
new_comma_mn, new_left_accolade_mn, new_left_square_mn, new_right_accolade_mn,
|
||||
new_right_square_mn,
|
||||
},
|
||||
mark_id_ast_id_map::MarkIdAstIdMap,
|
||||
nodes::{set_parent_for_all, MarkupNode},
|
||||
},
|
||||
slow_pool::{MarkNodeId, SlowPool},
|
||||
syntax_highlight::HighlightStyle,
|
||||
};
|
||||
|
||||
pub fn header_to_markup(app_header: &AppHeader, mark_node_pool: &mut SlowPool) -> MarkNodeId {
|
||||
use super::from_def2::add_node;
|
||||
|
||||
pub fn header_to_markup(
|
||||
app_header: &AppHeader,
|
||||
mark_node_pool: &mut SlowPool,
|
||||
mark_id_ast_id_map: &mut MarkIdAstIdMap,
|
||||
) -> MarkNodeId {
|
||||
let expr_id = app_header.ast_node_id;
|
||||
let ast_node_id = ASTNodeId::AExprId(expr_id);
|
||||
|
||||
let app_node_id = header_mn("app ".to_owned(), expr_id, mark_node_pool);
|
||||
let app_node_id = header_mn(
|
||||
"app ".to_owned(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let app_name_node_id = header_val_mn(
|
||||
app_header.app_name.clone(),
|
||||
expr_id,
|
||||
ast_node_id,
|
||||
HighlightStyle::String,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let full_app_node = MarkupNode::Nested {
|
||||
ast_node_id,
|
||||
children_ids: vec![app_node_id, app_name_node_id],
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 1,
|
||||
};
|
||||
|
||||
let packages_node_id = header_mn(" packages ".to_owned(), expr_id, mark_node_pool);
|
||||
let packages_node_id = header_mn(
|
||||
" packages ".to_owned(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let pack_left_acc_node_id = mark_node_pool.add(new_left_accolade_mn(expr_id, None));
|
||||
let pack_left_acc_node_id = add_node(
|
||||
new_left_accolade_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let pack_base_node_id = header_val_mn(
|
||||
"base: ".to_owned(),
|
||||
expr_id,
|
||||
ast_node_id,
|
||||
HighlightStyle::RecordField,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let pack_val_node_id = header_val_mn(
|
||||
app_header.packages_base.clone(),
|
||||
expr_id,
|
||||
ast_node_id,
|
||||
HighlightStyle::String,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let pack_right_acc_node_id = mark_node_pool.add(new_right_accolade_mn(expr_id, None));
|
||||
let pack_right_acc_node_id = add_node(
|
||||
new_right_accolade_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let full_packages_node = MarkupNode::Nested {
|
||||
ast_node_id,
|
||||
children_ids: vec![
|
||||
packages_node_id,
|
||||
pack_left_acc_node_id,
|
||||
|
@ -66,18 +94,34 @@ pub fn header_to_markup(app_header: &AppHeader, mark_node_pool: &mut SlowPool) -
|
|||
newlines_at_end: 1,
|
||||
};
|
||||
|
||||
let imports_node_id = header_mn(" imports ".to_owned(), expr_id, mark_node_pool);
|
||||
let imports_node_id = header_mn(
|
||||
" imports ".to_owned(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let imports_left_square_node_id = mark_node_pool.add(new_left_square_mn(expr_id, None));
|
||||
let imports_left_square_node_id = add_node(
|
||||
new_left_square_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let mut import_child_ids: Vec<MarkNodeId> = add_header_mn_list(
|
||||
&app_header.imports,
|
||||
expr_id,
|
||||
ast_node_id,
|
||||
HighlightStyle::Import,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let imports_right_square_node_id = mark_node_pool.add(new_right_square_mn(expr_id, None));
|
||||
let imports_right_square_node_id = add_node(
|
||||
new_right_square_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let mut full_import_children = vec![imports_node_id, imports_left_square_node_id];
|
||||
|
||||
|
@ -85,26 +129,46 @@ pub fn header_to_markup(app_header: &AppHeader, mark_node_pool: &mut SlowPool) -
|
|||
full_import_children.push(imports_right_square_node_id);
|
||||
|
||||
let full_import_node = MarkupNode::Nested {
|
||||
ast_node_id,
|
||||
children_ids: full_import_children,
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 1,
|
||||
};
|
||||
|
||||
let provides_node_id = header_mn(" provides ".to_owned(), expr_id, mark_node_pool);
|
||||
let provides_node_id = header_mn(
|
||||
" provides ".to_owned(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let provides_left_square_node_id = mark_node_pool.add(new_left_square_mn(expr_id, None));
|
||||
let provides_left_square_node_id = add_node(
|
||||
new_left_square_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let mut provides_val_node_ids: Vec<MarkNodeId> = add_header_mn_list(
|
||||
&app_header.provides,
|
||||
expr_id,
|
||||
ast_node_id,
|
||||
HighlightStyle::Provides,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let provides_right_square_node_id = mark_node_pool.add(new_right_square_mn(expr_id, None));
|
||||
let provides_right_square_node_id = add_node(
|
||||
new_right_square_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let provides_end_node_id = header_mn(" to base".to_owned(), expr_id, mark_node_pool);
|
||||
let provides_end_node_id = header_mn(
|
||||
" to base".to_owned(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let mut full_provides_children = vec![provides_node_id, provides_left_square_node_id];
|
||||
|
||||
|
@ -113,19 +177,37 @@ pub fn header_to_markup(app_header: &AppHeader, mark_node_pool: &mut SlowPool) -
|
|||
full_provides_children.push(provides_end_node_id);
|
||||
|
||||
let full_provides_node = MarkupNode::Nested {
|
||||
ast_node_id,
|
||||
children_ids: full_provides_children,
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 1,
|
||||
};
|
||||
|
||||
let full_app_node_id = mark_node_pool.add(full_app_node);
|
||||
let full_packages_node = mark_node_pool.add(full_packages_node);
|
||||
let full_import_node_id = mark_node_pool.add(full_import_node);
|
||||
let full_provides_node_id = mark_node_pool.add(full_provides_node);
|
||||
let full_app_node_id = add_node(
|
||||
full_app_node,
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
let full_packages_node = add_node(
|
||||
full_packages_node,
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
let full_import_node_id = add_node(
|
||||
full_import_node,
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
let full_provides_node_id = add_node(
|
||||
full_provides_node,
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let header_mark_node = MarkupNode::Nested {
|
||||
ast_node_id,
|
||||
children_ids: vec![
|
||||
full_app_node_id,
|
||||
full_packages_node,
|
||||
|
@ -136,7 +218,12 @@ pub fn header_to_markup(app_header: &AppHeader, mark_node_pool: &mut SlowPool) -
|
|||
newlines_at_end: 1,
|
||||
};
|
||||
|
||||
let header_mn_id = mark_node_pool.add(header_mark_node);
|
||||
let header_mn_id = add_node(
|
||||
header_mark_node,
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
set_parent_for_all(header_mn_id, mark_node_pool);
|
||||
|
||||
|
@ -146,9 +233,10 @@ pub fn header_to_markup(app_header: &AppHeader, mark_node_pool: &mut SlowPool) -
|
|||
// Used for provides and imports
|
||||
fn add_header_mn_list(
|
||||
str_vec: &[String],
|
||||
expr_id: ExprId,
|
||||
ast_node_id: ASTNodeId,
|
||||
highlight_style: HighlightStyle,
|
||||
mark_node_pool: &mut SlowPool,
|
||||
mark_id_ast_id_map: &mut MarkIdAstIdMap,
|
||||
) -> Vec<MarkNodeId> {
|
||||
let nr_of_elts = str_vec.len();
|
||||
|
||||
|
@ -158,13 +246,22 @@ fn add_header_mn_list(
|
|||
.map(|(indx, provide_str)| {
|
||||
let provide_str = header_val_mn(
|
||||
provide_str.to_owned(),
|
||||
expr_id,
|
||||
ast_node_id,
|
||||
highlight_style,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
if indx != nr_of_elts - 1 {
|
||||
vec![provide_str, mark_node_pool.add(new_comma_mn(expr_id, None))]
|
||||
vec![
|
||||
provide_str,
|
||||
add_node(
|
||||
new_comma_mn(),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
),
|
||||
]
|
||||
} else {
|
||||
vec![provide_str]
|
||||
}
|
||||
|
@ -173,33 +270,37 @@ fn add_header_mn_list(
|
|||
.collect()
|
||||
}
|
||||
|
||||
fn header_mn(content: String, expr_id: ExprId, mark_node_pool: &mut SlowPool) -> MarkNodeId {
|
||||
fn header_mn(
|
||||
content: String,
|
||||
ast_node_id: ASTNodeId,
|
||||
mark_node_pool: &mut SlowPool,
|
||||
mark_id_ast_id_map: &mut MarkIdAstIdMap,
|
||||
) -> MarkNodeId {
|
||||
let mark_node = MarkupNode::Text {
|
||||
content,
|
||||
ast_node_id: ASTNodeId::AExprId(expr_id),
|
||||
syn_high_style: HighlightStyle::PackageRelated,
|
||||
attributes: Attributes::default(),
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 0,
|
||||
};
|
||||
|
||||
mark_node_pool.add(mark_node)
|
||||
add_node(mark_node, ast_node_id, mark_node_pool, mark_id_ast_id_map)
|
||||
}
|
||||
|
||||
fn header_val_mn(
|
||||
content: String,
|
||||
expr_id: ExprId,
|
||||
ast_node_id: ASTNodeId,
|
||||
highlight_style: HighlightStyle,
|
||||
mark_node_pool: &mut SlowPool,
|
||||
mark_id_ast_id_map: &mut MarkIdAstIdMap,
|
||||
) -> MarkNodeId {
|
||||
let mark_node = MarkupNode::Text {
|
||||
content,
|
||||
ast_node_id: ASTNodeId::AExprId(expr_id),
|
||||
syn_high_style: highlight_style,
|
||||
attributes: Attributes::default(),
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 0,
|
||||
};
|
||||
|
||||
mark_node_pool.add(mark_node)
|
||||
add_node(mark_node, ast_node_id, mark_node_pool, mark_id_ast_id_map)
|
||||
}
|
||||
|
|
29
code_markup/src/markup/mark_id_ast_id_map.rs
Normal file
29
code_markup/src/markup/mark_id_ast_id_map.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use roc_ast::lang::core::ast::ASTNodeId;
|
||||
|
||||
use crate::markup_error::MarkNodeIdWithoutCorrespondingASTNodeId;
|
||||
use crate::{markup_error::MarkResult, slow_pool::MarkNodeId};
|
||||
|
||||
/// A hashmap is wrapped to allow for an easy swap out with more performant alternatives
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MarkIdAstIdMap {
|
||||
map: HashMap<MarkNodeId, ASTNodeId>,
|
||||
}
|
||||
|
||||
impl MarkIdAstIdMap {
|
||||
pub fn insert(&mut self, mn_id: MarkNodeId, ast_id: ASTNodeId) {
|
||||
self.map.insert(mn_id, ast_id);
|
||||
}
|
||||
|
||||
pub fn get(&self, mn_id: MarkNodeId) -> MarkResult<ASTNodeId> {
|
||||
match self.map.get(&mn_id) {
|
||||
Some(ast_node_id) => Ok(*ast_node_id),
|
||||
None => MarkNodeIdWithoutCorrespondingASTNodeId {
|
||||
node_id: mn_id,
|
||||
keys_str: format!("{:?}", self.map.keys()),
|
||||
}
|
||||
.fail(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
pub mod attribute;
|
||||
pub mod common_nodes;
|
||||
pub mod convert;
|
||||
pub mod mark_id_ast_id_map;
|
||||
pub mod nodes;
|
||||
pub mod top_level_def;
|
||||
|
|
|
@ -4,7 +4,10 @@ use crate::{
|
|||
syntax_highlight::HighlightStyle,
|
||||
};
|
||||
|
||||
use super::{attribute::Attributes, common_nodes::new_comma_mn_ast};
|
||||
use super::{
|
||||
attribute::Attributes, common_nodes::new_comma_mn, convert::from_def2::add_node,
|
||||
mark_id_ast_id_map::MarkIdAstIdMap,
|
||||
};
|
||||
|
||||
use crate::markup_error::{ExpectedTextNode, NestedNodeMissingChild, NestedNodeRequired};
|
||||
use itertools::Itertools;
|
||||
|
@ -18,42 +21,29 @@ use std::fmt;
|
|||
#[derive(Debug)]
|
||||
pub enum MarkupNode {
|
||||
Nested {
|
||||
ast_node_id: ASTNodeId,
|
||||
children_ids: Vec<MarkNodeId>,
|
||||
parent_id_opt: Option<MarkNodeId>,
|
||||
newlines_at_end: usize,
|
||||
},
|
||||
Text {
|
||||
content: String,
|
||||
ast_node_id: ASTNodeId,
|
||||
syn_high_style: HighlightStyle,
|
||||
attributes: Attributes,
|
||||
parent_id_opt: Option<MarkNodeId>,
|
||||
newlines_at_end: usize,
|
||||
},
|
||||
Blank {
|
||||
ast_node_id: ASTNodeId,
|
||||
attributes: Attributes,
|
||||
parent_id_opt: Option<MarkNodeId>,
|
||||
newlines_at_end: usize,
|
||||
},
|
||||
Indent {
|
||||
ast_node_id: ASTNodeId,
|
||||
indent_level: usize,
|
||||
parent_id_opt: Option<MarkNodeId>,
|
||||
},
|
||||
}
|
||||
|
||||
impl MarkupNode {
|
||||
pub fn get_ast_node_id(&self) -> ASTNodeId {
|
||||
match self {
|
||||
MarkupNode::Nested { ast_node_id, .. } => *ast_node_id,
|
||||
MarkupNode::Text { ast_node_id, .. } => *ast_node_id,
|
||||
MarkupNode::Blank { ast_node_id, .. } => *ast_node_id,
|
||||
MarkupNode::Indent { ast_node_id, .. } => *ast_node_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_parent_id_opt(&self) -> Option<MarkNodeId> {
|
||||
match self {
|
||||
MarkupNode::Nested { parent_id_opt, .. } => *parent_id_opt,
|
||||
|
@ -85,24 +75,24 @@ impl MarkupNode {
|
|||
// return (index of child in list of children, closest ast index of child corresponding to ast node)
|
||||
pub fn get_child_indices(
|
||||
&self,
|
||||
child_id: MarkNodeId,
|
||||
mark_node_pool: &SlowPool,
|
||||
mark_node_id: MarkNodeId,
|
||||
ast_node_id: ASTNodeId,
|
||||
mark_id_ast_id_map: &MarkIdAstIdMap,
|
||||
) -> MarkResult<(usize, usize)> {
|
||||
match self {
|
||||
MarkupNode::Nested { children_ids, .. } => {
|
||||
let mut mark_child_index_opt: Option<usize> = None;
|
||||
let mut child_ids_with_ast: Vec<MarkNodeId> = Vec::new();
|
||||
let self_ast_id = self.get_ast_node_id();
|
||||
|
||||
for (indx, &mark_child_id) in children_ids.iter().enumerate() {
|
||||
if mark_child_id == child_id {
|
||||
if mark_child_id == mark_node_id {
|
||||
mark_child_index_opt = Some(indx);
|
||||
}
|
||||
|
||||
let child_mark_node = mark_node_pool.get(mark_child_id);
|
||||
let child_ast_node_id = mark_id_ast_id_map.get(mark_child_id)?;
|
||||
// a node that points to the same ast_node as the parent is a ',', '[', ']'
|
||||
// those are not "real" ast children
|
||||
if child_mark_node.get_ast_node_id() != self_ast_id {
|
||||
if child_ast_node_id != ast_node_id {
|
||||
child_ids_with_ast.push(mark_child_id)
|
||||
}
|
||||
}
|
||||
|
@ -145,7 +135,7 @@ impl MarkupNode {
|
|||
}
|
||||
} else {
|
||||
NestedNodeMissingChild {
|
||||
node_id: child_id,
|
||||
node_id: mark_node_id,
|
||||
children_ids: children_ids.clone(),
|
||||
}
|
||||
.fail()
|
||||
|
@ -258,6 +248,14 @@ impl MarkupNode {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn make_nested_mn(children_ids: Vec<MarkNodeId>, newlines_at_end: usize) -> MarkupNode {
|
||||
MarkupNode::Nested {
|
||||
children_ids,
|
||||
parent_id_opt: None,
|
||||
newlines_at_end,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_string<'a>(env: &Env<'a>, pool_str: &PoolStr) -> String {
|
||||
pool_str.as_str(env.pool).to_owned()
|
||||
}
|
||||
|
@ -269,6 +267,7 @@ pub const LEFT_SQUARE_BR: &str = "[ ";
|
|||
pub const RIGHT_SQUARE_BR: &str = " ]";
|
||||
pub const COLON: &str = ": ";
|
||||
pub const COMMA: &str = ", ";
|
||||
pub const DOT: &str = ".";
|
||||
pub const STRING_QUOTES: &str = "\"\"";
|
||||
pub const EQUALS: &str = " = ";
|
||||
pub const ARROW: &str = " -> ";
|
||||
|
@ -279,36 +278,34 @@ pub fn new_markup_node(
|
|||
node_id: ASTNodeId,
|
||||
highlight_style: HighlightStyle,
|
||||
mark_node_pool: &mut SlowPool,
|
||||
mark_id_ast_id_map: &mut MarkIdAstIdMap,
|
||||
indent_level: usize,
|
||||
) -> MarkNodeId {
|
||||
let content_node = MarkupNode::Text {
|
||||
content: text,
|
||||
ast_node_id: node_id,
|
||||
syn_high_style: highlight_style,
|
||||
attributes: Attributes::default(),
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 0,
|
||||
};
|
||||
|
||||
let content_node_id = mark_node_pool.add(content_node);
|
||||
let content_node_id = add_node(content_node, node_id, mark_node_pool, mark_id_ast_id_map);
|
||||
|
||||
if indent_level > 0 {
|
||||
let indent_node = MarkupNode::Indent {
|
||||
ast_node_id: node_id,
|
||||
indent_level,
|
||||
parent_id_opt: None,
|
||||
};
|
||||
|
||||
let indent_node_id = mark_node_pool.add(indent_node);
|
||||
let indent_node_id = add_node(indent_node, node_id, mark_node_pool, mark_id_ast_id_map);
|
||||
|
||||
let nested_node = MarkupNode::Nested {
|
||||
ast_node_id: node_id,
|
||||
children_ids: vec![indent_node_id, content_node_id],
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 0,
|
||||
};
|
||||
|
||||
mark_node_pool.add(nested_node)
|
||||
add_node(nested_node, node_id, mark_node_pool, mark_id_ast_id_map)
|
||||
} else {
|
||||
content_node_id
|
||||
}
|
||||
|
@ -318,7 +315,6 @@ pub fn set_parent_for_all(markup_node_id: MarkNodeId, mark_node_pool: &mut SlowP
|
|||
let node = mark_node_pool.get(markup_node_id);
|
||||
|
||||
if let MarkupNode::Nested {
|
||||
ast_node_id: _,
|
||||
children_ids,
|
||||
parent_id_opt: _,
|
||||
newlines_at_end: _,
|
||||
|
@ -426,7 +422,6 @@ pub fn get_root_mark_node_id(mark_node_id: MarkNodeId, mark_node_pool: &SlowPool
|
|||
pub fn join_mark_nodes_spaces(
|
||||
mark_nodes_ids: Vec<MarkNodeId>,
|
||||
with_prepend: bool,
|
||||
ast_node_id: ASTNodeId,
|
||||
mark_node_pool: &mut SlowPool,
|
||||
) -> Vec<MarkNodeId> {
|
||||
let space_range_max = if with_prepend {
|
||||
|
@ -439,7 +434,6 @@ pub fn join_mark_nodes_spaces(
|
|||
.map(|_| {
|
||||
let space_node = MarkupNode::Text {
|
||||
content: " ".to_string(),
|
||||
ast_node_id,
|
||||
syn_high_style: HighlightStyle::Blank,
|
||||
attributes: Attributes::default(),
|
||||
parent_id_opt: None,
|
||||
|
@ -458,12 +452,9 @@ pub fn join_mark_nodes_spaces(
|
|||
}
|
||||
|
||||
// put comma mark nodes between each node in mark_nodes
|
||||
pub fn join_mark_nodes_commas(
|
||||
mark_nodes: Vec<MarkupNode>,
|
||||
ast_node_id: ASTNodeId,
|
||||
) -> Vec<MarkupNode> {
|
||||
pub fn join_mark_nodes_commas(mark_nodes: Vec<MarkupNode>) -> Vec<MarkupNode> {
|
||||
let join_nodes: Vec<MarkupNode> = (0..(mark_nodes.len() - 1))
|
||||
.map(|_| new_comma_mn_ast(ast_node_id, None))
|
||||
.map(|_| new_comma_mn())
|
||||
.collect();
|
||||
|
||||
mark_nodes.into_iter().interleave(join_nodes).collect()
|
||||
|
|
|
@ -14,37 +14,43 @@ use crate::{
|
|||
syntax_highlight::HighlightStyle,
|
||||
};
|
||||
|
||||
// Top Level Defined Value. example: `main = "Hello, World!"`
|
||||
pub fn tld_mark_node<'a>(
|
||||
use super::{
|
||||
common_nodes::new_assign_mn, convert::from_def2::add_node, mark_id_ast_id_map::MarkIdAstIdMap,
|
||||
};
|
||||
|
||||
// represents for example: `main = "Hello, World!"`
|
||||
pub fn assignment_mark_node<'a>(
|
||||
identifier_id: IdentId,
|
||||
expr_mark_node_id: MarkNodeId,
|
||||
ast_node_id: ASTNodeId,
|
||||
mark_node_pool: &mut SlowPool,
|
||||
mark_id_ast_id_map: &mut MarkIdAstIdMap,
|
||||
env: &Env<'a>,
|
||||
) -> ASTResult<MarkupNode> {
|
||||
let val_name = env.ident_ids.get_name_str_res(identifier_id)?;
|
||||
|
||||
let val_name_mn = MarkupNode::Text {
|
||||
content: val_name.to_owned(),
|
||||
ast_node_id,
|
||||
syn_high_style: HighlightStyle::Value,
|
||||
attributes: Attributes::default(),
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 0,
|
||||
};
|
||||
|
||||
let val_name_mn_id = mark_node_pool.add(val_name_mn);
|
||||
let val_name_mn_id = add_node(val_name_mn, ast_node_id, mark_node_pool, mark_id_ast_id_map);
|
||||
|
||||
let equals_mn_id = mark_node_pool.add(new_equals_mn(ast_node_id, None));
|
||||
|
||||
let full_let_node = MarkupNode::Nested {
|
||||
let equals_mn_id = add_node(
|
||||
new_equals_mn(),
|
||||
ast_node_id,
|
||||
children_ids: vec![val_name_mn_id, equals_mn_id, expr_mark_node_id],
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 3,
|
||||
};
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
Ok(full_let_node)
|
||||
Ok(new_assign_mn(
|
||||
val_name_mn_id,
|
||||
equals_mn_id,
|
||||
expr_mark_node_id,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn tld_w_comments_mark_node(
|
||||
|
@ -52,9 +58,15 @@ pub fn tld_w_comments_mark_node(
|
|||
def_mark_node_id: MarkNodeId,
|
||||
ast_node_id: ASTNodeId,
|
||||
mark_node_pool: &mut SlowPool,
|
||||
mark_id_ast_id_map: &mut MarkIdAstIdMap,
|
||||
comments_before: bool,
|
||||
) -> ASTResult<MarkupNode> {
|
||||
let comment_mn_id = mark_node_pool.add(new_comments_mn(comments, ast_node_id, 1));
|
||||
let comment_mn_id = add_node(
|
||||
new_comments_mn(comments, 1),
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
mark_id_ast_id_map,
|
||||
);
|
||||
|
||||
let children_ids = if comments_before {
|
||||
vec![comment_mn_id, def_mark_node_id]
|
||||
|
@ -63,7 +75,6 @@ pub fn tld_w_comments_mark_node(
|
|||
};
|
||||
|
||||
let tld_w_comment_node = MarkupNode::Nested {
|
||||
ast_node_id,
|
||||
children_ids,
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 2,
|
||||
|
|
|
@ -24,6 +24,16 @@ pub enum MarkError {
|
|||
node_type: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
#[snafu(display(
|
||||
"MarkNodeIdWithoutCorrespondingASTNodeId: MarkupNode with id {} was not found in MarkIdAstIdMap, available keys are: {}.",
|
||||
node_id,
|
||||
keys_str
|
||||
))]
|
||||
MarkNodeIdWithoutCorrespondingASTNodeId {
|
||||
node_id: MarkNodeId,
|
||||
keys_str: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
#[snafu(display("NestedNodeMissingChild: expected to find child with id {} in Nested MarkupNode, but it was missing. Id's of the children are {:?}.", node_id, children_ids))]
|
||||
NestedNodeMissingChild {
|
||||
node_id: MarkNodeId,
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
use std::fmt;
|
||||
|
||||
use crate::markup::nodes::MarkupNode;
|
||||
use crate::markup::{mark_id_ast_id_map::MarkIdAstIdMap, nodes::MarkupNode};
|
||||
|
||||
pub type MarkNodeId = usize;
|
||||
|
||||
|
@ -34,14 +32,15 @@ impl SlowPool {
|
|||
// TODO delete children of old node, this requires SlowPool to be changed to
|
||||
// make sure the indexes still make sense after removal/compaction
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SlowPool {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "\n\n(mark_node_pool)\n")?;
|
||||
pub fn debug_string(&self, mark_id_ast_id_map: &MarkIdAstIdMap) -> String {
|
||||
let mut ret_str = String::new();
|
||||
|
||||
for (index, node) in self.nodes.iter().enumerate() {
|
||||
let ast_node_id_str = format!("{:?}", node.get_ast_node_id());
|
||||
for (mark_node_id, node) in self.nodes.iter().enumerate() {
|
||||
let ast_node_id_str = match mark_id_ast_id_map.get(mark_node_id) {
|
||||
Ok(ast_id) => format!("{:?}", ast_id),
|
||||
Err(err) => format!("{:?}", err),
|
||||
};
|
||||
let ast_node_id: String = ast_node_id_str
|
||||
.chars()
|
||||
.filter(|c| c.is_ascii_digit())
|
||||
|
@ -55,17 +54,16 @@ impl fmt::Display for SlowPool {
|
|||
child_str = format!("children: {:?}", node_children);
|
||||
}
|
||||
|
||||
writeln!(
|
||||
f,
|
||||
ret_str.push_str(&format!(
|
||||
"{}: {} ({}) ast_id {:?} {}",
|
||||
index,
|
||||
mark_node_id,
|
||||
node.node_type_as_string(),
|
||||
node.get_content(),
|
||||
ast_node_id.parse::<usize>().unwrap(),
|
||||
child_str
|
||||
)?;
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
ret_str
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ use crate::colors::{from_hsb, RgbaTup};
|
|||
#[derive(Hash, Eq, PartialEq, Copy, Clone, Debug, Deserialize, Serialize)]
|
||||
pub enum HighlightStyle {
|
||||
Operator, // =+-<>...
|
||||
Comma,
|
||||
String,
|
||||
FunctionName,
|
||||
FunctionArgName,
|
||||
|
@ -21,6 +20,9 @@ pub enum HighlightStyle {
|
|||
Blank,
|
||||
Comment,
|
||||
DocsComment,
|
||||
UppercaseIdent,
|
||||
LowercaseIdent, // TODO we probably don't want all lowercase identifiers to have the same color?
|
||||
Keyword, // if, else, when...
|
||||
}
|
||||
|
||||
pub fn default_highlight_map() -> HashMap<HighlightStyle, RgbaTup> {
|
||||
|
@ -31,7 +33,6 @@ pub fn default_highlight_map() -> HashMap<HighlightStyle, RgbaTup> {
|
|||
let mut highlight_map = HashMap::new();
|
||||
[
|
||||
(Operator, from_hsb(185, 50, 75)),
|
||||
(Comma, from_hsb(258, 50, 90)),
|
||||
(String, from_hsb(346, 65, 97)),
|
||||
(FunctionName, almost_white),
|
||||
(FunctionArgName, from_hsb(225, 50, 100)),
|
||||
|
@ -46,6 +47,9 @@ pub fn default_highlight_map() -> HashMap<HighlightStyle, RgbaTup> {
|
|||
(Blank, from_hsb(258, 50, 90)),
|
||||
(Comment, from_hsb(258, 50, 90)), // TODO check color
|
||||
(DocsComment, from_hsb(258, 50, 90)), // TODO check color
|
||||
(UppercaseIdent, almost_white),
|
||||
(LowercaseIdent, from_hsb(225, 50, 100)),
|
||||
(Keyword, almost_white),
|
||||
]
|
||||
.iter()
|
||||
.for_each(|tup| {
|
||||
|
|
|
@ -1511,6 +1511,16 @@ fn expr_spec<'a>(
|
|||
|
||||
builder.add_make_named(block, MOD_APP, type_name, tag_value_id)
|
||||
}
|
||||
ExprBox { symbol } => {
|
||||
let value_id = env.symbols[symbol];
|
||||
|
||||
with_new_heap_cell(builder, block, value_id)
|
||||
}
|
||||
ExprUnbox { symbol } => {
|
||||
let tuple_id = env.symbols[symbol];
|
||||
|
||||
builder.add_get_tuple_field(block, tuple_id, BOX_VALUE_INDEX)
|
||||
}
|
||||
Struct(fields) => build_tuple_value(builder, env, block, fields),
|
||||
UnionAtIndex {
|
||||
index,
|
||||
|
@ -1705,6 +1715,13 @@ fn layout_spec_help(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Boxed(inner_layout) => {
|
||||
let inner_type = layout_spec_help(builder, inner_layout, when_recursive)?;
|
||||
let cell_type = builder.add_heap_cell_type();
|
||||
|
||||
builder.add_tuple_type(&[cell_type, inner_type])
|
||||
}
|
||||
RecursivePointer => match when_recursive {
|
||||
WhenRecursive::Unreachable => {
|
||||
unreachable!()
|
||||
|
@ -1787,6 +1804,10 @@ const LIST_BAG_INDEX: u32 = 1;
|
|||
const DICT_CELL_INDEX: u32 = LIST_CELL_INDEX;
|
||||
const DICT_BAG_INDEX: u32 = LIST_BAG_INDEX;
|
||||
|
||||
#[allow(dead_code)]
|
||||
const BOX_CELL_INDEX: u32 = LIST_CELL_INDEX;
|
||||
const BOX_VALUE_INDEX: u32 = LIST_BAG_INDEX;
|
||||
|
||||
const TAG_CELL_INDEX: u32 = 0;
|
||||
const TAG_DATA_INDEX: u32 = 1;
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ roc_region = { path = "../region" }
|
|||
roc_module = { path = "../module" }
|
||||
roc_types = { path = "../types" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
lazy_static = "1.4.0"
|
||||
|
||||
[build-dependencies]
|
||||
# dunce can be removed once ziglang/zig#5109 is fixed
|
||||
|
|
|
@ -887,14 +887,23 @@ pub fn listSublist(
|
|||
}
|
||||
|
||||
const keep_len = std.math.min(len, size - start);
|
||||
const drop_len = std.math.max(start, 0);
|
||||
const drop_start_len = start;
|
||||
const drop_end_len = size - (start + keep_len);
|
||||
|
||||
// Decrement the reference counts of elements before `start`.
|
||||
var i: usize = 0;
|
||||
while (i < drop_len) : (i += 1) {
|
||||
while (i < drop_start_len) : (i += 1) {
|
||||
const element = source_ptr + i * element_width;
|
||||
dec(element);
|
||||
}
|
||||
|
||||
// Decrement the reference counts of elements after `start + keep_len`.
|
||||
i = 0;
|
||||
while (i < drop_end_len) : (i += 1) {
|
||||
const element = source_ptr + (start + keep_len + i) * element_width;
|
||||
dec(element);
|
||||
}
|
||||
|
||||
const output = RocList.allocate(alignment, keep_len, element_width);
|
||||
const target_ptr = output.bytes orelse unreachable;
|
||||
|
||||
|
|
|
@ -18,10 +18,118 @@ interface Dict
|
|||
]
|
||||
imports []
|
||||
|
||||
## A [dictionary](https://en.wikipedia.org/wiki/Associative_array) that lets you can associate keys with values.
|
||||
##
|
||||
## ### Inserting
|
||||
##
|
||||
## The most basic way to use a dictionary is to start with an empty one and then:
|
||||
## 1. Call [Dict.insert] passing a key and a value, to associate that key with that value in the dictionary.
|
||||
## 2. Later, call [Dict.get] passing the same key as before, and it will return the value you stored.
|
||||
##
|
||||
## Here's an example of a dictionary which uses a city's name as the key, and its population as the associated value.
|
||||
##
|
||||
## populationByCity =
|
||||
## Dict.empty
|
||||
## |> Dict.insert "London" 8_961_989
|
||||
## |> Dict.insert "Philadelphia" 1_603_797
|
||||
## |> Dict.insert "Shanghai" 24_870_895
|
||||
## |> Dict.insert "Delhi" 16_787_941
|
||||
## |> Dict.insert "Amsterdam" 872_680
|
||||
##
|
||||
## ### Converting to a [List]
|
||||
##
|
||||
## We can call [Dict.toList] on `populationByCity` to turn it into a list of key-value pairs:
|
||||
##
|
||||
## Dict.toList populationByCity == [
|
||||
## { k: "London", v: 8961989 },
|
||||
## { k: "Philadelphia", v: 1603797 },
|
||||
## { k: "Shanghai", v: 24870895 },
|
||||
## { k: "Delhi", v: 16787941 },
|
||||
## { k: "Amsterdam", v: 872680 },
|
||||
## ]
|
||||
##
|
||||
## We can use the similar [Dict.keyList] and [Dict.values] functions to get only the keys or only the values,
|
||||
## instead of getting these `{ k, v }` records that contain both.
|
||||
##
|
||||
## You may notice that these lists have the same order as the original insertion order. This will be true if
|
||||
## all you ever do is [insert] and [get] operations on the dictionary, but [remove] operations can change this order.
|
||||
## Let's see how that looks.
|
||||
##
|
||||
## ### Removing
|
||||
##
|
||||
## We can remove an element from the dictionary, like so:
|
||||
##
|
||||
## populationByCity
|
||||
## |> Dict.remove "Philadelphia"
|
||||
## |> Dict.toList
|
||||
## ==
|
||||
## [
|
||||
## { k: "London", v: 8961989 },
|
||||
## { k: "Amsterdam", v: 872680 },
|
||||
## { k: "Shanghai", v: 24870895 },
|
||||
## { k: "Delhi", v: 16787941 },
|
||||
## ]
|
||||
##
|
||||
## Notice that the order changed! Philadelphia has been not only removed from the list, but Amsterdam - the last
|
||||
## entry we inserted - has been moved into the spot where Philadelphia was previously. This is exactly what
|
||||
## [Dict.remove] does: it removes an element and moves the most recent insertion into the vacated spot.
|
||||
##
|
||||
## This move is done as a performance optimization, and it lets [remove] have
|
||||
## [constant time complexity](https://en.wikipedia.org/wiki/Time_complexity#Constant_time). If you need a removal
|
||||
## operation which preserves ordering, [Dict.removeShift] will remove the element and then shift everything after it
|
||||
## over one spot. Be aware that this shifting requires copying every single entry after the removed element, though,
|
||||
## so it can be massively more costly than [remove]! This makes [remove] the recommended default choice;
|
||||
## [removeShift] should only be used if maintaining original insertion order is absolutely necessary.
|
||||
##
|
||||
##
|
||||
## ### Removing
|
||||
##
|
||||
## ### Equality
|
||||
##
|
||||
## When comparing two dictionaries for equality, they are `==` only if their both their contents and their
|
||||
## orderings match. This preserves the property that if `dict1 == dict2`, you should be able to rely on
|
||||
## `fn dict1 == fn dict2` also being `True`, even if `fn` relies on the dictionary's ordering (for example, if
|
||||
## `fn` is `Dict.toList` or calls it internally.)
|
||||
##
|
||||
## The [Dict.hasSameContents] function gives an alternative to `==` which ignores ordering
|
||||
## and returns `True` if both dictionaries have the same keys and associated values.
|
||||
Dict k v : [ @Dict k v ] # TODO k should require a hashing and equating constraint
|
||||
|
||||
## An empty dictionary.
|
||||
empty : Dict * *
|
||||
|
||||
size : Dict * * -> Nat
|
||||
|
||||
isEmpty : Dict * * -> Bool
|
||||
|
||||
## Returns a [List] of the dictionary's key/value pairs.
|
||||
##
|
||||
## See [walk] to walk over the key/value pairs without creating an intermediate data structure.
|
||||
toList : Dict k v -> List { k, v }
|
||||
|
||||
## Returns a [List] of the dictionary's keys.
|
||||
##
|
||||
## See [keySet] to get a [Set] of keys instead, or [walkKeys] to walk over the keys without creating
|
||||
## an intermediate data structure.
|
||||
keyList : Dict key * -> List key
|
||||
|
||||
## Returns a [Set] of the dictionary's keys.
|
||||
##
|
||||
## See [keyList] to get a [List] of keys instead, or [walkKeys] to walk over the keys without creating
|
||||
## an intermediate data structure.
|
||||
keySet : Dict key * -> Set key
|
||||
|
||||
## Returns a [List] of the dictionary's values.
|
||||
##
|
||||
## See [walkValues] to walk over the values without creating an intermediate data structure.
|
||||
values : Dict * value -> List value
|
||||
|
||||
walk : Dict k v, state, (state, k, v -> state) -> state
|
||||
|
||||
walkKeys : Dict key *, state, (state, key -> state) -> state
|
||||
|
||||
walkValues : Dict * value, state, (state, value -> state) -> state
|
||||
|
||||
## Convert each key and value in the #Dict to something new, by calling a conversion
|
||||
## function on each of them. Then return a new #Map of the converted keys and values.
|
||||
##
|
||||
|
@ -32,9 +140,9 @@ isEmpty : Dict * * -> Bool
|
|||
## `map` functions like this are common in Roc, and they all work similarly.
|
||||
## See for example [List.map], [Result.map], and `Set.map`.
|
||||
map :
|
||||
Dict beforeKey beforeValue,
|
||||
({ key: beforeKey, value: beforeValue } -> { key: afterKey, value: afterValue })
|
||||
-> Dict afterKey afterValue
|
||||
Dict beforeKey beforeVal,
|
||||
({ k: beforeKey, v: beforeVal } -> { k: afterKey, v: afterVal })
|
||||
-> Dict afterKey afterVal
|
||||
|
||||
# DESIGN NOTES: The reason for panicking when given NaN is that:
|
||||
# * If we allowed NaN in, Dict.insert would no longer be idempotent.
|
||||
|
@ -47,3 +155,56 @@ map :
|
|||
## defined to be unequal to *NaN*, inserting a *NaN* key results in an entry
|
||||
## that can never be retrieved or removed from the [Dict].
|
||||
insert : Dict key val, key, val -> Dict key val
|
||||
|
||||
## Removes a key from the dictionary in [constant time](https://en.wikipedia.org/wiki/Time_complexity#Constant_time), without preserving insertion order.
|
||||
##
|
||||
## Since the internal [List] which determines the order of operations like [toList] and [walk] cannot have gaps in it,
|
||||
## whenever an element is removed from the middle of that list, something must be done to eliminate the resulting gap.
|
||||
##
|
||||
## * [removeShift] eliminates the gap by shifting over every element after the removed one. This takes [linear time](https://en.wikipedia.org/wiki/Time_complexity#Linear_time),
|
||||
## and preserves the original ordering.
|
||||
## * [remove] eliminates the gap by replacing the removed element with the one at the end of the list - that is, the most recent insertion. This takes [constant time](https://en.wikipedia.org/wiki/Time_complexity#Constant_time), but does not preserve the original ordering.
|
||||
##
|
||||
## For example, suppose we have a `populationByCity` with these contents:
|
||||
##
|
||||
## Dict.toList populationByCity == [
|
||||
## { k: "London", v: 8961989 },
|
||||
## { k: "Philadelphia", v: 1603797 },
|
||||
## { k: "Shanghai", v: 24870895 },
|
||||
## { k: "Delhi", v: 16787941 },
|
||||
## { k: "Amsterdam", v: 872680 },
|
||||
## ]
|
||||
##
|
||||
## Using `Dict.remove "Philadelphia"` on this will replace the `"Philadelphia"` entry with the most recent insertion,
|
||||
## which is `"Amsterdam"` in this case.
|
||||
##
|
||||
## populationByCity
|
||||
## |> Dict.remove "Philadelphia"
|
||||
## |> Dict.toList
|
||||
## ==
|
||||
## [
|
||||
## { k: "London", v: 8961989 },
|
||||
## { k: "Amsterdam", v: 872680 },
|
||||
## { k: "Shanghai", v: 24870895 },
|
||||
## { k: "Delhi", v: 16787941 },
|
||||
## ]
|
||||
##
|
||||
## Both [remove] and [removeShift] leave the dictionary with the same contents; they only differ in ordering and in
|
||||
## performance. Since ordering only affects operations like [toList] and [walk], [remove] is the better default
|
||||
## choice because it has much better performance characteristics; [removeShift] should only be used when it's
|
||||
## absolutely necessary for operations like [toList] and [walk] to preserve the exact original insertion order.
|
||||
remove : Dict k v, k -> Dict k v
|
||||
|
||||
## Removes a key from the dictionary in [linear time](https://en.wikipedia.org/wiki/Time_complexity#Linear_time), while preserving insertion order.
|
||||
##
|
||||
## It's better to use [remove] than this by default, since [remove] has [constant time complexity](https://en.wikipedia.org/wiki/Time_complexity#Constant_time),
|
||||
## which commonly leads [removeShift] to take many times as long to run as [remove] does. However, [remove] does not
|
||||
## preserve insertion order, so the slower [removeShift] exists only for use cases where it's abolutely necessary for
|
||||
## ordering-sensitive functions like [toList] and [walk] to preserve the exact original insertion order.
|
||||
##
|
||||
## See the [remove] documentation for more details about the differences between [remove] and [removeShift].
|
||||
removeShift : Dict k v, k -> Dict k v
|
||||
|
||||
## Returns whether both dictionaries have the same keys, and the same values associated with those keys.
|
||||
## This is different from `==` in that it disregards the ordering of the keys and values.
|
||||
hasSameContents : Dict k v, Dict k v -> Bool
|
||||
|
|
|
@ -120,6 +120,8 @@ interface Num
|
|||
toU64Checked,
|
||||
toU128,
|
||||
toU128Checked,
|
||||
toNat,
|
||||
toNatChecked,
|
||||
toFloat,
|
||||
toStr
|
||||
]
|
||||
|
|
|
@ -3,16 +3,25 @@ use roc_module::ident::TagName;
|
|||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::Region;
|
||||
use roc_types::builtin_aliases::{
|
||||
bool_type, dec_type, dict_type, f32_type, f64_type, float_type, i128_type, i16_type, i32_type,
|
||||
i64_type, i8_type, int_type, list_type, nat_type, num_type, ordering_type, result_type,
|
||||
set_type, str_type, str_utf8_byte_problem_type, u128_type, u16_type, u32_type, u64_type,
|
||||
u8_type,
|
||||
bool_type, box_type, dec_type, dict_type, f32_type, f64_type, float_type, i128_type, i16_type,
|
||||
i32_type, i64_type, i8_type, int_type, list_type, nat_type, num_type, ordering_type,
|
||||
result_type, set_type, str_type, str_utf8_byte_problem_type, u128_type, u16_type, u32_type,
|
||||
u64_type, u8_type,
|
||||
};
|
||||
use roc_types::solved_types::SolvedType;
|
||||
use roc_types::subs::VarId;
|
||||
use roc_types::types::RecordField;
|
||||
use std::collections::HashMap;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref STDLIB: StdLib = standard_stdlib();
|
||||
}
|
||||
|
||||
/// A global static that stores our initialized standard library definitions
|
||||
pub fn borrow_stdlib() -> &'static StdLib {
|
||||
&STDLIB
|
||||
}
|
||||
|
||||
/// Example:
|
||||
///
|
||||
/// let_tvars! { a, b, c }
|
||||
|
@ -592,7 +601,21 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
|||
add_top_level_function_type!(
|
||||
Symbol::NUM_TO_U128_CHECKED,
|
||||
vec![int_type(flex(TVAR1))],
|
||||
Box::new(result_type(u128_type(), out_of_bounds)),
|
||||
Box::new(result_type(u128_type(), out_of_bounds.clone())),
|
||||
);
|
||||
|
||||
// toNat : Int * -> Nat
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_TO_NAT,
|
||||
vec![int_type(flex(TVAR1))],
|
||||
Box::new(nat_type()),
|
||||
);
|
||||
|
||||
// toNatChecked : Int * -> Result Nat [ OutOfBounds ]*
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_TO_NAT_CHECKED,
|
||||
vec![int_type(flex(TVAR1))],
|
||||
Box::new(result_type(nat_type(), out_of_bounds)),
|
||||
);
|
||||
|
||||
// toStr : Num a -> Str
|
||||
|
@ -1782,6 +1805,20 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
|||
Box::new(bool_type()),
|
||||
);
|
||||
|
||||
// Box.box : a -> Box a
|
||||
add_top_level_function_type!(
|
||||
Symbol::BOX_BOX_FUNCTION,
|
||||
vec![flex(TVAR1)],
|
||||
Box::new(box_type(flex(TVAR1))),
|
||||
);
|
||||
|
||||
// Box.unbox : Box a -> a
|
||||
add_top_level_function_type!(
|
||||
Symbol::BOX_UNBOX,
|
||||
vec![box_type(flex(TVAR1))],
|
||||
Box::new(flex(TVAR1)),
|
||||
);
|
||||
|
||||
types
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
use crate::env::Env;
|
||||
use crate::scope::Scope;
|
||||
use roc_collections::all::{ImMap, MutMap, MutSet, SendMap};
|
||||
use roc_error_macros::todo_abilities;
|
||||
use roc_module::ident::{Ident, Lowercase, TagName};
|
||||
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
|
||||
use roc_parse::ast::{AssignedField, Pattern, Tag, TypeAnnotation, TypeHeader};
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::{VarStore, Variable};
|
||||
use roc_types::types::{Alias, AliasKind, LambdaSet, Problem, RecordField, Type};
|
||||
use roc_types::types::{
|
||||
Alias, AliasCommon, AliasKind, LambdaSet, Problem, RecordField, Type, TypeExtension,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Annotation {
|
||||
|
@ -28,25 +31,27 @@ pub struct IntroducedVariables {
|
|||
// But then between annotations, the same name can occur multiple times,
|
||||
// but a variable can only have one name. Therefore
|
||||
// `ftv : SendMap<Variable, Lowercase>`.
|
||||
pub wildcards: Vec<Variable>,
|
||||
pub wildcards: Vec<Loc<Variable>>,
|
||||
pub lambda_sets: Vec<Variable>,
|
||||
pub inferred: Vec<Variable>,
|
||||
pub var_by_name: SendMap<Lowercase, Variable>,
|
||||
pub inferred: Vec<Loc<Variable>>,
|
||||
// NB: A mapping of a -> Loc<v1> in this map has the region of the first-seen var, but there
|
||||
// may be multiple occurrences of it!
|
||||
pub var_by_name: SendMap<Lowercase, Loc<Variable>>,
|
||||
pub name_by_var: SendMap<Variable, Lowercase>,
|
||||
pub host_exposed_aliases: MutMap<Symbol, Variable>,
|
||||
}
|
||||
|
||||
impl IntroducedVariables {
|
||||
pub fn insert_named(&mut self, name: Lowercase, var: Variable) {
|
||||
pub fn insert_named(&mut self, name: Lowercase, var: Loc<Variable>) {
|
||||
self.var_by_name.insert(name.clone(), var);
|
||||
self.name_by_var.insert(var, name);
|
||||
self.name_by_var.insert(var.value, name);
|
||||
}
|
||||
|
||||
pub fn insert_wildcard(&mut self, var: Variable) {
|
||||
pub fn insert_wildcard(&mut self, var: Loc<Variable>) {
|
||||
self.wildcards.push(var);
|
||||
}
|
||||
|
||||
pub fn insert_inferred(&mut self, var: Variable) {
|
||||
pub fn insert_inferred(&mut self, var: Loc<Variable>) {
|
||||
self.inferred.push(var);
|
||||
}
|
||||
|
||||
|
@ -69,7 +74,7 @@ impl IntroducedVariables {
|
|||
}
|
||||
|
||||
pub fn var_by_name(&self, name: &Lowercase) -> Option<&Variable> {
|
||||
self.var_by_name.get(name)
|
||||
self.var_by_name.get(name).map(|v| &v.value)
|
||||
}
|
||||
|
||||
pub fn name_by_var(&self, var: Variable) -> Option<&Lowercase> {
|
||||
|
@ -242,6 +247,7 @@ pub fn find_type_def_symbols(
|
|||
SpaceBefore(inner, _) | SpaceAfter(inner, _) => {
|
||||
stack.push(inner);
|
||||
}
|
||||
Where(..) => todo_abilities!(),
|
||||
Inferred | Wildcard | Malformed(_) => {}
|
||||
}
|
||||
}
|
||||
|
@ -284,7 +290,7 @@ fn can_annotation_help(
|
|||
let ret = can_annotation_help(
|
||||
env,
|
||||
&return_type.value,
|
||||
region,
|
||||
return_type.region,
|
||||
scope,
|
||||
var_store,
|
||||
introduced_variables,
|
||||
|
@ -312,7 +318,7 @@ fn can_annotation_help(
|
|||
let arg_ann = can_annotation_help(
|
||||
env,
|
||||
&arg.value,
|
||||
region,
|
||||
arg.region,
|
||||
scope,
|
||||
var_store,
|
||||
introduced_variables,
|
||||
|
@ -337,6 +343,26 @@ fn can_annotation_help(
|
|||
return error;
|
||||
}
|
||||
|
||||
// For now, aliases of function types cannot be delayed.
|
||||
// This is a limitation of the current implementation,
|
||||
// and this totally should be possible in the future.
|
||||
let is_import = !symbol.is_builtin() && (env.home != symbol.module_id());
|
||||
let is_structural = alias.kind == AliasKind::Structural;
|
||||
if !is_import && is_structural && alias.lambda_set_variables.is_empty() {
|
||||
let mut type_var_to_arg = Vec::new();
|
||||
|
||||
for (loc_var, arg_ann) in alias.type_variables.iter().zip(args) {
|
||||
let name = loc_var.value.0.clone();
|
||||
|
||||
type_var_to_arg.push((name, arg_ann));
|
||||
}
|
||||
|
||||
Type::DelayedAlias(AliasCommon {
|
||||
symbol,
|
||||
type_arguments: type_var_to_arg,
|
||||
lambda_set_variables: alias.lambda_set_variables.clone(),
|
||||
})
|
||||
} else {
|
||||
let (type_arguments, lambda_set_variables, actual) =
|
||||
instantiate_and_freshen_alias_type(
|
||||
var_store,
|
||||
|
@ -355,6 +381,7 @@ fn can_annotation_help(
|
|||
kind: alias.kind,
|
||||
}
|
||||
}
|
||||
}
|
||||
None => Type::Apply(symbol, args, region),
|
||||
}
|
||||
}
|
||||
|
@ -366,7 +393,7 @@ fn can_annotation_help(
|
|||
None => {
|
||||
let var = var_store.fresh();
|
||||
|
||||
introduced_variables.insert_named(name, var);
|
||||
introduced_variables.insert_named(name, Loc::at(region, var));
|
||||
|
||||
Type::Variable(var)
|
||||
}
|
||||
|
@ -430,7 +457,8 @@ fn can_annotation_help(
|
|||
} else {
|
||||
let var = var_store.fresh();
|
||||
|
||||
introduced_variables.insert_named(var_name.clone(), var);
|
||||
introduced_variables
|
||||
.insert_named(var_name.clone(), Loc::at(loc_var.region, var));
|
||||
vars.push((var_name.clone(), Type::Variable(var)));
|
||||
|
||||
lowercase_vars.push(Loc::at(loc_var.region, (var_name, var)));
|
||||
|
@ -537,7 +565,7 @@ fn can_annotation_help(
|
|||
// just `a` does not mean the same as `{}a`, so even
|
||||
// if there are no fields, still make this a `Record`,
|
||||
// not an EmptyRec
|
||||
Type::Record(Default::default(), Box::new(ext_type))
|
||||
Type::Record(Default::default(), TypeExtension::from_type(ext_type))
|
||||
}
|
||||
|
||||
None => Type::EmptyRec,
|
||||
|
@ -554,7 +582,7 @@ fn can_annotation_help(
|
|||
references,
|
||||
);
|
||||
|
||||
Type::Record(field_types, Box::new(ext_type))
|
||||
Type::Record(field_types, TypeExtension::from_type(ext_type))
|
||||
}
|
||||
}
|
||||
TagUnion { tags, ext, .. } => {
|
||||
|
@ -575,7 +603,7 @@ fn can_annotation_help(
|
|||
// just `a` does not mean the same as `{}a`, so even
|
||||
// if there are no fields, still make this a `Record`,
|
||||
// not an EmptyRec
|
||||
Type::TagUnion(Default::default(), Box::new(ext_type))
|
||||
Type::TagUnion(Default::default(), TypeExtension::from_type(ext_type))
|
||||
}
|
||||
|
||||
None => Type::EmptyTagUnion,
|
||||
|
@ -597,7 +625,7 @@ fn can_annotation_help(
|
|||
// in theory we save a lot of time by sorting once here
|
||||
insertion_sort_by(&mut tag_types, |a, b| a.0.cmp(&b.0));
|
||||
|
||||
Type::TagUnion(tag_types, Box::new(ext_type))
|
||||
Type::TagUnion(tag_types, TypeExtension::from_type(ext_type))
|
||||
}
|
||||
}
|
||||
SpaceBefore(nested, _) | SpaceAfter(nested, _) => can_annotation_help(
|
||||
|
@ -613,7 +641,7 @@ fn can_annotation_help(
|
|||
Wildcard => {
|
||||
let var = var_store.fresh();
|
||||
|
||||
introduced_variables.insert_wildcard(var);
|
||||
introduced_variables.insert_wildcard(Loc::at(region, var));
|
||||
|
||||
Type::Variable(var)
|
||||
}
|
||||
|
@ -622,16 +650,17 @@ fn can_annotation_help(
|
|||
// make a fresh unconstrained variable, and let the type solver fill it in for us 🤠
|
||||
let var = var_store.fresh();
|
||||
|
||||
introduced_variables.insert_inferred(var);
|
||||
introduced_variables.insert_inferred(Loc::at(region, var));
|
||||
|
||||
Type::Variable(var)
|
||||
}
|
||||
Where(..) => todo_abilities!(),
|
||||
Malformed(string) => {
|
||||
malformed(env, region, string);
|
||||
|
||||
let var = var_store.fresh();
|
||||
|
||||
introduced_variables.insert_wildcard(var);
|
||||
introduced_variables.insert_wildcard(Loc::at(region, var));
|
||||
|
||||
Type::Variable(var)
|
||||
}
|
||||
|
@ -682,7 +711,7 @@ fn can_extension_type<'a>(
|
|||
local_aliases,
|
||||
references,
|
||||
);
|
||||
if valid_extension_type(ext_type.shallow_dealias()) {
|
||||
if valid_extension_type(shallow_dealias_with_scope(scope, &ext_type)) {
|
||||
ext_type
|
||||
} else {
|
||||
// Report an error but mark the extension variable to be inferred
|
||||
|
@ -697,7 +726,7 @@ fn can_extension_type<'a>(
|
|||
|
||||
let var = var_store.fresh();
|
||||
|
||||
introduced_variables.insert_inferred(var);
|
||||
introduced_variables.insert_inferred(Loc::at_zero(var));
|
||||
|
||||
Type::Variable(var)
|
||||
}
|
||||
|
@ -706,6 +735,29 @@ fn can_extension_type<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
/// a shallow dealias, continue until the first constructor is not an alias.
|
||||
fn shallow_dealias_with_scope<'a>(scope: &'a mut Scope, typ: &'a Type) -> &'a Type {
|
||||
let mut result = typ;
|
||||
loop {
|
||||
match result {
|
||||
Type::Alias { actual, .. } => {
|
||||
// another loop
|
||||
result = actual;
|
||||
}
|
||||
Type::DelayedAlias(AliasCommon { symbol, .. }) => match scope.lookup_alias(*symbol) {
|
||||
None => unreachable!(),
|
||||
Some(alias) => {
|
||||
result = &alias.typ;
|
||||
}
|
||||
},
|
||||
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn instantiate_and_freshen_alias_type(
|
||||
var_store: &mut VarStore,
|
||||
introduced_variables: &mut IntroducedVariables,
|
||||
|
@ -866,7 +918,10 @@ fn can_assigned_fields<'a>(
|
|||
Type::Variable(*var)
|
||||
} else {
|
||||
let field_var = var_store.fresh();
|
||||
introduced_variables.insert_named(field_name.clone(), field_var);
|
||||
introduced_variables.insert_named(
|
||||
field_name.clone(),
|
||||
Loc::at(loc_field_name.region, field_var),
|
||||
);
|
||||
Type::Variable(field_var)
|
||||
}
|
||||
};
|
||||
|
|
|
@ -247,6 +247,8 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
|
|||
NUM_TO_U64_CHECKED => num_to_u64_checked,
|
||||
NUM_TO_U128 => num_to_u128,
|
||||
NUM_TO_U128_CHECKED => num_to_u128_checked,
|
||||
NUM_TO_NAT => num_to_nat,
|
||||
NUM_TO_NAT_CHECKED => num_to_nat_checked,
|
||||
NUM_TO_STR => num_to_str,
|
||||
RESULT_MAP => result_map,
|
||||
RESULT_MAP_ERR => result_map_err,
|
||||
|
@ -254,6 +256,8 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
|
|||
RESULT_WITH_DEFAULT => result_with_default,
|
||||
RESULT_IS_OK => result_is_ok,
|
||||
RESULT_IS_ERR => result_is_err,
|
||||
BOX_BOX_FUNCTION => box_box,
|
||||
BOX_UNBOX => box_unbox,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -455,6 +459,12 @@ fn num_to_u128(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
|
||||
}
|
||||
|
||||
// Num.toNat : Int * -> Nat
|
||||
fn num_to_nat(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
// Defer to IntCast
|
||||
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
|
||||
}
|
||||
|
||||
fn to_num_checked(symbol: Symbol, var_store: &mut VarStore, lowlevel: LowLevel) -> Def {
|
||||
let bool_var = var_store.fresh();
|
||||
let num_var_1 = var_store.fresh();
|
||||
|
@ -561,6 +571,7 @@ num_to_checked! {
|
|||
num_to_u32_checked
|
||||
num_to_u64_checked
|
||||
num_to_u128_checked
|
||||
num_to_nat_checked
|
||||
}
|
||||
|
||||
// Num.toStr : Num a -> Str
|
||||
|
@ -5314,6 +5325,16 @@ fn num_bytes_to(symbol: Symbol, var_store: &mut VarStore, offset: i64, low_level
|
|||
)
|
||||
}
|
||||
|
||||
/// Box.box : a -> Box a
|
||||
fn box_box(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
lowlevel_1(symbol, LowLevel::BoxExpr, var_store)
|
||||
}
|
||||
|
||||
/// Box.unbox : Box a -> a
|
||||
fn box_unbox(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
lowlevel_1(symbol, LowLevel::UnboxExpr, var_store)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn defn_help(
|
||||
fn_name: Symbol,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::expected::{Expected, PExpected};
|
||||
use roc_collections::soa::{Index, Slice};
|
||||
use roc_collections::soa::{EitherIndex, Index, Slice};
|
||||
use roc_module::ident::TagName;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::{Loc, Region};
|
||||
|
@ -120,13 +120,26 @@ impl Constraints {
|
|||
pub const PCATEGORY_CHARACTER: Index<PatternCategory> = Index::new(10);
|
||||
|
||||
#[inline(always)]
|
||||
pub fn push_type(&mut self, typ: Type) -> Index<Type> {
|
||||
pub fn push_type(&mut self, typ: Type) -> EitherIndex<Type, Variable> {
|
||||
match typ {
|
||||
Type::EmptyRec => Self::EMPTY_RECORD,
|
||||
Type::EmptyTagUnion => Self::EMPTY_TAG_UNION,
|
||||
other => Index::push_new(&mut self.types, other),
|
||||
Type::EmptyRec => EitherIndex::from_left(Self::EMPTY_RECORD),
|
||||
Type::EmptyTagUnion => EitherIndex::from_left(Self::EMPTY_TAG_UNION),
|
||||
Type::Variable(var) => Self::push_type_variable(var),
|
||||
other => {
|
||||
let index: Index<Type> = Index::push_new(&mut self.types, other);
|
||||
EitherIndex::from_left(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
const fn push_type_variable(var: Variable) -> EitherIndex<Type, Variable> {
|
||||
// that's right, we use the variable's integer value as the index
|
||||
// that way, we don't need to push anything onto a vector
|
||||
let index: Index<Variable> = Index::new(var.index());
|
||||
|
||||
EitherIndex::from_right(index)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn push_expected_type(&mut self, expected: Expected<Type>) -> Index<Expected<Type>> {
|
||||
|
@ -180,13 +193,56 @@ impl Constraints {
|
|||
category: Category,
|
||||
region: Region,
|
||||
) -> Constraint {
|
||||
let type_index = Index::push_new(&mut self.types, typ);
|
||||
let type_index = self.push_type(typ);
|
||||
let expected_index = Index::push_new(&mut self.expectations, expected);
|
||||
let category_index = Self::push_category(self, category);
|
||||
|
||||
Constraint::Eq(type_index, expected_index, category_index, region)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn equal_types_var(
|
||||
&mut self,
|
||||
var: Variable,
|
||||
expected: Expected<Type>,
|
||||
category: Category,
|
||||
region: Region,
|
||||
) -> Constraint {
|
||||
let type_index = Self::push_type_variable(var);
|
||||
let expected_index = Index::push_new(&mut self.expectations, expected);
|
||||
let category_index = Self::push_category(self, category);
|
||||
|
||||
Constraint::Eq(type_index, expected_index, category_index, region)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn equal_types_with_storage(
|
||||
&mut self,
|
||||
typ: Type,
|
||||
expected: Expected<Type>,
|
||||
category: Category,
|
||||
region: Region,
|
||||
storage_var: Variable,
|
||||
) -> Constraint {
|
||||
let type_index = self.push_type(typ);
|
||||
let expected_index = Index::push_new(&mut self.expectations, expected);
|
||||
let category_index = Self::push_category(self, category);
|
||||
|
||||
let equal = Constraint::Eq(type_index, expected_index, category_index, region);
|
||||
|
||||
let storage_type_index = Self::push_type_variable(storage_var);
|
||||
let storage_category = Category::Storage(std::file!(), std::line!());
|
||||
let storage_category_index = Self::push_category(self, storage_category);
|
||||
let storage = Constraint::Eq(
|
||||
storage_type_index,
|
||||
expected_index,
|
||||
storage_category_index,
|
||||
region,
|
||||
);
|
||||
|
||||
self.and_constraint([equal, storage])
|
||||
}
|
||||
|
||||
pub fn equal_pattern_types(
|
||||
&mut self,
|
||||
typ: Type,
|
||||
|
@ -194,7 +250,7 @@ impl Constraints {
|
|||
category: PatternCategory,
|
||||
region: Region,
|
||||
) -> Constraint {
|
||||
let type_index = Index::push_new(&mut self.types, typ);
|
||||
let type_index = self.push_type(typ);
|
||||
let expected_index = Index::push_new(&mut self.pattern_expectations, expected);
|
||||
let category_index = Self::push_pattern_category(self, category);
|
||||
|
||||
|
@ -208,7 +264,7 @@ impl Constraints {
|
|||
category: PatternCategory,
|
||||
region: Region,
|
||||
) -> Constraint {
|
||||
let type_index = Index::push_new(&mut self.types, typ);
|
||||
let type_index = self.push_type(typ);
|
||||
let expected_index = Index::push_new(&mut self.pattern_expectations, expected);
|
||||
let category_index = Index::push_new(&mut self.pattern_categories, category);
|
||||
|
||||
|
@ -216,7 +272,7 @@ impl Constraints {
|
|||
}
|
||||
|
||||
pub fn is_open_type(&mut self, typ: Type) -> Constraint {
|
||||
let type_index = Index::push_new(&mut self.types, typ);
|
||||
let type_index = self.push_type(typ);
|
||||
|
||||
Constraint::IsOpenType(type_index)
|
||||
}
|
||||
|
@ -309,7 +365,7 @@ impl Constraints {
|
|||
let let_index = Index::new(self.let_constraints.len() as _);
|
||||
self.let_constraints.push(let_contraint);
|
||||
|
||||
Constraint::Let(let_index)
|
||||
Constraint::Let(let_index, Slice::default())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -335,7 +391,7 @@ impl Constraints {
|
|||
let let_index = Index::new(self.let_constraints.len() as _);
|
||||
self.let_constraints.push(let_contraint);
|
||||
|
||||
Constraint::Let(let_index)
|
||||
Constraint::Let(let_index, Slice::default())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -353,6 +409,7 @@ impl Constraints {
|
|||
I3: IntoIterator<Item = (Symbol, Loc<Type>)>,
|
||||
I3::IntoIter: ExactSizeIterator,
|
||||
{
|
||||
// defs and ret constraint are stored consequtively, so we only need to store one index
|
||||
let defs_and_ret_constraint = Index::new(self.constraints.len() as _);
|
||||
|
||||
self.constraints.push(defs_constraint);
|
||||
|
@ -368,7 +425,55 @@ impl Constraints {
|
|||
let let_index = Index::new(self.let_constraints.len() as _);
|
||||
self.let_constraints.push(let_contraint);
|
||||
|
||||
Constraint::Let(let_index)
|
||||
Constraint::Let(let_index, Slice::default())
|
||||
}
|
||||
|
||||
/// A variant of `Let` used specifically for imports. When importing types from another module,
|
||||
/// we use a StorageSubs to store the data, and copy over the relevant
|
||||
/// variables/content/flattype/tagname etc.
|
||||
///
|
||||
/// The general idea is to let-generalize the imorted types in the target module.
|
||||
/// More concretely, we need to simulate what `type_to_var` (solve.rs) does to a `Type`.
|
||||
/// While the copying puts all the data the right place, it misses that `type_to_var` puts
|
||||
/// the variables that it creates (to store the nodes of a Type in Subs) in the pool of the
|
||||
/// current rank (so they can be generalized).
|
||||
///
|
||||
/// So, during copying of an import (`copy_import_to`, subs.rs) we track the variables that
|
||||
/// we need to put into the pool (simulating what `type_to_var` would do). Those variables
|
||||
/// then need to find their way to the pool, and a convenient approach turned out to be to
|
||||
/// tag them onto the `Let` that we used to add the imported values.
|
||||
#[inline(always)]
|
||||
pub fn let_import_constraint<I1, I2>(
|
||||
&mut self,
|
||||
rigid_vars: I1,
|
||||
def_types: I2,
|
||||
module_constraint: Constraint,
|
||||
pool_variables: &[Variable],
|
||||
) -> Constraint
|
||||
where
|
||||
I1: IntoIterator<Item = Variable>,
|
||||
I2: IntoIterator<Item = (Symbol, Loc<Type>)>,
|
||||
I2::IntoIter: ExactSizeIterator,
|
||||
{
|
||||
// defs and ret constraint are stored consequtively, so we only need to store one index
|
||||
let defs_and_ret_constraint = Index::new(self.constraints.len() as _);
|
||||
|
||||
self.constraints.push(Constraint::True);
|
||||
self.constraints.push(module_constraint);
|
||||
|
||||
let let_contraint = LetConstraint {
|
||||
rigid_vars: self.variable_slice(rigid_vars),
|
||||
flex_vars: Slice::default(),
|
||||
def_types: self.def_types_slice(def_types),
|
||||
defs_and_ret_constraint,
|
||||
};
|
||||
|
||||
let let_index = Index::new(self.let_constraints.len() as _);
|
||||
self.let_constraints.push(let_contraint);
|
||||
|
||||
let pool_slice = self.variable_slice(pool_variables.iter().copied());
|
||||
|
||||
Constraint::Let(let_index, pool_slice)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -408,6 +513,7 @@ impl Constraints {
|
|||
region,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn contains_save_the_environment(&self, constraint: &Constraint) -> bool {
|
||||
match constraint {
|
||||
Constraint::Eq(..) => false,
|
||||
|
@ -416,7 +522,7 @@ impl Constraints {
|
|||
Constraint::Pattern(..) => false,
|
||||
Constraint::True => false,
|
||||
Constraint::SaveTheEnvironment => true,
|
||||
Constraint::Let(index) => {
|
||||
Constraint::Let(index, _) => {
|
||||
let let_constraint = &self.let_constraints[index.index()];
|
||||
|
||||
let offset = let_constraint.defs_and_ret_constraint.index();
|
||||
|
@ -446,35 +552,63 @@ impl Constraints {
|
|||
filename: &'static str,
|
||||
line_number: u32,
|
||||
) -> Constraint {
|
||||
let type_index = Index::push_new(&mut self.types, typ);
|
||||
let type_index = self.push_type(typ);
|
||||
let string_index = Index::push_new(&mut self.strings, filename);
|
||||
|
||||
Constraint::Store(type_index, variable, string_index, line_number)
|
||||
}
|
||||
|
||||
pub fn store_index(
|
||||
&mut self,
|
||||
type_index: EitherIndex<Type, Variable>,
|
||||
variable: Variable,
|
||||
filename: &'static str,
|
||||
line_number: u32,
|
||||
) -> Constraint {
|
||||
let string_index = Index::push_new(&mut self.strings, filename);
|
||||
|
||||
Constraint::Store(type_index, variable, string_index, line_number)
|
||||
}
|
||||
}
|
||||
|
||||
static_assertions::assert_eq_size!([u8; 3 * 8], Constraint);
|
||||
roc_error_macros::assert_sizeof_default!(Constraint, 3 * 8);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum Constraint {
|
||||
Eq(Index<Type>, Index<Expected<Type>>, Index<Category>, Region),
|
||||
Store(Index<Type>, Variable, Index<&'static str>, u32),
|
||||
Eq(
|
||||
EitherIndex<Type, Variable>,
|
||||
Index<Expected<Type>>,
|
||||
Index<Category>,
|
||||
Region,
|
||||
),
|
||||
Store(
|
||||
EitherIndex<Type, Variable>,
|
||||
Variable,
|
||||
Index<&'static str>,
|
||||
u32,
|
||||
),
|
||||
Lookup(Symbol, Index<Expected<Type>>, Region),
|
||||
Pattern(
|
||||
Index<Type>,
|
||||
EitherIndex<Type, Variable>,
|
||||
Index<PExpected<Type>>,
|
||||
Index<PatternCategory>,
|
||||
Region,
|
||||
),
|
||||
True, // Used for things that always unify, e.g. blanks and runtime errors
|
||||
/// Used for things that always unify, e.g. blanks and runtime errors
|
||||
True,
|
||||
SaveTheEnvironment,
|
||||
Let(Index<LetConstraint>),
|
||||
/// A Let constraint introduces symbols and their annotation at a certain level of nesting
|
||||
///
|
||||
/// The `Slice<Variable>` is used for imports where we manually put the Content into Subs
|
||||
/// by copying from another module, but have to make sure that any variables we use to store
|
||||
/// these contents are added to `Pool` at the correct rank
|
||||
Let(Index<LetConstraint>, Slice<Variable>),
|
||||
And(Slice<Constraint>),
|
||||
/// Presence constraints
|
||||
IsOpenType(Index<Type>), // Theory; always applied to a variable? if yes the use that
|
||||
IsOpenType(EitherIndex<Type, Variable>), // Theory; always applied to a variable? if yes the use that
|
||||
IncludesTag(Index<IncludesTag>),
|
||||
PatternPresence(
|
||||
Index<Type>,
|
||||
EitherIndex<Type, Variable>,
|
||||
Index<PExpected<Type>>,
|
||||
Index<PatternCategory>,
|
||||
Region,
|
||||
|
@ -503,3 +637,36 @@ pub struct IncludesTag {
|
|||
pub pattern_category: Index<PatternCategory>,
|
||||
pub region: Region,
|
||||
}
|
||||
|
||||
/// Custom impl to limit vertical space used by the debug output
|
||||
impl std::fmt::Debug for Constraint {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Eq(arg0, arg1, arg2, arg3) => {
|
||||
write!(f, "Eq({:?}, {:?}, {:?}, {:?})", arg0, arg1, arg2, arg3)
|
||||
}
|
||||
Self::Store(arg0, arg1, arg2, arg3) => {
|
||||
write!(f, "Store({:?}, {:?}, {:?}, {:?})", arg0, arg1, arg2, arg3)
|
||||
}
|
||||
Self::Lookup(arg0, arg1, arg2) => {
|
||||
write!(f, "Lookup({:?}, {:?}, {:?})", arg0, arg1, arg2)
|
||||
}
|
||||
Self::Pattern(arg0, arg1, arg2, arg3) => {
|
||||
write!(f, "Pattern({:?}, {:?}, {:?}, {:?})", arg0, arg1, arg2, arg3)
|
||||
}
|
||||
Self::True => write!(f, "True"),
|
||||
Self::SaveTheEnvironment => write!(f, "SaveTheEnvironment"),
|
||||
Self::Let(arg0, arg1) => f.debug_tuple("Let").field(arg0).field(arg1).finish(),
|
||||
Self::And(arg0) => f.debug_tuple("And").field(arg0).finish(),
|
||||
Self::IsOpenType(arg0) => f.debug_tuple("IsOpenType").field(arg0).finish(),
|
||||
Self::IncludesTag(arg0) => f.debug_tuple("IncludesTag").field(arg0).finish(),
|
||||
Self::PatternPresence(arg0, arg1, arg2, arg3) => {
|
||||
write!(
|
||||
f,
|
||||
"PatternPresence({:?}, {:?}, {:?}, {:?})",
|
||||
arg0, arg1, arg2, arg3
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ use crate::procedure::References;
|
|||
use crate::scope::create_alias;
|
||||
use crate::scope::Scope;
|
||||
use roc_collections::all::{default_hasher, ImMap, ImSet, MutMap, MutSet, SendMap};
|
||||
use roc_error_macros::todo_abilities;
|
||||
use roc_module::ident::Lowercase;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_parse::ast;
|
||||
|
@ -286,21 +287,19 @@ pub fn canonicalize_defs<'a>(
|
|||
|
||||
// Record all the annotation's references in output.references.lookups
|
||||
for symbol in can_ann.references {
|
||||
output.references.lookups.insert(symbol);
|
||||
output.references.type_lookups.insert(symbol);
|
||||
output.references.referenced_type_defs.insert(symbol);
|
||||
}
|
||||
|
||||
let mut can_vars: Vec<Loc<(Lowercase, Variable)>> = Vec::with_capacity(vars.len());
|
||||
let mut is_phantom = false;
|
||||
|
||||
let mut var_by_name = can_ann.introduced_variables.var_by_name.clone();
|
||||
for loc_lowercase in vars.iter() {
|
||||
if let Some(var) = can_ann
|
||||
.introduced_variables
|
||||
.var_by_name(&loc_lowercase.value)
|
||||
{
|
||||
if let Some(var) = var_by_name.remove(&loc_lowercase.value) {
|
||||
// This is a valid lowercase rigid var for the type def.
|
||||
can_vars.push(Loc {
|
||||
value: (loc_lowercase.value.clone(), *var),
|
||||
value: (loc_lowercase.value.clone(), var.value),
|
||||
region: loc_lowercase.region,
|
||||
});
|
||||
} else {
|
||||
|
@ -319,6 +318,33 @@ pub fn canonicalize_defs<'a>(
|
|||
continue;
|
||||
}
|
||||
|
||||
let IntroducedVariables {
|
||||
wildcards,
|
||||
inferred,
|
||||
..
|
||||
} = can_ann.introduced_variables;
|
||||
let num_unbound = var_by_name.len() + wildcards.len() + inferred.len();
|
||||
if num_unbound > 0 {
|
||||
let one_occurrence = var_by_name
|
||||
.iter()
|
||||
.map(|(_, v)| v)
|
||||
.chain(wildcards.iter())
|
||||
.chain(inferred.iter())
|
||||
.next()
|
||||
.unwrap()
|
||||
.region;
|
||||
|
||||
env.problems.push(Problem::UnboundTypeVariable {
|
||||
typ: symbol,
|
||||
num_unbound,
|
||||
one_occurrence,
|
||||
kind,
|
||||
});
|
||||
|
||||
// Bail out
|
||||
continue;
|
||||
}
|
||||
|
||||
let alias = create_alias(
|
||||
symbol,
|
||||
name.region,
|
||||
|
@ -331,7 +357,7 @@ pub fn canonicalize_defs<'a>(
|
|||
|
||||
// Now that we know the alias dependency graph, we can try to insert recursion variables
|
||||
// where aliases are recursive tag unions, or detect illegal recursions.
|
||||
let mut aliases = correct_mutual_recursive_type_alias(env, &aliases, var_store);
|
||||
let mut aliases = correct_mutual_recursive_type_alias(env, aliases, var_store);
|
||||
for (symbol, alias) in aliases.iter() {
|
||||
scope.add_alias(
|
||||
*symbol,
|
||||
|
@ -383,7 +409,8 @@ pub fn canonicalize_defs<'a>(
|
|||
CanDefs {
|
||||
refs_by_symbol,
|
||||
can_defs_by_symbol,
|
||||
aliases,
|
||||
// The result needs a thread-safe `SendMap`
|
||||
aliases: aliases.into_iter().collect(),
|
||||
},
|
||||
scope,
|
||||
output,
|
||||
|
@ -409,7 +436,7 @@ pub fn sort_can_defs(
|
|||
|
||||
// Determine the full set of references by traversing the graph.
|
||||
let mut visited_symbols = MutSet::default();
|
||||
let returned_lookups = ImSet::clone(&output.references.lookups);
|
||||
let returned_lookups = ImSet::clone(&output.references.value_lookups);
|
||||
|
||||
// Start with the return expression's referenced locals. They're the only ones that count!
|
||||
//
|
||||
|
@ -482,10 +509,10 @@ pub fn sort_can_defs(
|
|||
let mut loc_succ = local_successors(references, &env.closures);
|
||||
|
||||
// if the current symbol is a closure, peek into its body
|
||||
if let Some(References { lookups, .. }) = env.closures.get(symbol) {
|
||||
if let Some(References { value_lookups, .. }) = env.closures.get(symbol) {
|
||||
let home = env.home;
|
||||
|
||||
for lookup in lookups {
|
||||
for lookup in value_lookups {
|
||||
if lookup != symbol && lookup.module_id() == home {
|
||||
// DO NOT register a self-call behind a lambda!
|
||||
//
|
||||
|
@ -532,8 +559,8 @@ pub fn sort_can_defs(
|
|||
let mut loc_succ = local_successors(references, &env.closures);
|
||||
|
||||
// if the current symbol is a closure, peek into its body
|
||||
if let Some(References { lookups, .. }) = env.closures.get(symbol) {
|
||||
for lookup in lookups {
|
||||
if let Some(References { value_lookups, .. }) = env.closures.get(symbol) {
|
||||
for lookup in value_lookups {
|
||||
loc_succ.insert(*lookup);
|
||||
}
|
||||
}
|
||||
|
@ -901,7 +928,7 @@ fn canonicalize_pending_def<'a>(
|
|||
can_defs_by_symbol: &mut MutMap<Symbol, Def>,
|
||||
var_store: &mut VarStore,
|
||||
refs_by_symbol: &mut MutMap<Symbol, (Region, References)>,
|
||||
aliases: &mut SendMap<Symbol, Alias>,
|
||||
aliases: &mut ImMap<Symbol, Alias>,
|
||||
) -> Output {
|
||||
use PendingDef::*;
|
||||
|
||||
|
@ -919,7 +946,7 @@ fn canonicalize_pending_def<'a>(
|
|||
// Record all the annotation's references in output.references.lookups
|
||||
|
||||
for symbol in type_annotation.references.iter() {
|
||||
output.references.lookups.insert(*symbol);
|
||||
output.references.type_lookups.insert(*symbol);
|
||||
output.references.referenced_type_defs.insert(*symbol);
|
||||
}
|
||||
|
||||
|
@ -1041,7 +1068,7 @@ fn canonicalize_pending_def<'a>(
|
|||
|
||||
// Record all the annotation's references in output.references.lookups
|
||||
for symbol in type_annotation.references.iter() {
|
||||
output.references.lookups.insert(*symbol);
|
||||
output.references.type_lookups.insert(*symbol);
|
||||
output.references.referenced_type_defs.insert(*symbol);
|
||||
}
|
||||
|
||||
|
@ -1121,7 +1148,7 @@ fn canonicalize_pending_def<'a>(
|
|||
// Recursion doesn't count as referencing. (If it did, all recursive functions
|
||||
// would result in circular def errors!)
|
||||
refs_by_symbol.entry(symbol).and_modify(|(_, refs)| {
|
||||
refs.lookups = refs.lookups.without(&symbol);
|
||||
refs.value_lookups = refs.value_lookups.without(&symbol);
|
||||
});
|
||||
|
||||
// renamed_closure_def = Some(&symbol);
|
||||
|
@ -1261,7 +1288,7 @@ fn canonicalize_pending_def<'a>(
|
|||
// Recursion doesn't count as referencing. (If it did, all recursive functions
|
||||
// would result in circular def errors!)
|
||||
refs_by_symbol.entry(symbol).and_modify(|(_, refs)| {
|
||||
refs.lookups = refs.lookups.without(&symbol);
|
||||
refs.value_lookups = refs.value_lookups.without(&symbol);
|
||||
});
|
||||
|
||||
loc_can_expr.value = Closure(ClosureData {
|
||||
|
@ -1356,7 +1383,8 @@ pub fn can_defs_with_return<'a>(
|
|||
// Now that we've collected all the references, check to see if any of the new idents
|
||||
// we defined went unused by the return expression. If any were unused, report it.
|
||||
for (symbol, region) in symbols_introduced {
|
||||
if !output.references.has_lookup(symbol) {
|
||||
if !output.references.has_value_lookup(symbol) && !output.references.has_type_lookup(symbol)
|
||||
{
|
||||
env.problem(Problem::UnusedDef(symbol, region));
|
||||
}
|
||||
}
|
||||
|
@ -1587,6 +1615,8 @@ fn to_pending_def<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
Ability { .. } => todo_abilities!(),
|
||||
|
||||
Expect(_condition) => todo!(),
|
||||
|
||||
SpaceBefore(sub_def, _) | SpaceAfter(sub_def, _) => {
|
||||
|
@ -1625,16 +1655,12 @@ fn pending_typed_body<'a>(
|
|||
/// Make aliases recursive
|
||||
fn correct_mutual_recursive_type_alias<'a>(
|
||||
env: &mut Env<'a>,
|
||||
original_aliases: &SendMap<Symbol, Alias>,
|
||||
mut original_aliases: SendMap<Symbol, Alias>,
|
||||
var_store: &mut VarStore,
|
||||
) -> SendMap<Symbol, Alias> {
|
||||
let mut symbols_introduced = ImSet::default();
|
||||
) -> ImMap<Symbol, Alias> {
|
||||
let symbols_introduced: Vec<Symbol> = original_aliases.keys().copied().collect();
|
||||
|
||||
for (key, _) in original_aliases.iter() {
|
||||
symbols_introduced.insert(*key);
|
||||
}
|
||||
|
||||
let all_successors_with_self = |symbol: &Symbol| -> ImSet<Symbol> {
|
||||
let all_successors_with_self = |symbol: &Symbol| -> Vec<Symbol> {
|
||||
match original_aliases.get(symbol) {
|
||||
Some(alias) => {
|
||||
let mut loc_succ = alias.typ.symbols();
|
||||
|
@ -1643,7 +1669,7 @@ fn correct_mutual_recursive_type_alias<'a>(
|
|||
|
||||
loc_succ
|
||||
}
|
||||
None => ImSet::default(),
|
||||
None => vec![],
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1651,44 +1677,39 @@ fn correct_mutual_recursive_type_alias<'a>(
|
|||
let defined_symbols: Vec<Symbol> = original_aliases.keys().copied().collect();
|
||||
|
||||
let cycles = strongly_connected_components(&defined_symbols, all_successors_with_self);
|
||||
let mut solved_aliases = SendMap::default();
|
||||
let mut solved_aliases = ImMap::default();
|
||||
|
||||
for cycle in cycles {
|
||||
debug_assert!(!cycle.is_empty());
|
||||
|
||||
let mut pending_aliases: SendMap<_, _> = cycle
|
||||
let mut pending_aliases: ImMap<_, _> = cycle
|
||||
.iter()
|
||||
.map(|&sym| (sym, original_aliases.get(&sym).unwrap().clone()))
|
||||
.map(|&sym| (sym, original_aliases.remove(&sym).unwrap()))
|
||||
.collect();
|
||||
|
||||
// Make sure we report only one error for the cycle, not an error for every
|
||||
// alias in the cycle.
|
||||
let mut can_still_report_error = true;
|
||||
|
||||
for &rec in cycle.iter() {
|
||||
// First, we need to instantiate the alias with any symbols in the currrent module it
|
||||
// We need to instantiate the alias with any symbols in the currrent module it
|
||||
// depends on.
|
||||
// We only need to worry about symbols in this SCC or any prior one, since the SCCs
|
||||
// were sorted topologically, and we've already instantiated aliases coming from other
|
||||
// modules.
|
||||
let mut to_instantiate: ImMap<_, _> = solved_aliases.clone().into_iter().collect();
|
||||
let mut others_in_scc = Vec::with_capacity(cycle.len() - 1);
|
||||
for &other in cycle.iter() {
|
||||
if rec != other {
|
||||
others_in_scc.push(other);
|
||||
if let Some(alias) = original_aliases.get(&other) {
|
||||
to_instantiate.insert(other, alias.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
// NB: ImMap::clone is O(1): https://docs.rs/im/latest/src/im/hash/map.rs.html#1527-1544
|
||||
let mut to_instantiate = solved_aliases.clone().union(pending_aliases.clone());
|
||||
|
||||
for &rec in cycle.iter() {
|
||||
let alias = pending_aliases.get_mut(&rec).unwrap();
|
||||
// Don't try to instantiate the alias itself in its definition.
|
||||
let original_alias_def = to_instantiate.remove(&rec).unwrap();
|
||||
alias.typ.instantiate_aliases(
|
||||
alias.region,
|
||||
&to_instantiate,
|
||||
var_store,
|
||||
&mut ImSet::default(),
|
||||
);
|
||||
to_instantiate.insert(rec, original_alias_def);
|
||||
|
||||
// Now mark the alias recursive, if it needs to be.
|
||||
let is_self_recursive = alias.typ.contains_symbol(rec);
|
||||
|
@ -1752,7 +1773,7 @@ fn make_tag_union_of_alias_recursive<'a>(
|
|||
.map(|l| (l.value.0.clone(), Type::Variable(l.value.1)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
make_tag_union_recursive_help(
|
||||
let made_recursive = make_tag_union_recursive_help(
|
||||
env,
|
||||
Loc::at(alias.header_region(), (alias_name, &alias_args)),
|
||||
alias.region,
|
||||
|
@ -1760,7 +1781,24 @@ fn make_tag_union_of_alias_recursive<'a>(
|
|||
&mut alias.typ,
|
||||
var_store,
|
||||
can_report_error,
|
||||
)
|
||||
);
|
||||
|
||||
match made_recursive {
|
||||
MakeTagUnionRecursive::Cyclic => Ok(()),
|
||||
MakeTagUnionRecursive::MadeRecursive { recursion_variable } => {
|
||||
alias.recursion_variables.clear();
|
||||
alias.recursion_variables.insert(recursion_variable);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
MakeTagUnionRecursive::InvalidRecursion => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
enum MakeTagUnionRecursive {
|
||||
Cyclic,
|
||||
MadeRecursive { recursion_variable: Variable },
|
||||
InvalidRecursion,
|
||||
}
|
||||
|
||||
/// Attempt to make a tag union recursive at the position of `recursive_alias`; for example,
|
||||
|
@ -1792,7 +1830,9 @@ fn make_tag_union_recursive_help<'a>(
|
|||
typ: &mut Type,
|
||||
var_store: &mut VarStore,
|
||||
can_report_error: &mut bool,
|
||||
) -> Result<(), ()> {
|
||||
) -> MakeTagUnionRecursive {
|
||||
use MakeTagUnionRecursive::*;
|
||||
|
||||
let Loc {
|
||||
value: (symbol, args),
|
||||
region: alias_region,
|
||||
|
@ -1800,15 +1840,17 @@ fn make_tag_union_recursive_help<'a>(
|
|||
let vars = args.iter().map(|(_, t)| t.clone()).collect::<Vec<_>>();
|
||||
match typ {
|
||||
Type::TagUnion(tags, ext) => {
|
||||
let rec_var = var_store.fresh();
|
||||
let mut pending_typ = Type::RecursiveTagUnion(rec_var, tags.to_vec(), ext.clone());
|
||||
let recursion_variable = var_store.fresh();
|
||||
let mut pending_typ =
|
||||
Type::RecursiveTagUnion(recursion_variable, tags.to_vec(), ext.clone());
|
||||
let substitution_result =
|
||||
pending_typ.substitute_alias(symbol, &vars, &Type::Variable(rec_var));
|
||||
pending_typ.substitute_alias(symbol, &vars, &Type::Variable(recursion_variable));
|
||||
match substitution_result {
|
||||
Ok(()) => {
|
||||
// We can substitute the alias presence for the variable exactly.
|
||||
*typ = pending_typ;
|
||||
Ok(())
|
||||
|
||||
MadeRecursive { recursion_variable }
|
||||
}
|
||||
Err(differing_recursion_region) => {
|
||||
env.problems.push(Problem::NestedDatatype {
|
||||
|
@ -1816,11 +1858,14 @@ fn make_tag_union_recursive_help<'a>(
|
|||
def_region: alias_region,
|
||||
differing_recursion_region,
|
||||
});
|
||||
Err(())
|
||||
|
||||
InvalidRecursion
|
||||
}
|
||||
}
|
||||
}
|
||||
Type::RecursiveTagUnion(_, _, _) => Ok(()),
|
||||
Type::RecursiveTagUnion(recursion_variable, _, _) => MadeRecursive {
|
||||
recursion_variable: *recursion_variable,
|
||||
},
|
||||
Type::Alias {
|
||||
actual,
|
||||
type_arguments,
|
||||
|
@ -1838,7 +1883,7 @@ fn make_tag_union_recursive_help<'a>(
|
|||
mark_cyclic_alias(env, typ, symbol, region, others, *can_report_error);
|
||||
*can_report_error = false;
|
||||
|
||||
Ok(())
|
||||
Cyclic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ use roc_module::ident::TagName;
|
|||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::{VarStore, Variable};
|
||||
use roc_types::types::{AliasKind, Type};
|
||||
use roc_types::types::{AliasKind, Type, TypeExtension};
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub(crate) struct HostedGeneratedFunctions {
|
||||
|
@ -210,7 +210,7 @@ fn build_effect_always(
|
|||
let signature = {
|
||||
// Effect.always : a -> Effect a
|
||||
let var_a = var_store.fresh();
|
||||
introduced_variables.insert_named("a".into(), var_a);
|
||||
introduced_variables.insert_named("a".into(), Loc::at_zero(var_a));
|
||||
|
||||
let effect_a = build_effect_alias(
|
||||
effect_symbol,
|
||||
|
@ -223,7 +223,7 @@ fn build_effect_always(
|
|||
);
|
||||
|
||||
let closure_var = var_store.fresh();
|
||||
introduced_variables.insert_wildcard(closure_var);
|
||||
introduced_variables.insert_wildcard(Loc::at_zero(closure_var));
|
||||
|
||||
Type::Function(
|
||||
vec![Type::Variable(var_a)],
|
||||
|
@ -402,8 +402,8 @@ fn build_effect_map(
|
|||
let var_a = var_store.fresh();
|
||||
let var_b = var_store.fresh();
|
||||
|
||||
introduced_variables.insert_named("a".into(), var_a);
|
||||
introduced_variables.insert_named("b".into(), var_b);
|
||||
introduced_variables.insert_named("a".into(), Loc::at_zero(var_a));
|
||||
introduced_variables.insert_named("b".into(), Loc::at_zero(var_b));
|
||||
|
||||
let effect_a = build_effect_alias(
|
||||
effect_symbol,
|
||||
|
@ -426,7 +426,7 @@ fn build_effect_map(
|
|||
);
|
||||
|
||||
let closure_var = var_store.fresh();
|
||||
introduced_variables.insert_wildcard(closure_var);
|
||||
introduced_variables.insert_wildcard(Loc::at_zero(closure_var));
|
||||
let a_to_b = {
|
||||
Type::Function(
|
||||
vec![Type::Variable(var_a)],
|
||||
|
@ -436,7 +436,7 @@ fn build_effect_map(
|
|||
};
|
||||
|
||||
let closure_var = var_store.fresh();
|
||||
introduced_variables.insert_wildcard(closure_var);
|
||||
introduced_variables.insert_wildcard(Loc::at_zero(closure_var));
|
||||
Type::Function(
|
||||
vec![effect_a, a_to_b],
|
||||
Box::new(Type::Variable(closure_var)),
|
||||
|
@ -571,8 +571,8 @@ fn build_effect_after(
|
|||
let var_a = var_store.fresh();
|
||||
let var_b = var_store.fresh();
|
||||
|
||||
introduced_variables.insert_named("a".into(), var_a);
|
||||
introduced_variables.insert_named("b".into(), var_b);
|
||||
introduced_variables.insert_named("a".into(), Loc::at_zero(var_a));
|
||||
introduced_variables.insert_named("b".into(), Loc::at_zero(var_b));
|
||||
|
||||
let effect_a = build_effect_alias(
|
||||
effect_symbol,
|
||||
|
@ -595,7 +595,7 @@ fn build_effect_after(
|
|||
);
|
||||
|
||||
let closure_var = var_store.fresh();
|
||||
introduced_variables.insert_wildcard(closure_var);
|
||||
introduced_variables.insert_wildcard(Loc::at_zero(closure_var));
|
||||
let a_to_effect_b = Type::Function(
|
||||
vec![Type::Variable(var_a)],
|
||||
Box::new(Type::Variable(closure_var)),
|
||||
|
@ -603,7 +603,7 @@ fn build_effect_after(
|
|||
);
|
||||
|
||||
let closure_var = var_store.fresh();
|
||||
introduced_variables.insert_wildcard(closure_var);
|
||||
introduced_variables.insert_wildcard(Loc::at_zero(closure_var));
|
||||
Type::Function(
|
||||
vec![effect_a, a_to_effect_b],
|
||||
Box::new(Type::Variable(closure_var)),
|
||||
|
@ -831,8 +831,8 @@ fn build_effect_forever(
|
|||
let var_a = var_store.fresh();
|
||||
let var_b = var_store.fresh();
|
||||
|
||||
introduced_variables.insert_named("a".into(), var_a);
|
||||
introduced_variables.insert_named("b".into(), var_b);
|
||||
introduced_variables.insert_named("a".into(), Loc::at_zero(var_a));
|
||||
introduced_variables.insert_named("b".into(), Loc::at_zero(var_b));
|
||||
|
||||
let effect_a = build_effect_alias(
|
||||
effect_symbol,
|
||||
|
@ -855,7 +855,7 @@ fn build_effect_forever(
|
|||
);
|
||||
|
||||
let closure_var = var_store.fresh();
|
||||
introduced_variables.insert_wildcard(closure_var);
|
||||
introduced_variables.insert_wildcard(Loc::at_zero(closure_var));
|
||||
|
||||
Type::Function(
|
||||
vec![effect_a],
|
||||
|
@ -1089,8 +1089,8 @@ fn build_effect_loop(
|
|||
let var_a = var_store.fresh();
|
||||
let var_b = var_store.fresh();
|
||||
|
||||
introduced_variables.insert_named("a".into(), var_a);
|
||||
introduced_variables.insert_named("b".into(), var_b);
|
||||
introduced_variables.insert_named("a".into(), Loc::at_zero(var_a));
|
||||
introduced_variables.insert_named("b".into(), Loc::at_zero(var_b));
|
||||
|
||||
let effect_b = build_effect_alias(
|
||||
effect_symbol,
|
||||
|
@ -1111,13 +1111,13 @@ fn build_effect_loop(
|
|||
(step_tag_name, vec![Type::Variable(var_a)]),
|
||||
(done_tag_name, vec![Type::Variable(var_b)]),
|
||||
],
|
||||
Box::new(Type::EmptyTagUnion),
|
||||
TypeExtension::Closed,
|
||||
)
|
||||
};
|
||||
|
||||
let effect_state_type = {
|
||||
let closure_var = var_store.fresh();
|
||||
introduced_variables.insert_wildcard(closure_var);
|
||||
introduced_variables.insert_wildcard(Loc::at_zero(closure_var));
|
||||
|
||||
let actual = {
|
||||
Type::TagUnion(
|
||||
|
@ -1129,7 +1129,7 @@ fn build_effect_loop(
|
|||
Box::new(state_type.clone()),
|
||||
)],
|
||||
)],
|
||||
Box::new(Type::EmptyTagUnion),
|
||||
TypeExtension::Closed,
|
||||
)
|
||||
};
|
||||
|
||||
|
@ -1145,7 +1145,7 @@ fn build_effect_loop(
|
|||
};
|
||||
|
||||
let closure_var = var_store.fresh();
|
||||
introduced_variables.insert_wildcard(closure_var);
|
||||
introduced_variables.insert_wildcard(Loc::at_zero(closure_var));
|
||||
|
||||
let step_type = Type::Function(
|
||||
vec![Type::Variable(var_a)],
|
||||
|
@ -1154,7 +1154,7 @@ fn build_effect_loop(
|
|||
);
|
||||
|
||||
let closure_var = var_store.fresh();
|
||||
introduced_variables.insert_wildcard(closure_var);
|
||||
introduced_variables.insert_wildcard(Loc::at_zero(closure_var));
|
||||
|
||||
Type::Function(
|
||||
vec![Type::Variable(var_a), step_type],
|
||||
|
@ -1559,7 +1559,7 @@ fn build_effect_alias(
|
|||
introduced_variables: &mut IntroducedVariables,
|
||||
) -> Type {
|
||||
let closure_var = var_store.fresh();
|
||||
introduced_variables.insert_wildcard(closure_var);
|
||||
introduced_variables.insert_wildcard(Loc::at_zero(closure_var));
|
||||
|
||||
let actual = {
|
||||
Type::TagUnion(
|
||||
|
@ -1571,7 +1571,7 @@ fn build_effect_alias(
|
|||
Box::new(a_type),
|
||||
)],
|
||||
)],
|
||||
Box::new(Type::EmptyTagUnion),
|
||||
TypeExtension::Closed,
|
||||
)
|
||||
};
|
||||
|
||||
|
@ -1600,7 +1600,7 @@ pub fn build_effect_actual(
|
|||
Box::new(a_type),
|
||||
)],
|
||||
)],
|
||||
Box::new(Type::EmptyTagUnion),
|
||||
TypeExtension::Closed,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -27,8 +27,11 @@ pub struct Env<'a> {
|
|||
/// current closure name (if any)
|
||||
pub closure_name_symbol: Option<Symbol>,
|
||||
|
||||
/// Symbols which were referenced by qualified lookups.
|
||||
pub qualified_lookups: MutSet<Symbol>,
|
||||
/// Symbols of values/functions which were referenced by qualified lookups.
|
||||
pub qualified_value_lookups: MutSet<Symbol>,
|
||||
|
||||
/// Symbols of types which were referenced by qualified lookups.
|
||||
pub qualified_type_lookups: MutSet<Symbol>,
|
||||
|
||||
pub top_level_symbols: MutSet<Symbol>,
|
||||
|
||||
|
@ -51,7 +54,8 @@ impl<'a> Env<'a> {
|
|||
exposed_ident_ids,
|
||||
problems: Vec::new(),
|
||||
closures: MutMap::default(),
|
||||
qualified_lookups: MutSet::default(),
|
||||
qualified_value_lookups: MutSet::default(),
|
||||
qualified_type_lookups: MutSet::default(),
|
||||
tailcallable_symbol: None,
|
||||
closure_name_symbol: None,
|
||||
top_level_symbols: MutSet::default(),
|
||||
|
@ -71,6 +75,8 @@ impl<'a> Env<'a> {
|
|||
ident
|
||||
);
|
||||
|
||||
let is_type_name = ident.starts_with(|c: char| c.is_uppercase());
|
||||
|
||||
let module_name = ModuleName::from(module_name_str);
|
||||
let ident = Ident::from(ident);
|
||||
|
||||
|
@ -83,7 +89,11 @@ impl<'a> Env<'a> {
|
|||
Some(ident_id) => {
|
||||
let symbol = Symbol::new(module_id, *ident_id);
|
||||
|
||||
self.qualified_lookups.insert(symbol);
|
||||
if is_type_name {
|
||||
self.qualified_type_lookups.insert(symbol);
|
||||
} else {
|
||||
self.qualified_value_lookups.insert(symbol);
|
||||
}
|
||||
|
||||
Ok(symbol)
|
||||
}
|
||||
|
@ -107,7 +117,11 @@ impl<'a> Env<'a> {
|
|||
Some(ident_id) => {
|
||||
let symbol = Symbol::new(module_id, *ident_id);
|
||||
|
||||
self.qualified_lookups.insert(symbol);
|
||||
if is_type_name {
|
||||
self.qualified_type_lookups.insert(symbol);
|
||||
} else {
|
||||
self.qualified_value_lookups.insert(symbol);
|
||||
}
|
||||
|
||||
Ok(symbol)
|
||||
}
|
||||
|
|
|
@ -493,7 +493,7 @@ pub fn canonicalize_expr<'a>(
|
|||
Ok((name, opaque_def)) => {
|
||||
let argument = Box::new(args.pop().unwrap());
|
||||
output.references.referenced_type_defs.insert(name);
|
||||
output.references.lookups.insert(name);
|
||||
output.references.type_lookups.insert(name);
|
||||
|
||||
let (type_arguments, lambda_set_variables, specialized_def_type) =
|
||||
freshen_opaque_def(var_store, opaque_def);
|
||||
|
@ -587,7 +587,7 @@ pub fn canonicalize_expr<'a>(
|
|||
}
|
||||
}
|
||||
ast::Expr::Var { module_name, ident } => {
|
||||
canonicalize_lookup(env, scope, module_name, ident, region)
|
||||
canonicalize_var_lookup(env, scope, module_name, ident, region)
|
||||
}
|
||||
ast::Expr::Underscore(name) => {
|
||||
// we parse underscores, but they are not valid expression syntax
|
||||
|
@ -661,8 +661,12 @@ pub fn canonicalize_expr<'a>(
|
|||
&loc_body_expr.value,
|
||||
);
|
||||
|
||||
let mut captured_symbols: MutSet<Symbol> =
|
||||
new_output.references.lookups.iter().copied().collect();
|
||||
let mut captured_symbols: MutSet<Symbol> = new_output
|
||||
.references
|
||||
.value_lookups
|
||||
.iter()
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
// filter out the closure's name itself
|
||||
captured_symbols.remove(&symbol);
|
||||
|
@ -684,7 +688,10 @@ pub fn canonicalize_expr<'a>(
|
|||
output.union(new_output);
|
||||
|
||||
// filter out aliases
|
||||
captured_symbols.retain(|s| !output.references.referenced_type_defs.contains(s));
|
||||
debug_assert!(captured_symbols
|
||||
.iter()
|
||||
.all(|s| !output.references.referenced_type_defs.contains(s)));
|
||||
// captured_symbols.retain(|s| !output.references.referenced_type_defs.contains(s));
|
||||
|
||||
// filter out functions that don't close over anything
|
||||
captured_symbols.retain(|s| !output.non_closures.contains(s));
|
||||
|
@ -693,7 +700,7 @@ pub fn canonicalize_expr<'a>(
|
|||
// went unreferenced. If any did, report them as unused arguments.
|
||||
for (sub_symbol, region) in scope.symbols() {
|
||||
if !original_scope.contains_symbol(*sub_symbol) {
|
||||
if !output.references.has_lookup(*sub_symbol) {
|
||||
if !output.references.has_value_lookup(*sub_symbol) {
|
||||
// The body never referenced this argument we declared. It's an unused argument!
|
||||
env.problem(Problem::UnusedArgument(symbol, *sub_symbol, *region));
|
||||
}
|
||||
|
@ -701,7 +708,7 @@ pub fn canonicalize_expr<'a>(
|
|||
// We shouldn't ultimately count arguments as referenced locals. Otherwise,
|
||||
// we end up with weird conclusions like the expression (\x -> x + 1)
|
||||
// references the (nonexistent) local variable x!
|
||||
output.references.lookups.remove(sub_symbol);
|
||||
output.references.value_lookups.remove(sub_symbol);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1082,8 +1089,10 @@ fn canonicalize_when_branch<'a>(
|
|||
for (symbol, region) in scope.symbols() {
|
||||
let symbol = *symbol;
|
||||
|
||||
if !output.references.has_lookup(symbol)
|
||||
&& !branch_output.references.has_lookup(symbol)
|
||||
if !output.references.has_value_lookup(symbol)
|
||||
&& !output.references.has_type_lookup(symbol)
|
||||
&& !branch_output.references.has_value_lookup(symbol)
|
||||
&& !branch_output.references.has_type_lookup(symbol)
|
||||
&& !original_scope.contains_symbol(symbol)
|
||||
{
|
||||
env.problem(Problem::UnusedDef(symbol, *region));
|
||||
|
@ -1107,7 +1116,7 @@ pub fn local_successors<'a>(
|
|||
references: &'a References,
|
||||
closures: &'a MutMap<Symbol, References>,
|
||||
) -> ImSet<Symbol> {
|
||||
let mut answer = references.lookups.clone();
|
||||
let mut answer = references.value_lookups.clone();
|
||||
|
||||
for call_symbol in references.calls.iter() {
|
||||
answer = answer.union(call_successors(*call_symbol, closures));
|
||||
|
@ -1127,7 +1136,7 @@ fn call_successors(call_symbol: Symbol, closures: &MutMap<Symbol, References>) -
|
|||
}
|
||||
|
||||
if let Some(references) = closures.get(&symbol) {
|
||||
answer.extend(references.lookups.iter().copied());
|
||||
answer.extend(references.value_lookups.iter().copied());
|
||||
queue.extend(references.calls.iter().copied());
|
||||
|
||||
seen.insert(symbol);
|
||||
|
@ -1152,7 +1161,7 @@ where
|
|||
Some((_, refs)) => {
|
||||
visited.insert(defined_symbol);
|
||||
|
||||
for local in refs.lookups.iter() {
|
||||
for local in refs.value_lookups.iter() {
|
||||
if !visited.contains(local) {
|
||||
let other_refs: References =
|
||||
references_from_local(*local, visited, refs_by_def, closures);
|
||||
|
@ -1160,7 +1169,7 @@ where
|
|||
answer = answer.union(other_refs);
|
||||
}
|
||||
|
||||
answer.lookups.insert(*local);
|
||||
answer.value_lookups.insert(*local);
|
||||
}
|
||||
|
||||
for call in refs.calls.iter() {
|
||||
|
@ -1194,7 +1203,7 @@ where
|
|||
|
||||
visited.insert(call_symbol);
|
||||
|
||||
for closed_over_local in references.lookups.iter() {
|
||||
for closed_over_local in references.value_lookups.iter() {
|
||||
if !visited.contains(closed_over_local) {
|
||||
let other_refs =
|
||||
references_from_local(*closed_over_local, visited, refs_by_def, closures);
|
||||
|
@ -1202,7 +1211,7 @@ where
|
|||
answer = answer.union(other_refs);
|
||||
}
|
||||
|
||||
answer.lookups.insert(*closed_over_local);
|
||||
answer.value_lookups.insert(*closed_over_local);
|
||||
}
|
||||
|
||||
for call in references.calls.iter() {
|
||||
|
@ -1335,7 +1344,7 @@ fn canonicalize_field<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
fn canonicalize_lookup(
|
||||
fn canonicalize_var_lookup(
|
||||
env: &mut Env<'_>,
|
||||
scope: &mut Scope,
|
||||
module_name: &str,
|
||||
|
@ -1350,7 +1359,7 @@ fn canonicalize_lookup(
|
|||
// Look it up in scope!
|
||||
match scope.lookup(&(*ident).into(), region) {
|
||||
Ok(symbol) => {
|
||||
output.references.lookups.insert(symbol);
|
||||
output.references.value_lookups.insert(symbol);
|
||||
|
||||
Var(symbol)
|
||||
}
|
||||
|
@ -1365,7 +1374,7 @@ fn canonicalize_lookup(
|
|||
// Look it up in the env!
|
||||
match env.qualified_lookup(module_name, ident, region) {
|
||||
Ok(symbol) => {
|
||||
output.references.lookups.insert(symbol);
|
||||
output.references.value_lookups.insert(symbol);
|
||||
|
||||
Var(symbol)
|
||||
}
|
||||
|
|
|
@ -23,21 +23,29 @@ pub struct Module {
|
|||
pub module_id: ModuleId,
|
||||
pub exposed_imports: MutMap<Symbol, Variable>,
|
||||
pub exposed_symbols: MutSet<Symbol>,
|
||||
pub references: MutSet<Symbol>,
|
||||
pub referenced_values: MutSet<Symbol>,
|
||||
pub referenced_types: MutSet<Symbol>,
|
||||
pub aliases: MutMap<Symbol, Alias>,
|
||||
pub rigid_variables: MutMap<Variable, Lowercase>,
|
||||
pub rigid_variables: RigidVariables,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct RigidVariables {
|
||||
pub named: MutMap<Variable, Lowercase>,
|
||||
pub wildcards: MutSet<Variable>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ModuleOutput {
|
||||
pub aliases: MutMap<Symbol, Alias>,
|
||||
pub rigid_variables: MutMap<Variable, Lowercase>,
|
||||
pub rigid_variables: RigidVariables,
|
||||
pub declarations: Vec<Declaration>,
|
||||
pub exposed_imports: MutMap<Symbol, Variable>,
|
||||
pub lookups: Vec<(Symbol, Variable, Region)>,
|
||||
pub problems: Vec<Problem>,
|
||||
pub ident_ids: IdentIds,
|
||||
pub references: MutSet<Symbol>,
|
||||
pub referenced_values: MutSet<Symbol>,
|
||||
pub referenced_types: MutSet<Symbol>,
|
||||
pub scope: Scope,
|
||||
}
|
||||
|
||||
|
@ -206,7 +214,7 @@ pub fn canonicalize_module_defs<'a>(
|
|||
}
|
||||
|
||||
let mut lookups = Vec::with_capacity(num_deps);
|
||||
let mut rigid_variables = MutMap::default();
|
||||
let mut rigid_variables = RigidVariables::default();
|
||||
|
||||
// Exposed values are treated like defs that appear before any others, e.g.
|
||||
//
|
||||
|
@ -291,38 +299,38 @@ pub fn canonicalize_module_defs<'a>(
|
|||
// See if any of the new idents we defined went unused.
|
||||
// If any were unused and also not exposed, report it.
|
||||
for (symbol, region) in symbols_introduced {
|
||||
if !output.references.has_lookup(symbol) && !exposed_symbols.contains(&symbol) {
|
||||
if !output.references.has_value_lookup(symbol)
|
||||
&& !output.references.has_type_lookup(symbol)
|
||||
&& !exposed_symbols.contains(&symbol)
|
||||
{
|
||||
env.problem(Problem::UnusedDef(symbol, region));
|
||||
}
|
||||
}
|
||||
|
||||
for (var, lowercase) in output.introduced_variables.name_by_var {
|
||||
rigid_variables.insert(var, lowercase.clone());
|
||||
rigid_variables.named.insert(var, lowercase.clone());
|
||||
}
|
||||
|
||||
for var in output.introduced_variables.wildcards {
|
||||
rigid_variables.insert(var, "*".into());
|
||||
rigid_variables.wildcards.insert(var.value);
|
||||
}
|
||||
|
||||
let mut references = MutSet::default();
|
||||
let mut referenced_values = MutSet::default();
|
||||
let mut referenced_types = MutSet::default();
|
||||
|
||||
// Gather up all the symbols that were referenced across all the defs' lookups.
|
||||
for symbol in output.references.lookups.iter() {
|
||||
references.insert(*symbol);
|
||||
}
|
||||
referenced_values.extend(output.references.value_lookups);
|
||||
referenced_types.extend(output.references.type_lookups);
|
||||
|
||||
// Gather up all the symbols that were referenced across all the defs' calls.
|
||||
for symbol in output.references.calls.iter() {
|
||||
references.insert(*symbol);
|
||||
}
|
||||
referenced_values.extend(output.references.calls);
|
||||
|
||||
// Gather up all the symbols that were referenced from other modules.
|
||||
for symbol in env.qualified_lookups.iter() {
|
||||
references.insert(*symbol);
|
||||
}
|
||||
referenced_values.extend(env.qualified_value_lookups.iter().copied());
|
||||
referenced_types.extend(env.qualified_type_lookups.iter().copied());
|
||||
|
||||
// add any builtins used by other builtins
|
||||
let transitive_builtins: Vec<Symbol> = references
|
||||
let transitive_builtins: Vec<Symbol> = referenced_values
|
||||
.iter()
|
||||
.filter(|s| s.is_builtin())
|
||||
.map(|s| crate::builtins::builtin_dependencies(*s))
|
||||
|
@ -330,7 +338,7 @@ pub fn canonicalize_module_defs<'a>(
|
|||
.copied()
|
||||
.collect();
|
||||
|
||||
references.extend(transitive_builtins);
|
||||
referenced_values.extend(transitive_builtins);
|
||||
|
||||
// NOTE previously we inserted builtin defs into the list of defs here
|
||||
// this is now done later, in file.rs.
|
||||
|
@ -340,7 +348,12 @@ pub fn canonicalize_module_defs<'a>(
|
|||
// symbols from this set
|
||||
let mut exposed_but_not_defined = exposed_symbols.clone();
|
||||
|
||||
match sort_can_defs(&mut env, defs, Output::default()) {
|
||||
let new_output = Output {
|
||||
aliases: output.aliases,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match sort_can_defs(&mut env, defs, new_output) {
|
||||
(Ok(mut declarations), output) => {
|
||||
use crate::def::Declaration::*;
|
||||
|
||||
|
@ -504,19 +517,15 @@ pub fn canonicalize_module_defs<'a>(
|
|||
}
|
||||
|
||||
// Incorporate any remaining output.lookups entries into references.
|
||||
for symbol in output.references.lookups {
|
||||
references.insert(symbol);
|
||||
}
|
||||
referenced_values.extend(output.references.value_lookups);
|
||||
referenced_types.extend(output.references.type_lookups);
|
||||
|
||||
// Incorporate any remaining output.calls entries into references.
|
||||
for symbol in output.references.calls {
|
||||
references.insert(symbol);
|
||||
}
|
||||
referenced_values.extend(output.references.calls);
|
||||
|
||||
// Gather up all the symbols that were referenced from other modules.
|
||||
for symbol in env.qualified_lookups.iter() {
|
||||
references.insert(*symbol);
|
||||
}
|
||||
referenced_values.extend(env.qualified_value_lookups.iter().copied());
|
||||
referenced_types.extend(env.qualified_type_lookups.iter().copied());
|
||||
|
||||
for declaration in declarations.iter_mut() {
|
||||
match declaration {
|
||||
|
@ -530,7 +539,7 @@ pub fn canonicalize_module_defs<'a>(
|
|||
|
||||
// TODO this loops over all symbols in the module, we can speed it up by having an
|
||||
// iterator over all builtin symbols
|
||||
for symbol in references.iter() {
|
||||
for symbol in referenced_values.iter() {
|
||||
if symbol.is_builtin() {
|
||||
// this can fail when the symbol is for builtin types, or has no implementation yet
|
||||
if let Some(def) = crate::builtins::builtin_defs_map(*symbol, var_store) {
|
||||
|
@ -544,7 +553,8 @@ pub fn canonicalize_module_defs<'a>(
|
|||
aliases,
|
||||
rigid_variables,
|
||||
declarations,
|
||||
references,
|
||||
referenced_values,
|
||||
referenced_types,
|
||||
exposed_imports: can_exposed_imports,
|
||||
problems: env.problems,
|
||||
lookups,
|
||||
|
|
|
@ -96,6 +96,7 @@ pub fn desugar_def<'a>(arena: &'a Bump, def: &'a Def<'a>) -> Def<'a> {
|
|||
SpaceBefore(def, _) | SpaceAfter(def, _) => desugar_def(arena, def),
|
||||
alias @ Alias { .. } => *alias,
|
||||
opaque @ Opaque { .. } => *opaque,
|
||||
ability @ Ability { .. } => *ability,
|
||||
ann @ Annotation(_, _) => *ann,
|
||||
AnnotatedBody {
|
||||
ann_pattern,
|
||||
|
|
|
@ -254,7 +254,7 @@ pub fn canonicalize_pattern<'a>(
|
|||
freshen_opaque_def(var_store, opaque_def);
|
||||
|
||||
output.references.referenced_type_defs.insert(opaque);
|
||||
output.references.lookups.insert(opaque);
|
||||
output.references.type_lookups.insert(opaque);
|
||||
|
||||
Pattern::UnwrappedOpaque {
|
||||
whole_var: var_store.fresh(),
|
||||
|
|
|
@ -45,7 +45,8 @@ impl Procedure {
|
|||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct References {
|
||||
pub bound_symbols: ImSet<Symbol>,
|
||||
pub lookups: ImSet<Symbol>,
|
||||
pub type_lookups: ImSet<Symbol>,
|
||||
pub value_lookups: ImSet<Symbol>,
|
||||
/// Aliases or opaque types referenced
|
||||
pub referenced_type_defs: ImSet<Symbol>,
|
||||
pub calls: ImSet<Symbol>,
|
||||
|
@ -57,7 +58,8 @@ impl References {
|
|||
}
|
||||
|
||||
pub fn union(mut self, other: References) -> Self {
|
||||
self.lookups = self.lookups.union(other.lookups);
|
||||
self.value_lookups = self.value_lookups.union(other.value_lookups);
|
||||
self.type_lookups = self.type_lookups.union(other.type_lookups);
|
||||
self.calls = self.calls.union(other.calls);
|
||||
self.bound_symbols = self.bound_symbols.union(other.bound_symbols);
|
||||
self.referenced_type_defs = self.referenced_type_defs.union(other.referenced_type_defs);
|
||||
|
@ -66,13 +68,18 @@ impl References {
|
|||
}
|
||||
|
||||
pub fn union_mut(&mut self, other: References) {
|
||||
self.lookups.extend(other.lookups);
|
||||
self.value_lookups.extend(other.value_lookups);
|
||||
self.type_lookups.extend(other.type_lookups);
|
||||
self.calls.extend(other.calls);
|
||||
self.bound_symbols.extend(other.bound_symbols);
|
||||
self.referenced_type_defs.extend(other.referenced_type_defs);
|
||||
}
|
||||
|
||||
pub fn has_lookup(&self, symbol: Symbol) -> bool {
|
||||
self.lookups.contains(&symbol)
|
||||
pub fn has_value_lookup(&self, symbol: Symbol) -> bool {
|
||||
self.value_lookups.contains(&symbol)
|
||||
}
|
||||
|
||||
pub fn has_type_lookup(&self, symbol: Symbol) -> bool {
|
||||
self.type_lookups.contains(&symbol)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -117,3 +117,56 @@ impl<T> Slice<T> {
|
|||
self.indices().map(|i| Index::new(i as _))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub struct EitherIndex<T, U> {
|
||||
index: u32,
|
||||
_marker: std::marker::PhantomData<(T, U)>,
|
||||
}
|
||||
|
||||
impl<T, U> Clone for EitherIndex<T, U> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
index: self.index,
|
||||
_marker: self._marker,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Copy for EitherIndex<T, U> {}
|
||||
|
||||
impl<T, U> std::fmt::Debug for EitherIndex<T, U> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Index({})", self.index)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> EitherIndex<T, U> {
|
||||
const MASK: u32 = 1 << 31;
|
||||
|
||||
pub const fn from_left(input: Index<T>) -> Self {
|
||||
assert!(input.index & Self::MASK == 0);
|
||||
|
||||
Self {
|
||||
index: input.index,
|
||||
_marker: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn from_right(input: Index<U>) -> Self {
|
||||
assert!(input.index & Self::MASK == 0);
|
||||
|
||||
Self {
|
||||
index: input.index | Self::MASK,
|
||||
_marker: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn split(self) -> Result<Index<T>, Index<U>> {
|
||||
if self.index & Self::MASK == 0 {
|
||||
Ok(Index::new(self.index))
|
||||
} else {
|
||||
Err(Index::new(self.index ^ Self::MASK))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,9 @@ use roc_module::ident::{Lowercase, TagName};
|
|||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::Region;
|
||||
use roc_types::subs::Variable;
|
||||
use roc_types::types::Reason;
|
||||
use roc_types::types::Type::{self, *};
|
||||
use roc_types::types::{AliasKind, Category};
|
||||
use roc_types::types::{Reason, TypeExtension};
|
||||
|
||||
#[must_use]
|
||||
#[inline(always)]
|
||||
|
@ -190,7 +190,7 @@ pub fn num_floatingpoint(range: Type) -> Type {
|
|||
TagName::Private(Symbol::NUM_AT_FLOATINGPOINT),
|
||||
vec![range.clone()],
|
||||
)],
|
||||
Box::new(Type::EmptyTagUnion),
|
||||
TypeExtension::Closed,
|
||||
);
|
||||
|
||||
builtin_alias(
|
||||
|
@ -209,7 +209,7 @@ pub fn num_u32() -> Type {
|
|||
fn num_unsigned32() -> Type {
|
||||
let alias_content = Type::TagUnion(
|
||||
vec![(TagName::Private(Symbol::NUM_AT_UNSIGNED32), vec![])],
|
||||
Box::new(Type::EmptyTagUnion),
|
||||
TypeExtension::Closed,
|
||||
);
|
||||
|
||||
builtin_alias(Symbol::NUM_UNSIGNED32, vec![], Box::new(alias_content))
|
||||
|
@ -219,7 +219,7 @@ fn num_unsigned32() -> Type {
|
|||
pub fn num_binary64() -> Type {
|
||||
let alias_content = Type::TagUnion(
|
||||
vec![(TagName::Private(Symbol::NUM_AT_BINARY64), vec![])],
|
||||
Box::new(Type::EmptyTagUnion),
|
||||
TypeExtension::Closed,
|
||||
);
|
||||
|
||||
builtin_alias(Symbol::NUM_BINARY64, vec![], Box::new(alias_content))
|
||||
|
@ -238,7 +238,7 @@ pub fn num_int(range: Type) -> Type {
|
|||
pub fn num_signed64() -> Type {
|
||||
let alias_content = Type::TagUnion(
|
||||
vec![(TagName::Private(Symbol::NUM_AT_SIGNED64), vec![])],
|
||||
Box::new(Type::EmptyTagUnion),
|
||||
TypeExtension::Closed,
|
||||
);
|
||||
|
||||
builtin_alias(Symbol::NUM_SIGNED64, vec![], Box::new(alias_content))
|
||||
|
@ -251,7 +251,7 @@ pub fn num_integer(range: Type) -> Type {
|
|||
TagName::Private(Symbol::NUM_AT_INTEGER),
|
||||
vec![range.clone()],
|
||||
)],
|
||||
Box::new(Type::EmptyTagUnion),
|
||||
TypeExtension::Closed,
|
||||
);
|
||||
|
||||
builtin_alias(
|
||||
|
@ -265,7 +265,7 @@ pub fn num_integer(range: Type) -> Type {
|
|||
pub fn num_num(typ: Type) -> Type {
|
||||
let alias_content = Type::TagUnion(
|
||||
vec![(TagName::Private(Symbol::NUM_AT_NUM), vec![typ.clone()])],
|
||||
Box::new(Type::EmptyTagUnion),
|
||||
TypeExtension::Closed,
|
||||
);
|
||||
|
||||
builtin_alias(
|
||||
|
|
|
@ -16,7 +16,9 @@ use roc_module::symbol::{ModuleId, Symbol};
|
|||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::Variable;
|
||||
use roc_types::types::Type::{self, *};
|
||||
use roc_types::types::{AliasKind, AnnotationSource, Category, PReason, Reason, RecordField};
|
||||
use roc_types::types::{
|
||||
AliasKind, AnnotationSource, Category, PReason, Reason, RecordField, TypeExtension,
|
||||
};
|
||||
|
||||
/// This is for constraining Defs
|
||||
#[derive(Default, Debug)]
|
||||
|
@ -119,32 +121,18 @@ pub fn constrain_expr(
|
|||
rec_constraints.push(field_con);
|
||||
}
|
||||
|
||||
let record_type = Type::Record(
|
||||
field_types,
|
||||
// TODO can we avoid doing Box::new on every single one of these?
|
||||
// We can put `static EMPTY_REC: Type = Type::EmptyRec`, but that requires a
|
||||
// lifetime parameter on `Type`
|
||||
Box::new(Type::EmptyRec),
|
||||
);
|
||||
let record_con = constraints.equal_types(
|
||||
let record_type = Type::Record(field_types, TypeExtension::Closed);
|
||||
|
||||
let record_con = constraints.equal_types_with_storage(
|
||||
record_type,
|
||||
expected.clone(),
|
||||
expected,
|
||||
Category::Record,
|
||||
region,
|
||||
*record_var,
|
||||
);
|
||||
|
||||
rec_constraints.push(record_con);
|
||||
|
||||
// variable to store in the AST
|
||||
let stored_con = constraints.equal_types(
|
||||
Type::Variable(*record_var),
|
||||
expected,
|
||||
Category::Storage(std::file!(), std::line!()),
|
||||
region,
|
||||
);
|
||||
|
||||
field_vars.push(*record_var);
|
||||
rec_constraints.push(stored_con);
|
||||
|
||||
let and_constraint = constraints.and_constraint(rec_constraints);
|
||||
constraints.exists(field_vars, and_constraint)
|
||||
|
@ -173,18 +161,19 @@ pub fn constrain_expr(
|
|||
cons.push(con);
|
||||
}
|
||||
|
||||
let fields_type = Type::Record(fields, Box::new(Type::Variable(*ext_var)));
|
||||
let fields_type =
|
||||
Type::Record(fields, TypeExtension::from_type(Type::Variable(*ext_var)));
|
||||
let record_type = Type::Variable(*record_var);
|
||||
|
||||
// NOTE from elm compiler: fields_type is separate so that Error propagates better
|
||||
let fields_con = constraints.equal_types(
|
||||
record_type.clone(),
|
||||
let fields_con = constraints.equal_types_var(
|
||||
*record_var,
|
||||
NoExpectation(fields_type),
|
||||
Category::Record,
|
||||
region,
|
||||
);
|
||||
let record_con =
|
||||
constraints.equal_types(record_type.clone(), expected, Category::Record, region);
|
||||
constraints.equal_types_var(*record_var, expected, Category::Record, region);
|
||||
|
||||
vars.push(*record_var);
|
||||
vars.push(*ext_var);
|
||||
|
@ -273,7 +262,7 @@ pub fn constrain_expr(
|
|||
|
||||
let fn_type = Variable(*fn_var);
|
||||
let fn_region = loc_fn.region;
|
||||
let fn_expected = NoExpectation(fn_type.clone());
|
||||
let fn_expected = NoExpectation(fn_type);
|
||||
|
||||
let fn_reason = Reason::FnCall {
|
||||
name: opt_symbol,
|
||||
|
@ -323,11 +312,7 @@ pub fn constrain_expr(
|
|||
|
||||
let expected_fn_type = ForReason(
|
||||
fn_reason,
|
||||
Function(
|
||||
arg_types,
|
||||
Box::new(closure_type),
|
||||
Box::new(ret_type.clone()),
|
||||
),
|
||||
Function(arg_types, Box::new(closure_type), Box::new(ret_type)),
|
||||
region,
|
||||
);
|
||||
|
||||
|
@ -335,9 +320,9 @@ pub fn constrain_expr(
|
|||
|
||||
let and_cons = [
|
||||
fn_con,
|
||||
constraints.equal_types(fn_type, expected_fn_type, category.clone(), fn_region),
|
||||
constraints.equal_types_var(*fn_var, expected_fn_type, category.clone(), fn_region),
|
||||
constraints.and_constraint(arg_cons),
|
||||
constraints.equal_types(ret_type, expected, category, region),
|
||||
constraints.equal_types_var(*ret_var, expected, category, region),
|
||||
];
|
||||
|
||||
let and_constraint = constraints.and_constraint(and_cons);
|
||||
|
@ -415,14 +400,12 @@ pub fn constrain_expr(
|
|||
pattern_state_constraints,
|
||||
ret_constraint,
|
||||
),
|
||||
// "the closure's type is equal to expected type"
|
||||
constraints.equal_types(function_type.clone(), expected, Category::Lambda, region),
|
||||
// "fn_var is equal to the closure's type" - fn_var is used in code gen
|
||||
constraints.equal_types(
|
||||
Type::Variable(*fn_var),
|
||||
NoExpectation(function_type),
|
||||
Category::Storage(std::file!(), std::line!()),
|
||||
constraints.equal_types_with_storage(
|
||||
function_type,
|
||||
expected,
|
||||
Category::Lambda,
|
||||
region,
|
||||
*fn_var,
|
||||
),
|
||||
closure_constraint,
|
||||
];
|
||||
|
@ -469,8 +452,8 @@ pub fn constrain_expr(
|
|||
|
||||
// TODO why does this cond var exist? is it for error messages?
|
||||
let first_cond_region = branches[0].0.region;
|
||||
let cond_var_is_bool_con = constraints.equal_types(
|
||||
Type::Variable(*cond_var),
|
||||
let cond_var_is_bool_con = constraints.equal_types_var(
|
||||
*cond_var,
|
||||
expect_bool(first_cond_region),
|
||||
Category::If,
|
||||
first_cond_region,
|
||||
|
@ -528,8 +511,8 @@ pub fn constrain_expr(
|
|||
),
|
||||
);
|
||||
|
||||
let ast_con = constraints.equal_types(
|
||||
Type::Variable(*branch_var),
|
||||
let ast_con = constraints.equal_types_var(
|
||||
*branch_var,
|
||||
NoExpectation(tipe),
|
||||
Category::Storage(std::file!(), std::line!()),
|
||||
region,
|
||||
|
@ -583,8 +566,8 @@ pub fn constrain_expr(
|
|||
),
|
||||
);
|
||||
|
||||
branch_cons.push(constraints.equal_types(
|
||||
Type::Variable(*branch_var),
|
||||
branch_cons.push(constraints.equal_types_var(
|
||||
*branch_var,
|
||||
expected,
|
||||
Category::Storage(std::file!(), std::line!()),
|
||||
region,
|
||||
|
@ -654,8 +637,8 @@ pub fn constrain_expr(
|
|||
branch_constraints.push(branch_con);
|
||||
}
|
||||
|
||||
branch_constraints.push(constraints.equal_types(
|
||||
typ,
|
||||
branch_constraints.push(constraints.equal_types_var(
|
||||
*expr_var,
|
||||
expected,
|
||||
Category::When,
|
||||
region,
|
||||
|
@ -665,7 +648,8 @@ pub fn constrain_expr(
|
|||
}
|
||||
|
||||
_ => {
|
||||
let branch_type = Variable(*expr_var);
|
||||
let branch_var = *expr_var;
|
||||
let branch_type = Variable(branch_var);
|
||||
let mut branch_cons = Vec::with_capacity(branches.len());
|
||||
|
||||
for (index, when_branch) in branches.iter().enumerate() {
|
||||
|
@ -703,8 +687,8 @@ pub fn constrain_expr(
|
|||
//
|
||||
// The return type of each branch must equal the return type of
|
||||
// the entire when-expression.
|
||||
branch_cons.push(constraints.equal_types(
|
||||
branch_type,
|
||||
branch_cons.push(constraints.equal_types_var(
|
||||
branch_var,
|
||||
expected,
|
||||
Category::When,
|
||||
region,
|
||||
|
@ -731,15 +715,15 @@ pub fn constrain_expr(
|
|||
let mut rec_field_types = SendMap::default();
|
||||
|
||||
let label = field.clone();
|
||||
rec_field_types.insert(label, RecordField::Demanded(field_type.clone()));
|
||||
rec_field_types.insert(label, RecordField::Demanded(field_type));
|
||||
|
||||
let record_type = Type::Record(rec_field_types, Box::new(ext_type));
|
||||
let record_type = Type::Record(rec_field_types, TypeExtension::from_type(ext_type));
|
||||
let record_expected = Expected::NoExpectation(record_type);
|
||||
|
||||
let category = Category::Access(field.clone());
|
||||
|
||||
let record_con = constraints.equal_types(
|
||||
Type::Variable(*record_var),
|
||||
let record_con = constraints.equal_types_var(
|
||||
*record_var,
|
||||
record_expected.clone(),
|
||||
category.clone(),
|
||||
region,
|
||||
|
@ -756,7 +740,7 @@ pub fn constrain_expr(
|
|||
record_expected,
|
||||
);
|
||||
|
||||
let eq = constraints.equal_types(field_type, expected, category, region);
|
||||
let eq = constraints.equal_types_var(field_var, expected, category, region);
|
||||
constraints.exists_many(
|
||||
[*record_var, field_var, ext_var],
|
||||
[constraint, eq, record_con],
|
||||
|
@ -780,17 +764,13 @@ pub fn constrain_expr(
|
|||
let mut field_types = SendMap::default();
|
||||
let label = field.clone();
|
||||
field_types.insert(label, RecordField::Demanded(field_type.clone()));
|
||||
let record_type = Type::Record(field_types, Box::new(ext_type));
|
||||
let record_type = Type::Record(field_types, TypeExtension::from_type(ext_type));
|
||||
|
||||
let category = Category::Accessor(field.clone());
|
||||
|
||||
let record_expected = Expected::NoExpectation(record_type.clone());
|
||||
let record_con = constraints.equal_types(
|
||||
Type::Variable(*record_var),
|
||||
record_expected,
|
||||
category.clone(),
|
||||
region,
|
||||
);
|
||||
let record_con =
|
||||
constraints.equal_types_var(*record_var, record_expected, category.clone(), region);
|
||||
|
||||
let lambda_set = Type::ClosureTag {
|
||||
name: *closure_name,
|
||||
|
@ -801,13 +781,13 @@ pub fn constrain_expr(
|
|||
|
||||
let function_type = Type::Function(
|
||||
vec![record_type],
|
||||
Box::new(closure_type.clone()),
|
||||
Box::new(closure_type),
|
||||
Box::new(field_type),
|
||||
);
|
||||
|
||||
let cons = [
|
||||
constraints.equal_types(
|
||||
closure_type,
|
||||
constraints.equal_types_var(
|
||||
*closure_var,
|
||||
NoExpectation(lambda_set),
|
||||
category.clone(),
|
||||
region,
|
||||
|
@ -847,8 +827,8 @@ pub fn constrain_expr(
|
|||
constrain_recursive_defs(constraints, env, defs, body_con),
|
||||
// Record the type of tne entire def-expression in the variable.
|
||||
// Code gen will need that later!
|
||||
constraints.equal_types(
|
||||
Type::Variable(*var),
|
||||
constraints.equal_types_var(
|
||||
*var,
|
||||
expected,
|
||||
Category::Storage(std::file!(), std::line!()),
|
||||
loc_ret.region,
|
||||
|
@ -882,8 +862,8 @@ pub fn constrain_expr(
|
|||
constrain_def(constraints, env, def, body_con),
|
||||
// Record the type of the entire def-expression in the variable.
|
||||
// Code gen will need that later!
|
||||
constraints.equal_types(
|
||||
Type::Variable(*var),
|
||||
constraints.equal_types_var(
|
||||
*var,
|
||||
expected.clone(),
|
||||
Category::Storage(std::file!(), std::line!()),
|
||||
ret_region,
|
||||
|
@ -919,10 +899,10 @@ pub fn constrain_expr(
|
|||
types.push(Type::Variable(*var));
|
||||
}
|
||||
|
||||
let union_con = constraints.equal_types(
|
||||
let union_con = constraints.equal_types_with_storage(
|
||||
Type::TagUnion(
|
||||
vec![(name.clone(), types)],
|
||||
Box::new(Type::Variable(*ext_var)),
|
||||
TypeExtension::from_type(Type::Variable(*ext_var)),
|
||||
),
|
||||
expected.clone(),
|
||||
Category::TagApply {
|
||||
|
@ -930,18 +910,12 @@ pub fn constrain_expr(
|
|||
args_count: arguments.len(),
|
||||
},
|
||||
region,
|
||||
);
|
||||
let ast_con = constraints.equal_types(
|
||||
Type::Variable(*variant_var),
|
||||
expected,
|
||||
Category::Storage(std::file!(), std::line!()),
|
||||
region,
|
||||
*variant_var,
|
||||
);
|
||||
|
||||
vars.push(*variant_var);
|
||||
vars.push(*ext_var);
|
||||
arg_cons.push(union_con);
|
||||
arg_cons.push(ast_con);
|
||||
|
||||
constraints.exists_many(vars, arg_cons)
|
||||
}
|
||||
|
@ -970,11 +944,11 @@ pub fn constrain_expr(
|
|||
types.push(Type::Variable(*var));
|
||||
}
|
||||
|
||||
let union_con = constraints.equal_types(
|
||||
let union_con = constraints.equal_types_with_storage(
|
||||
Type::FunctionOrTagUnion(
|
||||
name.clone(),
|
||||
*closure_name,
|
||||
Box::new(Type::Variable(*ext_var)),
|
||||
TypeExtension::from_type(Type::Variable(*ext_var)),
|
||||
),
|
||||
expected.clone(),
|
||||
Category::TagApply {
|
||||
|
@ -982,18 +956,12 @@ pub fn constrain_expr(
|
|||
args_count: arguments.len(),
|
||||
},
|
||||
region,
|
||||
);
|
||||
let ast_con = constraints.equal_types(
|
||||
Type::Variable(*variant_var),
|
||||
expected,
|
||||
Category::Storage(std::file!(), std::line!()),
|
||||
region,
|
||||
*variant_var,
|
||||
);
|
||||
|
||||
vars.push(*variant_var);
|
||||
vars.push(*ext_var);
|
||||
arg_cons.push(union_con);
|
||||
arg_cons.push(ast_con);
|
||||
|
||||
constraints.exists_many(vars, arg_cons)
|
||||
}
|
||||
|
@ -1028,11 +996,12 @@ pub fn constrain_expr(
|
|||
|
||||
// Link the entire wrapped opaque type (with the now-constrained argument) to the
|
||||
// expected type
|
||||
let opaque_con = constraints.equal_types(
|
||||
let opaque_con = constraints.equal_types_with_storage(
|
||||
opaque_type,
|
||||
expected.clone(),
|
||||
expected,
|
||||
Category::OpaqueWrap(*name),
|
||||
region,
|
||||
*opaque_var,
|
||||
);
|
||||
|
||||
// Link the entire wrapped opaque type (with the now-constrained argument) to the type
|
||||
|
@ -1045,14 +1014,6 @@ pub fn constrain_expr(
|
|||
arg_loc_expr.region,
|
||||
);
|
||||
|
||||
// Store the entire wrapped opaque type in `opaque_var`
|
||||
let storage_con = constraints.equal_types(
|
||||
Type::Variable(*opaque_var),
|
||||
expected,
|
||||
Category::Storage(std::file!(), std::line!()),
|
||||
region,
|
||||
);
|
||||
|
||||
let mut vars = vec![*arg_var, *opaque_var];
|
||||
// Also add the fresh variables we created for the type argument and lambda sets
|
||||
vars.extend(type_arguments.iter().map(|(_, t)| {
|
||||
|
@ -1062,18 +1023,12 @@ pub fn constrain_expr(
|
|||
v.0.expect_variable("all lambda sets should be fresh variables here")
|
||||
}));
|
||||
|
||||
constraints.exists_many(
|
||||
vars,
|
||||
[arg_con, opaque_con, link_type_variables_con, storage_con],
|
||||
)
|
||||
constraints.exists_many(vars, [arg_con, opaque_con, link_type_variables_con])
|
||||
}
|
||||
|
||||
RunLowLevel { args, ret_var, op } => {
|
||||
// This is a modified version of what we do for function calls.
|
||||
|
||||
// The operation's return type
|
||||
let ret_type = Variable(*ret_var);
|
||||
|
||||
// This will be used in the occurs check
|
||||
let mut vars = Vec::with_capacity(1 + args.len());
|
||||
|
||||
|
@ -1103,7 +1058,7 @@ pub fn constrain_expr(
|
|||
let category = Category::LowLevelOpResult(*op);
|
||||
|
||||
// Deviation: elm uses an additional And here
|
||||
let eq = constraints.equal_types(ret_type, expected, category, region);
|
||||
let eq = constraints.equal_types_var(*ret_var, expected, category, region);
|
||||
arg_cons.push(eq);
|
||||
constraints.exists_many(vars, arg_cons)
|
||||
}
|
||||
|
@ -1114,9 +1069,6 @@ pub fn constrain_expr(
|
|||
} => {
|
||||
// This is a modified version of what we do for function calls.
|
||||
|
||||
// The operation's return type
|
||||
let ret_type = Variable(*ret_var);
|
||||
|
||||
// This will be used in the occurs check
|
||||
let mut vars = Vec::with_capacity(1 + args.len());
|
||||
|
||||
|
@ -1146,7 +1098,7 @@ pub fn constrain_expr(
|
|||
let category = Category::ForeignCall;
|
||||
|
||||
// Deviation: elm uses an additional And here
|
||||
let eq = constraints.equal_types(ret_type, expected, category, region);
|
||||
let eq = constraints.equal_types_var(*ret_var, expected, category, region);
|
||||
arg_cons.push(eq);
|
||||
constraints.exists_many(vars, arg_cons)
|
||||
}
|
||||
|
@ -1248,14 +1200,7 @@ fn constrain_empty_record(
|
|||
region: Region,
|
||||
expected: Expected<Type>,
|
||||
) -> Constraint {
|
||||
let expected_index = constraints.push_expected_type(expected);
|
||||
|
||||
Constraint::Eq(
|
||||
Constraints::EMPTY_RECORD,
|
||||
expected_index,
|
||||
Constraints::CATEGORY_RECORD,
|
||||
region,
|
||||
)
|
||||
constraints.equal_types(Type::EmptyRec, expected, Category::Record, region)
|
||||
}
|
||||
|
||||
/// Constrain top-level module declarations
|
||||
|
@ -1455,8 +1400,8 @@ fn constrain_def(
|
|||
def_pattern_state.vars.push(*pattern_var);
|
||||
pattern_types.push(Type::Variable(*pattern_var));
|
||||
|
||||
let pattern_con = constraints.equal_types(
|
||||
Type::Variable(*pattern_var),
|
||||
let pattern_con = constraints.equal_types_var(
|
||||
*pattern_var,
|
||||
Expected::NoExpectation(loc_ann.clone()),
|
||||
Category::Storage(std::file!(), std::line!()),
|
||||
loc_pattern.region,
|
||||
|
@ -1496,6 +1441,8 @@ fn constrain_def(
|
|||
vars.push(*fn_var);
|
||||
let defs_constraint = constraints.and_constraint(state.constraints);
|
||||
|
||||
let signature_closure_type = *signature_closure_type.clone();
|
||||
let signature_index = constraints.push_type(signature);
|
||||
let cons = [
|
||||
constraints.let_constraint(
|
||||
[],
|
||||
|
@ -1504,21 +1451,31 @@ fn constrain_def(
|
|||
defs_constraint,
|
||||
ret_constraint,
|
||||
),
|
||||
constraints.equal_types(
|
||||
Type::Variable(closure_var),
|
||||
constraints.equal_types_var(
|
||||
closure_var,
|
||||
Expected::FromAnnotation(
|
||||
def.loc_pattern.clone(),
|
||||
arity,
|
||||
AnnotationSource::TypedBody {
|
||||
region: annotation.region,
|
||||
},
|
||||
*signature_closure_type.clone(),
|
||||
signature_closure_type,
|
||||
),
|
||||
Category::ClosureSize,
|
||||
region,
|
||||
),
|
||||
constraints.store(signature.clone(), *fn_var, std::file!(), std::line!()),
|
||||
constraints.store(signature, expr_var, std::file!(), std::line!()),
|
||||
constraints.store_index(
|
||||
signature_index,
|
||||
*fn_var,
|
||||
std::file!(),
|
||||
std::line!(),
|
||||
),
|
||||
constraints.store_index(
|
||||
signature_index,
|
||||
expr_var,
|
||||
std::file!(),
|
||||
std::line!(),
|
||||
),
|
||||
constraints.store(ret_type, ret_var, std::file!(), std::line!()),
|
||||
closure_constraint,
|
||||
];
|
||||
|
@ -1661,12 +1618,12 @@ fn constrain_closure_size(
|
|||
let tag_name = TagName::Closure(name);
|
||||
Type::TagUnion(
|
||||
vec![(tag_name, tag_arguments)],
|
||||
Box::new(Type::Variable(closure_ext_var)),
|
||||
TypeExtension::from_type(Type::Variable(closure_ext_var)),
|
||||
)
|
||||
};
|
||||
|
||||
let finalizer = constraints.equal_types(
|
||||
Type::Variable(closure_var),
|
||||
let finalizer = constraints.equal_types_var(
|
||||
closure_var,
|
||||
NoExpectation(closure_type),
|
||||
Category::ClosureSize,
|
||||
region,
|
||||
|
@ -1691,7 +1648,7 @@ fn instantiate_rigids(
|
|||
headers: &mut SendMap<Symbol, Loc<Type>>,
|
||||
) -> InstantiateRigids {
|
||||
let mut annotation = annotation.clone();
|
||||
let mut new_rigid_variables = Vec::new();
|
||||
let mut new_rigid_variables: Vec<Variable> = Vec::new();
|
||||
|
||||
let mut rigid_substitution: ImMap<Variable, Type> = ImMap::default();
|
||||
for (name, var) in introduced_vars.var_by_name.iter() {
|
||||
|
@ -1700,23 +1657,24 @@ fn instantiate_rigids(
|
|||
match ftv.entry(name.clone()) {
|
||||
Occupied(occupied) => {
|
||||
let existing_rigid = occupied.get();
|
||||
rigid_substitution.insert(*var, Type::Variable(*existing_rigid));
|
||||
rigid_substitution.insert(var.value, Type::Variable(*existing_rigid));
|
||||
}
|
||||
Vacant(vacant) => {
|
||||
// It's possible to use this rigid in nested defs
|
||||
vacant.insert(*var);
|
||||
new_rigid_variables.push(*var);
|
||||
vacant.insert(var.value);
|
||||
new_rigid_variables.push(var.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// wildcards are always freshly introduced in this annotation
|
||||
new_rigid_variables.extend(introduced_vars.wildcards.iter().copied());
|
||||
new_rigid_variables.extend(introduced_vars.wildcards.iter().map(|v| v.value));
|
||||
|
||||
// lambda set vars are always freshly introduced in this annotation
|
||||
new_rigid_variables.extend(introduced_vars.lambda_sets.iter().copied());
|
||||
|
||||
let new_infer_variables = introduced_vars.inferred.clone();
|
||||
let new_infer_variables: Vec<Variable> =
|
||||
introduced_vars.inferred.iter().map(|v| v.value).collect();
|
||||
|
||||
// Instantiate rigid variables
|
||||
if !rigid_substitution.is_empty() {
|
||||
|
@ -1908,8 +1866,8 @@ pub fn rec_defs_help(
|
|||
def_pattern_state.vars.push(*pattern_var);
|
||||
pattern_types.push(Type::Variable(*pattern_var));
|
||||
|
||||
let pattern_con = constraints.equal_types(
|
||||
Type::Variable(*pattern_var),
|
||||
let pattern_con = constraints.equal_types_var(
|
||||
*pattern_var,
|
||||
Expected::NoExpectation(loc_ann.clone()),
|
||||
Category::Storage(std::file!(), std::line!()),
|
||||
loc_pattern.region,
|
||||
|
@ -1945,6 +1903,7 @@ pub fn rec_defs_help(
|
|||
|
||||
vars.push(*fn_var);
|
||||
|
||||
let signature_index = constraints.push_type(signature);
|
||||
let state_constraints = constraints.and_constraint(state.constraints);
|
||||
let cons = [
|
||||
constraints.let_constraint(
|
||||
|
@ -1962,13 +1921,18 @@ pub fn rec_defs_help(
|
|||
),
|
||||
// "fn_var is equal to the closure's type" - fn_var is used in code gen
|
||||
// Store type into AST vars. We use Store so errors aren't reported twice
|
||||
constraints.store(
|
||||
signature.clone(),
|
||||
constraints.store_index(
|
||||
signature_index,
|
||||
*fn_var,
|
||||
std::file!(),
|
||||
std::line!(),
|
||||
),
|
||||
constraints.store(signature, expr_var, std::file!(), std::line!()),
|
||||
constraints.store_index(
|
||||
signature_index,
|
||||
expr_var,
|
||||
std::file!(),
|
||||
std::line!(),
|
||||
),
|
||||
constraints.store(ret_type, ret_var, std::file!(), std::line!()),
|
||||
closure_constraint,
|
||||
];
|
||||
|
|
|
@ -1,19 +1,101 @@
|
|||
use roc_builtins::std::StdLib;
|
||||
use roc_can::constraint::{Constraint, Constraints};
|
||||
use roc_can::def::Declaration;
|
||||
use roc_collections::all::{MutMap, MutSet, SendMap};
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::symbol::{ModuleId, Symbol};
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_region::all::Loc;
|
||||
use roc_types::solved_types::{FreeVars, SolvedType};
|
||||
use roc_types::subs::{VarStore, Variable};
|
||||
use roc_types::types::{Alias, Problem};
|
||||
|
||||
pub type SubsByModule = MutMap<ModuleId, ExposedModuleTypes>;
|
||||
/// The types of all exposed values/functions of a collection of modules
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ExposedByModule {
|
||||
exposed: MutMap<ModuleId, ExposedModuleTypes>,
|
||||
}
|
||||
|
||||
impl ExposedByModule {
|
||||
pub fn insert(&mut self, module_id: ModuleId, exposed: ExposedModuleTypes) {
|
||||
self.exposed.insert(module_id, exposed);
|
||||
}
|
||||
|
||||
pub fn get(&self, module_id: &ModuleId) -> Option<&ExposedModuleTypes> {
|
||||
self.exposed.get(module_id)
|
||||
}
|
||||
|
||||
/// Convenient when you need mutable access to the StorageSubs in the ExposedModuleTypes
|
||||
pub fn get_mut(&mut self, module_id: &ModuleId) -> Option<&mut ExposedModuleTypes> {
|
||||
self.exposed.get_mut(module_id)
|
||||
}
|
||||
|
||||
/// Create a clone of `self` that has just a subset of the modules
|
||||
///
|
||||
/// Useful when we know what modules a particular module imports, and want just
|
||||
/// the exposed types for those exposed modules.
|
||||
pub fn retain_modules<'a>(&self, it: impl Iterator<Item = &'a ModuleId>) -> Self {
|
||||
let mut output = Self::default();
|
||||
|
||||
for module_id in it {
|
||||
match self.exposed.get(module_id) {
|
||||
None => {
|
||||
internal_error!("Module {:?} did not register its exposed values", module_id)
|
||||
}
|
||||
Some(exposed_types) => {
|
||||
output.exposed.insert(*module_id, exposed_types.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ExposedForModule {
|
||||
pub exposed_by_module: ExposedByModule,
|
||||
pub imported_values: Vec<Symbol>,
|
||||
}
|
||||
|
||||
impl ExposedForModule {
|
||||
pub fn new<'a>(
|
||||
it: impl Iterator<Item = &'a Symbol>,
|
||||
exposed_by_module: ExposedByModule,
|
||||
) -> Self {
|
||||
let mut imported_values = Vec::new();
|
||||
|
||||
for symbol in it {
|
||||
// Today, builtins are not actually imported,
|
||||
// but generated in each module that uses them
|
||||
//
|
||||
// This will change when we write builtins in roc
|
||||
if symbol.is_builtin() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(ExposedModuleTypes::Valid { .. }) =
|
||||
exposed_by_module.exposed.get(&symbol.module_id())
|
||||
{
|
||||
imported_values.push(*symbol);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
imported_values,
|
||||
exposed_by_module,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The types of all exposed values/functions of a module
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ExposedModuleTypes {
|
||||
Invalid,
|
||||
Valid(MutMap<Symbol, SolvedType>, MutMap<Symbol, Alias>),
|
||||
Valid {
|
||||
stored_vars_by_symbol: Vec<(Symbol, Variable)>,
|
||||
storage_subs: roc_types::subs::StorageSubs,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn constrain_module(
|
||||
|
@ -30,17 +112,56 @@ pub struct Import {
|
|||
pub solved_type: SolvedType,
|
||||
}
|
||||
|
||||
pub fn constrain_imported_values(
|
||||
pub fn introduce_builtin_imports(
|
||||
constraints: &mut Constraints,
|
||||
imports: Vec<Import>,
|
||||
imports: Vec<Symbol>,
|
||||
body_con: Constraint,
|
||||
var_store: &mut VarStore,
|
||||
) -> (Vec<Variable>, Constraint) {
|
||||
let mut def_types = SendMap::default();
|
||||
) -> Constraint {
|
||||
let stdlib = roc_builtins::std::borrow_stdlib();
|
||||
let (rigid_vars, def_types) = constrain_builtin_imports(stdlib, imports, var_store);
|
||||
constraints.let_import_constraint(rigid_vars, def_types, body_con, &[])
|
||||
}
|
||||
|
||||
pub fn constrain_builtin_imports(
|
||||
stdlib: &StdLib,
|
||||
imports: Vec<Symbol>,
|
||||
var_store: &mut VarStore,
|
||||
) -> (Vec<Variable>, Vec<(Symbol, Loc<roc_types::types::Type>)>) {
|
||||
let mut def_types = Vec::new();
|
||||
let mut rigid_vars = Vec::new();
|
||||
|
||||
for import in imports {
|
||||
for symbol in imports {
|
||||
let mut free_vars = FreeVars::default();
|
||||
|
||||
let import = match stdlib.types.get(&symbol) {
|
||||
Some((solved_type, region)) => {
|
||||
let loc_symbol = Loc {
|
||||
value: symbol,
|
||||
region: *region,
|
||||
};
|
||||
|
||||
Import {
|
||||
loc_symbol,
|
||||
solved_type: solved_type.clone(),
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let is_valid_alias = stdlib.applies.contains(&symbol)
|
||||
// This wasn't a builtin value or Apply; maybe it was a builtin alias.
|
||||
|| roc_types::builtin_aliases::aliases().contains_key(&symbol);
|
||||
|
||||
// if !is_valid_alias {
|
||||
// panic!(
|
||||
// "Could not find {:?} in builtin types {:?} or builtin aliases",
|
||||
// symbol, stdlib.types,
|
||||
// );
|
||||
// }
|
||||
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let loc_symbol = import.loc_symbol;
|
||||
|
||||
// an imported symbol can be either an alias or a value
|
||||
|
@ -55,13 +176,13 @@ pub fn constrain_imported_values(
|
|||
var_store,
|
||||
);
|
||||
|
||||
def_types.insert(
|
||||
def_types.push((
|
||||
loc_symbol.value,
|
||||
Loc {
|
||||
region: loc_symbol.region,
|
||||
value: typ,
|
||||
},
|
||||
);
|
||||
));
|
||||
|
||||
for (_, var) in free_vars.named_vars {
|
||||
rigid_vars.push(var);
|
||||
|
@ -80,188 +201,5 @@ pub fn constrain_imported_values(
|
|||
}
|
||||
}
|
||||
|
||||
(
|
||||
rigid_vars.clone(),
|
||||
constraints.let_constraint(rigid_vars, [], def_types, Constraint::True, body_con),
|
||||
)
|
||||
}
|
||||
|
||||
/// Run pre_constrain_imports to get imported_symbols and imported_aliases.
|
||||
pub fn constrain_imports(
|
||||
constraints: &mut Constraints,
|
||||
imported_symbols: Vec<Import>,
|
||||
constraint: Constraint,
|
||||
var_store: &mut VarStore,
|
||||
) -> Constraint {
|
||||
let (_introduced_rigids, constraint) =
|
||||
constrain_imported_values(constraints, imported_symbols, constraint, var_store);
|
||||
|
||||
// TODO determine what to do with those rigids
|
||||
// for var in introduced_rigids {
|
||||
// output.ftv.insert(var, format!("internal_{:?}", var).into());
|
||||
// }
|
||||
|
||||
constraint
|
||||
}
|
||||
|
||||
pub struct ConstrainableImports {
|
||||
pub imported_symbols: Vec<Import>,
|
||||
pub imported_aliases: MutMap<Symbol, Alias>,
|
||||
pub unused_imports: MutMap<ModuleId, Region>,
|
||||
}
|
||||
|
||||
/// Run this before constraining imports.
|
||||
///
|
||||
/// Constraining imports is split into two different functions, because this
|
||||
/// part of the work needs to be done on the main thread, whereas the rest of it
|
||||
/// can be done on a different thread.
|
||||
pub fn pre_constrain_imports(
|
||||
home: ModuleId,
|
||||
references: &MutSet<Symbol>,
|
||||
imported_modules: MutMap<ModuleId, Region>,
|
||||
exposed_types: &mut SubsByModule,
|
||||
stdlib: &StdLib,
|
||||
) -> ConstrainableImports {
|
||||
let mut imported_symbols = Vec::with_capacity(references.len());
|
||||
let mut imported_aliases = MutMap::default();
|
||||
let mut unused_imports = imported_modules; // We'll remove these as we encounter them.
|
||||
|
||||
// Translate referenced symbols into constraints. We do this on the main
|
||||
// thread because we need exclusive access to the exposed_types map, in order
|
||||
// to get the necessary constraint info for any aliases we imported. We also
|
||||
// resolve builtin types now, so we can use a reference to stdlib instead of
|
||||
// having to either clone it or recreate it from scratch on the other thread.
|
||||
for &symbol in references.iter() {
|
||||
let module_id = symbol.module_id();
|
||||
|
||||
// We used this module, so clearly it is not unused!
|
||||
unused_imports.remove(&module_id);
|
||||
|
||||
let builtin_applies = [
|
||||
Symbol::LIST_LIST,
|
||||
Symbol::STR_STR,
|
||||
Symbol::DICT_DICT,
|
||||
Symbol::SET_SET,
|
||||
];
|
||||
|
||||
if module_id.is_builtin() && builtin_applies.contains(&symbol) {
|
||||
// For builtin modules, we create imports from the
|
||||
// hardcoded builtin map.
|
||||
match stdlib.types.get(&symbol) {
|
||||
Some((solved_type, region)) => {
|
||||
let loc_symbol = Loc {
|
||||
value: symbol,
|
||||
region: *region,
|
||||
};
|
||||
|
||||
imported_symbols.push(Import {
|
||||
loc_symbol,
|
||||
solved_type: solved_type.clone(),
|
||||
});
|
||||
}
|
||||
None => {
|
||||
if module_id == home {
|
||||
continue;
|
||||
}
|
||||
|
||||
if module_id == ModuleId::RESULT {
|
||||
let region = Region::zero(); // TODO this should be the region where this symbol was declared in its home module. Look that up!
|
||||
let loc_symbol = Loc {
|
||||
value: symbol,
|
||||
region,
|
||||
};
|
||||
|
||||
match exposed_types.get(&module_id) {
|
||||
Some(ExposedModuleTypes::Valid(solved_types, new_aliases)) => {
|
||||
// If the exposed value was invalid (e.g. it didn't have
|
||||
// a corresponding definition), it won't have an entry
|
||||
// in solved_types
|
||||
if let Some(solved_type) = solved_types.get(&symbol) {
|
||||
// TODO should this be a union?
|
||||
for (k, v) in new_aliases.clone() {
|
||||
imported_aliases.insert(k, v);
|
||||
}
|
||||
|
||||
imported_symbols.push(Import {
|
||||
loc_symbol,
|
||||
solved_type: solved_type.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Some(ExposedModuleTypes::Invalid) => {
|
||||
// If that module was invalid, use True constraints
|
||||
// for everything imported from it.
|
||||
imported_symbols.push(Import {
|
||||
loc_symbol,
|
||||
solved_type: SolvedType::Erroneous(Problem::InvalidModule),
|
||||
});
|
||||
}
|
||||
None => {
|
||||
panic!("Module {:?} does not have info for module {:?} in its exposed types", module_id, home)
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
let is_valid_alias = stdlib.applies.contains(&symbol)
|
||||
// This wasn't a builtin value or Apply; maybe it was a builtin alias.
|
||||
|| roc_types::builtin_aliases::aliases().contains_key(&symbol);
|
||||
|
||||
if !is_valid_alias {
|
||||
panic!(
|
||||
"Could not find {:?} in builtin types {:?} or builtin aliases",
|
||||
symbol, stdlib.types,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if module_id != home {
|
||||
// We already have constraints for our own symbols.
|
||||
let region = Region::zero(); // TODO this should be the region where this symbol was declared in its home module. Look that up!
|
||||
let loc_symbol = Loc {
|
||||
value: symbol,
|
||||
region,
|
||||
};
|
||||
|
||||
match exposed_types.get(&module_id) {
|
||||
Some(ExposedModuleTypes::Valid(solved_types, new_aliases)) => {
|
||||
// If the exposed value was invalid (e.g. it didn't have
|
||||
// a corresponding definition), it won't have an entry
|
||||
// in solved_types
|
||||
if let Some(solved_type) = solved_types.get(&symbol) {
|
||||
// TODO should this be a union?
|
||||
for (k, v) in new_aliases.clone() {
|
||||
imported_aliases.insert(k, v);
|
||||
}
|
||||
|
||||
imported_symbols.push(Import {
|
||||
loc_symbol,
|
||||
solved_type: solved_type.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Some(ExposedModuleTypes::Invalid) => {
|
||||
// If that module was invalid, use True constraints
|
||||
// for everything imported from it.
|
||||
imported_symbols.push(Import {
|
||||
loc_symbol,
|
||||
solved_type: SolvedType::Erroneous(Problem::InvalidModule),
|
||||
});
|
||||
}
|
||||
None => {
|
||||
panic!(
|
||||
"Module {:?} does not have info for module {:?} in its exposed types.\n I was looking for symbol {:?}",
|
||||
home, module_id, symbol,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ConstrainableImports {
|
||||
imported_symbols,
|
||||
imported_aliases,
|
||||
unused_imports,
|
||||
}
|
||||
(rigid_vars, def_types)
|
||||
}
|
||||
|
|
|
@ -9,7 +9,9 @@ use roc_module::ident::Lowercase;
|
|||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::Variable;
|
||||
use roc_types::types::{AliasKind, Category, PReason, PatternCategory, Reason, RecordField, Type};
|
||||
use roc_types::types::{
|
||||
AliasKind, Category, PReason, PatternCategory, Reason, RecordField, Type, TypeExtension,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PatternState {
|
||||
|
@ -391,7 +393,7 @@ pub fn constrain_pattern(
|
|||
state.vars.push(*var);
|
||||
}
|
||||
|
||||
let record_type = Type::Record(field_types, Box::new(ext_type));
|
||||
let record_type = Type::Record(field_types, TypeExtension::from_type(ext_type));
|
||||
|
||||
let whole_con = constraints.equal_types(
|
||||
Type::Variable(*whole_var),
|
||||
|
@ -504,12 +506,22 @@ pub fn constrain_pattern(
|
|||
);
|
||||
|
||||
// Link the entire wrapped opaque type (with the now-constrained argument) to the type
|
||||
// variables of the opaque type
|
||||
// TODO: better expectation here
|
||||
let link_type_variables_con = constraints.equal_types(
|
||||
(**specialized_def_type).clone(),
|
||||
Expected::NoExpectation(arg_pattern_type),
|
||||
Category::OpaqueWrap(*opaque),
|
||||
// variables of the opaque type.
|
||||
//
|
||||
// For example, suppose we have `O k := [ A k, B k ]`, and the pattern `@O (A s) -> s == ""`.
|
||||
// Previous constraints will have solved `typeof s ~ Str`, and we have the
|
||||
// `specialized_def_type` being `[ A k1, B k1 ]`, specializing `k` as `k1` for this opaque
|
||||
// usage.
|
||||
// We now want to link `typeof s ~ k1`, so to capture this relationship, we link
|
||||
// the type of `A s` (the arg type) to `[ A k1, B k1 ]` (the specialized opaque type).
|
||||
//
|
||||
// This must **always** be a presence constraint, that is enforcing
|
||||
// `[ A k1, B k1 ] += typeof (A s)`, because we are in a destructure position and not
|
||||
// all constructors are covered in this branch!
|
||||
let link_type_variables_con = constraints.pattern_presence(
|
||||
arg_pattern_type,
|
||||
PExpected::NoExpectation((**specialized_def_type).clone()),
|
||||
PatternCategory::Opaque(*opaque),
|
||||
loc_arg_pattern.region,
|
||||
);
|
||||
|
||||
|
|
|
@ -3,7 +3,9 @@ use crate::{
|
|||
spaces::{fmt_comments_only, fmt_spaces, NewlineAt, INDENT},
|
||||
Buf,
|
||||
};
|
||||
use roc_parse::ast::{AssignedField, Collection, Expr, Tag, TypeAnnotation, TypeHeader};
|
||||
use roc_parse::ast::{
|
||||
AssignedField, Collection, Expr, ExtractSpaces, HasClause, Tag, TypeAnnotation, TypeHeader,
|
||||
};
|
||||
use roc_parse::ident::UppercaseIdent;
|
||||
use roc_region::all::Loc;
|
||||
|
||||
|
@ -159,6 +161,10 @@ impl<'a> Formattable for TypeAnnotation<'a> {
|
|||
Apply(_, _, args) => args.iter().any(|loc_arg| loc_arg.value.is_multiline()),
|
||||
As(lhs, _, _) => lhs.value.is_multiline(),
|
||||
|
||||
Where(annot, has_clauses) => {
|
||||
annot.is_multiline() || has_clauses.iter().any(|has| has.is_multiline())
|
||||
}
|
||||
|
||||
Record { fields, ext } => {
|
||||
match ext {
|
||||
Some(ann) if ann.value.is_multiline() => return true,
|
||||
|
@ -291,6 +297,15 @@ impl<'a> Formattable for TypeAnnotation<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
Where(annot, has_clauses) => {
|
||||
annot.format_with_options(buf, parens, newlines, indent);
|
||||
buf.push_str(" ");
|
||||
for (i, has) in has_clauses.iter().enumerate() {
|
||||
buf.push_str(if i == 0 { "| " } else { ", " });
|
||||
has.format_with_options(buf, parens, newlines, indent);
|
||||
}
|
||||
}
|
||||
|
||||
SpaceBefore(ann, spaces) => {
|
||||
buf.newline();
|
||||
|
||||
|
@ -514,3 +529,22 @@ impl<'a> Formattable for Tag<'a> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Formattable for HasClause<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
self.var.value.is_multiline() || self.ability.is_multiline()
|
||||
}
|
||||
|
||||
fn format_with_options<'buf>(
|
||||
&self,
|
||||
buf: &mut Buf<'buf>,
|
||||
parens: Parens,
|
||||
newlines: Newlines,
|
||||
indent: u16,
|
||||
) {
|
||||
buf.push_str(self.var.value.extract_spaces().item);
|
||||
buf.push_str(" has ");
|
||||
self.ability
|
||||
.format_with_options(buf, parens, newlines, indent);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::annotation::{Formattable, Newlines, Parens};
|
|||
use crate::pattern::fmt_pattern;
|
||||
use crate::spaces::{fmt_spaces, INDENT};
|
||||
use crate::Buf;
|
||||
use roc_parse::ast::{Def, Expr, Pattern, TypeHeader};
|
||||
use roc_parse::ast::{AbilityDemand, Def, Expr, ExtractSpaces, Pattern, TypeHeader};
|
||||
use roc_region::all::Loc;
|
||||
|
||||
/// A Located formattable value is also formattable
|
||||
|
@ -22,6 +22,7 @@ impl<'a> Formattable for Def<'a> {
|
|||
SpaceBefore(sub_def, spaces) | SpaceAfter(sub_def, spaces) => {
|
||||
spaces.iter().any(|s| s.is_comment()) || sub_def.is_multiline()
|
||||
}
|
||||
Ability { demands, .. } => demands.iter().any(|d| d.is_multiline()),
|
||||
NotYetImplemented(s) => todo!("{}", s),
|
||||
}
|
||||
}
|
||||
|
@ -83,6 +84,32 @@ impl<'a> Formattable for Def<'a> {
|
|||
|
||||
ann.format(buf, indent + INDENT)
|
||||
}
|
||||
Ability {
|
||||
header: TypeHeader { name, vars },
|
||||
loc_has: _,
|
||||
demands,
|
||||
} => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(name.value);
|
||||
for var in *vars {
|
||||
buf.spaces(1);
|
||||
fmt_pattern(buf, &var.value, indent, Parens::NotNeeded);
|
||||
}
|
||||
|
||||
buf.push_str(" has");
|
||||
|
||||
if !self.is_multiline() {
|
||||
debug_assert_eq!(demands.len(), 1);
|
||||
buf.push_str(" ");
|
||||
demands[0].format(buf, indent + INDENT);
|
||||
} else {
|
||||
for demand in demands.iter() {
|
||||
buf.newline();
|
||||
buf.indent(indent + INDENT);
|
||||
demand.format(buf, indent + INDENT);
|
||||
}
|
||||
}
|
||||
}
|
||||
Body(loc_pattern, loc_expr) => {
|
||||
fmt_body(buf, &loc_pattern.value, &loc_expr.value, indent);
|
||||
}
|
||||
|
@ -167,3 +194,15 @@ pub fn fmt_body<'a, 'buf>(
|
|||
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Formattable for AbilityDemand<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
self.name.value.is_multiline() || self.typ.is_multiline()
|
||||
}
|
||||
|
||||
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
|
||||
buf.push_str(self.name.value.extract_spaces().item);
|
||||
buf.push_str(" : ");
|
||||
self.typ.value.format(buf, indent + INDENT);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -117,6 +117,7 @@ impl<'a> Formattable for Expr<'a> {
|
|||
) {
|
||||
use self::Expr::*;
|
||||
|
||||
//dbg!(self);
|
||||
let format_newlines = newlines == Newlines::Yes;
|
||||
let apply_needs_parens = parens == Parens::InApply;
|
||||
|
||||
|
@ -453,14 +454,17 @@ fn fmt_bin_ops<'a, 'buf>(
|
|||
|| (&loc_right_side.value).is_multiline()
|
||||
|| lefts.iter().any(|(expr, _)| expr.value.is_multiline());
|
||||
|
||||
let mut curr_indent = indent;
|
||||
|
||||
for (loc_left_side, loc_bin_op) in lefts {
|
||||
let bin_op = loc_bin_op.value;
|
||||
|
||||
loc_left_side.format_with_options(buf, apply_needs_parens, Newlines::No, indent);
|
||||
loc_left_side.format_with_options(buf, apply_needs_parens, Newlines::No, curr_indent);
|
||||
|
||||
if is_multiline {
|
||||
buf.newline();
|
||||
buf.indent(indent + INDENT);
|
||||
curr_indent = indent + INDENT;
|
||||
buf.indent(curr_indent);
|
||||
} else {
|
||||
buf.spaces(1);
|
||||
}
|
||||
|
|
|
@ -2595,7 +2595,7 @@ mod test_fmt {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn pipline_apply_lambda() {
|
||||
fn pipline_apply_lambda_1() {
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
shout
|
||||
|
@ -2606,6 +2606,19 @@ mod test_fmt {
|
|||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pipline_apply_lambda_2() {
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
shout
|
||||
|> List.map
|
||||
xs
|
||||
(\i -> i)
|
||||
|> List.join
|
||||
"#
|
||||
));
|
||||
}
|
||||
|
||||
// MODULES
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -875,6 +875,12 @@ trait Backend<'a> {
|
|||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
}
|
||||
Expr::ExprBox { symbol } => {
|
||||
self.set_last_seen(*symbol, stmt);
|
||||
}
|
||||
Expr::ExprUnbox { symbol } => {
|
||||
self.set_last_seen(*symbol, stmt);
|
||||
}
|
||||
Expr::Struct(syms) => {
|
||||
for sym in *syms {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
|
|
|
@ -240,8 +240,12 @@ fn build_has_tag_id_help<'a, 'ctx, 'env>(
|
|||
tag_value.into(),
|
||||
);
|
||||
|
||||
env.builder
|
||||
.build_int_cast(tag_id_i64, env.context.i16_type(), "to_i16")
|
||||
env.builder.build_int_cast_sign_flag(
|
||||
tag_id_i64,
|
||||
env.context.i16_type(),
|
||||
true,
|
||||
"to_i16",
|
||||
)
|
||||
};
|
||||
|
||||
let answer = env.builder.build_int_compare(
|
||||
|
|
|
@ -24,7 +24,7 @@ use crate::llvm::build_str::{
|
|||
};
|
||||
use crate::llvm::compare::{generic_eq, generic_neq};
|
||||
use crate::llvm::convert::{
|
||||
self, basic_type_from_builtin, basic_type_from_layout, basic_type_from_layout_1,
|
||||
self, argument_type_from_layout, basic_type_from_builtin, basic_type_from_layout,
|
||||
block_of_memory_slices,
|
||||
};
|
||||
use crate::llvm::refcounting::{
|
||||
|
@ -1128,6 +1128,30 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
|
|||
..
|
||||
} => build_tag(env, scope, union_layout, *tag_id, arguments, None, parent),
|
||||
|
||||
ExprBox { symbol } => {
|
||||
let (value, layout) = load_symbol_and_layout(scope, symbol);
|
||||
let basic_type = basic_type_from_layout(env, layout);
|
||||
let allocation = reserve_with_refcount_help(
|
||||
env,
|
||||
basic_type,
|
||||
layout.stack_size(env.target_info),
|
||||
layout.alignment_bytes(env.target_info),
|
||||
);
|
||||
|
||||
env.builder.build_store(allocation, value);
|
||||
|
||||
allocation.into()
|
||||
}
|
||||
|
||||
ExprUnbox { symbol } => {
|
||||
let value = load_symbol(scope, symbol);
|
||||
|
||||
debug_assert!(value.is_pointer_value());
|
||||
|
||||
env.builder
|
||||
.build_load(value.into_pointer_value(), "load_boxed_value")
|
||||
}
|
||||
|
||||
Reset { symbol, .. } => {
|
||||
let (tag_ptr, layout) = load_symbol_and_layout(scope, symbol);
|
||||
let tag_ptr = tag_ptr.into_pointer_value();
|
||||
|
@ -1826,7 +1850,7 @@ pub fn tag_pointer_read_tag_id<'a, 'ctx, 'env>(
|
|||
let masked = env.builder.build_and(as_int, mask_intval, "mask");
|
||||
|
||||
env.builder
|
||||
.build_int_cast(masked, env.context.i8_type(), "to_u8")
|
||||
.build_int_cast_sign_flag(masked, env.context.i8_type(), false, "to_u8")
|
||||
}
|
||||
|
||||
pub fn tag_pointer_clear_tag_id<'a, 'ctx, 'env>(
|
||||
|
@ -2581,6 +2605,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
|
|||
let align_bytes = layout.alignment_bytes(env.target_info);
|
||||
|
||||
if align_bytes > 0 {
|
||||
debug_assert!(value.is_pointer_value(), "{:?}\n{:?}", value, layout);
|
||||
let value_ptr = value.into_pointer_value();
|
||||
|
||||
// We can only do this if the function itself writes data into this
|
||||
|
@ -4225,7 +4250,7 @@ fn build_proc_header<'a, 'ctx, 'env>(
|
|||
let mut arg_basic_types = Vec::with_capacity_in(args.len(), arena);
|
||||
|
||||
for (layout, _) in args.iter() {
|
||||
let arg_type = basic_type_from_layout_1(env, layout);
|
||||
let arg_type = argument_type_from_layout(env, layout);
|
||||
|
||||
arg_basic_types.push(arg_type);
|
||||
}
|
||||
|
@ -5918,8 +5943,11 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
let arg = load_symbol(scope, &args[0]).into_int_value();
|
||||
|
||||
let to = basic_type_from_layout(env, layout).into_int_type();
|
||||
let to_signed = intwidth_from_layout(*layout).is_signed();
|
||||
|
||||
env.builder.build_int_cast(arg, to, "inc_cast").into()
|
||||
env.builder
|
||||
.build_int_cast_sign_flag(arg, to, to_signed, "inc_cast")
|
||||
.into()
|
||||
}
|
||||
Eq => {
|
||||
debug_assert_eq!(args.len(), 2);
|
||||
|
@ -6152,6 +6180,10 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
unreachable!("these are higher order, and are handled elsewhere")
|
||||
}
|
||||
|
||||
BoxExpr | UnboxExpr => {
|
||||
unreachable!("The {:?} operation is turned into mono Expr", op)
|
||||
}
|
||||
|
||||
PtrCast | RefCountInc | RefCountDec => {
|
||||
unreachable!("Not used in LLVM backend: {:?}", op);
|
||||
}
|
||||
|
@ -6533,7 +6565,13 @@ fn build_int_binop<'a, 'ctx, 'env>(
|
|||
NumGte => bd.build_int_compare(SGE, lhs, rhs, "int_gte").into(),
|
||||
NumLt => bd.build_int_compare(SLT, lhs, rhs, "int_lt").into(),
|
||||
NumLte => bd.build_int_compare(SLE, lhs, rhs, "int_lte").into(),
|
||||
NumRemUnchecked => bd.build_int_signed_rem(lhs, rhs, "rem_int").into(),
|
||||
NumRemUnchecked => {
|
||||
if int_width.is_signed() {
|
||||
bd.build_int_signed_rem(lhs, rhs, "rem_int").into()
|
||||
} else {
|
||||
bd.build_int_unsigned_rem(lhs, rhs, "rem_uint").into()
|
||||
}
|
||||
}
|
||||
NumIsMultipleOf => {
|
||||
// this builds the following construct
|
||||
//
|
||||
|
@ -6607,7 +6645,13 @@ fn build_int_binop<'a, 'ctx, 'env>(
|
|||
&[lhs.into(), rhs.into()],
|
||||
&bitcode::NUM_POW_INT[int_width],
|
||||
),
|
||||
NumDivUnchecked => bd.build_int_signed_div(lhs, rhs, "div_int").into(),
|
||||
NumDivUnchecked => {
|
||||
if int_width.is_signed() {
|
||||
bd.build_int_signed_div(lhs, rhs, "div_int").into()
|
||||
} else {
|
||||
bd.build_int_unsigned_div(lhs, rhs, "div_uint").into()
|
||||
}
|
||||
}
|
||||
NumDivCeilUnchecked => call_bitcode_fn(
|
||||
env,
|
||||
&[lhs.into(), rhs.into()],
|
||||
|
@ -7030,7 +7074,12 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
|
|||
let target_int_type = convert::int_type_from_int_width(env, target_int_width);
|
||||
let target_int_val: BasicValueEnum<'ctx> = env
|
||||
.builder
|
||||
.build_int_cast(arg, target_int_type, "int_cast")
|
||||
.build_int_cast_sign_flag(
|
||||
arg,
|
||||
target_int_type,
|
||||
target_int_width.is_signed(),
|
||||
"int_cast",
|
||||
)
|
||||
.into();
|
||||
|
||||
let return_type =
|
||||
|
|
|
@ -65,7 +65,12 @@ pub fn dict_len<'a, 'ctx, 'env>(
|
|||
);
|
||||
|
||||
env.builder
|
||||
.build_int_cast(length_i64.into_int_value(), env.ptr_int(), "to_usize")
|
||||
.build_int_cast_sign_flag(
|
||||
length_i64.into_int_value(),
|
||||
env.ptr_int(),
|
||||
false,
|
||||
"to_usize",
|
||||
)
|
||||
.into()
|
||||
}
|
||||
_ => unreachable!("Invalid layout given to Dict.len : {:?}", dict_layout),
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::llvm::build::tag_pointer_clear_tag_id;
|
|||
use crate::llvm::build::Env;
|
||||
use crate::llvm::build::{get_tag_id, FAST_CALL_CONV, TAG_DATA_INDEX};
|
||||
use crate::llvm::build_str;
|
||||
use crate::llvm::convert::{basic_type_from_layout, basic_type_from_layout_1};
|
||||
use crate::llvm::convert::basic_type_from_layout;
|
||||
use bumpalo::collections::Vec;
|
||||
use inkwell::values::{
|
||||
BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue,
|
||||
|
@ -13,6 +13,8 @@ use roc_builtins::bitcode;
|
|||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout};
|
||||
|
||||
use super::convert::argument_type_from_union_layout;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum WhenRecursive<'a> {
|
||||
Unreachable,
|
||||
|
@ -68,8 +70,11 @@ fn build_hash_layout<'a, 'ctx, 'env>(
|
|||
when_recursive,
|
||||
),
|
||||
|
||||
Layout::Union(union_layout) => {
|
||||
build_hash_tag(env, layout_ids, layout, union_layout, seed, val)
|
||||
Layout::Union(union_layout) => build_hash_tag(env, layout_ids, union_layout, seed, val),
|
||||
|
||||
Layout::Boxed(_inner_layout) => {
|
||||
// build_hash_box(env, layout_ids, layout, inner_layout, seed, val)
|
||||
todo!()
|
||||
}
|
||||
|
||||
Layout::RecursivePointer => match when_recursive {
|
||||
|
@ -87,14 +92,7 @@ fn build_hash_layout<'a, 'ctx, 'env>(
|
|||
.build_bitcast(val, bt, "i64_to_opaque")
|
||||
.into_pointer_value();
|
||||
|
||||
build_hash_tag(
|
||||
env,
|
||||
layout_ids,
|
||||
&layout,
|
||||
&union_layout,
|
||||
seed,
|
||||
field_cast.into(),
|
||||
)
|
||||
build_hash_tag(env, layout_ids, &union_layout, seed, field_cast.into())
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -308,7 +306,6 @@ fn hash_struct<'a, 'ctx, 'env>(
|
|||
fn build_hash_tag<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
layout: &Layout<'a>,
|
||||
union_layout: &UnionLayout<'a>,
|
||||
seed: IntValue<'ctx>,
|
||||
value: BasicValueEnum<'ctx>,
|
||||
|
@ -318,7 +315,7 @@ fn build_hash_tag<'a, 'ctx, 'env>(
|
|||
|
||||
let symbol = Symbol::GENERIC_HASH;
|
||||
let fn_name = layout_ids
|
||||
.get(symbol, layout)
|
||||
.get(symbol, &Layout::Union(*union_layout))
|
||||
.to_symbol_string(symbol, &env.interns);
|
||||
|
||||
let function = match env.module.get_function(fn_name.as_str()) {
|
||||
|
@ -326,7 +323,7 @@ fn build_hash_tag<'a, 'ctx, 'env>(
|
|||
None => {
|
||||
let seed_type = env.context.i64_type();
|
||||
|
||||
let arg_type = basic_type_from_layout_1(env, layout);
|
||||
let arg_type = argument_type_from_union_layout(env, union_layout);
|
||||
|
||||
let function_value = crate::llvm::refcounting::build_header_help(
|
||||
env,
|
||||
|
@ -805,7 +802,7 @@ fn hash_ptr_to_struct<'a, 'ctx, 'env>(
|
|||
) -> IntValue<'ctx> {
|
||||
use inkwell::types::BasicType;
|
||||
|
||||
let wrapper_type = basic_type_from_layout_1(env, &Layout::Union(*union_layout));
|
||||
let wrapper_type = argument_type_from_union_layout(env, union_layout);
|
||||
|
||||
// cast the opaque pointer to a pointer of the correct shape
|
||||
let wrapper_ptr = env
|
||||
|
|
|
@ -43,7 +43,7 @@ fn pass_element_as_opaque<'a, 'ctx, 'env>(
|
|||
env.builder.build_bitcast(
|
||||
element_ptr,
|
||||
env.context.i8_type().ptr_type(AddressSpace::Generic),
|
||||
"to_opaque",
|
||||
"pass_element_as_opaque",
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -75,7 +75,7 @@ pub fn pass_as_opaque<'a, 'ctx, 'env>(
|
|||
env.builder.build_bitcast(
|
||||
ptr,
|
||||
env.context.i8_type().ptr_type(AddressSpace::Generic),
|
||||
"to_opaque",
|
||||
"pass_as_opaque",
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -407,10 +407,19 @@ pub fn list_walk_generic<'a, 'ctx, 'env>(
|
|||
ListWalk::WalkBackwardsUntil => todo!(),
|
||||
};
|
||||
|
||||
let default_ptr = if default_layout.is_passed_by_reference() {
|
||||
debug_assert!(default.is_pointer_value());
|
||||
default.into_pointer_value()
|
||||
} else {
|
||||
let default_ptr = builder.build_alloca(default.get_type(), "default_ptr");
|
||||
env.builder.build_store(default_ptr, default);
|
||||
default_ptr
|
||||
};
|
||||
|
||||
let result_ptr = env.builder.build_alloca(default.get_type(), "result");
|
||||
let result_ptr = {
|
||||
let basic_type = basic_type_from_layout(env, default_layout);
|
||||
env.builder.build_alloca(basic_type, "result")
|
||||
};
|
||||
|
||||
match variant {
|
||||
ListWalk::Walk | ListWalk::WalkBackwards => {
|
||||
|
@ -467,8 +476,12 @@ pub fn list_walk_generic<'a, 'ctx, 'env>(
|
|||
}
|
||||
}
|
||||
|
||||
if default_layout.is_passed_by_reference() {
|
||||
result_ptr.into()
|
||||
} else {
|
||||
env.builder.build_load(result_ptr, "load_result")
|
||||
}
|
||||
}
|
||||
|
||||
/// List.range : Int a, Int a -> List (Int a)
|
||||
pub fn list_range<'a, 'ctx, 'env>(
|
||||
|
|
|
@ -179,7 +179,7 @@ pub fn str_number_of_bytes<'a, 'ctx, 'env>(
|
|||
|
||||
// cast to the appropriate usize of the current build
|
||||
env.builder
|
||||
.build_int_cast(length, env.ptr_int(), "len_as_usize")
|
||||
.build_int_cast_sign_flag(length, env.ptr_int(), false, "len_as_usize")
|
||||
}
|
||||
|
||||
/// Str.startsWith : Str, Str -> Bool
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::llvm::bitcode::call_bitcode_fn;
|
|||
use crate::llvm::build::{get_tag_id, tag_pointer_clear_tag_id, Env, FAST_CALL_CONV};
|
||||
use crate::llvm::build_list::{list_len, load_list_ptr};
|
||||
use crate::llvm::build_str::str_equal;
|
||||
use crate::llvm::convert::{basic_type_from_layout, basic_type_from_layout_1};
|
||||
use crate::llvm::convert::basic_type_from_layout;
|
||||
use bumpalo::collections::Vec;
|
||||
use inkwell::types::BasicType;
|
||||
use inkwell::values::{
|
||||
|
@ -15,6 +15,7 @@ use roc_module::symbol::Symbol;
|
|||
use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout};
|
||||
|
||||
use super::build::load_roc_value;
|
||||
use super::convert::argument_type_from_union_layout;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum WhenRecursive<'a> {
|
||||
|
@ -176,12 +177,21 @@ fn build_eq<'a, 'ctx, 'env>(
|
|||
env,
|
||||
layout_ids,
|
||||
when_recursive,
|
||||
lhs_layout,
|
||||
union_layout,
|
||||
lhs_val,
|
||||
rhs_val,
|
||||
),
|
||||
|
||||
Layout::Boxed(inner_layout) => build_box_eq(
|
||||
env,
|
||||
layout_ids,
|
||||
when_recursive,
|
||||
lhs_layout,
|
||||
inner_layout,
|
||||
lhs_val,
|
||||
rhs_val,
|
||||
),
|
||||
|
||||
Layout::RecursivePointer => match when_recursive {
|
||||
WhenRecursive::Unreachable => {
|
||||
unreachable!("recursion pointers should never be compared directly")
|
||||
|
@ -207,7 +217,6 @@ fn build_eq<'a, 'ctx, 'env>(
|
|||
env,
|
||||
layout_ids,
|
||||
WhenRecursive::Loop(union_layout),
|
||||
&layout,
|
||||
&union_layout,
|
||||
field1_cast.into(),
|
||||
field2_cast.into(),
|
||||
|
@ -345,12 +354,12 @@ fn build_neq<'a, 'ctx, 'env>(
|
|||
|
||||
result.into()
|
||||
}
|
||||
|
||||
Layout::Union(union_layout) => {
|
||||
let is_equal = build_tag_eq(
|
||||
env,
|
||||
layout_ids,
|
||||
when_recursive,
|
||||
lhs_layout,
|
||||
union_layout,
|
||||
lhs_val,
|
||||
rhs_val,
|
||||
|
@ -362,6 +371,23 @@ fn build_neq<'a, 'ctx, 'env>(
|
|||
result.into()
|
||||
}
|
||||
|
||||
Layout::Boxed(inner_layout) => {
|
||||
let is_equal = build_box_eq(
|
||||
env,
|
||||
layout_ids,
|
||||
when_recursive,
|
||||
lhs_layout,
|
||||
inner_layout,
|
||||
lhs_val,
|
||||
rhs_val,
|
||||
)
|
||||
.into_int_value();
|
||||
|
||||
let result: IntValue = env.builder.build_not(is_equal, "negate");
|
||||
|
||||
result.into()
|
||||
}
|
||||
|
||||
Layout::RecursivePointer => {
|
||||
unreachable!("recursion pointers should never be compared directly")
|
||||
}
|
||||
|
@ -764,7 +790,6 @@ fn build_tag_eq<'a, 'ctx, 'env>(
|
|||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
when_recursive: WhenRecursive<'a>,
|
||||
tag_layout: &Layout<'a>,
|
||||
union_layout: &UnionLayout<'a>,
|
||||
tag1: BasicValueEnum<'ctx>,
|
||||
tag2: BasicValueEnum<'ctx>,
|
||||
|
@ -772,15 +797,16 @@ fn build_tag_eq<'a, 'ctx, 'env>(
|
|||
let block = env.builder.get_insert_block().expect("to be in a function");
|
||||
let di_location = env.builder.get_current_debug_location().unwrap();
|
||||
|
||||
let tag_layout = Layout::Union(*union_layout);
|
||||
let symbol = Symbol::GENERIC_EQ;
|
||||
let fn_name = layout_ids
|
||||
.get(symbol, tag_layout)
|
||||
.get(symbol, &tag_layout)
|
||||
.to_symbol_string(symbol, &env.interns);
|
||||
|
||||
let function = match env.module.get_function(fn_name.as_str()) {
|
||||
Some(function_value) => function_value,
|
||||
None => {
|
||||
let arg_type = basic_type_from_layout_1(env, tag_layout);
|
||||
let arg_type = argument_type_from_union_layout(env, union_layout);
|
||||
|
||||
let function_value = crate::llvm::refcounting::build_header_help(
|
||||
env,
|
||||
|
@ -1101,8 +1127,10 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
|
|||
let i8_type = env.context.i8_type();
|
||||
|
||||
let sum = env.builder.build_int_add(
|
||||
env.builder.build_int_cast(is_null_1, i8_type, "to_u8"),
|
||||
env.builder.build_int_cast(is_null_2, i8_type, "to_u8"),
|
||||
env.builder
|
||||
.build_int_cast_sign_flag(is_null_1, i8_type, false, "to_u8"),
|
||||
env.builder
|
||||
.build_int_cast_sign_flag(is_null_2, i8_type, false, "to_u8"),
|
||||
"sum_is_null",
|
||||
);
|
||||
|
||||
|
@ -1252,3 +1280,141 @@ fn eq_ptr_to_struct<'a, 'ctx, 'env>(
|
|||
)
|
||||
.into_int_value()
|
||||
}
|
||||
|
||||
/// ----
|
||||
|
||||
fn build_box_eq<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
when_recursive: WhenRecursive<'a>,
|
||||
box_layout: &Layout<'a>,
|
||||
inner_layout: &Layout<'a>,
|
||||
tag1: BasicValueEnum<'ctx>,
|
||||
tag2: BasicValueEnum<'ctx>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let block = env.builder.get_insert_block().expect("to be in a function");
|
||||
let di_location = env.builder.get_current_debug_location().unwrap();
|
||||
|
||||
let symbol = Symbol::GENERIC_EQ;
|
||||
let fn_name = layout_ids
|
||||
.get(symbol, box_layout)
|
||||
.to_symbol_string(symbol, &env.interns);
|
||||
|
||||
let function = match env.module.get_function(fn_name.as_str()) {
|
||||
Some(function_value) => function_value,
|
||||
None => {
|
||||
let arg_type = basic_type_from_layout(env, box_layout);
|
||||
|
||||
let function_value = crate::llvm::refcounting::build_header_help(
|
||||
env,
|
||||
&fn_name,
|
||||
env.context.bool_type().into(),
|
||||
&[arg_type, arg_type],
|
||||
);
|
||||
|
||||
build_box_eq_help(
|
||||
env,
|
||||
layout_ids,
|
||||
when_recursive,
|
||||
function_value,
|
||||
inner_layout,
|
||||
);
|
||||
|
||||
function_value
|
||||
}
|
||||
};
|
||||
|
||||
env.builder.position_at_end(block);
|
||||
env.builder
|
||||
.set_current_debug_location(env.context, di_location);
|
||||
let call = env
|
||||
.builder
|
||||
.build_call(function, &[tag1.into(), tag2.into()], "tag_eq");
|
||||
|
||||
call.set_call_convention(FAST_CALL_CONV);
|
||||
|
||||
call.try_as_basic_value().left().unwrap()
|
||||
}
|
||||
|
||||
fn build_box_eq_help<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
when_recursive: WhenRecursive<'a>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
inner_layout: &Layout<'a>,
|
||||
) {
|
||||
let ctx = env.context;
|
||||
let builder = env.builder;
|
||||
|
||||
{
|
||||
use inkwell::debug_info::AsDIScope;
|
||||
|
||||
let func_scope = parent.get_subprogram().unwrap();
|
||||
let lexical_block = env.dibuilder.create_lexical_block(
|
||||
/* scope */ func_scope.as_debug_info_scope(),
|
||||
/* file */ env.compile_unit.get_file(),
|
||||
/* line_no */ 0,
|
||||
/* column_no */ 0,
|
||||
);
|
||||
|
||||
let loc = env.dibuilder.create_debug_location(
|
||||
ctx,
|
||||
/* line */ 0,
|
||||
/* column */ 0,
|
||||
/* current_scope */ lexical_block.as_debug_info_scope(),
|
||||
/* inlined_at */ None,
|
||||
);
|
||||
builder.set_current_debug_location(ctx, loc);
|
||||
}
|
||||
|
||||
// Add args to scope
|
||||
let mut it = parent.get_param_iter();
|
||||
let box1 = it.next().unwrap();
|
||||
let box2 = it.next().unwrap();
|
||||
|
||||
box1.set_name(Symbol::ARG_1.as_str(&env.interns));
|
||||
box2.set_name(Symbol::ARG_2.as_str(&env.interns));
|
||||
|
||||
let return_true = ctx.append_basic_block(parent, "return_true");
|
||||
env.builder.position_at_end(return_true);
|
||||
env.builder
|
||||
.build_return(Some(&env.context.bool_type().const_all_ones()));
|
||||
|
||||
let entry = ctx.append_basic_block(parent, "entry");
|
||||
env.builder.position_at_end(entry);
|
||||
|
||||
let ptr_equal = env.builder.build_int_compare(
|
||||
IntPredicate::EQ,
|
||||
env.builder
|
||||
.build_ptr_to_int(box1.into_pointer_value(), env.ptr_int(), "pti"),
|
||||
env.builder
|
||||
.build_ptr_to_int(box2.into_pointer_value(), env.ptr_int(), "pti"),
|
||||
"compare_pointers",
|
||||
);
|
||||
|
||||
let compare_inner_value = ctx.append_basic_block(parent, "compare_inner_value");
|
||||
|
||||
env.builder
|
||||
.build_conditional_branch(ptr_equal, return_true, compare_inner_value);
|
||||
|
||||
env.builder.position_at_end(compare_inner_value);
|
||||
|
||||
// clear the tag_id so we get a pointer to the actual data
|
||||
let box1 = box1.into_pointer_value();
|
||||
let box2 = box2.into_pointer_value();
|
||||
|
||||
let value1 = env.builder.build_load(box1, "load_box1");
|
||||
let value2 = env.builder.build_load(box2, "load_box2");
|
||||
|
||||
let is_equal = build_eq(
|
||||
env,
|
||||
layout_ids,
|
||||
value1,
|
||||
value2,
|
||||
inner_layout,
|
||||
inner_layout,
|
||||
when_recursive,
|
||||
);
|
||||
|
||||
env.builder.build_return(Some(&is_equal));
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::llvm::build::Env;
|
||||
use bumpalo::collections::Vec;
|
||||
use inkwell::context::Context;
|
||||
use inkwell::types::{BasicType, BasicTypeEnum, FloatType, IntType, StructType};
|
||||
|
@ -7,7 +8,7 @@ use roc_mono::layout::{Builtin, Layout, UnionLayout};
|
|||
use roc_target::TargetInfo;
|
||||
|
||||
fn basic_type_from_record<'a, 'ctx, 'env>(
|
||||
env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
fields: &[Layout<'_>],
|
||||
) -> BasicTypeEnum<'ctx> {
|
||||
let mut field_types = Vec::with_capacity_in(fields.len(), env.arena);
|
||||
|
@ -22,7 +23,7 @@ fn basic_type_from_record<'a, 'ctx, 'env>(
|
|||
}
|
||||
|
||||
pub fn basic_type_from_layout<'a, 'ctx, 'env>(
|
||||
env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout: &Layout<'_>,
|
||||
) -> BasicTypeEnum<'ctx> {
|
||||
use Layout::*;
|
||||
|
@ -33,7 +34,26 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>(
|
|||
..
|
||||
} => basic_type_from_record(env, sorted_fields),
|
||||
LambdaSet(lambda_set) => basic_type_from_layout(env, &lambda_set.runtime_representation()),
|
||||
Union(union_layout) => {
|
||||
Boxed(inner_layout) => {
|
||||
let inner_type = basic_type_from_layout(env, inner_layout);
|
||||
|
||||
inner_type.ptr_type(AddressSpace::Generic).into()
|
||||
}
|
||||
Union(union_layout) => basic_type_from_union_layout(env, union_layout),
|
||||
RecursivePointer => env
|
||||
.context
|
||||
.i64_type()
|
||||
.ptr_type(AddressSpace::Generic)
|
||||
.as_basic_type_enum(),
|
||||
|
||||
Builtin(builtin) => basic_type_from_builtin(env, builtin),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn basic_type_from_union_layout<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
union_layout: &UnionLayout<'_>,
|
||||
) -> BasicTypeEnum<'ctx> {
|
||||
use UnionLayout::*;
|
||||
|
||||
let tag_id_type = basic_type_from_layout(env, &union_layout.tag_id_layout());
|
||||
|
@ -60,8 +80,7 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>(
|
|||
}
|
||||
}
|
||||
NullableUnwrapped { other_fields, .. } => {
|
||||
let block =
|
||||
block_of_memory_slices(env.context, &[other_fields], env.target_info);
|
||||
let block = block_of_memory_slices(env.context, &[other_fields], env.target_info);
|
||||
block.ptr_type(AddressSpace::Generic).into()
|
||||
}
|
||||
NonNullableUnwrapped(fields) => {
|
||||
|
@ -70,84 +89,9 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>(
|
|||
}
|
||||
}
|
||||
}
|
||||
RecursivePointer => {
|
||||
// TODO make this dynamic
|
||||
env.context
|
||||
.i64_type()
|
||||
.ptr_type(AddressSpace::Generic)
|
||||
.as_basic_type_enum()
|
||||
}
|
||||
|
||||
Builtin(builtin) => basic_type_from_builtin(env, builtin),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn basic_type_from_layout_1<'a, 'ctx, 'env>(
|
||||
env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
|
||||
layout: &Layout<'_>,
|
||||
) -> BasicTypeEnum<'ctx> {
|
||||
use Layout::*;
|
||||
|
||||
match layout {
|
||||
Struct {
|
||||
field_layouts: sorted_fields,
|
||||
..
|
||||
} => basic_type_from_record(env, sorted_fields),
|
||||
LambdaSet(lambda_set) => {
|
||||
basic_type_from_layout_1(env, &lambda_set.runtime_representation())
|
||||
}
|
||||
Union(union_layout) => {
|
||||
use UnionLayout::*;
|
||||
|
||||
let tag_id_type = basic_type_from_layout(env, &union_layout.tag_id_layout());
|
||||
|
||||
match union_layout {
|
||||
NonRecursive(tags) => {
|
||||
let data = block_of_memory_slices(env.context, tags, env.target_info);
|
||||
let struct_type = env.context.struct_type(&[data, tag_id_type], false);
|
||||
|
||||
struct_type.ptr_type(AddressSpace::Generic).into()
|
||||
}
|
||||
Recursive(tags)
|
||||
| NullableWrapped {
|
||||
other_tags: tags, ..
|
||||
} => {
|
||||
let data = block_of_memory_slices(env.context, tags, env.target_info);
|
||||
|
||||
if union_layout.stores_tag_id_as_data(env.target_info) {
|
||||
env.context
|
||||
.struct_type(&[data, tag_id_type], false)
|
||||
.ptr_type(AddressSpace::Generic)
|
||||
.into()
|
||||
} else {
|
||||
data.ptr_type(AddressSpace::Generic).into()
|
||||
}
|
||||
}
|
||||
NullableUnwrapped { other_fields, .. } => {
|
||||
let block =
|
||||
block_of_memory_slices(env.context, &[other_fields], env.target_info);
|
||||
block.ptr_type(AddressSpace::Generic).into()
|
||||
}
|
||||
NonNullableUnwrapped(fields) => {
|
||||
let block = block_of_memory_slices(env.context, &[fields], env.target_info);
|
||||
block.ptr_type(AddressSpace::Generic).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
RecursivePointer => {
|
||||
// TODO make this dynamic
|
||||
env.context
|
||||
.i64_type()
|
||||
.ptr_type(AddressSpace::Generic)
|
||||
.as_basic_type_enum()
|
||||
}
|
||||
|
||||
Builtin(builtin) => basic_type_from_builtin(env, builtin),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn basic_type_from_builtin<'a, 'ctx, 'env>(
|
||||
env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
builtin: &Builtin<'_>,
|
||||
) -> BasicTypeEnum<'ctx> {
|
||||
use Builtin::*;
|
||||
|
@ -166,8 +110,48 @@ pub fn basic_type_from_builtin<'a, 'ctx, 'env>(
|
|||
}
|
||||
}
|
||||
|
||||
/// Turn a layout into a BasicType that we use in LLVM function arguments.
|
||||
///
|
||||
/// This makes it possible to pass values as something different from how they are typically stored.
|
||||
/// Current differences
|
||||
///
|
||||
/// - tag unions are passed by-reference. That means that
|
||||
/// * `f : [ Some I64, None ] -> I64` is typed `{ { i64, i8 }, i64 }* -> i64`
|
||||
/// * `f : { x : [ Some I64, None ] } -> I64 is typed `{ { { i64, i8 }, i64 } } -> i64`
|
||||
///
|
||||
/// Ideas exist to have (bigger than 2 register) records also be passed by-reference, but this
|
||||
/// is not currently implemented
|
||||
pub fn argument_type_from_layout<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout: &Layout<'_>,
|
||||
) -> BasicTypeEnum<'ctx> {
|
||||
use Layout::*;
|
||||
|
||||
match layout {
|
||||
LambdaSet(lambda_set) => {
|
||||
argument_type_from_layout(env, &lambda_set.runtime_representation())
|
||||
}
|
||||
Union(union_layout) => argument_type_from_union_layout(env, union_layout),
|
||||
other => basic_type_from_layout(env, other),
|
||||
}
|
||||
}
|
||||
|
||||
/// Non-recursive tag unions are stored on the stack, but passed by-reference
|
||||
pub fn argument_type_from_union_layout<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
union_layout: &UnionLayout<'_>,
|
||||
) -> BasicTypeEnum<'ctx> {
|
||||
let heap_type = basic_type_from_union_layout(env, union_layout);
|
||||
|
||||
if let UnionLayout::NonRecursive(_) = union_layout {
|
||||
heap_type.ptr_type(AddressSpace::Generic).into()
|
||||
} else {
|
||||
heap_type
|
||||
}
|
||||
}
|
||||
|
||||
pub fn int_type_from_int_width<'a, 'ctx, 'env>(
|
||||
env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
int_width: IntWidth,
|
||||
) -> IntType<'ctx> {
|
||||
use IntWidth::*;
|
||||
|
@ -182,7 +166,7 @@ pub fn int_type_from_int_width<'a, 'ctx, 'env>(
|
|||
}
|
||||
|
||||
pub fn float_type_from_float_width<'a, 'ctx, 'env>(
|
||||
env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
float_width: FloatWidth,
|
||||
) -> FloatType<'ctx> {
|
||||
use FloatWidth::*;
|
||||
|
@ -267,33 +251,23 @@ pub fn str_list_int(ctx: &Context, target_info: TargetInfo) -> IntType<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn zig_dict_type<'a, 'ctx, 'env>(
|
||||
env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
|
||||
) -> StructType<'ctx> {
|
||||
pub fn zig_dict_type<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> StructType<'ctx> {
|
||||
env.module.get_struct_type("dict.RocDict").unwrap()
|
||||
}
|
||||
|
||||
pub fn zig_list_type<'a, 'ctx, 'env>(
|
||||
env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
|
||||
) -> StructType<'ctx> {
|
||||
pub fn zig_list_type<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> StructType<'ctx> {
|
||||
env.module.get_struct_type("list.RocList").unwrap()
|
||||
}
|
||||
|
||||
pub fn zig_str_type<'a, 'ctx, 'env>(
|
||||
env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
|
||||
) -> StructType<'ctx> {
|
||||
pub fn zig_str_type<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> StructType<'ctx> {
|
||||
env.module.get_struct_type("str.RocStr").unwrap()
|
||||
}
|
||||
|
||||
pub fn zig_has_tag_id_type<'a, 'ctx, 'env>(
|
||||
env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
|
||||
) -> StructType<'ctx> {
|
||||
pub fn zig_has_tag_id_type<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> StructType<'ctx> {
|
||||
env.module.get_struct_type("list.HasTagId").unwrap()
|
||||
}
|
||||
|
||||
pub fn zig_with_overflow_roc_dec<'a, 'ctx, 'env>(
|
||||
env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
|
||||
) -> StructType<'ctx> {
|
||||
pub fn zig_with_overflow_roc_dec<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> StructType<'ctx> {
|
||||
env.module
|
||||
.get_struct_type("utils.WithOverflow(dec.RocDec)")
|
||||
.unwrap()
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::llvm::build::{
|
|||
FAST_CALL_CONV, TAG_DATA_INDEX, TAG_ID_INDEX,
|
||||
};
|
||||
use crate::llvm::build_list::{incrementing_elem_loop, list_len, load_list};
|
||||
use crate::llvm::convert::{basic_type_from_layout, basic_type_from_layout_1};
|
||||
use crate::llvm::convert::basic_type_from_layout;
|
||||
use bumpalo::collections::Vec;
|
||||
use inkwell::basic_block::BasicBlock;
|
||||
use inkwell::context::Context;
|
||||
|
@ -20,6 +20,8 @@ use roc_module::symbol::Symbol;
|
|||
use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout};
|
||||
use roc_target::TargetInfo;
|
||||
|
||||
use super::convert::argument_type_from_union_layout;
|
||||
|
||||
/// "Infinite" reference count, for static values
|
||||
/// Ref counts are encoded as negative numbers where isize::MIN represents 1
|
||||
pub const REFCOUNT_MAX: usize = 0_usize;
|
||||
|
@ -589,6 +591,12 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>(
|
|||
modify_refcount_builtin(env, layout_ids, mode, when_recursive, layout, builtin)
|
||||
}
|
||||
|
||||
Boxed(inner) => {
|
||||
let function = modify_refcount_boxed(env, layout_ids, mode, inner);
|
||||
|
||||
Some(function)
|
||||
}
|
||||
|
||||
Union(variant) => {
|
||||
use UnionLayout::*;
|
||||
|
||||
|
@ -890,6 +898,76 @@ fn modify_refcount_str_help<'a, 'ctx, 'env>(
|
|||
builder.build_return(None);
|
||||
}
|
||||
|
||||
fn modify_refcount_boxed<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
mode: Mode,
|
||||
inner_layout: &'a Layout<'a>,
|
||||
) -> FunctionValue<'ctx> {
|
||||
let block = env.builder.get_insert_block().expect("to be in a function");
|
||||
let di_location = env.builder.get_current_debug_location().unwrap();
|
||||
|
||||
let boxed_layout = env.arena.alloc(Layout::Boxed(inner_layout));
|
||||
|
||||
let (_, fn_name) = function_name_from_mode(
|
||||
layout_ids,
|
||||
&env.interns,
|
||||
"increment_boxed",
|
||||
"decrement_boxed",
|
||||
boxed_layout,
|
||||
mode,
|
||||
);
|
||||
|
||||
let function = match env.module.get_function(fn_name.as_str()) {
|
||||
Some(function_value) => function_value,
|
||||
None => {
|
||||
let basic_type = basic_type_from_layout(env, boxed_layout);
|
||||
let function_value = build_header(env, basic_type, mode, &fn_name);
|
||||
|
||||
modify_refcount_box_help(env, mode, inner_layout, function_value);
|
||||
|
||||
function_value
|
||||
}
|
||||
};
|
||||
|
||||
env.builder.position_at_end(block);
|
||||
env.builder
|
||||
.set_current_debug_location(env.context, di_location);
|
||||
|
||||
function
|
||||
}
|
||||
|
||||
fn modify_refcount_box_help<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
mode: Mode,
|
||||
inner_layout: &Layout<'a>,
|
||||
fn_val: FunctionValue<'ctx>,
|
||||
) {
|
||||
let builder = env.builder;
|
||||
let ctx = env.context;
|
||||
|
||||
// Add a basic block for the entry point
|
||||
let entry = ctx.append_basic_block(fn_val, "entry");
|
||||
|
||||
builder.position_at_end(entry);
|
||||
|
||||
debug_info_init!(env, fn_val);
|
||||
|
||||
// Add args to scope
|
||||
let arg_symbol = Symbol::ARG_1;
|
||||
let arg_val = fn_val.get_param_iter().next().unwrap();
|
||||
arg_val.set_name(arg_symbol.as_str(&env.interns));
|
||||
|
||||
let boxed = arg_val.into_pointer_value();
|
||||
let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, boxed);
|
||||
let call_mode = mode_to_call_mode(fn_val, mode);
|
||||
let boxed_layout = Layout::Boxed(inner_layout);
|
||||
refcount_ptr.modify(call_mode, &boxed_layout, env);
|
||||
|
||||
// this function returns void
|
||||
builder.build_return(None);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn modify_refcount_dict<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
|
@ -1618,7 +1696,8 @@ fn modify_refcount_union<'a, 'ctx, 'env>(
|
|||
when_recursive: &WhenRecursive<'a>,
|
||||
fields: &'a [&'a [Layout<'a>]],
|
||||
) -> FunctionValue<'ctx> {
|
||||
let layout = Layout::Union(UnionLayout::NonRecursive(fields));
|
||||
let union_layout = UnionLayout::NonRecursive(fields);
|
||||
let layout = Layout::Union(union_layout);
|
||||
|
||||
let block = env.builder.get_insert_block().expect("to be in a function");
|
||||
let di_location = env.builder.get_current_debug_location().unwrap();
|
||||
|
@ -1635,7 +1714,7 @@ fn modify_refcount_union<'a, 'ctx, 'env>(
|
|||
let function = match env.module.get_function(fn_name.as_str()) {
|
||||
Some(function_value) => function_value,
|
||||
None => {
|
||||
let basic_type = basic_type_from_layout_1(env, &layout);
|
||||
let basic_type = argument_type_from_union_layout(env, &union_layout);
|
||||
let function_value = build_header(env, basic_type, mode, &fn_name);
|
||||
|
||||
modify_refcount_union_help(
|
||||
|
@ -1699,9 +1778,9 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
|
|||
.build_load(tag_id_ptr, "load_tag_id")
|
||||
.into_int_value();
|
||||
|
||||
let tag_id_u8 = env
|
||||
.builder
|
||||
.build_int_cast(tag_id, env.context.i8_type(), "tag_id_u8");
|
||||
let tag_id_u8 =
|
||||
env.builder
|
||||
.build_int_cast_sign_flag(tag_id, env.context.i8_type(), false, "tag_id_u8");
|
||||
|
||||
// next, make a jump table for all possible values of the tag_id
|
||||
let mut cases = Vec::with_capacity_in(tags.len(), env.arena);
|
||||
|
|
|
@ -594,6 +594,10 @@ impl<'a> WasmBackend<'a> {
|
|||
index,
|
||||
} => self.expr_union_at_index(*structure, *tag_id, union_layout, *index, sym),
|
||||
|
||||
Expr::ExprBox { .. } | Expr::ExprUnbox { .. } => {
|
||||
todo!("Expression `{}`", expr.to_pretty(100))
|
||||
}
|
||||
|
||||
Expr::Reuse { .. } | Expr::Reset { .. } | Expr::RuntimeErrorFunction(_) => {
|
||||
todo!("Expression `{}`", expr.to_pretty(100))
|
||||
}
|
||||
|
@ -932,11 +936,13 @@ impl<'a> WasmBackend<'a> {
|
|||
}
|
||||
_ => internal_error!("Cannot create struct {:?} with storage {:?}", sym, storage),
|
||||
};
|
||||
} else {
|
||||
} else if !fields.is_empty() {
|
||||
// Struct expression but not Struct layout => single element. Copy it.
|
||||
let field_storage = self.storage.get(&fields[0]).to_owned();
|
||||
self.storage
|
||||
.clone_value(&mut self.code_builder, storage, &field_storage, fields[0]);
|
||||
} else {
|
||||
// Empty record. Nothing to do.
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -102,6 +102,7 @@ impl WasmLayout {
|
|||
| NullableWrapped { .. }
|
||||
| NullableUnwrapped { .. },
|
||||
)
|
||||
| Layout::Boxed(_)
|
||||
| Layout::RecursivePointer => Self::Primitive(PTR_TYPE, PTR_SIZE),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -682,6 +682,10 @@ impl<'a> LowLevelCall<'a> {
|
|||
Hash => todo!("{:?}", self.lowlevel),
|
||||
|
||||
Eq | NotEq => self.eq_or_neq(backend),
|
||||
|
||||
BoxExpr | UnboxExpr => {
|
||||
unreachable!("The {:?} operation is turned into mono Expr", self.lowlevel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -742,6 +746,8 @@ impl<'a> LowLevelCall<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
Layout::Boxed(_) => todo!(),
|
||||
|
||||
Layout::RecursivePointer => {
|
||||
internal_error!(
|
||||
"Tried to apply `==` to RecursivePointer values {:?}",
|
||||
|
|
|
@ -4,6 +4,7 @@ use crate::docs::TypeAnnotation::{
|
|||
};
|
||||
use crate::file::LoadedModule;
|
||||
use roc_can::scope::Scope;
|
||||
use roc_error_macros::todo_abilities;
|
||||
use roc_module::ident::ModuleName;
|
||||
use roc_module::symbol::IdentIds;
|
||||
use roc_parse::ast::CommentOrNewline;
|
||||
|
@ -251,6 +252,8 @@ fn generate_entry_doc<'a>(
|
|||
(acc, None)
|
||||
}
|
||||
|
||||
Def::Ability { .. } => todo_abilities!(),
|
||||
|
||||
Def::Body(_, _) => (acc, None),
|
||||
|
||||
Def::Expect(c) => todo!("documentation for tests {:?}", c),
|
||||
|
|
|
@ -4,16 +4,17 @@ use crossbeam::channel::{bounded, Sender};
|
|||
use crossbeam::deque::{Injector, Stealer, Worker};
|
||||
use crossbeam::thread;
|
||||
use parking_lot::Mutex;
|
||||
use roc_builtins::std::StdLib;
|
||||
use roc_builtins::std::{borrow_stdlib, StdLib};
|
||||
use roc_can::constraint::{Constraint as ConstraintSoa, Constraints};
|
||||
use roc_can::def::Declaration;
|
||||
use roc_can::module::{canonicalize_module_defs, Module};
|
||||
use roc_collections::all::{default_hasher, BumpMap, MutMap, MutSet};
|
||||
use roc_constrain::module::{
|
||||
constrain_imports, constrain_module, pre_constrain_imports, ConstrainableImports,
|
||||
ExposedModuleTypes, Import, SubsByModule,
|
||||
constrain_builtin_imports, constrain_module, ExposedByModule, ExposedForModule,
|
||||
ExposedModuleTypes,
|
||||
};
|
||||
use roc_module::ident::{Ident, ModuleName, QualifiedModuleName};
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::ident::{Ident, ModuleName, QualifiedModuleName, TagName};
|
||||
use roc_module::symbol::{
|
||||
IdentIds, Interns, ModuleId, ModuleIds, PQModuleName, PackageModuleIds, PackageQualified,
|
||||
Symbol,
|
||||
|
@ -35,7 +36,7 @@ use roc_solve::solve;
|
|||
use roc_target::TargetInfo;
|
||||
use roc_types::solved_types::Solved;
|
||||
use roc_types::subs::{Subs, VarStore, Variable};
|
||||
use roc_types::types::Alias;
|
||||
use roc_types::types::{Alias, AliasCommon, TypeExtension};
|
||||
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::io;
|
||||
|
@ -312,7 +313,6 @@ fn start_phase<'a>(
|
|||
var_store,
|
||||
imported_modules,
|
||||
&mut state.exposed_types,
|
||||
state.stdlib,
|
||||
dep_idents,
|
||||
declarations,
|
||||
)
|
||||
|
@ -566,13 +566,11 @@ enum Msg<'a> {
|
|||
decls: Vec<Declaration>,
|
||||
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||
module_timing: ModuleTiming,
|
||||
unused_imports: MutMap<ModuleId, Region>,
|
||||
},
|
||||
FinishedAllTypeChecking {
|
||||
solved_subs: Solved<Subs>,
|
||||
exposed_vars_by_symbol: Vec<(Symbol, Variable)>,
|
||||
exposed_aliases_by_symbol: MutMap<Symbol, Alias>,
|
||||
exposed_values: Vec<Symbol>,
|
||||
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||
documentation: MutMap<ModuleId, ModuleDocumentation>,
|
||||
},
|
||||
|
@ -631,8 +629,7 @@ struct State<'a> {
|
|||
pub root_id: ModuleId,
|
||||
pub platform_data: Option<PlatformData>,
|
||||
pub goal_phase: Phase,
|
||||
pub stdlib: &'a StdLib,
|
||||
pub exposed_types: SubsByModule,
|
||||
pub exposed_types: ExposedByModule,
|
||||
pub output_path: Option<&'a str>,
|
||||
pub platform_path: PlatformPath<'a>,
|
||||
pub target_info: TargetInfo,
|
||||
|
@ -671,8 +668,7 @@ impl<'a> State<'a> {
|
|||
root_id: ModuleId,
|
||||
target_info: TargetInfo,
|
||||
goal_phase: Phase,
|
||||
stdlib: &'a StdLib,
|
||||
exposed_types: SubsByModule,
|
||||
exposed_types: ExposedByModule,
|
||||
arc_modules: Arc<Mutex<PackageModuleIds<'a>>>,
|
||||
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
|
||||
) -> Self {
|
||||
|
@ -683,7 +679,6 @@ impl<'a> State<'a> {
|
|||
target_info,
|
||||
platform_data: None,
|
||||
goal_phase,
|
||||
stdlib,
|
||||
output_path: None,
|
||||
platform_path: PlatformPath::NotSpecified,
|
||||
module_cache: ModuleCache::default(),
|
||||
|
@ -794,14 +789,14 @@ enum BuildTask<'a> {
|
|||
Solve {
|
||||
module: Module,
|
||||
ident_ids: IdentIds,
|
||||
imported_symbols: Vec<Import>,
|
||||
imported_builtins: Vec<Symbol>,
|
||||
exposed_for_module: ExposedForModule,
|
||||
module_timing: ModuleTiming,
|
||||
constraints: Constraints,
|
||||
constraint: ConstraintSoa,
|
||||
var_store: VarStore,
|
||||
declarations: Vec<Declaration>,
|
||||
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||
unused_imports: MutMap<ModuleId, Region>,
|
||||
},
|
||||
BuildPendingSpecializations {
|
||||
module_timing: ModuleTiming,
|
||||
|
@ -877,7 +872,7 @@ pub fn load_and_typecheck<'a>(
|
|||
filename: PathBuf,
|
||||
stdlib: &'a StdLib,
|
||||
src_dir: &Path,
|
||||
exposed_types: SubsByModule,
|
||||
exposed_types: ExposedByModule,
|
||||
target_info: TargetInfo,
|
||||
) -> Result<LoadedModule, LoadingProblem<'a>> {
|
||||
use LoadResult::*;
|
||||
|
@ -904,7 +899,7 @@ pub fn load_and_monomorphize<'a>(
|
|||
filename: PathBuf,
|
||||
stdlib: &'a StdLib,
|
||||
src_dir: &Path,
|
||||
exposed_types: SubsByModule,
|
||||
exposed_types: ExposedByModule,
|
||||
target_info: TargetInfo,
|
||||
) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>> {
|
||||
use LoadResult::*;
|
||||
|
@ -932,7 +927,7 @@ pub fn load_and_monomorphize_from_str<'a>(
|
|||
src: &'a str,
|
||||
stdlib: &'a StdLib,
|
||||
src_dir: &Path,
|
||||
exposed_types: SubsByModule,
|
||||
exposed_types: ExposedByModule,
|
||||
target_info: TargetInfo,
|
||||
) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>> {
|
||||
use LoadResult::*;
|
||||
|
@ -1096,9 +1091,9 @@ enum LoadResult<'a> {
|
|||
fn load<'a>(
|
||||
arena: &'a Bump,
|
||||
load_start: LoadStart<'a>,
|
||||
stdlib: &'a StdLib,
|
||||
_stdlib: &'a StdLib,
|
||||
src_dir: &Path,
|
||||
exposed_types: SubsByModule,
|
||||
exposed_types: ExposedByModule,
|
||||
goal_phase: Phase,
|
||||
target_info: TargetInfo,
|
||||
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
|
||||
|
@ -1108,7 +1103,6 @@ fn load<'a>(
|
|||
load_single_threaded(
|
||||
arena,
|
||||
load_start,
|
||||
stdlib,
|
||||
src_dir,
|
||||
exposed_types,
|
||||
goal_phase,
|
||||
|
@ -1118,7 +1112,6 @@ fn load<'a>(
|
|||
load_multi_threaded(
|
||||
arena,
|
||||
load_start,
|
||||
stdlib,
|
||||
src_dir,
|
||||
exposed_types,
|
||||
goal_phase,
|
||||
|
@ -1132,9 +1125,8 @@ fn load<'a>(
|
|||
fn load_single_threaded<'a>(
|
||||
arena: &'a Bump,
|
||||
load_start: LoadStart<'a>,
|
||||
stdlib: &'a StdLib,
|
||||
src_dir: &Path,
|
||||
exposed_types: SubsByModule,
|
||||
exposed_types: ExposedByModule,
|
||||
goal_phase: Phase,
|
||||
target_info: TargetInfo,
|
||||
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
|
||||
|
@ -1155,7 +1147,6 @@ fn load_single_threaded<'a>(
|
|||
root_id,
|
||||
target_info,
|
||||
goal_phase,
|
||||
stdlib,
|
||||
exposed_types,
|
||||
arc_modules,
|
||||
ident_ids_by_module,
|
||||
|
@ -1219,7 +1210,6 @@ fn state_thread_step<'a>(
|
|||
solved_subs,
|
||||
exposed_vars_by_symbol,
|
||||
exposed_aliases_by_symbol,
|
||||
exposed_values,
|
||||
dep_idents,
|
||||
documentation,
|
||||
} => {
|
||||
|
@ -1229,7 +1219,6 @@ fn state_thread_step<'a>(
|
|||
let typechecked = finish(
|
||||
state,
|
||||
solved_subs,
|
||||
exposed_values,
|
||||
exposed_aliases_by_symbol,
|
||||
exposed_vars_by_symbol,
|
||||
dep_idents,
|
||||
|
@ -1312,9 +1301,8 @@ fn state_thread_step<'a>(
|
|||
fn load_multi_threaded<'a>(
|
||||
arena: &'a Bump,
|
||||
load_start: LoadStart<'a>,
|
||||
stdlib: &'a StdLib,
|
||||
src_dir: &Path,
|
||||
exposed_types: SubsByModule,
|
||||
exposed_types: ExposedByModule,
|
||||
goal_phase: Phase,
|
||||
target_info: TargetInfo,
|
||||
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
|
||||
|
@ -1329,7 +1317,6 @@ fn load_multi_threaded<'a>(
|
|||
root_id,
|
||||
target_info,
|
||||
goal_phase,
|
||||
stdlib,
|
||||
exposed_types,
|
||||
arc_modules,
|
||||
ident_ids_by_module,
|
||||
|
@ -1622,6 +1609,34 @@ fn debug_print_ir(state: &State, flag: &str) {
|
|||
println!("{}", result);
|
||||
}
|
||||
|
||||
/// Report modules that are imported, but from which nothing is used
|
||||
fn report_unused_imported_modules<'a>(
|
||||
state: &mut State<'a>,
|
||||
module_id: ModuleId,
|
||||
constrained_module: &ConstrainedModule,
|
||||
) {
|
||||
let mut unused_imported_modules = constrained_module.imported_modules.clone();
|
||||
|
||||
for symbol in constrained_module.module.referenced_values.iter() {
|
||||
unused_imported_modules.remove(&symbol.module_id());
|
||||
}
|
||||
|
||||
for symbol in constrained_module.module.referenced_types.iter() {
|
||||
unused_imported_modules.remove(&symbol.module_id());
|
||||
}
|
||||
|
||||
let existing = match state.module_cache.can_problems.entry(module_id) {
|
||||
Vacant(entry) => entry.insert(std::vec::Vec::new()),
|
||||
Occupied(entry) => entry.into_mut(),
|
||||
};
|
||||
|
||||
for (unused, region) in unused_imported_modules.drain() {
|
||||
if !unused.is_builtin() {
|
||||
existing.push(roc_problem::can::Problem::UnusedImport(unused, region));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update<'a>(
|
||||
mut state: State<'a>,
|
||||
msg: Msg<'a>,
|
||||
|
@ -1919,6 +1934,8 @@ fn update<'a>(
|
|||
state.module_cache.documentation.insert(module_id, docs);
|
||||
}
|
||||
|
||||
report_unused_imported_modules(&mut state, module_id, &constrained_module);
|
||||
|
||||
state
|
||||
.module_cache
|
||||
.aliases
|
||||
|
@ -1945,7 +1962,6 @@ fn update<'a>(
|
|||
decls,
|
||||
dep_idents,
|
||||
mut module_timing,
|
||||
mut unused_imports,
|
||||
} => {
|
||||
log!("solved types for {:?}", module_id);
|
||||
module_timing.end_time = SystemTime::now();
|
||||
|
@ -1955,17 +1971,6 @@ fn update<'a>(
|
|||
.type_problems
|
||||
.insert(module_id, solved_module.problems);
|
||||
|
||||
let existing = match state.module_cache.can_problems.entry(module_id) {
|
||||
Vacant(entry) => entry.insert(std::vec::Vec::new()),
|
||||
Occupied(entry) => entry.into_mut(),
|
||||
};
|
||||
|
||||
for (unused, region) in unused_imports.drain() {
|
||||
if !unused.is_builtin() {
|
||||
existing.push(roc_problem::can::Problem::UnusedImport(unused, region));
|
||||
}
|
||||
}
|
||||
|
||||
let work = state.dependencies.notify(module_id, Phase::SolveTypes);
|
||||
|
||||
// if there is a platform, the Package-Config module provides host-exposed,
|
||||
|
@ -2006,7 +2011,6 @@ fn update<'a>(
|
|||
.send(Msg::FinishedAllTypeChecking {
|
||||
solved_subs,
|
||||
exposed_vars_by_symbol: solved_module.exposed_vars_by_symbol,
|
||||
exposed_values: solved_module.exposed_symbols,
|
||||
exposed_aliases_by_symbol: solved_module.aliases,
|
||||
dep_idents,
|
||||
documentation,
|
||||
|
@ -2023,7 +2027,10 @@ fn update<'a>(
|
|||
} else {
|
||||
state.exposed_types.insert(
|
||||
module_id,
|
||||
ExposedModuleTypes::Valid(solved_module.solved_types, solved_module.aliases),
|
||||
ExposedModuleTypes::Valid {
|
||||
stored_vars_by_symbol: solved_module.stored_vars_by_symbol,
|
||||
storage_subs: solved_module.storage_subs,
|
||||
},
|
||||
);
|
||||
|
||||
if state.goal_phase > Phase::SolveTypes {
|
||||
|
@ -2223,6 +2230,12 @@ fn finish_specialization(
|
|||
subs: Subs,
|
||||
exposed_to_host: ExposedToHost,
|
||||
) -> Result<MonomorphizedModule, LoadingProblem> {
|
||||
if false {
|
||||
println!(
|
||||
"total Type clones: {} ",
|
||||
roc_types::types::get_type_clone_count()
|
||||
);
|
||||
}
|
||||
let module_ids = Arc::try_unwrap(state.arc_modules)
|
||||
.unwrap_or_else(|_| panic!("There were still outstanding Arc references to module_ids"))
|
||||
.into_inner()
|
||||
|
@ -2324,7 +2337,6 @@ fn finish_specialization(
|
|||
fn finish(
|
||||
state: State,
|
||||
solved: Solved<Subs>,
|
||||
exposed_values: Vec<Symbol>,
|
||||
exposed_aliases_by_symbol: MutMap<Symbol, Alias>,
|
||||
exposed_vars_by_symbol: Vec<(Symbol, Variable)>,
|
||||
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||
|
@ -2347,6 +2359,8 @@ fn finish(
|
|||
.map(|(id, (path, src))| (id, (path, src.into())))
|
||||
.collect();
|
||||
|
||||
let exposed_values = exposed_vars_by_symbol.iter().map(|x| x.0).collect();
|
||||
|
||||
LoadedModule {
|
||||
module_id: state.root_id,
|
||||
interns,
|
||||
|
@ -4317,7 +4331,7 @@ fn send_header_two<'a>(
|
|||
impl<'a> BuildTask<'a> {
|
||||
// TODO trim down these arguments - possibly by moving Constraint into Module
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn solve_module(
|
||||
fn solve_module(
|
||||
module: Module,
|
||||
ident_ids: IdentIds,
|
||||
module_timing: ModuleTiming,
|
||||
|
@ -4325,40 +4339,33 @@ impl<'a> BuildTask<'a> {
|
|||
constraint: ConstraintSoa,
|
||||
var_store: VarStore,
|
||||
imported_modules: MutMap<ModuleId, Region>,
|
||||
exposed_types: &mut SubsByModule,
|
||||
stdlib: &StdLib,
|
||||
exposed_types: &mut ExposedByModule,
|
||||
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||
declarations: Vec<Declaration>,
|
||||
) -> Self {
|
||||
let home = module.module_id;
|
||||
let exposed_by_module = exposed_types.retain_modules(imported_modules.keys());
|
||||
let exposed_for_module =
|
||||
ExposedForModule::new(module.referenced_values.iter(), exposed_by_module);
|
||||
|
||||
// Get the constraints for this module's imports. We do this on the main thread
|
||||
// to avoid having to lock the map of exposed types, or to clone it
|
||||
// (which would be more expensive for the main thread).
|
||||
let ConstrainableImports {
|
||||
imported_symbols,
|
||||
imported_aliases: _,
|
||||
unused_imports,
|
||||
} = pre_constrain_imports(
|
||||
home,
|
||||
&module.references,
|
||||
imported_modules,
|
||||
exposed_types,
|
||||
stdlib,
|
||||
);
|
||||
let imported_builtins = module
|
||||
.referenced_values
|
||||
.iter()
|
||||
.filter(|s| s.is_builtin())
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
// Next, solve this module in the background.
|
||||
Self::Solve {
|
||||
module,
|
||||
ident_ids,
|
||||
imported_symbols,
|
||||
imported_builtins,
|
||||
exposed_for_module,
|
||||
constraints,
|
||||
constraint,
|
||||
var_store,
|
||||
declarations,
|
||||
dep_idents,
|
||||
module_timing,
|
||||
unused_imports,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4368,25 +4375,19 @@ fn run_solve<'a>(
|
|||
module: Module,
|
||||
ident_ids: IdentIds,
|
||||
mut module_timing: ModuleTiming,
|
||||
imported_symbols: Vec<Import>,
|
||||
imported_builtins: Vec<Symbol>,
|
||||
mut exposed_for_module: ExposedForModule,
|
||||
mut constraints: Constraints,
|
||||
constraint: ConstraintSoa,
|
||||
mut var_store: VarStore,
|
||||
decls: Vec<Declaration>,
|
||||
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||
unused_imports: MutMap<ModuleId, Region>,
|
||||
) -> Msg<'a> {
|
||||
// We have more constraining work to do now, so we'll add it to our timings.
|
||||
let constrain_start = SystemTime::now();
|
||||
|
||||
// Finish constraining the module by wrapping the existing Constraint
|
||||
// in the ones we just computed. We can do this off the main thread.
|
||||
let constraint = constrain_imports(
|
||||
&mut constraints,
|
||||
imported_symbols,
|
||||
constraint,
|
||||
&mut var_store,
|
||||
);
|
||||
let (mut rigid_vars, mut def_types) =
|
||||
constrain_builtin_imports(borrow_stdlib(), imported_builtins, &mut var_store);
|
||||
|
||||
let constrain_end = SystemTime::now();
|
||||
|
||||
|
@ -4399,25 +4400,92 @@ fn run_solve<'a>(
|
|||
..
|
||||
} = module;
|
||||
|
||||
// TODO
|
||||
// if false { debug_assert!(constraint.validate(), "{:?}", &constraint); }
|
||||
let mut subs = Subs::new_from_varstore(var_store);
|
||||
|
||||
let (solved_subs, solved_env, problems) =
|
||||
roc_solve::module::run_solve(&constraints, constraint, rigid_variables, var_store);
|
||||
let mut import_variables = Vec::new();
|
||||
|
||||
for symbol in exposed_for_module.imported_values {
|
||||
let module_id = symbol.module_id();
|
||||
match exposed_for_module.exposed_by_module.get_mut(&module_id) {
|
||||
Some(t) => match t {
|
||||
ExposedModuleTypes::Invalid => {
|
||||
// make the type a flex var, so it unifies with anything
|
||||
// this way the error is only reported in the module it originates in
|
||||
let variable = subs.fresh_unnamed_flex_var();
|
||||
|
||||
def_types.push((
|
||||
symbol,
|
||||
Loc::at_zero(roc_types::types::Type::Variable(variable)),
|
||||
));
|
||||
}
|
||||
ExposedModuleTypes::Valid {
|
||||
stored_vars_by_symbol,
|
||||
storage_subs,
|
||||
} => {
|
||||
let variable = match stored_vars_by_symbol.iter().find(|(s, _)| *s == symbol) {
|
||||
None => {
|
||||
// Today we define builtins in each module that uses them
|
||||
// so even though they have a different module name from
|
||||
// the surrounding module, they are not technically imported
|
||||
debug_assert!(symbol.is_builtin());
|
||||
continue;
|
||||
}
|
||||
Some((_, x)) => *x,
|
||||
};
|
||||
|
||||
let copied_import = storage_subs.export_variable_to(&mut subs, variable);
|
||||
|
||||
// not a typo; rigids are turned into flex during type inference, but when imported we must
|
||||
// consider them rigid variables
|
||||
rigid_vars.extend(copied_import.rigid);
|
||||
rigid_vars.extend(copied_import.flex);
|
||||
|
||||
import_variables.extend(copied_import.registered);
|
||||
|
||||
def_types.push((
|
||||
symbol,
|
||||
Loc::at_zero(roc_types::types::Type::Variable(copied_import.variable)),
|
||||
));
|
||||
}
|
||||
},
|
||||
None => {
|
||||
internal_error!("Imported module {:?} is not available", module_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let actual_constraint =
|
||||
constraints.let_import_constraint(rigid_vars, def_types, constraint, &import_variables);
|
||||
|
||||
let mut solve_aliases = default_aliases();
|
||||
|
||||
for (name, alias) in aliases.iter() {
|
||||
solve_aliases.insert(*name, alias.clone());
|
||||
}
|
||||
|
||||
let (solved_subs, solved_env, problems) = roc_solve::module::run_solve(
|
||||
&constraints,
|
||||
actual_constraint,
|
||||
rigid_variables,
|
||||
subs,
|
||||
solve_aliases,
|
||||
);
|
||||
|
||||
let exposed_vars_by_symbol: Vec<_> = solved_env
|
||||
.vars_by_symbol()
|
||||
.filter(|(k, _)| exposed_symbols.contains(k))
|
||||
.collect();
|
||||
|
||||
let solved_types = roc_solve::module::make_solved_types(&solved_subs, &exposed_vars_by_symbol);
|
||||
let mut solved_subs = solved_subs;
|
||||
let (storage_subs, stored_vars_by_symbol) =
|
||||
roc_solve::module::exposed_types_storage_subs(&mut solved_subs, &exposed_vars_by_symbol);
|
||||
|
||||
let solved_module = SolvedModule {
|
||||
exposed_vars_by_symbol,
|
||||
exposed_symbols: exposed_symbols.into_iter().collect::<Vec<_>>(),
|
||||
solved_types,
|
||||
problems,
|
||||
aliases,
|
||||
stored_vars_by_symbol,
|
||||
storage_subs,
|
||||
};
|
||||
|
||||
// Record the final timings
|
||||
|
@ -4436,7 +4504,6 @@ fn run_solve<'a>(
|
|||
dep_idents,
|
||||
solved_module,
|
||||
module_timing,
|
||||
unused_imports,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4556,7 +4623,8 @@ fn canonicalize_and_constrain<'a>(
|
|||
module_id,
|
||||
exposed_imports: module_output.exposed_imports,
|
||||
exposed_symbols,
|
||||
references: module_output.references,
|
||||
referenced_values: module_output.referenced_values,
|
||||
referenced_types: module_output.referenced_types,
|
||||
aliases: module_output.aliases,
|
||||
rigid_variables: module_output.rigid_variables,
|
||||
};
|
||||
|
@ -5048,25 +5116,25 @@ fn run_task<'a>(
|
|||
Solve {
|
||||
module,
|
||||
module_timing,
|
||||
imported_symbols,
|
||||
imported_builtins,
|
||||
exposed_for_module,
|
||||
constraints,
|
||||
constraint,
|
||||
var_store,
|
||||
ident_ids,
|
||||
declarations,
|
||||
dep_idents,
|
||||
unused_imports,
|
||||
} => Ok(run_solve(
|
||||
module,
|
||||
ident_ids,
|
||||
module_timing,
|
||||
imported_symbols,
|
||||
imported_builtins,
|
||||
exposed_for_module,
|
||||
constraints,
|
||||
constraint,
|
||||
var_store,
|
||||
declarations,
|
||||
dep_idents,
|
||||
unused_imports,
|
||||
)),
|
||||
BuildPendingSpecializations {
|
||||
module_id,
|
||||
|
@ -5324,3 +5392,210 @@ fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> Strin
|
|||
|
||||
buf
|
||||
}
|
||||
|
||||
/// Builtin aliases that are not covered by type checker optimizations
|
||||
///
|
||||
/// Types like `F64` and `I32` are hardcoded into Subs and therefore we don't define them here.
|
||||
/// All that remains are the generic number types (Num, Int, Float) and Result
|
||||
fn default_aliases() -> roc_solve::solve::Aliases {
|
||||
use roc_types::types::Type;
|
||||
|
||||
let mut solve_aliases = roc_solve::solve::Aliases::default();
|
||||
|
||||
let mut var_store = VarStore::default();
|
||||
|
||||
{
|
||||
let symbol = Symbol::NUM_NUM;
|
||||
let tvar = var_store.fresh();
|
||||
|
||||
let typ = Type::TagUnion(
|
||||
vec![(
|
||||
TagName::Private(Symbol::NUM_AT_NUM),
|
||||
vec![Type::Variable(tvar)],
|
||||
)],
|
||||
TypeExtension::Closed,
|
||||
);
|
||||
|
||||
let alias = Alias {
|
||||
region: Region::zero(),
|
||||
type_variables: vec![Loc::at_zero(("range".into(), tvar))],
|
||||
lambda_set_variables: Default::default(),
|
||||
recursion_variables: Default::default(),
|
||||
typ,
|
||||
kind: roc_types::types::AliasKind::Structural,
|
||||
};
|
||||
|
||||
solve_aliases.insert(symbol, alias);
|
||||
}
|
||||
|
||||
// FloatingPoint range : [ @FloatingPoint range ]
|
||||
{
|
||||
let symbol = Symbol::NUM_FLOATINGPOINT;
|
||||
let tvar = var_store.fresh();
|
||||
|
||||
let typ = Type::TagUnion(
|
||||
vec![(
|
||||
TagName::Private(Symbol::NUM_AT_FLOATINGPOINT),
|
||||
vec![Type::Variable(tvar)],
|
||||
)],
|
||||
TypeExtension::Closed,
|
||||
);
|
||||
|
||||
let alias = Alias {
|
||||
region: Region::zero(),
|
||||
type_variables: vec![Loc::at_zero(("range".into(), tvar))],
|
||||
lambda_set_variables: Default::default(),
|
||||
recursion_variables: Default::default(),
|
||||
typ,
|
||||
kind: roc_types::types::AliasKind::Structural,
|
||||
};
|
||||
|
||||
solve_aliases.insert(symbol, alias);
|
||||
}
|
||||
|
||||
// Int range : Num (Integer range)
|
||||
{
|
||||
let symbol = Symbol::NUM_INT;
|
||||
let tvar = var_store.fresh();
|
||||
|
||||
let typ = Type::DelayedAlias(AliasCommon {
|
||||
symbol: Symbol::NUM_NUM,
|
||||
type_arguments: vec![(
|
||||
"range".into(),
|
||||
Type::DelayedAlias(AliasCommon {
|
||||
symbol: Symbol::NUM_INTEGER,
|
||||
type_arguments: vec![("range".into(), Type::Variable(tvar))],
|
||||
lambda_set_variables: vec![],
|
||||
}),
|
||||
)],
|
||||
lambda_set_variables: vec![],
|
||||
});
|
||||
|
||||
let alias = Alias {
|
||||
region: Region::zero(),
|
||||
type_variables: vec![Loc::at_zero(("range".into(), tvar))],
|
||||
lambda_set_variables: Default::default(),
|
||||
recursion_variables: Default::default(),
|
||||
typ,
|
||||
kind: roc_types::types::AliasKind::Structural,
|
||||
};
|
||||
|
||||
solve_aliases.insert(symbol, alias);
|
||||
}
|
||||
|
||||
{
|
||||
let symbol = Symbol::NUM_FLOAT;
|
||||
let tvar = var_store.fresh();
|
||||
|
||||
let typ = Type::DelayedAlias(AliasCommon {
|
||||
symbol: Symbol::NUM_NUM,
|
||||
type_arguments: vec![(
|
||||
"range".into(),
|
||||
Type::DelayedAlias(AliasCommon {
|
||||
symbol: Symbol::NUM_FLOATINGPOINT,
|
||||
type_arguments: vec![("range".into(), Type::Variable(tvar))],
|
||||
lambda_set_variables: vec![],
|
||||
}),
|
||||
)],
|
||||
lambda_set_variables: vec![],
|
||||
});
|
||||
|
||||
let alias = Alias {
|
||||
region: Region::zero(),
|
||||
type_variables: vec![Loc::at_zero(("range".into(), tvar))],
|
||||
lambda_set_variables: Default::default(),
|
||||
recursion_variables: Default::default(),
|
||||
typ,
|
||||
kind: roc_types::types::AliasKind::Structural,
|
||||
};
|
||||
|
||||
solve_aliases.insert(symbol, alias);
|
||||
}
|
||||
|
||||
{
|
||||
let symbol = Symbol::NUM_INTEGER;
|
||||
let tvar = var_store.fresh();
|
||||
|
||||
let typ = Type::TagUnion(
|
||||
vec![(
|
||||
TagName::Private(Symbol::NUM_AT_INTEGER),
|
||||
vec![Type::Variable(tvar)],
|
||||
)],
|
||||
TypeExtension::Closed,
|
||||
);
|
||||
|
||||
let alias = Alias {
|
||||
region: Region::zero(),
|
||||
type_variables: vec![Loc::at_zero(("range".into(), tvar))],
|
||||
lambda_set_variables: Default::default(),
|
||||
recursion_variables: Default::default(),
|
||||
typ,
|
||||
kind: roc_types::types::AliasKind::Structural,
|
||||
};
|
||||
|
||||
solve_aliases.insert(symbol, alias);
|
||||
}
|
||||
|
||||
{
|
||||
let symbol = Symbol::RESULT_RESULT;
|
||||
let tvar1 = var_store.fresh();
|
||||
let tvar2 = var_store.fresh();
|
||||
|
||||
let typ = Type::TagUnion(
|
||||
vec![
|
||||
(TagName::Global("Ok".into()), vec![Type::Variable(tvar1)]),
|
||||
(TagName::Global("Err".into()), vec![Type::Variable(tvar2)]),
|
||||
],
|
||||
TypeExtension::Closed,
|
||||
);
|
||||
|
||||
let alias = Alias {
|
||||
region: Region::zero(),
|
||||
type_variables: vec![
|
||||
Loc::at_zero(("ok".into(), tvar1)),
|
||||
Loc::at_zero(("err".into(), tvar2)),
|
||||
],
|
||||
lambda_set_variables: Default::default(),
|
||||
recursion_variables: Default::default(),
|
||||
typ,
|
||||
kind: roc_types::types::AliasKind::Structural,
|
||||
};
|
||||
|
||||
solve_aliases.insert(symbol, alias);
|
||||
}
|
||||
|
||||
let mut unit_function = |alias_name: Symbol, at_tag_name: Symbol| {
|
||||
let typ = Type::TagUnion(
|
||||
vec![(TagName::Private(at_tag_name), vec![])],
|
||||
TypeExtension::Closed,
|
||||
);
|
||||
|
||||
let alias = Alias {
|
||||
region: Region::zero(),
|
||||
type_variables: vec![],
|
||||
lambda_set_variables: Default::default(),
|
||||
recursion_variables: Default::default(),
|
||||
typ,
|
||||
kind: roc_types::types::AliasKind::Structural,
|
||||
};
|
||||
|
||||
solve_aliases.insert(alias_name, alias);
|
||||
};
|
||||
|
||||
unit_function(Symbol::NUM_SIGNED8, Symbol::NUM_AT_SIGNED8);
|
||||
unit_function(Symbol::NUM_SIGNED16, Symbol::NUM_AT_SIGNED16);
|
||||
unit_function(Symbol::NUM_SIGNED32, Symbol::NUM_AT_SIGNED32);
|
||||
unit_function(Symbol::NUM_SIGNED64, Symbol::NUM_AT_SIGNED64);
|
||||
unit_function(Symbol::NUM_SIGNED128, Symbol::NUM_AT_SIGNED128);
|
||||
|
||||
unit_function(Symbol::NUM_UNSIGNED8, Symbol::NUM_AT_UNSIGNED8);
|
||||
unit_function(Symbol::NUM_UNSIGNED16, Symbol::NUM_AT_UNSIGNED16);
|
||||
unit_function(Symbol::NUM_UNSIGNED32, Symbol::NUM_AT_UNSIGNED32);
|
||||
unit_function(Symbol::NUM_UNSIGNED64, Symbol::NUM_AT_UNSIGNED64);
|
||||
unit_function(Symbol::NUM_UNSIGNED128, Symbol::NUM_AT_UNSIGNED128);
|
||||
|
||||
unit_function(Symbol::NUM_BINARY32, Symbol::NUM_AT_BINARY32);
|
||||
unit_function(Symbol::NUM_BINARY64, Symbol::NUM_AT_BINARY64);
|
||||
|
||||
solve_aliases
|
||||
}
|
||||
|
|
|
@ -18,8 +18,7 @@ mod test_load {
|
|||
use bumpalo::Bump;
|
||||
use roc_can::def::Declaration::*;
|
||||
use roc_can::def::Def;
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_constrain::module::SubsByModule;
|
||||
use roc_constrain::module::ExposedByModule;
|
||||
use roc_load::file::LoadedModule;
|
||||
use roc_module::ident::ModuleName;
|
||||
use roc_module::symbol::{Interns, ModuleId};
|
||||
|
@ -111,7 +110,6 @@ mod test_load {
|
|||
let stdlib = roc_builtins::std::standard_stdlib();
|
||||
|
||||
let mut file_handles: Vec<_> = Vec::new();
|
||||
let exposed_types = MutMap::default();
|
||||
|
||||
// create a temporary directory
|
||||
let dir = tempdir()?;
|
||||
|
@ -146,7 +144,7 @@ mod test_load {
|
|||
full_file_path,
|
||||
arena.alloc(stdlib),
|
||||
dir.path(),
|
||||
exposed_types,
|
||||
Default::default(),
|
||||
TARGET_INFO,
|
||||
)
|
||||
};
|
||||
|
@ -159,7 +157,7 @@ mod test_load {
|
|||
fn load_fixture(
|
||||
dir_name: &str,
|
||||
module_name: &str,
|
||||
subs_by_module: SubsByModule,
|
||||
subs_by_module: ExposedByModule,
|
||||
) -> LoadedModule {
|
||||
let src_dir = fixtures_dir().join(dir_name);
|
||||
let filename = src_dir.join(format!("{}.roc", module_name));
|
||||
|
@ -325,7 +323,7 @@ mod test_load {
|
|||
|
||||
#[test]
|
||||
fn interface_with_deps() {
|
||||
let subs_by_module = MutMap::default();
|
||||
let subs_by_module = Default::default();
|
||||
let src_dir = fixtures_dir().join("interface_with_deps");
|
||||
let filename = src_dir.join("Primary.roc");
|
||||
let arena = Bump::new();
|
||||
|
@ -373,7 +371,7 @@ mod test_load {
|
|||
|
||||
#[test]
|
||||
fn load_unit() {
|
||||
let subs_by_module = MutMap::default();
|
||||
let subs_by_module = Default::default();
|
||||
let loaded_module = load_fixture("no_deps", "Unit", subs_by_module);
|
||||
|
||||
expect_types(
|
||||
|
@ -386,7 +384,7 @@ mod test_load {
|
|||
|
||||
#[test]
|
||||
fn import_alias() {
|
||||
let subs_by_module = MutMap::default();
|
||||
let subs_by_module = Default::default();
|
||||
let loaded_module = load_fixture("interface_with_deps", "ImportAlias", subs_by_module);
|
||||
|
||||
expect_types(
|
||||
|
@ -399,7 +397,7 @@ mod test_load {
|
|||
|
||||
#[test]
|
||||
fn load_and_typecheck() {
|
||||
let subs_by_module = MutMap::default();
|
||||
let subs_by_module = Default::default();
|
||||
let loaded_module = load_fixture("interface_with_deps", "WithBuiltins", subs_by_module);
|
||||
|
||||
expect_types(
|
||||
|
@ -419,7 +417,7 @@ mod test_load {
|
|||
|
||||
#[test]
|
||||
fn iface_quicksort() {
|
||||
let subs_by_module = MutMap::default();
|
||||
let subs_by_module = Default::default();
|
||||
let loaded_module = load_fixture("interface_with_deps", "Quicksort", subs_by_module);
|
||||
|
||||
expect_types(
|
||||
|
@ -435,7 +433,7 @@ mod test_load {
|
|||
|
||||
#[test]
|
||||
fn quicksort_one_def() {
|
||||
let subs_by_module = MutMap::default();
|
||||
let subs_by_module = Default::default();
|
||||
let loaded_module = load_fixture("app_with_deps", "QuicksortOneDef", subs_by_module);
|
||||
|
||||
expect_types(
|
||||
|
@ -448,7 +446,7 @@ mod test_load {
|
|||
|
||||
#[test]
|
||||
fn app_quicksort() {
|
||||
let subs_by_module = MutMap::default();
|
||||
let subs_by_module = Default::default();
|
||||
let loaded_module = load_fixture("app_with_deps", "Quicksort", subs_by_module);
|
||||
|
||||
expect_types(
|
||||
|
@ -464,7 +462,7 @@ mod test_load {
|
|||
|
||||
#[test]
|
||||
fn load_astar() {
|
||||
let subs_by_module = MutMap::default();
|
||||
let subs_by_module = Default::default();
|
||||
let loaded_module = load_fixture("interface_with_deps", "AStar", subs_by_module);
|
||||
|
||||
expect_types(
|
||||
|
@ -482,7 +480,7 @@ mod test_load {
|
|||
|
||||
#[test]
|
||||
fn load_principal_types() {
|
||||
let subs_by_module = MutMap::default();
|
||||
let subs_by_module = Default::default();
|
||||
let loaded_module = load_fixture("no_deps", "Principal", subs_by_module);
|
||||
|
||||
expect_types(
|
||||
|
@ -496,7 +494,7 @@ mod test_load {
|
|||
|
||||
#[test]
|
||||
fn iface_dep_types() {
|
||||
let subs_by_module = MutMap::default();
|
||||
let subs_by_module = Default::default();
|
||||
let loaded_module = load_fixture("interface_with_deps", "Primary", subs_by_module);
|
||||
|
||||
expect_types(
|
||||
|
@ -511,14 +509,14 @@ mod test_load {
|
|||
"w" => "Dep1.Identity {}",
|
||||
"succeed" => "a -> Dep1.Identity a",
|
||||
"yay" => "Res.Res {} err",
|
||||
"withDefault" => "Res.Res a *, a -> a",
|
||||
"withDefault" => "Res.Res a err, a -> a",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn app_dep_types() {
|
||||
let subs_by_module = MutMap::default();
|
||||
let subs_by_module = Default::default();
|
||||
let loaded_module = load_fixture("app_with_deps", "Primary", subs_by_module);
|
||||
|
||||
expect_types(
|
||||
|
@ -533,14 +531,14 @@ mod test_load {
|
|||
"w" => "Dep1.Identity {}",
|
||||
"succeed" => "a -> Dep1.Identity a",
|
||||
"yay" => "Res.Res {} err",
|
||||
"withDefault" => "Res.Res a *, a -> a",
|
||||
"withDefault" => "Res.Res a err, a -> a",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn imported_dep_regression() {
|
||||
let subs_by_module = MutMap::default();
|
||||
let subs_by_module = Default::default();
|
||||
let loaded_module = load_fixture("interface_with_deps", "OneDep", subs_by_module);
|
||||
|
||||
expect_types(
|
||||
|
@ -590,7 +588,7 @@ mod test_load {
|
|||
#[test]
|
||||
#[should_panic(expected = "FILE NOT FOUND")]
|
||||
fn file_not_found() {
|
||||
let subs_by_module = MutMap::default();
|
||||
let subs_by_module = Default::default();
|
||||
let loaded_module = load_fixture("interface_with_deps", "invalid$name", subs_by_module);
|
||||
|
||||
expect_types(
|
||||
|
@ -604,7 +602,7 @@ mod test_load {
|
|||
#[test]
|
||||
#[should_panic(expected = "FILE NOT FOUND")]
|
||||
fn imported_file_not_found() {
|
||||
let subs_by_module = MutMap::default();
|
||||
let subs_by_module = Default::default();
|
||||
let loaded_module = load_fixture("no_deps", "MissingDep", subs_by_module);
|
||||
|
||||
expect_types(
|
||||
|
|
|
@ -124,6 +124,8 @@ pub enum LowLevel {
|
|||
PtrCast,
|
||||
RefCountInc,
|
||||
RefCountDec,
|
||||
BoxExpr,
|
||||
UnboxExpr,
|
||||
}
|
||||
|
||||
macro_rules! higher_order {
|
||||
|
|
|
@ -1035,6 +1035,8 @@ define_builtins! {
|
|||
142 NUM_TO_U64_CHECKED: "toU64Checked"
|
||||
143 NUM_TO_U128: "toU128"
|
||||
144 NUM_TO_U128_CHECKED: "toU128Checked"
|
||||
145 NUM_TO_NAT: "toNat"
|
||||
146 NUM_TO_NAT_CHECKED: "toNatChecked"
|
||||
}
|
||||
2 BOOL: "Bool" => {
|
||||
0 BOOL_BOOL: "Bool" // the Bool.Bool type alias
|
||||
|
@ -1199,6 +1201,12 @@ define_builtins! {
|
|||
14 SET_CONTAINS: "contains"
|
||||
15 SET_TO_DICT: "toDict"
|
||||
}
|
||||
8 BOX: "Box" => {
|
||||
0 BOX_BOX_TYPE: "Box" imported // the Box.Box opaque type
|
||||
1 BOX_BOX_FUNCTION: "box" // Box.box
|
||||
2 BOX_UNBOX: "unbox"
|
||||
|
||||
num_modules: 8 // Keep this count up to date by hand! (TODO: see the mut_map! macro for how we could determine this count correctly in the macro)
|
||||
}
|
||||
|
||||
num_modules: 9 // Keep this count up to date by hand! (TODO: see the mut_map! macro for how we could determine this count correctly in the macro)
|
||||
}
|
||||
|
|
|
@ -724,6 +724,23 @@ impl<'a> BorrowInfState<'a> {
|
|||
// the function must take it as an owned parameter
|
||||
self.own_args_if_param(xs);
|
||||
}
|
||||
|
||||
ExprBox { symbol: x } => {
|
||||
self.own_var(z);
|
||||
|
||||
// if the used symbol is an argument to the current function,
|
||||
// the function must take it as an owned parameter
|
||||
self.own_args_if_param(&[*x]);
|
||||
}
|
||||
|
||||
ExprUnbox { symbol: x } => {
|
||||
// if the boxed value is owned, the box is
|
||||
self.if_is_owned_then_own(*x, z);
|
||||
|
||||
// if the extracted value is owned, the structure must be too
|
||||
self.if_is_owned_then_own(z, *x);
|
||||
}
|
||||
|
||||
Reset { symbol: x, .. } => {
|
||||
self.own_var(z);
|
||||
self.own_var(*x);
|
||||
|
@ -1012,6 +1029,10 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
|
|||
|
||||
ExpectTrue => arena.alloc_slice_copy(&[irrelevant]),
|
||||
|
||||
BoxExpr | UnboxExpr => {
|
||||
unreachable!("These lowlevel operations are turned into mono Expr's")
|
||||
}
|
||||
|
||||
PtrCast | RefCountInc | RefCountDec => {
|
||||
unreachable!("Only inserted *after* borrow checking: {:?}", op);
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ pub fn eq_generic<'a>(
|
|||
Layout::Builtin(Builtin::List(elem_layout)) => eq_list(root, ident_ids, ctx, elem_layout),
|
||||
Layout::Struct { field_layouts, .. } => eq_struct(root, ident_ids, ctx, field_layouts),
|
||||
Layout::Union(union_layout) => eq_tag_union(root, ident_ids, ctx, union_layout),
|
||||
Layout::Boxed(inner_layout) => eq_boxed(root, ident_ids, ctx, inner_layout),
|
||||
Layout::LambdaSet(_) => unreachable!("`==` is not defined on functions"),
|
||||
Layout::RecursivePointer => {
|
||||
unreachable!(
|
||||
|
@ -528,6 +529,15 @@ fn eq_tag_fields<'a>(
|
|||
stmt
|
||||
}
|
||||
|
||||
fn eq_boxed<'a>(
|
||||
_root: &mut CodeGenHelp<'a>,
|
||||
_ident_ids: &mut IdentIds,
|
||||
_ctx: &mut Context<'a>,
|
||||
_inner_layout: &'a Layout<'a>,
|
||||
) -> Stmt<'a> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// List equality
|
||||
/// We can't use `ListGetUnsafe` because it increments the refcount, and we don't want that.
|
||||
/// Another way to dereference a heap pointer is to use `Expr::UnionAtIndex`.
|
||||
|
|
|
@ -378,7 +378,13 @@ impl<'a> CodeGenHelp<'a> {
|
|||
Layout::Union(UnionLayout::NonRecursive(new_tags.into_bump_slice()))
|
||||
}
|
||||
|
||||
Layout::Union(_) => layout,
|
||||
Layout::Union(_) => {
|
||||
// we always fully unroll recursive types. That means tha when we find a
|
||||
// recursive tag union we can replace it with the layout
|
||||
layout
|
||||
}
|
||||
|
||||
Layout::Boxed(inner) => self.replace_rec_ptr(ctx, *inner),
|
||||
|
||||
Layout::LambdaSet(lambda_set) => {
|
||||
self.replace_rec_ptr(ctx, lambda_set.runtime_representation())
|
||||
|
@ -476,5 +482,7 @@ fn layout_needs_helper_proc(layout: &Layout, op: HelperOp) -> bool {
|
|||
Layout::Union(_) => true,
|
||||
|
||||
Layout::LambdaSet(_) | Layout::RecursivePointer => false,
|
||||
|
||||
Layout::Boxed(_) => true,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -122,6 +122,7 @@ pub fn refcount_generic<'a>(
|
|||
refcount_generic(root, ident_ids, ctx, runtime_layout, structure)
|
||||
}
|
||||
Layout::RecursivePointer => rc_todo(),
|
||||
Layout::Boxed(_) => rc_todo(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -155,6 +156,7 @@ pub fn is_rc_implemented_yet(layout: &Layout) -> bool {
|
|||
is_rc_implemented_yet(&lambda_set.runtime_representation())
|
||||
}
|
||||
Layout::RecursivePointer => true,
|
||||
Layout::Boxed(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -114,6 +114,10 @@ pub fn occurring_variables_expr(expr: &Expr<'_>, result: &mut MutSet<Symbol>) {
|
|||
result.insert(*x);
|
||||
}
|
||||
|
||||
ExprBox { symbol } | ExprUnbox { symbol } => {
|
||||
result.insert(*symbol);
|
||||
}
|
||||
|
||||
EmptyArray | RuntimeErrorFunction(_) | Literal(_) => {}
|
||||
|
||||
GetTagId {
|
||||
|
@ -756,6 +760,28 @@ impl<'a> Context<'a> {
|
|||
self.arena.alloc(Stmt::Let(z, v, l, b))
|
||||
}
|
||||
|
||||
ExprBox { symbol: x } => {
|
||||
// mimics Tag
|
||||
self.add_inc_before_consume_all(
|
||||
&[x],
|
||||
self.arena.alloc(Stmt::Let(z, v, l, b)),
|
||||
b_live_vars,
|
||||
)
|
||||
}
|
||||
|
||||
ExprUnbox { symbol: x } => {
|
||||
// mimics UnionAtIndex
|
||||
let b = self.add_dec_if_needed(x, b, b_live_vars);
|
||||
let info_x = self.get_var_info(x);
|
||||
let b = if info_x.consume {
|
||||
self.add_inc(z, 1, b)
|
||||
} else {
|
||||
b
|
||||
};
|
||||
|
||||
self.arena.alloc(Stmt::Let(z, v, l, b))
|
||||
}
|
||||
|
||||
EmptyArray | Literal(_) | Reset { .. } | RuntimeErrorFunction(_) => {
|
||||
// EmptyArray is always stack-allocated
|
||||
// function pointers are persistent
|
||||
|
|
|
@ -112,6 +112,11 @@ pub struct PartialProcs<'a> {
|
|||
/// maps a function name (symbol) to an index
|
||||
symbols: Vec<'a, Symbol>,
|
||||
|
||||
/// An entry (a, b) means `a` directly references the lambda value of `b`,
|
||||
/// i.e. this came from a `let a = b in ...` where `b` was defined as a
|
||||
/// lambda earlier.
|
||||
references: Vec<'a, (Symbol, Symbol)>,
|
||||
|
||||
partial_procs: Vec<'a, PartialProc<'a>>,
|
||||
}
|
||||
|
||||
|
@ -119,6 +124,7 @@ impl<'a> PartialProcs<'a> {
|
|||
fn new_in(arena: &'a Bump) -> Self {
|
||||
Self {
|
||||
symbols: Vec::new_in(arena),
|
||||
references: Vec::new_in(arena),
|
||||
partial_procs: Vec::new_in(arena),
|
||||
}
|
||||
}
|
||||
|
@ -126,7 +132,16 @@ impl<'a> PartialProcs<'a> {
|
|||
self.symbol_to_id(symbol).is_some()
|
||||
}
|
||||
|
||||
fn symbol_to_id(&self, symbol: Symbol) -> Option<PartialProcId> {
|
||||
fn symbol_to_id(&self, mut symbol: Symbol) -> Option<PartialProcId> {
|
||||
while let Some(real_symbol) = self
|
||||
.references
|
||||
.iter()
|
||||
.find(|(alias, _)| *alias == symbol)
|
||||
.map(|(_, real)| real)
|
||||
{
|
||||
symbol = *real_symbol;
|
||||
}
|
||||
|
||||
self.symbols
|
||||
.iter()
|
||||
.position(|s| *s == symbol)
|
||||
|
@ -157,6 +172,21 @@ impl<'a> PartialProcs<'a> {
|
|||
|
||||
id
|
||||
}
|
||||
|
||||
pub fn insert_alias(&mut self, alias: Symbol, real_symbol: Symbol) {
|
||||
debug_assert!(
|
||||
!self.contains_key(alias),
|
||||
"{:?} is inserted as a partial proc twice: that's a bug!",
|
||||
alias,
|
||||
);
|
||||
debug_assert!(
|
||||
self.contains_key(real_symbol),
|
||||
"{:?} is not a partial proc or another alias: that's a bug!",
|
||||
real_symbol,
|
||||
);
|
||||
|
||||
self.references.push((alias, real_symbol));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
@ -1505,6 +1535,14 @@ pub enum Expr<'a> {
|
|||
},
|
||||
EmptyArray,
|
||||
|
||||
ExprBox {
|
||||
symbol: Symbol,
|
||||
},
|
||||
|
||||
ExprUnbox {
|
||||
symbol: Symbol,
|
||||
},
|
||||
|
||||
Reuse {
|
||||
symbol: Symbol,
|
||||
update_tag_id: bool,
|
||||
|
@ -1682,6 +1720,10 @@ impl<'a> Expr<'a> {
|
|||
.text("GetTagId ")
|
||||
.append(symbol_to_doc(alloc, *structure)),
|
||||
|
||||
ExprBox { symbol, .. } => alloc.text("Box ").append(symbol_to_doc(alloc, *symbol)),
|
||||
|
||||
ExprUnbox { symbol, .. } => alloc.text("Unbox ").append(symbol_to_doc(alloc, *symbol)),
|
||||
|
||||
UnionAtIndex {
|
||||
tag_id,
|
||||
structure,
|
||||
|
@ -4615,6 +4657,18 @@ pub fn with_hole<'a>(
|
|||
let xs = arg_symbols[0];
|
||||
match_on_closure_argument!(ListFindUnsafe, [xs])
|
||||
}
|
||||
BoxExpr => {
|
||||
debug_assert_eq!(arg_symbols.len(), 1);
|
||||
let x = arg_symbols[0];
|
||||
|
||||
Stmt::Let(assigned, Expr::ExprBox { symbol: x }, layout, hole)
|
||||
}
|
||||
UnboxExpr => {
|
||||
debug_assert_eq!(arg_symbols.len(), 1);
|
||||
let x = arg_symbols[0];
|
||||
|
||||
Stmt::Let(assigned, Expr::ExprUnbox { symbol: x }, layout, hole)
|
||||
}
|
||||
_ => {
|
||||
let call = self::Call {
|
||||
call_type: CallType::LowLevel {
|
||||
|
@ -6179,6 +6233,14 @@ fn substitute_in_expr<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
ExprBox { symbol } => {
|
||||
substitute(subs, *symbol).map(|new_symbol| ExprBox { symbol: new_symbol })
|
||||
}
|
||||
|
||||
ExprUnbox { symbol } => {
|
||||
substitute(subs, *symbol).map(|new_symbol| ExprUnbox { symbol: new_symbol })
|
||||
}
|
||||
|
||||
StructAtIndex {
|
||||
index,
|
||||
structure,
|
||||
|
@ -6660,10 +6722,10 @@ where
|
|||
}
|
||||
|
||||
// Otherwise we're dealing with an alias to something that doesn't need to be specialized, or
|
||||
// whose usages will already be specialized in the rest of the program. Let's just build the
|
||||
// rest of the program now to get our hole.
|
||||
let mut result = build_rest(env, procs, layout_cache);
|
||||
// whose usages will already be specialized in the rest of the program.
|
||||
if procs.is_imported_module_thunk(right) {
|
||||
let result = build_rest(env, procs, layout_cache);
|
||||
|
||||
// if this is an imported symbol, then we must make sure it is
|
||||
// specialized, and wrap the original in a function pointer.
|
||||
add_needed_external(procs, env, variable, right);
|
||||
|
@ -6673,25 +6735,25 @@ where
|
|||
|
||||
force_thunk(env, right, layout, left, env.arena.alloc(result))
|
||||
} else if env.is_imported_symbol(right) {
|
||||
let result = build_rest(env, procs, layout_cache);
|
||||
|
||||
// if this is an imported symbol, then we must make sure it is
|
||||
// specialized, and wrap the original in a function pointer.
|
||||
add_needed_external(procs, env, variable, right);
|
||||
|
||||
// then we must construct its closure; since imported symbols have no closure, we use the empty struct
|
||||
let_empty_struct(left, env.arena.alloc(result))
|
||||
} else if procs.partial_procs.contains_key(right) {
|
||||
// This is an alias to a function defined in this module.
|
||||
// Attach the alias, then build the rest of the module, so that we reference and specialize
|
||||
// the correct proc.
|
||||
procs.partial_procs.insert_alias(left, right);
|
||||
build_rest(env, procs, layout_cache)
|
||||
} else {
|
||||
// This should be a fully specialized value. Replace the alias with the original symbol.
|
||||
let mut result = build_rest(env, procs, layout_cache);
|
||||
substitute_in_exprs(env.arena, &mut result, left, right);
|
||||
|
||||
// if the substituted variable is a function, make sure we specialize it
|
||||
reuse_function_symbol(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
Some(variable),
|
||||
right,
|
||||
result,
|
||||
right,
|
||||
)
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8434,7 +8496,7 @@ pub fn num_argument_to_int_or_float(
|
|||
debug_assert!(args.len() == 1);
|
||||
|
||||
// Recurse on the second argument
|
||||
let var = subs[args.variables().into_iter().next().unwrap()];
|
||||
let var = subs[args.all_variables().into_iter().next().unwrap()];
|
||||
num_argument_to_int_or_float(subs, target_info, var, false)
|
||||
}
|
||||
|
||||
|
@ -8452,7 +8514,7 @@ pub fn num_argument_to_int_or_float(
|
|||
debug_assert!(args.len() == 1);
|
||||
|
||||
// Recurse on the second argument
|
||||
let var = subs[args.variables().into_iter().next().unwrap()];
|
||||
let var = subs[args.all_variables().into_iter().next().unwrap()];
|
||||
num_argument_to_int_or_float(subs, target_info, var, true)
|
||||
}
|
||||
|
||||
|
|
|
@ -250,6 +250,7 @@ pub enum Layout<'a> {
|
|||
field_order_hash: FieldOrderHash,
|
||||
field_layouts: &'a [Layout<'a>],
|
||||
},
|
||||
Boxed(&'a Layout<'a>),
|
||||
Union(UnionLayout<'a>),
|
||||
LambdaSet(LambdaSet<'a>),
|
||||
RecursivePointer,
|
||||
|
@ -997,7 +998,7 @@ impl<'a> Layout<'a> {
|
|||
}
|
||||
}
|
||||
LambdaSet(lambda_set) => lambda_set.runtime_representation().safe_to_memcpy(),
|
||||
RecursivePointer => {
|
||||
Boxed(_) | RecursivePointer => {
|
||||
// We cannot memcpy pointers, because then we would have the same pointer in multiple places!
|
||||
false
|
||||
}
|
||||
|
@ -1066,6 +1067,7 @@ impl<'a> Layout<'a> {
|
|||
.runtime_representation()
|
||||
.stack_size_without_alignment(target_info),
|
||||
RecursivePointer => target_info.ptr_width() as u32,
|
||||
Boxed(_) => target_info.ptr_width() as u32,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1114,6 +1116,7 @@ impl<'a> Layout<'a> {
|
|||
.alignment_bytes(target_info),
|
||||
Layout::Builtin(builtin) => builtin.alignment_bytes(target_info),
|
||||
Layout::RecursivePointer => target_info.ptr_width() as u32,
|
||||
Layout::Boxed(_) => target_info.ptr_width() as u32,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1126,6 +1129,7 @@ impl<'a> Layout<'a> {
|
|||
.runtime_representation()
|
||||
.allocation_alignment_bytes(target_info),
|
||||
Layout::RecursivePointer => unreachable!("should be looked up to get an actual layout"),
|
||||
Layout::Boxed(inner) => inner.allocation_alignment_bytes(target_info),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1172,6 +1176,7 @@ impl<'a> Layout<'a> {
|
|||
}
|
||||
LambdaSet(lambda_set) => lambda_set.runtime_representation().contains_refcounted(),
|
||||
RecursivePointer => true,
|
||||
Boxed(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1196,6 +1201,10 @@ impl<'a> Layout<'a> {
|
|||
Union(union_layout) => union_layout.to_doc(alloc, parens),
|
||||
LambdaSet(lambda_set) => lambda_set.runtime_representation().to_doc(alloc, parens),
|
||||
RecursivePointer => alloc.text("*self"),
|
||||
Boxed(inner) => alloc
|
||||
.text("Boxed(")
|
||||
.append(inner.to_doc(alloc, parens))
|
||||
.append(")"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1617,6 +1626,15 @@ fn layout_from_flat_type<'a>(
|
|||
Symbol::LIST_LIST => list_layout_from_elem(env, args[0]),
|
||||
Symbol::DICT_DICT => dict_layout_from_key_value(env, args[0], args[1]),
|
||||
Symbol::SET_SET => dict_layout_from_key_value(env, args[0], Variable::EMPTY_RECORD),
|
||||
Symbol::BOX_BOX_TYPE => {
|
||||
// Num.Num should only ever have 1 argument, e.g. Num.Num Int.Integer
|
||||
debug_assert_eq!(args.len(), 1);
|
||||
|
||||
let inner_var = args[0];
|
||||
let inner_layout = Layout::from_var(env, inner_var)?;
|
||||
|
||||
Ok(Layout::Boxed(env.arena.alloc(inner_layout)))
|
||||
}
|
||||
_ => {
|
||||
panic!(
|
||||
"TODO layout_from_flat_type for Apply({:?}, {:?})",
|
||||
|
@ -1976,7 +1994,15 @@ pub fn union_sorted_tags<'a>(
|
|||
|
||||
let mut tags_vec = std::vec::Vec::new();
|
||||
let result = match roc_types::pretty_print::chase_ext_tag_union(subs, var, &mut tags_vec) {
|
||||
Ok(()) | Err((_, Content::FlexVar(_))) | Err((_, Content::RecursionVar { .. })) => {
|
||||
Ok(())
|
||||
// Admit type variables in the extension for now. This may come from things that never got
|
||||
// monomorphized, like in
|
||||
// x : [ A ]*
|
||||
// x = A
|
||||
// x
|
||||
// In such cases it's fine to drop the variable. We may be proven wrong in the future...
|
||||
| Err((_, Content::FlexVar(_) | Content::RigidVar(_)))
|
||||
| Err((_, Content::RecursionVar { .. })) => {
|
||||
let opt_rec_var = get_recursion_var(subs, var);
|
||||
union_sorted_tags_help(arena, tags_vec, opt_rec_var, subs, target_info)
|
||||
}
|
||||
|
@ -2574,7 +2600,7 @@ pub fn ext_var_is_empty_tag_union(subs: &Subs, ext_var: Variable) -> bool {
|
|||
// the ext_var is empty
|
||||
let mut ext_fields = std::vec::Vec::new();
|
||||
match roc_types::pretty_print::chase_ext_tag_union(subs, ext_var, &mut ext_fields) {
|
||||
Ok(()) | Err((_, Content::FlexVar(_))) => ext_fields.is_empty(),
|
||||
Ok(()) | Err((_, Content::FlexVar(_) | Content::RigidVar(_))) => ext_fields.is_empty(),
|
||||
Err(content) => panic!("invalid content in ext_var: {:?}", content),
|
||||
}
|
||||
}
|
||||
|
@ -2652,7 +2678,7 @@ fn unwrap_num_tag<'a>(
|
|||
Content::Alias(Symbol::NUM_INTEGER, args, _, _) => {
|
||||
debug_assert!(args.len() == 1);
|
||||
|
||||
let precision_var = subs[args.variables().into_iter().next().unwrap()];
|
||||
let precision_var = subs[args.all_variables().into_iter().next().unwrap()];
|
||||
|
||||
let precision = subs.get_content_without_compacting(precision_var);
|
||||
|
||||
|
@ -2688,7 +2714,7 @@ fn unwrap_num_tag<'a>(
|
|||
Content::Alias(Symbol::NUM_FLOATINGPOINT, args, _, _) => {
|
||||
debug_assert!(args.len() == 1);
|
||||
|
||||
let precision_var = subs[args.variables().into_iter().next().unwrap()];
|
||||
let precision_var = subs[args.all_variables().into_iter().next().unwrap()];
|
||||
|
||||
let precision = subs.get_content_without_compacting(precision_var);
|
||||
|
||||
|
|
|
@ -239,6 +239,12 @@ fn insert_reset<'a>(
|
|||
stack.push((symbol, expr, expr_layout));
|
||||
stmt = rest;
|
||||
}
|
||||
|
||||
ExprBox { .. } | ExprUnbox { .. } => {
|
||||
// TODO
|
||||
break;
|
||||
}
|
||||
|
||||
Literal(_)
|
||||
| Call(_)
|
||||
| Tag { .. }
|
||||
|
@ -620,6 +626,8 @@ fn has_live_var_expr<'a>(expr: &'a Expr<'a>, needle: Symbol) -> bool {
|
|||
symbol, arguments, ..
|
||||
} => needle == *symbol || arguments.iter().any(|s| *s == needle),
|
||||
Expr::Reset { symbol, .. } => needle == *symbol,
|
||||
Expr::ExprBox { symbol, .. } => needle == *symbol,
|
||||
Expr::ExprUnbox { symbol, .. } => needle == *symbol,
|
||||
Expr::RuntimeErrorFunction(_) => false,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,20 @@ pub enum Spaced<'a, T> {
|
|||
SpaceAfter(&'a Spaced<'a, T>, &'a [CommentOrNewline<'a>]),
|
||||
}
|
||||
|
||||
impl<'a, T> Spaced<'a, T> {
|
||||
/// A `Spaced` is multiline if it has newlines or comments before or after the item, since
|
||||
/// comments induce newlines!
|
||||
pub fn is_multiline(&self) -> bool {
|
||||
match self {
|
||||
Spaced::Item(_) => false,
|
||||
Spaced::SpaceBefore(_, spaces) | Spaced::SpaceAfter(_, spaces) => {
|
||||
debug_assert!(!spaces.is_empty());
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Debug> Debug for Spaced<'a, T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
|
@ -248,6 +262,22 @@ impl<'a> TypeHeader<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// The `has` keyword associated with ability definitions.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Has<'a> {
|
||||
Has,
|
||||
SpaceBefore(&'a Has<'a>, &'a [CommentOrNewline<'a>]),
|
||||
SpaceAfter(&'a Has<'a>, &'a [CommentOrNewline<'a>]),
|
||||
}
|
||||
|
||||
/// An ability demand is a value defining the ability; for example `hash : a -> U64 | a has Hash`
|
||||
/// for a `Hash` ability.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct AbilityDemand<'a> {
|
||||
pub name: Loc<Spaced<'a, &'a str>>,
|
||||
pub typ: Loc<TypeAnnotation<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Def<'a> {
|
||||
// TODO in canonicalization, validate the pattern; only certain patterns
|
||||
|
@ -269,6 +299,15 @@ pub enum Def<'a> {
|
|||
typ: Loc<TypeAnnotation<'a>>,
|
||||
},
|
||||
|
||||
/// An ability definition. E.g.
|
||||
/// Hash has
|
||||
/// hash : a -> U64 | a has Hash
|
||||
Ability {
|
||||
header: TypeHeader<'a>,
|
||||
loc_has: Loc<Has<'a>>,
|
||||
demands: &'a [AbilityDemand<'a>],
|
||||
},
|
||||
|
||||
// TODO in canonicalization, check to see if there are any newlines after the
|
||||
// annotation; if not, and if it's followed by a Body, then the annotation
|
||||
// applies to that expr! (TODO: verify that the pattern for both annotation and body match.)
|
||||
|
@ -304,6 +343,13 @@ impl<'a> Def<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct HasClause<'a> {
|
||||
pub var: Loc<Spaced<'a, &'a str>>,
|
||||
// Should always be a zero-argument `Apply`; we'll check this in canonicalization
|
||||
pub ability: Loc<TypeAnnotation<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum TypeAnnotation<'a> {
|
||||
/// A function. The types of its arguments, then the type of its return value.
|
||||
|
@ -343,6 +389,9 @@ pub enum TypeAnnotation<'a> {
|
|||
/// The `*` type variable, e.g. in (List *)
|
||||
Wildcard,
|
||||
|
||||
/// A "where" clause demanding abilities designated by a `|`, e.g. `a -> U64 | a has Hash`
|
||||
Where(&'a Loc<TypeAnnotation<'a>>, &'a [Loc<HasClause<'a>>]),
|
||||
|
||||
// We preserve this for the formatter; canonicalization ignores it.
|
||||
SpaceBefore(&'a TypeAnnotation<'a>, &'a [CommentOrNewline<'a>]),
|
||||
SpaceAfter(&'a TypeAnnotation<'a>, &'a [CommentOrNewline<'a>]),
|
||||
|
@ -814,6 +863,15 @@ impl<'a> Spaceable<'a> for Def<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> Spaceable<'a> for Has<'a> {
|
||||
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
|
||||
Has::SpaceBefore(self, spaces)
|
||||
}
|
||||
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
|
||||
Has::SpaceAfter(self, spaces)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Expr<'a> {
|
||||
pub fn loc_ref(&'a self, region: Region) -> Loc<&'a Self> {
|
||||
Loc {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::ast::{
|
||||
AssignedField, Collection, CommentOrNewline, Def, Expr, ExtractSpaces, Pattern, Spaceable,
|
||||
AssignedField, Collection, CommentOrNewline, Def, Expr, ExtractSpaces, Has, Pattern, Spaceable,
|
||||
TypeAnnotation, TypeHeader,
|
||||
};
|
||||
use crate::blankspace::{space0_after_e, space0_around_ee, space0_before_e, space0_e};
|
||||
|
@ -1071,6 +1071,187 @@ fn finish_parsing_alias_or_opaque<'a>(
|
|||
parse_defs_expr(options, start_column, def_state, arena, state)
|
||||
}
|
||||
|
||||
mod ability {
|
||||
use super::*;
|
||||
use crate::{
|
||||
ast::{AbilityDemand, Spaceable, Spaced},
|
||||
parser::EAbility,
|
||||
};
|
||||
|
||||
/// Parses a single ability demand line; see `parse_demand`.
|
||||
fn parse_demand_help<'a>(
|
||||
start_column: u32,
|
||||
) -> impl Parser<'a, AbilityDemand<'a>, EAbility<'a>> {
|
||||
map!(
|
||||
and!(
|
||||
specialize(|_, pos| EAbility::DemandName(pos), loc!(lowercase_ident())),
|
||||
skip_first!(
|
||||
and!(
|
||||
// TODO: do we get anything from picking up spaces here?
|
||||
space0_e(start_column, EAbility::DemandName),
|
||||
word1(b':', EAbility::DemandColon)
|
||||
),
|
||||
specialize(
|
||||
EAbility::Type,
|
||||
// Require the type to be more indented than the name
|
||||
type_annotation::located_help(start_column + 1, true)
|
||||
)
|
||||
)
|
||||
),
|
||||
|(name, typ): (Loc<&'a str>, Loc<TypeAnnotation<'a>>)| {
|
||||
AbilityDemand {
|
||||
name: name.map_owned(Spaced::Item),
|
||||
typ,
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
pub enum IndentLevel {
|
||||
PendingMin(u32),
|
||||
Exact(u32),
|
||||
}
|
||||
|
||||
/// Parses an ability demand like `hash : a -> U64 | a has Hash`, in the context of a larger
|
||||
/// ability definition.
|
||||
/// This is basically the same as parsing a free-floating annotation, but with stricter rules.
|
||||
pub fn parse_demand<'a>(
|
||||
indent: IndentLevel,
|
||||
) -> impl Parser<'a, (u32, AbilityDemand<'a>), EAbility<'a>> {
|
||||
move |arena, state: State<'a>| {
|
||||
let initial = state.clone();
|
||||
|
||||
// Put no restrictions on the indent after the spaces; we'll check it manually.
|
||||
match space0_e(0, EAbility::DemandName).parse(arena, state) {
|
||||
Err((MadeProgress, fail, _)) => Err((NoProgress, fail, initial)),
|
||||
Err((NoProgress, fail, _)) => Err((NoProgress, fail, initial)),
|
||||
|
||||
Ok((_progress, spaces, state)) => {
|
||||
match indent {
|
||||
IndentLevel::PendingMin(min_indent) if state.column() < min_indent => {
|
||||
let indent_difference = state.column() as i32 - min_indent as i32;
|
||||
Err((
|
||||
MadeProgress,
|
||||
EAbility::DemandAlignment(indent_difference, state.pos()),
|
||||
initial,
|
||||
))
|
||||
}
|
||||
IndentLevel::Exact(wanted) if state.column() < wanted => {
|
||||
// This demand is not indented correctly
|
||||
let indent_difference = state.column() as i32 - wanted as i32;
|
||||
Err((
|
||||
// Rollback because the deindent may be because there is a next
|
||||
// expression
|
||||
NoProgress,
|
||||
EAbility::DemandAlignment(indent_difference, state.pos()),
|
||||
initial,
|
||||
))
|
||||
}
|
||||
IndentLevel::Exact(wanted) if state.column() > wanted => {
|
||||
// This demand is not indented correctly
|
||||
let indent_difference = state.column() as i32 - wanted as i32;
|
||||
Err((
|
||||
MadeProgress,
|
||||
EAbility::DemandAlignment(indent_difference, state.pos()),
|
||||
initial,
|
||||
))
|
||||
}
|
||||
_ => {
|
||||
let indent_column = state.column();
|
||||
|
||||
let parser = parse_demand_help(indent_column);
|
||||
|
||||
match parser.parse(arena, state) {
|
||||
Err((MadeProgress, fail, state)) => {
|
||||
Err((MadeProgress, fail, state))
|
||||
}
|
||||
Err((NoProgress, fail, _)) => {
|
||||
// We made progress relative to the entire ability definition,
|
||||
// so this is an error.
|
||||
Err((MadeProgress, fail, initial))
|
||||
}
|
||||
|
||||
Ok((_, mut demand, state)) => {
|
||||
// Tag spaces onto the parsed demand name
|
||||
if !spaces.is_empty() {
|
||||
demand.name = arena
|
||||
.alloc(demand.name.value)
|
||||
.with_spaces_before(spaces, demand.name.region);
|
||||
}
|
||||
|
||||
Ok((MadeProgress, (indent_column, demand), state))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn finish_parsing_ability<'a>(
|
||||
start_column: u32,
|
||||
options: ExprParseOptions,
|
||||
name: Loc<&'a str>,
|
||||
args: &'a [Loc<Pattern<'a>>],
|
||||
loc_has: Loc<Has<'a>>,
|
||||
arena: &'a Bump,
|
||||
state: State<'a>,
|
||||
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
|
||||
let mut demands = Vec::with_capacity_in(2, arena);
|
||||
|
||||
let min_indent_for_demand = start_column + 1;
|
||||
|
||||
// Parse the first demand. This will determine the indentation level all the
|
||||
// other demands must observe.
|
||||
let (_, (demand_indent_level, first_demand), mut state) =
|
||||
ability::parse_demand(ability::IndentLevel::PendingMin(min_indent_for_demand))
|
||||
.parse(arena, state)
|
||||
.map_err(|(progress, err, state)| {
|
||||
(progress, EExpr::Ability(err, state.pos()), state)
|
||||
})?;
|
||||
demands.push(first_demand);
|
||||
|
||||
let demand_indent = ability::IndentLevel::Exact(demand_indent_level);
|
||||
let demand_parser = ability::parse_demand(demand_indent);
|
||||
|
||||
loop {
|
||||
match demand_parser.parse(arena, state.clone()) {
|
||||
Ok((_, (_indent, demand), next_state)) => {
|
||||
state = next_state;
|
||||
demands.push(demand);
|
||||
}
|
||||
Err((MadeProgress, problem, old_state)) => {
|
||||
return Err((
|
||||
MadeProgress,
|
||||
EExpr::Ability(problem, old_state.pos()),
|
||||
old_state,
|
||||
));
|
||||
}
|
||||
Err((NoProgress, _, old_state)) => {
|
||||
state = old_state;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let def_region = Region::span_across(&name.region, &demands.last().unwrap().typ.region);
|
||||
let def = Def::Ability {
|
||||
header: TypeHeader { name, vars: args },
|
||||
loc_has,
|
||||
demands: demands.into_bump_slice(),
|
||||
};
|
||||
let loc_def = &*(arena.alloc(Loc::at(def_region, def)));
|
||||
|
||||
let def_state = DefState {
|
||||
defs: bumpalo::vec![in arena; loc_def],
|
||||
spaces_after: &[],
|
||||
};
|
||||
|
||||
parse_defs_expr(options, start_column, def_state, arena, state)
|
||||
}
|
||||
|
||||
fn parse_expr_operator<'a>(
|
||||
min_indent: u32,
|
||||
options: ExprParseOptions,
|
||||
|
@ -1290,6 +1471,62 @@ fn parse_expr_end<'a>(
|
|||
|
||||
match parser.parse(arena, state.clone()) {
|
||||
Err((MadeProgress, f, s)) => Err((MadeProgress, f, s)),
|
||||
Ok((
|
||||
_,
|
||||
has @ Loc {
|
||||
value:
|
||||
Expr::Var {
|
||||
module_name: "",
|
||||
ident: "has",
|
||||
},
|
||||
..
|
||||
},
|
||||
state,
|
||||
)) if matches!(expr_state.expr.value, Expr::GlobalTag(..)) => {
|
||||
// This is an ability definition, `Ability arg1 ... has ...`.
|
||||
|
||||
let name = expr_state.expr.map_owned(|e| match e {
|
||||
Expr::GlobalTag(name) => name,
|
||||
_ => unreachable!(),
|
||||
});
|
||||
|
||||
let mut arguments = Vec::with_capacity_in(expr_state.arguments.len(), arena);
|
||||
for argument in expr_state.arguments {
|
||||
match expr_to_pattern_help(arena, &argument.value) {
|
||||
Ok(good) => {
|
||||
arguments.push(Loc::at(argument.region, good));
|
||||
}
|
||||
Err(_) => {
|
||||
let start = argument.region.start();
|
||||
let err = &*arena.alloc(EPattern::Start(start));
|
||||
return Err((
|
||||
MadeProgress,
|
||||
EExpr::Pattern(err, argument.region.start()),
|
||||
state,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Attach any spaces to the `has` keyword
|
||||
let has = if !expr_state.spaces_after.is_empty() {
|
||||
arena
|
||||
.alloc(Has::Has)
|
||||
.with_spaces_before(expr_state.spaces_after, has.region)
|
||||
} else {
|
||||
Loc::at(has.region, Has::Has)
|
||||
};
|
||||
|
||||
finish_parsing_ability(
|
||||
start_column,
|
||||
options,
|
||||
name,
|
||||
arguments.into_bump_slice(),
|
||||
has,
|
||||
arena,
|
||||
state,
|
||||
)
|
||||
}
|
||||
Ok((_, mut arg, state)) => {
|
||||
let new_end = state.pos();
|
||||
|
||||
|
@ -1762,6 +1999,7 @@ mod when {
|
|||
((_, _), _),
|
||||
State<'a>,
|
||||
) = branch_alternatives(min_indent, options, None).parse(arena, state)?;
|
||||
|
||||
let original_indent = pattern_indent_level;
|
||||
|
||||
state.indent_column = pattern_indent_level;
|
||||
|
|
|
@ -104,6 +104,7 @@ impl_space_problem! {
|
|||
ETypeTagUnion<'a>,
|
||||
ETypedIdent<'a>,
|
||||
EWhen<'a>,
|
||||
EAbility<'a>,
|
||||
PInParens<'a>,
|
||||
PRecord<'a>
|
||||
}
|
||||
|
@ -331,6 +332,7 @@ pub enum EExpr<'a> {
|
|||
DefMissingFinalExpr2(&'a EExpr<'a>, Position),
|
||||
Type(EType<'a>, Position),
|
||||
Pattern(&'a EPattern<'a>, Position),
|
||||
Ability(EAbility<'a>, Position),
|
||||
IndentDefBody(Position),
|
||||
IndentEquals(Position),
|
||||
IndentAnnotation(Position),
|
||||
|
@ -472,6 +474,16 @@ pub enum EWhen<'a> {
|
|||
PatternAlignment(u32, Position),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EAbility<'a> {
|
||||
Space(BadInputError, Position),
|
||||
Type(EType<'a>, Position),
|
||||
|
||||
DemandAlignment(i32, Position),
|
||||
DemandName(Position),
|
||||
DemandColon(Position),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EIf<'a> {
|
||||
Space(BadInputError, Position),
|
||||
|
@ -564,6 +576,8 @@ pub enum EType<'a> {
|
|||
TStart(Position),
|
||||
TEnd(Position),
|
||||
TFunctionArgument(Position),
|
||||
TWhereBar(Position),
|
||||
THasClause(Position),
|
||||
///
|
||||
TIndentStart(Position),
|
||||
TIndentEnd(Position),
|
||||
|
@ -1406,6 +1420,32 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
pub fn word3<'a, ToError, E>(
|
||||
word_1: u8,
|
||||
word_2: u8,
|
||||
word_3: u8,
|
||||
to_error: ToError,
|
||||
) -> impl Parser<'a, (), E>
|
||||
where
|
||||
ToError: Fn(Position) -> E,
|
||||
E: 'a,
|
||||
{
|
||||
debug_assert_ne!(word_1, b'\n');
|
||||
debug_assert_ne!(word_2, b'\n');
|
||||
debug_assert_ne!(word_3, b'\n');
|
||||
|
||||
let needle = [word_1, word_2, word_3];
|
||||
|
||||
move |_arena: &'a Bump, state: State<'a>| {
|
||||
if state.bytes().starts_with(&needle) {
|
||||
let state = state.advance(3);
|
||||
Ok((MadeProgress, (), state))
|
||||
} else {
|
||||
Err((NoProgress, to_error(state.pos()), state))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! word1_check_indent {
|
||||
($word:expr, $word_problem:expr, $min_indent:expr, $indent_problem:expr) => {
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
use crate::ast::{AssignedField, Pattern, Tag, TypeAnnotation, TypeHeader};
|
||||
use crate::ast::{
|
||||
AssignedField, CommentOrNewline, HasClause, Pattern, Spaced, Tag, TypeAnnotation, TypeHeader,
|
||||
};
|
||||
use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
|
||||
use crate::ident::lowercase_ident;
|
||||
use crate::keyword;
|
||||
use crate::parser::then;
|
||||
use crate::parser::{
|
||||
allocated, backtrackable, optional, specialize, specialize_ref, word1, word2, EType,
|
||||
allocated, backtrackable, optional, specialize, specialize_ref, word1, word2, word3, EType,
|
||||
ETypeApply, ETypeInParens, ETypeInlineAlias, ETypeRecord, ETypeTagUnion, ParseResult, Parser,
|
||||
Progress::{self, *},
|
||||
};
|
||||
|
@ -240,7 +244,6 @@ where
|
|||
fn record_type_field<'a>(
|
||||
min_indent: u32,
|
||||
) -> impl Parser<'a, AssignedField<'a, TypeAnnotation<'a>>, ETypeRecord<'a>> {
|
||||
use crate::ident::lowercase_ident;
|
||||
use crate::parser::Either::*;
|
||||
use AssignedField::*;
|
||||
|
||||
|
@ -368,6 +371,75 @@ fn loc_applied_args_e<'a>(
|
|||
zero_or_more!(loc_applied_arg(min_indent))
|
||||
}
|
||||
|
||||
fn has_clause<'a>(min_indent: u32) -> impl Parser<'a, Loc<HasClause<'a>>, EType<'a>> {
|
||||
map!(
|
||||
// Suppose we are trying to parse "a has Hash"
|
||||
and!(
|
||||
space0_around_ee(
|
||||
// Parse "a", with appropriate spaces
|
||||
specialize(
|
||||
|_, pos| EType::TBadTypeVariable(pos),
|
||||
loc!(map!(lowercase_ident(), Spaced::Item)),
|
||||
),
|
||||
min_indent,
|
||||
EType::TIndentStart,
|
||||
EType::TIndentEnd
|
||||
),
|
||||
then(
|
||||
// Parse "has"; we don't care about this keyword
|
||||
word3(b'h', b'a', b's', EType::THasClause),
|
||||
// Parse "Hash"; this may be qualified from another module like "Hash.Hash"
|
||||
|arena, state, _progress, _output| {
|
||||
space0_before_e(
|
||||
specialize(EType::TApply, loc!(parse_concrete_type)),
|
||||
state.column() + 1,
|
||||
EType::TIndentStart,
|
||||
)
|
||||
.parse(arena, state)
|
||||
}
|
||||
)
|
||||
),
|
||||
|(var, ability): (Loc<Spaced<'a, &'a str>>, Loc<TypeAnnotation<'a>>)| {
|
||||
let region = Region::span_across(&var.region, &ability.region);
|
||||
let has_clause = HasClause { var, ability };
|
||||
Loc::at(region, has_clause)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// Parse a chain of `has` clauses, e.g. " | a has Hash, b has Eq".
|
||||
/// Returns the clauses and spaces before the starting "|", if there were any.
|
||||
fn has_clause_chain<'a>(
|
||||
min_indent: u32,
|
||||
) -> impl Parser<'a, (&'a [CommentOrNewline<'a>], &'a [Loc<HasClause<'a>>]), EType<'a>> {
|
||||
move |arena, state: State<'a>| {
|
||||
let (_, (spaces_before, ()), state) = and!(
|
||||
space0_e(min_indent, EType::TIndentStart),
|
||||
word1(b'|', EType::TWhereBar)
|
||||
)
|
||||
.parse(arena, state)?;
|
||||
|
||||
let min_demand_indent = state.column() + 1;
|
||||
// Parse the first clause (there must be one), then the rest
|
||||
let (_, first_clause, state) = has_clause(min_demand_indent).parse(arena, state)?;
|
||||
|
||||
let (_, mut clauses, state) = zero_or_more!(skip_first!(
|
||||
word1(b',', EType::THasClause),
|
||||
has_clause(min_demand_indent)
|
||||
))
|
||||
.parse(arena, state)?;
|
||||
|
||||
// Usually the number of clauses shouldn't be too large, so this is okay
|
||||
clauses.insert(0, first_clause);
|
||||
|
||||
Ok((
|
||||
MadeProgress,
|
||||
(spaces_before, clauses.into_bump_slice()),
|
||||
state,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn expression<'a>(
|
||||
min_indent: u32,
|
||||
is_trailing_comma_valid: bool,
|
||||
|
@ -404,7 +476,7 @@ fn expression<'a>(
|
|||
]
|
||||
.parse(arena, state.clone());
|
||||
|
||||
match result {
|
||||
let (progress, annot, state) = match result {
|
||||
Ok((p2, (rest, _dropped_spaces), state)) => {
|
||||
let (p3, return_type, state) =
|
||||
space0_before_e(term(min_indent), min_indent, EType::TIndentStart)
|
||||
|
@ -421,7 +493,7 @@ fn expression<'a>(
|
|||
value: TypeAnnotation::Function(output, arena.alloc(return_type)),
|
||||
};
|
||||
let progress = p1.or(p2).or(p3);
|
||||
Ok((progress, result, state))
|
||||
(progress, result, state)
|
||||
}
|
||||
Err(err) => {
|
||||
if !is_trailing_comma_valid {
|
||||
|
@ -442,7 +514,36 @@ fn expression<'a>(
|
|||
}
|
||||
|
||||
// We ran into trouble parsing the function bits; just return the single term
|
||||
Ok((p1, first, state))
|
||||
(p1, first, state)
|
||||
}
|
||||
};
|
||||
|
||||
// Finally, try to parse a where clause if there is one.
|
||||
// The where clause must be at least as deep as where the type annotation started.
|
||||
let min_where_clause_indent = min_indent;
|
||||
match has_clause_chain(min_where_clause_indent).parse(arena, state.clone()) {
|
||||
Ok((where_progress, (spaces_before, has_chain), state)) => {
|
||||
use crate::ast::Spaceable;
|
||||
|
||||
let region = Region::span_across(&annot.region, &has_chain.last().unwrap().region);
|
||||
let type_annot = if !spaces_before.is_empty() {
|
||||
let spaced = arena
|
||||
.alloc(annot.value)
|
||||
.with_spaces_before(spaces_before, annot.region);
|
||||
&*arena.alloc(spaced)
|
||||
} else {
|
||||
&*arena.alloc(annot)
|
||||
};
|
||||
let where_annot = TypeAnnotation::Where(type_annot, has_chain);
|
||||
Ok((
|
||||
where_progress.or(progress),
|
||||
Loc::at(region, where_annot),
|
||||
state,
|
||||
))
|
||||
}
|
||||
Err(_) => {
|
||||
// Ran into a problem parsing a where clause; don't suppose there is one.
|
||||
Ok((progress, annot, state))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
Defs(
|
||||
[
|
||||
@0-36 Ability {
|
||||
header: TypeHeader {
|
||||
name: @0-4 "Hash",
|
||||
vars: [],
|
||||
},
|
||||
loc_has: @5-8 Has,
|
||||
demands: [
|
||||
AbilityDemand {
|
||||
name: @11-15 SpaceBefore(
|
||||
"hash",
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
typ: @33-36 Function(
|
||||
[
|
||||
@18-19 BoundVariable(
|
||||
"a",
|
||||
),
|
||||
],
|
||||
@33-36 Apply(
|
||||
"",
|
||||
"U64",
|
||||
[],
|
||||
),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
@38-39 SpaceBefore(
|
||||
Num(
|
||||
"1",
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
)
|
|
@ -0,0 +1,5 @@
|
|||
Hash has
|
||||
hash : a
|
||||
-> U64
|
||||
|
||||
1
|
|
@ -0,0 +1,62 @@
|
|||
Defs(
|
||||
[
|
||||
@0-45 Ability {
|
||||
header: TypeHeader {
|
||||
name: @0-4 "Hash",
|
||||
vars: [],
|
||||
},
|
||||
loc_has: @5-8 Has,
|
||||
demands: [
|
||||
AbilityDemand {
|
||||
name: @11-15 SpaceBefore(
|
||||
"hash",
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
typ: @23-26 Function(
|
||||
[
|
||||
@18-19 BoundVariable(
|
||||
"a",
|
||||
),
|
||||
],
|
||||
@23-26 Apply(
|
||||
"",
|
||||
"U64",
|
||||
[],
|
||||
),
|
||||
),
|
||||
},
|
||||
AbilityDemand {
|
||||
name: @29-34 SpaceBefore(
|
||||
"hash2",
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
typ: @42-45 Function(
|
||||
[
|
||||
@37-38 BoundVariable(
|
||||
"a",
|
||||
),
|
||||
],
|
||||
@42-45 Apply(
|
||||
"",
|
||||
"U64",
|
||||
[],
|
||||
),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
@47-48 SpaceBefore(
|
||||
Num(
|
||||
"1",
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
)
|
|
@ -0,0 +1,5 @@
|
|||
Hash has
|
||||
hash : a -> U64
|
||||
hash2 : a -> U64
|
||||
|
||||
1
|
|
@ -0,0 +1,37 @@
|
|||
Defs(
|
||||
[
|
||||
@0-24 Ability {
|
||||
header: TypeHeader {
|
||||
name: @0-4 "Hash",
|
||||
vars: [],
|
||||
},
|
||||
loc_has: @5-8 Has,
|
||||
demands: [
|
||||
AbilityDemand {
|
||||
name: @9-13 "hash",
|
||||
typ: @21-24 Function(
|
||||
[
|
||||
@16-17 BoundVariable(
|
||||
"a",
|
||||
),
|
||||
],
|
||||
@21-24 Apply(
|
||||
"",
|
||||
"U64",
|
||||
[],
|
||||
),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
@26-27 SpaceBefore(
|
||||
Num(
|
||||
"1",
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
)
|
|
@ -0,0 +1,3 @@
|
|||
Hash has hash : a -> U64
|
||||
|
||||
1
|
|
@ -0,0 +1,48 @@
|
|||
Defs(
|
||||
[
|
||||
@0-27 Annotation(
|
||||
@0-1 Identifier(
|
||||
"f",
|
||||
),
|
||||
@15-27 Where(
|
||||
@15-16 Function(
|
||||
[
|
||||
@4-5 BoundVariable(
|
||||
"a",
|
||||
),
|
||||
],
|
||||
@15-16 Function(
|
||||
[
|
||||
@10-11 BoundVariable(
|
||||
"b",
|
||||
),
|
||||
],
|
||||
@15-16 BoundVariable(
|
||||
"c",
|
||||
),
|
||||
),
|
||||
),
|
||||
[
|
||||
@20-27 HasClause {
|
||||
var: @20-21 "a",
|
||||
ability: @26-27 Apply(
|
||||
"",
|
||||
"A",
|
||||
[],
|
||||
),
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
@29-30 SpaceBefore(
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "f",
|
||||
},
|
||||
[
|
||||
Newline,
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
)
|
|
@ -0,0 +1,3 @@
|
|||
f : a -> (b -> c) | a has A
|
||||
|
||||
f
|
|
@ -0,0 +1,64 @@
|
|||
Defs(
|
||||
[
|
||||
@0-48 Annotation(
|
||||
@0-1 Identifier(
|
||||
"f",
|
||||
),
|
||||
@15-48 Where(
|
||||
@15-16 Function(
|
||||
[
|
||||
@4-5 BoundVariable(
|
||||
"a",
|
||||
),
|
||||
],
|
||||
@15-16 Function(
|
||||
[
|
||||
@10-11 BoundVariable(
|
||||
"b",
|
||||
),
|
||||
],
|
||||
@15-16 BoundVariable(
|
||||
"c",
|
||||
),
|
||||
),
|
||||
),
|
||||
[
|
||||
@20-27 HasClause {
|
||||
var: @20-21 "a",
|
||||
ability: @26-27 Apply(
|
||||
"",
|
||||
"A",
|
||||
[],
|
||||
),
|
||||
},
|
||||
@29-37 HasClause {
|
||||
var: @29-30 "b",
|
||||
ability: @35-37 Apply(
|
||||
"",
|
||||
"Eq",
|
||||
[],
|
||||
),
|
||||
},
|
||||
@39-48 HasClause {
|
||||
var: @39-40 "c",
|
||||
ability: @45-48 Apply(
|
||||
"",
|
||||
"Ord",
|
||||
[],
|
||||
),
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
@50-51 SpaceBefore(
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "f",
|
||||
},
|
||||
[
|
||||
Newline,
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
)
|
|
@ -0,0 +1,3 @@
|
|||
f : a -> (b -> c) | a has A, b has Eq, c has Ord
|
||||
|
||||
f
|
|
@ -0,0 +1,79 @@
|
|||
Defs(
|
||||
[
|
||||
@0-67 Annotation(
|
||||
@0-1 Identifier(
|
||||
"f",
|
||||
),
|
||||
@15-67 Where(
|
||||
@15-16 SpaceBefore(
|
||||
Function(
|
||||
[
|
||||
@4-5 BoundVariable(
|
||||
"a",
|
||||
),
|
||||
],
|
||||
@15-16 Function(
|
||||
[
|
||||
@10-11 BoundVariable(
|
||||
"b",
|
||||
),
|
||||
],
|
||||
@15-16 BoundVariable(
|
||||
"c",
|
||||
),
|
||||
),
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
[
|
||||
@24-34 HasClause {
|
||||
var: @24-25 "a",
|
||||
ability: @30-34 Apply(
|
||||
"",
|
||||
"Hash",
|
||||
[],
|
||||
),
|
||||
},
|
||||
@42-50 HasClause {
|
||||
var: @42-43 SpaceBefore(
|
||||
"b",
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
ability: @48-50 Apply(
|
||||
"",
|
||||
"Eq",
|
||||
[],
|
||||
),
|
||||
},
|
||||
@58-67 HasClause {
|
||||
var: @58-59 SpaceBefore(
|
||||
"c",
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
ability: @64-67 Apply(
|
||||
"",
|
||||
"Ord",
|
||||
[],
|
||||
),
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
@69-70 SpaceBefore(
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "f",
|
||||
},
|
||||
[
|
||||
Newline,
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
)
|
|
@ -0,0 +1,6 @@
|
|||
f : a -> (b -> c)
|
||||
| a has Hash,
|
||||
b has Eq,
|
||||
c has Ord
|
||||
|
||||
f
|
|
@ -0,0 +1,34 @@
|
|||
Defs(
|
||||
[
|
||||
@0-15 Annotation(
|
||||
@0-1 Identifier(
|
||||
"f",
|
||||
),
|
||||
@4-15 Where(
|
||||
@4-5 BoundVariable(
|
||||
"a",
|
||||
),
|
||||
[
|
||||
@8-15 HasClause {
|
||||
var: @8-9 "a",
|
||||
ability: @14-15 Apply(
|
||||
"",
|
||||
"A",
|
||||
[],
|
||||
),
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
@17-18 SpaceBefore(
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "f",
|
||||
},
|
||||
[
|
||||
Newline,
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
)
|
|
@ -0,0 +1,3 @@
|
|||
f : a | a has A
|
||||
|
||||
f
|
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