Merge remote-tracking branch 'origin/trunk' into builtins-in-roc-delayed-alias

This commit is contained in:
Folkert 2022-03-18 21:25:52 +01:00
commit 4e1197165b
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
181 changed files with 9495 additions and 2273 deletions

View file

@ -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
View file

@ -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",

View file

@ -33,6 +33,7 @@ members = [
"ast",
"cli",
"code_markup",
"highlight",
"error_macros",
"reporting",
"repl_cli",

View file

@ -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
View file

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

View file

@ -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)

View file

@ -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!(),

View file

@ -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)
}

View file

@ -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(

View file

@ -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);

View file

@ -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());

View file

@ -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 {

View file

@ -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);
}
}

View file

@ -0,0 +1 @@
This is not a .roc file, and should be ignored by the formatter.

View file

@ -0,0 +1 @@
This is not a .roc file, and should be ignored by the formatter.

View 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")`

View file

@ -0,0 +1 @@
This is not a .roc file, and should be ignored by the formatter.

View file

@ -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" }

View file

@ -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,
)
}

View file

@ -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))
}

View file

@ -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,
)
}
};

View file

@ -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,
)
}

View file

@ -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)
}

View 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(),
}
}
}

View file

@ -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;

View file

@ -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()

View file

@ -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,

View file

@ -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,

View file

@ -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
}
}

View file

@ -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| {

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -120,6 +120,8 @@ interface Num
toU64Checked,
toU128,
toU128Checked,
toNat,
toNatChecked,
toFloat,
toStr
]

View file

@ -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
}

View file

@ -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)
}
};

View file

@ -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,

View file

@ -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
)
}
}
}
}

View file

@ -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
}
}
}

View file

@ -10,7 +10,7 @@ use roc_module::ident::TagName;
use roc_module::symbol::Symbol;
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::{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,
)
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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,

View file

@ -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,

View file

@ -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(),

View file

@ -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)
}
}

View file

@ -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))
}
}
}

View file

@ -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(

View file

@ -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,
];

View file

@ -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)
}

View file

@ -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,
);

View file

@ -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);
}
}

View file

@ -2,7 +2,7 @@ use crate::annotation::{Formattable, Newlines, Parens};
use crate::pattern::fmt_pattern;
use crate::spaces::{fmt_spaces, INDENT};
use crate::Buf;
use roc_parse::ast::{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);
}
}

View file

@ -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);
}

View file

@ -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]

View file

@ -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);

View file

@ -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(

View file

@ -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 =

View file

@ -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),

View file

@ -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

View file

@ -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>(

View file

@ -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

View file

@ -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));
}

View file

@ -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()

View file

@ -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);

View file

@ -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.
}
}

View file

@ -102,6 +102,7 @@ impl WasmLayout {
| NullableWrapped { .. }
| NullableUnwrapped { .. },
)
| Layout::Boxed(_)
| Layout::RecursivePointer => Self::Primitive(PTR_TYPE, PTR_SIZE),
}
}

View file

@ -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 {:?}",

View file

@ -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),

View file

@ -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
}

View file

@ -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(

View file

@ -124,6 +124,8 @@ pub enum LowLevel {
PtrCast,
RefCountInc,
RefCountDec,
BoxExpr,
UnboxExpr,
}
macro_rules! higher_order {

View file

@ -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)
}

View file

@ -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);
}

View file

@ -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`.

View file

@ -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,
}
}

View file

@ -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,
}
}

View file

@ -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

View file

@ -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)
}

View file

@ -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);

View file

@ -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,
}
}

View file

@ -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 {

View file

@ -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;

View file

@ -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) => {

View file

@ -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))
}
}
})

View file

@ -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,
],
),
)

View file

@ -0,0 +1,5 @@
Hash has
hash : a
-> U64
1

View file

@ -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,
],
),
)

View file

@ -0,0 +1,5 @@
Hash has
hash : a -> U64
hash2 : a -> U64
1

View file

@ -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,
],
),
)

View file

@ -0,0 +1,3 @@
Hash has hash : a -> U64
1

View file

@ -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,
],
),
)

View file

@ -0,0 +1,3 @@
f : a -> (b -> c) | a has A
f

View file

@ -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,
],
),
)

View file

@ -0,0 +1,3 @@
f : a -> (b -> c) | a has A, b has Eq, c has Ord
f

View file

@ -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,
],
),
)

View file

@ -0,0 +1,6 @@
f : a -> (b -> c)
| a has Hash,
b has Eq,
c has Ord
f

View file

@ -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,
],
),
)

View file

@ -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