diff --git a/.cargo/config b/.cargo/config index 7f9d89b452..584d158f1c 100644 --- a/.cargo/config +++ b/.cargo/config @@ -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"] diff --git a/Cargo.lock b/Cargo.lock index cf3ea9ff8b..74c0b69c0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index f02c317fbc..482c686b7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ members = [ "ast", "cli", "code_markup", + "highlight", "error_macros", "reporting", "repl_cli", diff --git a/Earthfile b/Earthfile index 170f06b9d6..a3189b62b6 100644 --- a/Earthfile +++ b/Earthfile @@ -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: diff --git a/FAQ.md b/FAQ.md index 24a02d9442..359c4ddb32 100644 --- a/FAQ.md +++ b/FAQ.md @@ -39,6 +39,90 @@ whether the feature should be in the language at all. In the case of this featur language doesn't have it; that way nobody has to learn (or spend time spreading the word) about the performance-boosting advice not to use it. +## Why can't functions be compared for equality using the `==` operator? + +Function equality has been proven to be undecidable in the general case because of the [halting problem](https://en.wikipedia.org/wiki/Halting_problem). +So while we as humans might be able to look at `\x -> x + 1` and `\x -> 1 + x` and know that they're equivalent, +in the general case it's not possible for a computer to do this reliably. + +There are some other potential ways to define function equality, but they all have problems. + +One way would be to have two functions be considered equal if their source code is equivalent. (Perhaps disregarding +comments and spaces.) This sounds reasonable, but it means that now revising a function to do +exactly the same thing as before (say, changing `\x -> x + 1` to `\x -> 1 + x`) can cause a bug in a +distant part of the code base. Defining function equality this way means that revising a function's internals +is no longer a safe, local operation - even if it gives all the same outputs for all the same inputs. + +Another option would be to define it using "reference equality." This is what JavaScript does, for example. +However, Roc does not use reference equality anywhere else in the language, and it would mean that (for example) +passing `\x -> x + 1` to a function compared to defining `fn = \x -> x + 1` elsewhere and then passing `fn` into +the function might give different answers. + +Both of these would make revising code riskier across the entire language, which is very undesirable. + +Another option would be to define that function equality always returns `False`. So both of these would evaluate +to `False`: + +* `(\x -> x + 1) == (\x -> 1 + x)` +* `(\x -> x + 1) == (\x -> x + 1)` + +This makes function equality effectively useless, while still technically allowing it. It has some other downsides: +* Now if you put a function inside a record, using `==` on that record will still type-check, but it will then return `False`. This could lead to bugs if you didn't realize you had accidentally put a function in there - for example, because you were actually storing a different type (e.g. an opaque type) and didn't realize it had a function inside it. +* If you put a function (or a value containing a function) into a `Dict` or `Set`, you'll never be able to get it out again. This is a common problem with [NaN](https://en.wikipedia.org/wiki/NaN), which is also defined not to be equal to itself. + +The first of these problems could be addressed by having function equality always return `True` instead of `False` (since that way it would not affect other fields' equality checks in a record), but that design has its own problems: +* Although function equality is still useless, `(\x -> x + 1) == (\x -> x)` returns `True`. Even if it didn't lead to bugs in practice, this would certainly be surprising and confusing to beginners. +* Now if you put several different functions into a `Dict` or `Set`, only one of them will be kept; the others will be discarded or overwritten. This could cause bugs if a value stored a function internally, and then other functions relied on that internal function for correctness. + +Each of these designs makes Roc a language that's some combination of more error-prone, more confusing, and more +brittle to change. Disallowing function equality at compile time eliminates all of these drawbacks. + +## Why doesn't Roc have a `Maybe` or `Option` or `Optional` type, or `null` or `nil` or `undefined`? + +It's common for programming languages to have a [null reference](https://en.wikipedia.org/wiki/Null_pointer) +(e.g. `null` in C, `nil` in Ruby, `None` in Python, or `undefined` in JavaScript). +The inventor of the null reference refers to it as his "[billion dollar mistake](https://en.wikipedia.org/wiki/Null_pointer#History)" because it "has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years." + +For this and other reasons, many languages do not include a null reference, but instead have a standard library +data type which can be used in situations where a null reference would otherwise be used. Common names for this +null reference alternative type include `Maybe` (like in Haskell or Elm), `Option` (like in OCaml or Rust), +and `Optional` (like in Java). + +By design, Roc does not have one of these. There are several reasons for this. + +First, if a function returns a potential error, Roc has the convention to use `Result` with an error type that +has a single tag describing what went wrong. (For example, `List.first : List a -> Result a [ ListWasEmpty ]*` +instead of `List.first : List a -> Maybe a`.) This is not only more self-descriptive, it also composes better with +other operations that can fail; there's no need to have functions like `Result.toMaybe` or `Maybe.toResult`, +because in Roc, the convention is that operations that can fail always use `Result`. + +Second, optional record fields can be handled using Roc's Optional Record Field language feature, so using a type like `Maybe` there would be less ergonomic. + +To describe something that's neither an optional field nor an operation that can fail, an explicit tag union can be +more descriptive than something like `Maybe`. For example, if a record type has an `artist` field, but the artist +information may not be available, compare these three alternative ways to represent that: + +* `artist : Maybe Artist` +* `artist : [ Loading, Loaded Artist ]` +* `artist : [ Unspecified, Specified Artist ]` + +All three versions tell us that we might not have access to an `Artist`. However, the `Maybe` version doesn't +tell us why that might be. The `Loading`/`Loaded` version tells us we don't have one *yet*, because we're +still loading it, whereas the `Unspecified`/`Specified` version tells us we don't have one and shouldn't expect +to have one later if we wait, because it wasn't specified. + +Naming aside, using explicit tag unions also makes it easier to transition to richer data models. For example, +after using `[ Loading, Loaded Artist ]` for awhile, we might realize that there's another possible state: loading +failed due to an error. If we modify this to be `[ Loading, Loaded Artist, Errored LoadingErr ]`, all +of our code for the `Loading` and `Loaded` states will still work. + +In contrast, if we'd had `Maybe Artist` and were using helper functions like `Maybe.isNone` (a common argument +for using `Maybe` even when it's less self-descriptive), we'd have to rewrite all the code which used those +helper functions. As such, a subtle downside of these helper functions is that they discourage any change to +the data model that would break their call sites, even if that change would improve the data model overall. + +On a historical note, `Maybe` may have been thought of as a substitute for null references—as opposed to something that emerged organically based on specific motivating use cases after `Result` already existed. That said, in languages that do not have an equivalent of Roc's tag unions, it's much less ergonomic to write something like `Result a [ ListWasEmpty ]*`, so that design would not fit those languages as well as it fits Roc. + ## Why doesn't Roc have higher-kinded polymorphism or arbitrary-rank types? _Since this is a FAQ answer, I'm going to assume familiarity with higher-kinded types and higher-rank types instead of including a primer on them._ @@ -159,12 +243,7 @@ Roc also has a different standard library from Elm. Some of the differences come * No `Char`. What most people think of as a "character" is a rendered glyph. However, rendered glyphs are comprised of [grapheme clusters](https://stackoverflow.com/a/27331885), which are a variable number of Unicode code points - and there's no upper bound on how many code points there can be in a single cluster. In a world of emoji, I think this makes `Char` error-prone and it's better to have `Str` be the only first-class unit. For convenience when working with unicode code points (e.g. for performance-critical tasks like parsing), the single-quote syntax is sugar for the corresponding `U32` code point - for example, writing `'鹏'` is exactly the same as writing `40527`. Like Rust, you get a compiler error if you put something in single quotes that's not a valid [Unicode scalar value](http://www.unicode.org/glossary/#unicode_scalar_value). * No `Debug.log` - the editor can do a better job at this, or you can write `expect x != x` to see what `x` is when the expectation fails. Using the editor means your code doesn't change, and using `expect` gives a natural reminder to remove the debugging code before shipping: the build will fail. * No `Debug.todo` - instead you can write a type annotation with no implementation below it; the type checker will treat it normally, but attempting to use the value will cause a runtime exception. This is a feature I've often wanted in Elm, because I like prototyping APIs by writing out the types only, but then when I want the compiler to type-check them for me, I end up having to add `Debug.todo` in various places. -* No `Maybe`. There are several reasons for this: - * If a function returns a potential error, I prefer `Result` with an error type that uses a no-payload tag to describe what went wrong. (For example, `List.first : List a -> Result a [ ListWasEmpty ]*` instead of `List.first : List a -> Maybe a`.) This is not only more self-descriptive, it also composes better with operations that have multiple ways to fail. - * Optional record fields can be handled using the explicit Optional Record Field language feature. - * To describe something that's neither an operation that can fail nor an optional field, I prefer using a more descriptive tag - e.g. for a nullable JSON decoder, instead of `nullable : Decoder a -> Decoder (Maybe a)`, making a self-documenting API like `nullable : Decoder a -> Decoder [ Null, NonNull a ]`. - * It's surprisingly easy to misuse - especially by overusing it when a different language feature (especially a custom tag union) would lead to nicer code. Joël's legendary [talk about Maybe](https://youtu.be/43eM4kNbb6c) is great, but the fact that a whole talk about such a simple type can be so useful speaks to how easy the type is to misuse. Imagine a 20-minute talk about `Result` - could it be anywhere near as hepful? - * On a historical note, it's conceivable that the creation of `Maybe` predated `Result`, and `Maybe` might have been thought of as a substitute for null pointers—as opposed to something that emerged organically based on specific motivating use cases after `Result` already existed. +* No `Maybe`. See the "Why doesn't Roc have a `Maybe`/`Option`/`Optional` type" FAQ question ## Why aren't Roc functions curried by default? diff --git a/LEGAL_DETAILS b/LEGAL_DETAILS index 588ad49184..687df7f907 100644 --- a/LEGAL_DETAILS +++ b/LEGAL_DETAILS @@ -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) diff --git a/ast/src/lang/core/def/def.rs b/ast/src/lang/core/def/def.rs index e78658457b..3f5b38a1e4 100644 --- a/ast/src/lang/core/def/def.rs +++ b/ast/src/lang/core/def/def.rs @@ -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!(), diff --git a/ast/src/lang/core/types.rs b/ast/src/lang/core/types.rs index 4702515554..59f727d2f7 100644 --- a/ast/src/lang/core/types.rs +++ b/ast/src/lang/core/types.rs @@ -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) } diff --git a/ast/src/module.rs b/ast/src/module.rs index e82e7b2c5d..b8685a2340 100644 --- a/ast/src/module.rs +++ b/ast/src/module.rs @@ -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( diff --git a/ast/src/solve_type.rs b/ast/src/solve_type.rs index 5d03f3a2f8..dc2e53f3a8 100644 --- a/ast/src/solve_type.rs +++ b/ast/src/solve_type.rs @@ -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); diff --git a/cli/src/build.rs b/cli/src/build.rs index 98563beedb..2d4f7fae53 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -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()); diff --git a/cli/src/format.rs b/cli/src/format.rs index d14fa95ac0..9975332a93 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -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) -> std::vec::Vec 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) -> std::vec::Vec 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, 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 { diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 0a9213731d..29ccc4d04c 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -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); } } diff --git a/cli/tests/fixtures/format/NotARocFile.txt b/cli/tests/fixtures/format/NotARocFile.txt new file mode 100644 index 0000000000..ea65efc5d0 --- /dev/null +++ b/cli/tests/fixtures/format/NotARocFile.txt @@ -0,0 +1 @@ +This is not a .roc file, and should be ignored by the formatter. \ No newline at end of file diff --git a/cli/tests/fixtures/format/formatted_directory/NestedNotARocFile.txt b/cli/tests/fixtures/format/formatted_directory/NestedNotARocFile.txt new file mode 100644 index 0000000000..ea65efc5d0 --- /dev/null +++ b/cli/tests/fixtures/format/formatted_directory/NestedNotARocFile.txt @@ -0,0 +1 @@ +This is not a .roc file, and should be ignored by the formatter. \ No newline at end of file diff --git a/cli/tests/fixtures/format/formatted_directory/NotARocFile b/cli/tests/fixtures/format/formatted_directory/NotARocFile new file mode 100644 index 0000000000..63b7992a35 --- /dev/null +++ b/cli/tests/fixtures/format/formatted_directory/NotARocFile @@ -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")` \ No newline at end of file diff --git a/cli/tests/fixtures/format/formatted_directory/ignored/NotARocFile.txt b/cli/tests/fixtures/format/formatted_directory/ignored/NotARocFile.txt new file mode 100644 index 0000000000..ea65efc5d0 --- /dev/null +++ b/cli/tests/fixtures/format/formatted_directory/ignored/NotARocFile.txt @@ -0,0 +1 @@ +This is not a .roc file, and should be ignored by the formatter. \ No newline at end of file diff --git a/code_markup/Cargo.toml b/code_markup/Cargo.toml index 528271cd81..07eb0aa4d7 100644 --- a/code_markup/Cargo.toml +++ b/code_markup/Cargo.toml @@ -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" } diff --git a/code_markup/src/markup/common_nodes.rs b/code_markup/src/markup/common_nodes.rs index b716e2ad02..0309815f2f 100644 --- a/code_markup/src/markup/common_nodes.rs +++ b/code_markup/src/markup/common_nodes.rs @@ -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) -> 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) -> 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) -> 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) -> 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, - 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) -> 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, -) -> 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) -> 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) -> 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) -> 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) -> 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, 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, + ) +} diff --git a/code_markup/src/markup/convert/from_ast.rs b/code_markup/src/markup/convert/from_ast.rs index 462fa3995c..4c3d470b04 100644 --- a/code_markup/src/markup/convert/from_ast.rs +++ b/code_markup/src/markup/convert/from_ast.rs @@ -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> { - let mut all_mark_node_ids = vec![header_to_markup(&ast.header, mark_node_pool)]; +) -> ASTResult<(Vec, 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)) } diff --git a/code_markup/src/markup/convert/from_def2.rs b/code_markup/src/markup/convert/from_def2.rs index 7ceaac2cf3..5e7e2ce818 100644 --- a/code_markup/src/markup/convert/from_def2.rs +++ b/code_markup/src/markup/convert/from_def2.rs @@ -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 { 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, + ) } }; diff --git a/code_markup/src/markup/convert/from_expr2.rs b/code_markup/src/markup/convert/from_expr2.rs index 06bf8851c9..5987541868 100644 --- a/code_markup/src/markup/convert/from_expr2.rs +++ b/code_markup/src/markup/convert/from_expr2.rs @@ -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 { @@ -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 = 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::>>()?; 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(add_node( + new_comma_mn(), + ast_node_id, + mark_node_pool, + mark_id_ast_id_map, + )); } } - 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_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(add_node( + new_comma_mn(), + ast_node_id, + mark_node_pool, + mark_id_ast_id_map, + )); } } - 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_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 = - join_mark_nodes_commas(arg_mark_nodes, ASTNodeId::AExprId(expr2_node_id)); + let args_with_commas: Vec = join_mark_nodes_commas(arg_mark_nodes); let mut args_with_commas_ids: Vec = 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, ) } diff --git a/code_markup/src/markup/convert/from_header.rs b/code_markup/src/markup/convert/from_header.rs index 7500b44714..3336b770b6 100644 --- a/code_markup/src/markup/convert/from_header.rs +++ b/code_markup/src/markup/convert/from_header.rs @@ -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 = 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 = 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 { 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) } diff --git a/code_markup/src/markup/mark_id_ast_id_map.rs b/code_markup/src/markup/mark_id_ast_id_map.rs new file mode 100644 index 0000000000..ebd928ed87 --- /dev/null +++ b/code_markup/src/markup/mark_id_ast_id_map.rs @@ -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, +} + +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 { + 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(), + } + } +} diff --git a/code_markup/src/markup/mod.rs b/code_markup/src/markup/mod.rs index 5b6dca5dfb..f9a4f0f98e 100644 --- a/code_markup/src/markup/mod.rs +++ b/code_markup/src/markup/mod.rs @@ -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; diff --git a/code_markup/src/markup/nodes.rs b/code_markup/src/markup/nodes.rs index 99d77b69f9..917983b0dd 100644 --- a/code_markup/src/markup/nodes.rs +++ b/code_markup/src/markup/nodes.rs @@ -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, parent_id_opt: Option, newlines_at_end: usize, }, Text { content: String, - ast_node_id: ASTNodeId, syn_high_style: HighlightStyle, attributes: Attributes, parent_id_opt: Option, newlines_at_end: usize, }, Blank { - ast_node_id: ASTNodeId, attributes: Attributes, parent_id_opt: Option, newlines_at_end: usize, }, Indent { - ast_node_id: ASTNodeId, indent_level: usize, parent_id_opt: Option, }, } 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 { 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 = None; let mut child_ids_with_ast: Vec = 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, 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, with_prepend: bool, - ast_node_id: ASTNodeId, mark_node_pool: &mut SlowPool, ) -> Vec { 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, - ast_node_id: ASTNodeId, -) -> Vec { +pub fn join_mark_nodes_commas(mark_nodes: Vec) -> Vec { let join_nodes: Vec = (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() diff --git a/code_markup/src/markup/top_level_def.rs b/code_markup/src/markup/top_level_def.rs index 4bdd6c420f..3fbea9fe76 100644 --- a/code_markup/src/markup/top_level_def.rs +++ b/code_markup/src/markup/top_level_def.rs @@ -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 { 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 { - 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, diff --git a/code_markup/src/markup_error.rs b/code_markup/src/markup_error.rs index c1843547a1..7a10252437 100644 --- a/code_markup/src/markup_error.rs +++ b/code_markup/src/markup_error.rs @@ -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, diff --git a/code_markup/src/slow_pool.rs b/code_markup/src/slow_pool.rs index 2d6118ddd9..94621dc92a 100644 --- a/code_markup/src/slow_pool.rs +++ b/code_markup/src/slow_pool.rs @@ -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::().unwrap(), child_str - )?; + )); } - Ok(()) + ret_str } } diff --git a/code_markup/src/syntax_highlight.rs b/code_markup/src/syntax_highlight.rs index 9a795bf00a..9f728a7913 100644 --- a/code_markup/src/syntax_highlight.rs +++ b/code_markup/src/syntax_highlight.rs @@ -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 { @@ -31,7 +33,6 @@ pub fn default_highlight_map() -> HashMap { 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 { (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| { diff --git a/compiler/alias_analysis/src/lib.rs b/compiler/alias_analysis/src/lib.rs index f9d38cc708..d492f3a12a 100644 --- a/compiler/alias_analysis/src/lib.rs +++ b/compiler/alias_analysis/src/lib.rs @@ -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; diff --git a/compiler/builtins/Cargo.toml b/compiler/builtins/Cargo.toml index 579cae97ed..36b973cc9e 100644 --- a/compiler/builtins/Cargo.toml +++ b/compiler/builtins/Cargo.toml @@ -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 diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index f9ef75410f..81a31d3ed4 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -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; diff --git a/compiler/builtins/docs/Dict.roc b/compiler/builtins/docs/Dict.roc index eda2e6e53c..0de2d5ad5d 100644 --- a/compiler/builtins/docs/Dict.roc +++ b/compiler/builtins/docs/Dict.roc @@ -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 diff --git a/compiler/builtins/docs/Num.roc b/compiler/builtins/docs/Num.roc index 286d256af7..766cdd1df4 100644 --- a/compiler/builtins/docs/Num.roc +++ b/compiler/builtins/docs/Num.roc @@ -120,6 +120,8 @@ interface Num toU64Checked, toU128, toU128Checked, + toNat, + toNatChecked, toFloat, toStr ] diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 351adc432b..5a3841047d 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -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 { 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 { 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 } diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index 1892b552c8..9fa5385640 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -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`. - pub wildcards: Vec, + pub wildcards: Vec>, pub lambda_sets: Vec, - pub inferred: Vec, - pub var_by_name: SendMap, + pub inferred: Vec>, + // NB: A mapping of a -> Loc in this map has the region of the first-seen var, but there + // may be multiple occurrences of it! + pub var_by_name: SendMap>, pub name_by_var: SendMap, pub host_exposed_aliases: MutMap, } impl IntroducedVariables { - pub fn insert_named(&mut self, name: Lowercase, var: Variable) { + pub fn insert_named(&mut self, name: Lowercase, var: Loc) { 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) { self.wildcards.push(var); } - pub fn insert_inferred(&mut self, var: Variable) { + pub fn insert_inferred(&mut self, var: Loc) { 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,22 +343,43 @@ fn can_annotation_help( return error; } - let (type_arguments, lambda_set_variables, actual) = - instantiate_and_freshen_alias_type( - var_store, - introduced_variables, - &alias.type_variables, - args, - &alias.lambda_set_variables, - alias.typ.clone(), - ); + // 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(); - Type::Alias { - symbol, - type_arguments, - lambda_set_variables, - actual: Box::new(actual), - kind: alias.kind, + 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, + introduced_variables, + &alias.type_variables, + args, + &alias.lambda_set_variables, + alias.typ.clone(), + ); + + Type::Alias { + symbol, + type_arguments, + lambda_set_variables, + actual: Box::new(actual), + 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) } }; diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index d1c8008a84..af80221154 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -247,6 +247,8 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option 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 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, diff --git a/compiler/can/src/constraint.rs b/compiler/can/src/constraint.rs index 70573d252a..c1ed33d192 100644 --- a/compiler/can/src/constraint.rs +++ b/compiler/can/src/constraint.rs @@ -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,14 +120,27 @@ impl Constraints { pub const PCATEGORY_CHARACTER: Index = Index::new(10); #[inline(always)] - pub fn push_type(&mut self, typ: Type) -> Index { + pub fn push_type(&mut self, typ: Type) -> EitherIndex { 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 = Index::push_new(&mut self.types, other); + EitherIndex::from_left(index) + } } } + #[inline(always)] + const fn push_type_variable(var: Variable) -> EitherIndex { + // 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 = Index::new(var.index()); + + EitherIndex::from_right(index) + } + #[inline(always)] pub fn push_expected_type(&mut self, expected: Expected) -> Index> { Index::push_new(&mut self.expectations, expected) @@ -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, + 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, + 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)>, 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( + &mut self, + rigid_vars: I1, + def_types: I2, + module_constraint: Constraint, + pool_variables: &[Variable], + ) -> Constraint + where + I1: IntoIterator, + I2: IntoIterator)>, + 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, + 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, Index>, Index, Region), - Store(Index, Variable, Index<&'static str>, u32), + Eq( + EitherIndex, + Index>, + Index, + Region, + ), + Store( + EitherIndex, + Variable, + Index<&'static str>, + u32, + ), Lookup(Symbol, Index>, Region), Pattern( - Index, + EitherIndex, Index>, Index, 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), + /// A Let constraint introduces symbols and their annotation at a certain level of nesting + /// + /// The `Slice` 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, Slice), And(Slice), /// Presence constraints - IsOpenType(Index), // Theory; always applied to a variable? if yes the use that + IsOpenType(EitherIndex), // Theory; always applied to a variable? if yes the use that IncludesTag(Index), PatternPresence( - Index, + EitherIndex, Index>, Index, Region, @@ -503,3 +637,36 @@ pub struct IncludesTag { pub pattern_category: Index, 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 + ) + } + } + } +} diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index fad03f9099..7d1c03d58e 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -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> = 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, var_store: &mut VarStore, refs_by_symbol: &mut MutMap, - aliases: &mut SendMap, + aliases: &mut ImMap, ) -> 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, + mut original_aliases: SendMap, var_store: &mut VarStore, -) -> SendMap { - let mut symbols_introduced = ImSet::default(); +) -> ImMap { + let symbols_introduced: Vec = original_aliases.keys().copied().collect(); - for (key, _) in original_aliases.iter() { - symbols_introduced.insert(*key); - } - - let all_successors_with_self = |symbol: &Symbol| -> ImSet { + let all_successors_with_self = |symbol: &Symbol| -> Vec { 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 = 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 - // 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()); - } - } - } + // 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. + // 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::>(); - 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::>(); 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 } } } diff --git a/compiler/can/src/effect_module.rs b/compiler/can/src/effect_module.rs index 5e0a84dff4..44bac10cc8 100644 --- a/compiler/can/src/effect_module.rs +++ b/compiler/can/src/effect_module.rs @@ -10,7 +10,7 @@ use roc_module::ident::TagName; use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; use roc_types::subs::{VarStore, Variable}; -use roc_types::types::{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, ) } diff --git a/compiler/can/src/env.rs b/compiler/can/src/env.rs index 516e192b74..2ea281203c 100644 --- a/compiler/can/src/env.rs +++ b/compiler/can/src/env.rs @@ -27,8 +27,11 @@ pub struct Env<'a> { /// current closure name (if any) pub closure_name_symbol: Option, - /// Symbols which were referenced by qualified lookups. - pub qualified_lookups: MutSet, + /// Symbols of values/functions which were referenced by qualified lookups. + pub qualified_value_lookups: MutSet, + + /// Symbols of types which were referenced by qualified lookups. + pub qualified_type_lookups: MutSet, pub top_level_symbols: MutSet, @@ -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) } diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 8a54994f68..a1f9510b83 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -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 = - new_output.references.lookups.iter().copied().collect(); + let mut captured_symbols: MutSet = 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, ) -> ImSet { - 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) - } 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) } diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index e04f2f70d7..68f65a583d 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -23,21 +23,29 @@ pub struct Module { pub module_id: ModuleId, pub exposed_imports: MutMap, pub exposed_symbols: MutSet, - pub references: MutSet, + pub referenced_values: MutSet, + pub referenced_types: MutSet, pub aliases: MutMap, - pub rigid_variables: MutMap, + pub rigid_variables: RigidVariables, +} + +#[derive(Debug, Default)] +pub struct RigidVariables { + pub named: MutMap, + pub wildcards: MutSet, } #[derive(Debug)] pub struct ModuleOutput { pub aliases: MutMap, - pub rigid_variables: MutMap, + pub rigid_variables: RigidVariables, pub declarations: Vec, pub exposed_imports: MutMap, pub lookups: Vec<(Symbol, Variable, Region)>, pub problems: Vec, pub ident_ids: IdentIds, - pub references: MutSet, + pub referenced_values: MutSet, + pub referenced_types: MutSet, 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 = references + let transitive_builtins: Vec = 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, diff --git a/compiler/can/src/operator.rs b/compiler/can/src/operator.rs index 0c8d00177d..bfd8e5725d 100644 --- a/compiler/can/src/operator.rs +++ b/compiler/can/src/operator.rs @@ -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, diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index 119f134f98..6e78fa5526 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -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(), diff --git a/compiler/can/src/procedure.rs b/compiler/can/src/procedure.rs index 0794b86b3e..6f616206e0 100644 --- a/compiler/can/src/procedure.rs +++ b/compiler/can/src/procedure.rs @@ -45,7 +45,8 @@ impl Procedure { #[derive(Clone, Debug, Default, PartialEq)] pub struct References { pub bound_symbols: ImSet, - pub lookups: ImSet, + pub type_lookups: ImSet, + pub value_lookups: ImSet, /// Aliases or opaque types referenced pub referenced_type_defs: ImSet, pub calls: ImSet, @@ -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) } } diff --git a/compiler/collections/src/soa.rs b/compiler/collections/src/soa.rs index 563dba4f67..69e6abb830 100644 --- a/compiler/collections/src/soa.rs +++ b/compiler/collections/src/soa.rs @@ -117,3 +117,56 @@ impl Slice { self.indices().map(|i| Index::new(i as _)) } } + +#[derive(PartialEq, Eq)] +pub struct EitherIndex { + index: u32, + _marker: std::marker::PhantomData<(T, U)>, +} + +impl Clone for EitherIndex { + fn clone(&self) -> Self { + Self { + index: self.index, + _marker: self._marker, + } + } +} + +impl Copy for EitherIndex {} + +impl std::fmt::Debug for EitherIndex { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Index({})", self.index) + } +} + +impl EitherIndex { + const MASK: u32 = 1 << 31; + + pub const fn from_left(input: Index) -> Self { + assert!(input.index & Self::MASK == 0); + + Self { + index: input.index, + _marker: std::marker::PhantomData, + } + } + + pub const fn from_right(input: Index) -> Self { + assert!(input.index & Self::MASK == 0); + + Self { + index: input.index | Self::MASK, + _marker: std::marker::PhantomData, + } + } + + pub const fn split(self) -> Result, Index> { + if self.index & Self::MASK == 0 { + Ok(Index::new(self.index)) + } else { + Err(Index::new(self.index ^ Self::MASK)) + } + } +} diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index 16cb4b25da..86a812aa64 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -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( diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index d9ca97f672..e933062308 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -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, ) -> 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>, ) -> InstantiateRigids { let mut annotation = annotation.clone(); - let mut new_rigid_variables = Vec::new(); + let mut new_rigid_variables: Vec = Vec::new(); let mut rigid_substitution: ImMap = 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 = + 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, ]; diff --git a/compiler/constrain/src/module.rs b/compiler/constrain/src/module.rs index 79640a2d30..8af7f4c38e 100644 --- a/compiler/constrain/src/module.rs +++ b/compiler/constrain/src/module.rs @@ -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; +/// The types of all exposed values/functions of a collection of modules +#[derive(Clone, Debug, Default)] +pub struct ExposedByModule { + exposed: MutMap, +} +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) -> 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, +} + +impl ExposedForModule { + pub fn new<'a>( + it: impl Iterator, + 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, MutMap), + 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, + imports: Vec, body_con: Constraint, var_store: &mut VarStore, -) -> (Vec, 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, + var_store: &mut VarStore, +) -> (Vec, Vec<(Symbol, Loc)>) { + 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, - 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, - pub imported_aliases: MutMap, - pub unused_imports: MutMap, -} - -/// 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, - imported_modules: MutMap, - 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) } diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index 0a543728bd..c6652908cb 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -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, ); diff --git a/compiler/fmt/src/annotation.rs b/compiler/fmt/src/annotation.rs index a4bc14ac21..8a6184f944 100644 --- a/compiler/fmt/src/annotation.rs +++ b/compiler/fmt/src/annotation.rs @@ -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); + } +} diff --git a/compiler/fmt/src/def.rs b/compiler/fmt/src/def.rs index 935e6c6e35..e19dfae2b5 100644 --- a/compiler/fmt/src/def.rs +++ b/compiler/fmt/src/def.rs @@ -2,7 +2,7 @@ use crate::annotation::{Formattable, Newlines, Parens}; use crate::pattern::fmt_pattern; use crate::spaces::{fmt_spaces, INDENT}; use crate::Buf; -use roc_parse::ast::{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); + } +} diff --git a/compiler/fmt/src/expr.rs b/compiler/fmt/src/expr.rs index f5cad3cc34..b54ff826b2 100644 --- a/compiler/fmt/src/expr.rs +++ b/compiler/fmt/src/expr.rs @@ -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); } diff --git a/compiler/fmt/tests/test_fmt.rs b/compiler/fmt/tests/test_fmt.rs index 72034e879e..c5bde106a7 100644 --- a/compiler/fmt/tests/test_fmt.rs +++ b/compiler/fmt/tests/test_fmt.rs @@ -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] diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index 5daa75e0b4..1d6fe2ea2c 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -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); diff --git a/compiler/gen_llvm/src/llvm/bitcode.rs b/compiler/gen_llvm/src/llvm/bitcode.rs index d80d626036..fdd3465063 100644 --- a/compiler/gen_llvm/src/llvm/bitcode.rs +++ b/compiler/gen_llvm/src/llvm/bitcode.rs @@ -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( diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index c8da6b8458..f13708464a 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -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 = diff --git a/compiler/gen_llvm/src/llvm/build_dict.rs b/compiler/gen_llvm/src/llvm/build_dict.rs index cbecc59b9a..b0f5aabb7f 100644 --- a/compiler/gen_llvm/src/llvm/build_dict.rs +++ b/compiler/gen_llvm/src/llvm/build_dict.rs @@ -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), diff --git a/compiler/gen_llvm/src/llvm/build_hash.rs b/compiler/gen_llvm/src/llvm/build_hash.rs index aa89583c77..94ffe8b28a 100644 --- a/compiler/gen_llvm/src/llvm/build_hash.rs +++ b/compiler/gen_llvm/src/llvm/build_hash.rs @@ -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 diff --git a/compiler/gen_llvm/src/llvm/build_list.rs b/compiler/gen_llvm/src/llvm/build_list.rs index dca6e49466..0f7a601e7f 100644 --- a/compiler/gen_llvm/src/llvm/build_list.rs +++ b/compiler/gen_llvm/src/llvm/build_list.rs @@ -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 = builder.build_alloca(default.get_type(), "default_ptr"); - env.builder.build_store(default_ptr, default); + 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,7 +476,11 @@ pub fn list_walk_generic<'a, 'ctx, 'env>( } } - env.builder.build_load(result_ptr, "load_result") + 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) diff --git a/compiler/gen_llvm/src/llvm/build_str.rs b/compiler/gen_llvm/src/llvm/build_str.rs index 966112c69d..615a3951ae 100644 --- a/compiler/gen_llvm/src/llvm/build_str.rs +++ b/compiler/gen_llvm/src/llvm/build_str.rs @@ -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 diff --git a/compiler/gen_llvm/src/llvm/compare.rs b/compiler/gen_llvm/src/llvm/compare.rs index 9f8a0928a6..96da0bd7ef 100644 --- a/compiler/gen_llvm/src/llvm/compare.rs +++ b/compiler/gen_llvm/src/llvm/compare.rs @@ -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)); +} diff --git a/compiler/gen_llvm/src/llvm/convert.rs b/compiler/gen_llvm/src/llvm/convert.rs index 8d516e539e..3b37bfa000 100644 --- a/compiler/gen_llvm/src/llvm/convert.rs +++ b/compiler/gen_llvm/src/llvm/convert.rs @@ -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,121 +34,64 @@ 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) => { - use UnionLayout::*; + Boxed(inner_layout) => { + let inner_type = basic_type_from_layout(env, inner_layout); - 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); - - env.context.struct_type(&[data, tag_id_type], false).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() + 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_layout_1<'a, 'ctx, 'env>( - env: &crate::llvm::build::Env<'a, 'ctx, 'env>, - layout: &Layout<'_>, +pub fn basic_type_from_union_layout<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + union_layout: &UnionLayout<'_>, ) -> BasicTypeEnum<'ctx> { - use Layout::*; + use UnionLayout::*; - 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()) + 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); + + env.context.struct_type(&[data, tag_id_type], false).into() } - Union(union_layout) => { - use UnionLayout::*; + Recursive(tags) + | NullableWrapped { + other_tags: tags, .. + } => { + let data = block_of_memory_slices(env.context, tags, env.target_info); - 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() - } + 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() } } - RecursivePointer => { - // TODO make this dynamic - env.context - .i64_type() - .ptr_type(AddressSpace::Generic) - .as_basic_type_enum() + 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() } - - 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() diff --git a/compiler/gen_llvm/src/llvm/refcounting.rs b/compiler/gen_llvm/src/llvm/refcounting.rs index da820087bf..1bd968c118 100644 --- a/compiler/gen_llvm/src/llvm/refcounting.rs +++ b/compiler/gen_llvm/src/llvm/refcounting.rs @@ -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); diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 4e4b0f5b21..d19b8778e5 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -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. } } diff --git a/compiler/gen_wasm/src/layout.rs b/compiler/gen_wasm/src/layout.rs index 1b70e0757a..ea11a9d546 100644 --- a/compiler/gen_wasm/src/layout.rs +++ b/compiler/gen_wasm/src/layout.rs @@ -102,6 +102,7 @@ impl WasmLayout { | NullableWrapped { .. } | NullableUnwrapped { .. }, ) + | Layout::Boxed(_) | Layout::RecursivePointer => Self::Primitive(PTR_TYPE, PTR_SIZE), } } diff --git a/compiler/gen_wasm/src/low_level.rs b/compiler/gen_wasm/src/low_level.rs index fb24a94327..a75594077d 100644 --- a/compiler/gen_wasm/src/low_level.rs +++ b/compiler/gen_wasm/src/low_level.rs @@ -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 {:?}", diff --git a/compiler/load/src/docs.rs b/compiler/load/src/docs.rs index bd8e526344..dd34893c43 100644 --- a/compiler/load/src/docs.rs +++ b/compiler/load/src/docs.rs @@ -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), diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index a68b73ddf5..80d311318c 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -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, dep_idents: MutMap, module_timing: ModuleTiming, - unused_imports: MutMap, }, FinishedAllTypeChecking { solved_subs: Solved, exposed_vars_by_symbol: Vec<(Symbol, Variable)>, exposed_aliases_by_symbol: MutMap, - exposed_values: Vec, dep_idents: MutMap, documentation: MutMap, }, @@ -631,8 +629,7 @@ struct State<'a> { pub root_id: ModuleId, pub platform_data: Option, 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>>, ident_ids_by_module: Arc>>, ) -> 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, + imported_builtins: Vec, + exposed_for_module: ExposedForModule, module_timing: ModuleTiming, constraints: Constraints, constraint: ConstraintSoa, var_store: VarStore, declarations: Vec, dep_idents: MutMap, - unused_imports: MutMap, }, 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> { 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, 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, 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, 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, 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, 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 { + 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, - exposed_values: Vec, exposed_aliases_by_symbol: MutMap, exposed_vars_by_symbol: Vec<(Symbol, Variable)>, dep_idents: MutMap, @@ -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, - exposed_types: &mut SubsByModule, - stdlib: &StdLib, + exposed_types: &mut ExposedByModule, dep_idents: MutMap, declarations: Vec, ) -> 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, + imported_builtins: Vec, + mut exposed_for_module: ExposedForModule, mut constraints: Constraints, constraint: ConstraintSoa, mut var_store: VarStore, decls: Vec, dep_idents: MutMap, - unused_imports: MutMap, ) -> 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::>(), - 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 +} diff --git a/compiler/load/tests/test_load.rs b/compiler/load/tests/test_load.rs index 6411d8ae91..137948f4a7 100644 --- a/compiler/load/tests/test_load.rs +++ b/compiler/load/tests/test_load.rs @@ -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( diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index e2d3f34464..736509d27d 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -124,6 +124,8 @@ pub enum LowLevel { PtrCast, RefCountInc, RefCountDec, + BoxExpr, + UnboxExpr, } macro_rules! higher_order { diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index add1c20265..96d5776d55 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -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) } diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index b49c17eb47..f51d697de6 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -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); } diff --git a/compiler/mono/src/code_gen_help/equality.rs b/compiler/mono/src/code_gen_help/equality.rs index cf4fbcff17..c11f8c421a 100644 --- a/compiler/mono/src/code_gen_help/equality.rs +++ b/compiler/mono/src/code_gen_help/equality.rs @@ -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`. diff --git a/compiler/mono/src/code_gen_help/mod.rs b/compiler/mono/src/code_gen_help/mod.rs index 8d5a239520..df600bf358 100644 --- a/compiler/mono/src/code_gen_help/mod.rs +++ b/compiler/mono/src/code_gen_help/mod.rs @@ -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, } } diff --git a/compiler/mono/src/code_gen_help/refcount.rs b/compiler/mono/src/code_gen_help/refcount.rs index 60e15b9f73..755bbac699 100644 --- a/compiler/mono/src/code_gen_help/refcount.rs +++ b/compiler/mono/src/code_gen_help/refcount.rs @@ -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, } } diff --git a/compiler/mono/src/inc_dec.rs b/compiler/mono/src/inc_dec.rs index 091c14bbd4..6836cc45fc 100644 --- a/compiler/mono/src/inc_dec.rs +++ b/compiler/mono/src/inc_dec.rs @@ -114,6 +114,10 @@ pub fn occurring_variables_expr(expr: &Expr<'_>, result: &mut MutSet) { 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 diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 1564ec22bc..b0cb32fc4e 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -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 { + fn symbol_to_id(&self, mut symbol: Symbol) -> Option { + 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 } } @@ -7253,7 +7315,7 @@ fn call_by_name_help<'a>( // this is a case like `Str.concat`, an imported standard function, applied to zero arguments // imported symbols cannot capture anything - let captured= &[]; + let captured = &[]; construct_closure_data( env, @@ -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) } diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index ce9a1a16fd..5730de27fa 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -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); diff --git a/compiler/mono/src/reset_reuse.rs b/compiler/mono/src/reset_reuse.rs index d29597323f..4937eff16a 100644 --- a/compiler/mono/src/reset_reuse.rs +++ b/compiler/mono/src/reset_reuse.rs @@ -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, } } diff --git a/compiler/parse/src/ast.rs b/compiler/parse/src/ast.rs index d833dbb4e5..125538628e 100644 --- a/compiler/parse/src/ast.rs +++ b/compiler/parse/src/ast.rs @@ -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>, + pub typ: Loc>, +} + #[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>, }, + /// An ability definition. E.g. + /// Hash has + /// hash : a -> U64 | a has Hash + Ability { + header: TypeHeader<'a>, + loc_has: Loc>, + 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>, + // Should always be a zero-argument `Apply`; we'll check this in canonicalization + pub ability: Loc>, +} + #[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>, &'a [Loc>]), + // 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 { diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index 4de9f94875..1a5b8be89b 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -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>)| { + 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>], + loc_has: Loc>, + 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; diff --git a/compiler/parse/src/parser.rs b/compiler/parse/src/parser.rs index 9df0d4f794..3c25922ba9 100644 --- a/compiler/parse/src/parser.rs +++ b/compiler/parse/src/parser.rs @@ -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) => { diff --git a/compiler/parse/src/type_annotation.rs b/compiler/parse/src/type_annotation.rs index 0d7df8b1c2..45b34b7157 100644 --- a/compiler/parse/src/type_annotation.rs +++ b/compiler/parse/src/type_annotation.rs @@ -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>, 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>, Loc>)| { + 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>]), 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)) } } }) diff --git a/compiler/parse/tests/snapshots/pass/ability_demand_signature_is_multiline.expr.result-ast b/compiler/parse/tests/snapshots/pass/ability_demand_signature_is_multiline.expr.result-ast new file mode 100644 index 0000000000..cd75525ef6 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/ability_demand_signature_is_multiline.expr.result-ast @@ -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, + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/ability_demand_signature_is_multiline.expr.roc b/compiler/parse/tests/snapshots/pass/ability_demand_signature_is_multiline.expr.roc new file mode 100644 index 0000000000..5e218feb6e --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/ability_demand_signature_is_multiline.expr.roc @@ -0,0 +1,5 @@ +Hash has + hash : a + -> U64 + +1 diff --git a/compiler/parse/tests/snapshots/pass/ability_multi_line.expr.result-ast b/compiler/parse/tests/snapshots/pass/ability_multi_line.expr.result-ast new file mode 100644 index 0000000000..0ad5d314f4 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/ability_multi_line.expr.result-ast @@ -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, + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/ability_multi_line.expr.roc b/compiler/parse/tests/snapshots/pass/ability_multi_line.expr.roc new file mode 100644 index 0000000000..795c19bad6 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/ability_multi_line.expr.roc @@ -0,0 +1,5 @@ +Hash has + hash : a -> U64 + hash2 : a -> U64 + +1 diff --git a/compiler/parse/tests/snapshots/pass/ability_single_line.expr.result-ast b/compiler/parse/tests/snapshots/pass/ability_single_line.expr.result-ast new file mode 100644 index 0000000000..de3ea93777 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/ability_single_line.expr.result-ast @@ -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, + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/ability_single_line.expr.roc b/compiler/parse/tests/snapshots/pass/ability_single_line.expr.roc new file mode 100644 index 0000000000..3c329799cb --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/ability_single_line.expr.roc @@ -0,0 +1,3 @@ +Hash has hash : a -> U64 + +1 diff --git a/compiler/parse/tests/snapshots/pass/where_clause_function.expr.result-ast b/compiler/parse/tests/snapshots/pass/where_clause_function.expr.result-ast new file mode 100644 index 0000000000..89f39700a8 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/where_clause_function.expr.result-ast @@ -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, + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/where_clause_function.expr.roc b/compiler/parse/tests/snapshots/pass/where_clause_function.expr.roc new file mode 100644 index 0000000000..ede845156a --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/where_clause_function.expr.roc @@ -0,0 +1,3 @@ +f : a -> (b -> c) | a has A + +f diff --git a/compiler/parse/tests/snapshots/pass/where_clause_multiple_has.expr.result-ast b/compiler/parse/tests/snapshots/pass/where_clause_multiple_has.expr.result-ast new file mode 100644 index 0000000000..223beca196 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/where_clause_multiple_has.expr.result-ast @@ -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, + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/where_clause_multiple_has.expr.roc b/compiler/parse/tests/snapshots/pass/where_clause_multiple_has.expr.roc new file mode 100644 index 0000000000..a56e9fb184 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/where_clause_multiple_has.expr.roc @@ -0,0 +1,3 @@ +f : a -> (b -> c) | a has A, b has Eq, c has Ord + +f diff --git a/compiler/parse/tests/snapshots/pass/where_clause_multiple_has_across_newlines.expr.result-ast b/compiler/parse/tests/snapshots/pass/where_clause_multiple_has_across_newlines.expr.result-ast new file mode 100644 index 0000000000..36428ce1b4 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/where_clause_multiple_has_across_newlines.expr.result-ast @@ -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, + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/where_clause_multiple_has_across_newlines.expr.roc b/compiler/parse/tests/snapshots/pass/where_clause_multiple_has_across_newlines.expr.roc new file mode 100644 index 0000000000..a5e89f075f --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/where_clause_multiple_has_across_newlines.expr.roc @@ -0,0 +1,6 @@ +f : a -> (b -> c) + | a has Hash, + b has Eq, + c has Ord + +f diff --git a/compiler/parse/tests/snapshots/pass/where_clause_non_function.expr.result-ast b/compiler/parse/tests/snapshots/pass/where_clause_non_function.expr.result-ast new file mode 100644 index 0000000000..ae9ce97671 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/where_clause_non_function.expr.result-ast @@ -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, + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/where_clause_non_function.expr.roc b/compiler/parse/tests/snapshots/pass/where_clause_non_function.expr.roc new file mode 100644 index 0000000000..eb3374f992 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/where_clause_non_function.expr.roc @@ -0,0 +1,3 @@ +f : a | a has A + +f diff --git a/compiler/parse/tests/snapshots/pass/where_clause_on_newline.expr.result-ast b/compiler/parse/tests/snapshots/pass/where_clause_on_newline.expr.result-ast new file mode 100644 index 0000000000..81df078444 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/where_clause_on_newline.expr.result-ast @@ -0,0 +1,48 @@ +Defs( + [ + @0-29 Annotation( + @0-1 Identifier( + "f", + ), + @9-29 Where( + @9-12 SpaceBefore( + Function( + [ + @4-5 BoundVariable( + "a", + ), + ], + @9-12 Apply( + "", + "U64", + [], + ), + ), + [ + Newline, + ], + ), + [ + @19-29 HasClause { + var: @19-20 "a", + ability: @25-29 Apply( + "", + "Hash", + [], + ), + }, + ], + ), + ), + ], + @31-32 SpaceBefore( + Var { + module_name: "", + ident: "f", + }, + [ + Newline, + Newline, + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/where_clause_on_newline.expr.roc b/compiler/parse/tests/snapshots/pass/where_clause_on_newline.expr.roc new file mode 100644 index 0000000000..7f29c770d3 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/where_clause_on_newline.expr.roc @@ -0,0 +1,4 @@ +f : a -> U64 + | a has Hash + +f diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index 684e129497..a5c24ce442 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -123,6 +123,9 @@ mod test_parse { fail/type_argument_no_arrow.expr, fail/type_double_comma.expr, pass/list_closing_indent_not_enough.expr, + pass/ability_single_line.expr, + pass/ability_multi_line.expr, + pass/ability_demand_signature_is_multiline.expr, pass/add_var_with_spaces.expr, pass/add_with_spaces.expr, pass/annotated_record_destructure.expr, @@ -273,6 +276,11 @@ mod test_parse { pass/when_with_negative_numbers.expr, pass/when_with_numbers.expr, pass/when_with_records.expr, + pass/where_clause_function.expr, + pass/where_clause_non_function.expr, + pass/where_clause_multiple_has.expr, + pass/where_clause_multiple_has_across_newlines.expr, + pass/where_clause_on_newline.expr, pass/zero_float.expr, pass/zero_int.expr, } diff --git a/compiler/problem/Cargo.toml b/compiler/problem/Cargo.toml index 56874ddecf..af0f1108a0 100644 --- a/compiler/problem/Cargo.toml +++ b/compiler/problem/Cargo.toml @@ -9,4 +9,5 @@ edition = "2018" roc_collections = { path = "../collections" } roc_region = { path = "../region" } roc_module = { path = "../module" } +roc_types = { path = "../types" } roc_parse = { path = "../parse" } diff --git a/compiler/problem/src/can.rs b/compiler/problem/src/can.rs index e5d23518f9..daa20c0778 100644 --- a/compiler/problem/src/can.rs +++ b/compiler/problem/src/can.rs @@ -5,6 +5,7 @@ use roc_module::symbol::{ModuleId, Symbol}; use roc_parse::ast::Base; use roc_parse::pattern::PatternType; use roc_region::all::{Loc, Region}; +use roc_types::types::AliasKind; #[derive(Clone, Copy, Debug, PartialEq)] pub struct CycleEntry { @@ -43,6 +44,12 @@ pub enum Problem { variable_region: Region, variable_name: Lowercase, }, + UnboundTypeVariable { + typ: Symbol, + num_unbound: usize, + one_occurrence: Region, + kind: AliasKind, + }, DuplicateRecordFieldValue { field_name: Lowercase, record_region: Region, diff --git a/compiler/solve/src/module.rs b/compiler/solve/src/module.rs index a767cfb2e7..64b3c654c2 100644 --- a/compiler/solve/src/module.rs +++ b/compiler/solve/src/module.rs @@ -1,41 +1,61 @@ -use crate::solve; +use crate::solve::{self, Aliases}; use roc_can::constraint::{Constraint as ConstraintSoa, Constraints}; +use roc_can::module::RigidVariables; use roc_collections::all::MutMap; -use roc_module::ident::Lowercase; use roc_module::symbol::Symbol; use roc_types::solved_types::{Solved, SolvedType}; -use roc_types::subs::{Subs, VarStore, Variable}; +use roc_types::subs::{StorageSubs, Subs, Variable}; use roc_types::types::Alias; #[derive(Debug)] pub struct SolvedModule { - pub solved_types: MutMap, - pub aliases: MutMap, - pub exposed_symbols: Vec, - pub exposed_vars_by_symbol: Vec<(Symbol, Variable)>, pub problems: Vec, + + /// all aliases and their definitions. this has to include non-exposed aliases + /// because exposed aliases can depend on non-exposed ones) + pub aliases: MutMap, + + /// Used when the goal phase is TypeChecking, and + /// to create the types for HostExposed. This + /// has some overlap with the StorageSubs fields, + /// so maybe we can get rid of this at some point + pub exposed_vars_by_symbol: Vec<(Symbol, Variable)>, + + /// Used when importing this module into another module + pub stored_vars_by_symbol: Vec<(Symbol, Variable)>, + pub storage_subs: StorageSubs, } pub fn run_solve( constraints: &Constraints, constraint: ConstraintSoa, - rigid_variables: MutMap, - var_store: VarStore, + rigid_variables: RigidVariables, + mut subs: Subs, + mut aliases: Aliases, ) -> (Solved, solve::Env, Vec) { let env = solve::Env::default(); - let mut subs = Subs::new_from_varstore(var_store); - - for (var, name) in rigid_variables { + for (var, name) in rigid_variables.named { subs.rigid_var(var, name); } + for var in rigid_variables.wildcards { + subs.rigid_var(var, "*".into()); + } + // Now that the module is parsed, canonicalized, and constrained, // we need to type check it. let mut problems = Vec::new(); // Run the solver to populate Subs. - let (solved_subs, solved_env) = solve::run(constraints, &env, &mut problems, subs, &constraint); + let (solved_subs, solved_env) = solve::run( + constraints, + &env, + &mut problems, + subs, + &mut aliases, + &constraint, + ); (solved_subs, solved_env, problems) } @@ -59,3 +79,19 @@ pub fn make_solved_types( solved_types } + +pub fn exposed_types_storage_subs( + solved_subs: &mut Solved, + exposed_vars_by_symbol: &[(Symbol, Variable)], +) -> (StorageSubs, Vec<(Symbol, Variable)>) { + let subs = solved_subs.inner_mut(); + let mut storage_subs = StorageSubs::new(Subs::new()); + let mut stored_vars_by_symbol = Vec::with_capacity(exposed_vars_by_symbol.len()); + + for (symbol, var) in exposed_vars_by_symbol.iter() { + let new_var = storage_subs.import_variable_from(subs, *var).variable; + stored_vars_by_symbol.push((*symbol, new_var)); + } + + (storage_subs, stored_vars_by_symbol) +} diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 68ee774bb7..e50c76eca9 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -13,7 +13,8 @@ use roc_types::subs::{ }; use roc_types::types::Type::{self, *}; use roc_types::types::{ - gather_fields_unsorted_iter, AliasKind, Category, ErrorType, PatternCategory, + gather_fields_unsorted_iter, AliasCommon, AliasKind, Category, ErrorType, PatternCategory, + TypeExtension, }; use roc_unify::unify::{unify, Mode, Unified::*}; @@ -76,6 +77,343 @@ pub enum TypeError { UnexposedLookup(Symbol), } +use roc_types::types::Alias; + +#[derive(Debug, Clone, Copy)] +struct DelayedAliasVariables { + start: u32, + type_variables_len: u8, + lambda_set_variables_len: u8, + recursion_variables_len: u8, +} + +impl DelayedAliasVariables { + fn recursion_variables(self, variables: &mut [Variable]) -> &mut [Variable] { + let start = self.start as usize + + (self.type_variables_len + self.lambda_set_variables_len) as usize; + let length = self.recursion_variables_len as usize; + + &mut variables[start..][..length] + } + + fn lambda_set_variables(self, variables: &mut [Variable]) -> &mut [Variable] { + let start = self.start as usize + self.type_variables_len as usize; + let length = self.lambda_set_variables_len as usize; + + &mut variables[start..][..length] + } + + fn type_variables(self, variables: &mut [Variable]) -> &mut [Variable] { + let start = self.start as usize; + let length = self.type_variables_len as usize; + + &mut variables[start..][..length] + } +} + +#[derive(Debug, Default)] +pub struct Aliases { + aliases: Vec<(Symbol, Type, DelayedAliasVariables)>, + variables: Vec, +} + +impl Aliases { + pub fn insert(&mut self, symbol: Symbol, alias: Alias) { + // debug_assert!(self.get(&symbol).is_none()); + + let alias_variables = + { + let start = self.variables.len() as _; + + self.variables + .extend(alias.type_variables.iter().map(|x| x.value.1)); + + self.variables.extend(alias.lambda_set_variables.iter().map( + |x| match x.as_inner() { + Type::Variable(v) => *v, + _ => unreachable!("lambda set type is not a variable"), + }, + )); + + let recursion_variables_len = alias.recursion_variables.len() as _; + self.variables.extend(alias.recursion_variables); + + DelayedAliasVariables { + start, + type_variables_len: alias.type_variables.len() as _, + lambda_set_variables_len: alias.lambda_set_variables.len() as _, + recursion_variables_len, + } + }; + + self.aliases.push((symbol, alias.typ, alias_variables)); + } + + fn instantiate_result_result( + subs: &mut Subs, + rank: Rank, + pools: &mut Pools, + alias_variables: AliasVariables, + ) -> Variable { + let tag_names_slice = Subs::RESULT_TAG_NAMES; + + let err_slice = SubsSlice::new(alias_variables.variables_start + 1, 1); + let ok_slice = SubsSlice::new(alias_variables.variables_start, 1); + + let variable_slices = + SubsSlice::extend_new(&mut subs.variable_slices, [err_slice, ok_slice]); + + let union_tags = UnionTags::from_slices(tag_names_slice, variable_slices); + let ext_var = Variable::EMPTY_TAG_UNION; + let flat_type = FlatType::TagUnion(union_tags, ext_var); + let content = Content::Structure(flat_type); + + register(subs, rank, pools, content) + } + + /// Instantiate an alias of the form `Foo a : [ @Foo a ]` + fn instantiate_num_at_alias( + subs: &mut Subs, + rank: Rank, + pools: &mut Pools, + tag_name_slice: SubsSlice, + range_slice: SubsSlice, + ) -> Variable { + let variable_slices = SubsSlice::extend_new(&mut subs.variable_slices, [range_slice]); + + let union_tags = UnionTags::from_slices(tag_name_slice, variable_slices); + let ext_var = Variable::EMPTY_TAG_UNION; + let flat_type = FlatType::TagUnion(union_tags, ext_var); + let content = Content::Structure(flat_type); + + register(subs, rank, pools, content) + } + + fn instantiate_builtin_aliases( + &mut self, + subs: &mut Subs, + rank: Rank, + pools: &mut Pools, + symbol: Symbol, + alias_variables: AliasVariables, + ) -> Option { + match symbol { + Symbol::RESULT_RESULT => { + let var = Self::instantiate_result_result(subs, rank, pools, alias_variables); + + Some(var) + } + Symbol::NUM_NUM => { + let var = Self::instantiate_num_at_alias( + subs, + rank, + pools, + Subs::NUM_AT_NUM, + SubsSlice::new(alias_variables.variables_start, 1), + ); + + Some(var) + } + Symbol::NUM_FLOATINGPOINT => { + let var = Self::instantiate_num_at_alias( + subs, + rank, + pools, + Subs::NUM_AT_FLOATINGPOINT, + SubsSlice::new(alias_variables.variables_start, 1), + ); + + Some(var) + } + Symbol::NUM_INTEGER => { + let var = Self::instantiate_num_at_alias( + subs, + rank, + pools, + Subs::NUM_AT_INTEGER, + SubsSlice::new(alias_variables.variables_start, 1), + ); + + Some(var) + } + Symbol::NUM_INT => { + // [ @Integer range ] + let integer_content_var = Self::instantiate_builtin_aliases( + self, + subs, + rank, + pools, + Symbol::NUM_INTEGER, + alias_variables, + ) + .unwrap(); + + // Integer range (alias variable is the same as `Int range`) + let integer_alias_variables = alias_variables; + let integer_content = Content::Alias( + Symbol::NUM_INTEGER, + integer_alias_variables, + integer_content_var, + AliasKind::Structural, + ); + let integer_alias_var = register(subs, rank, pools, integer_content); + + // [ @Num (Integer range) ] + let num_alias_variables = + AliasVariables::insert_into_subs(subs, [integer_alias_var], []); + let num_content_var = Self::instantiate_builtin_aliases( + self, + subs, + rank, + pools, + Symbol::NUM_NUM, + num_alias_variables, + ) + .unwrap(); + + let num_content = Content::Alias( + Symbol::NUM_NUM, + num_alias_variables, + num_content_var, + AliasKind::Structural, + ); + + Some(register(subs, rank, pools, num_content)) + } + Symbol::NUM_FLOAT => { + // [ @FloatingPoint range ] + let fpoint_content_var = Self::instantiate_builtin_aliases( + self, + subs, + rank, + pools, + Symbol::NUM_FLOATINGPOINT, + alias_variables, + ) + .unwrap(); + + // FloatingPoint range (alias variable is the same as `Float range`) + let fpoint_alias_variables = alias_variables; + let fpoint_content = Content::Alias( + Symbol::NUM_FLOATINGPOINT, + fpoint_alias_variables, + fpoint_content_var, + AliasKind::Structural, + ); + let fpoint_alias_var = register(subs, rank, pools, fpoint_content); + + // [ @Num (FloatingPoint range) ] + let num_alias_variables = + AliasVariables::insert_into_subs(subs, [fpoint_alias_var], []); + let num_content_var = Self::instantiate_builtin_aliases( + self, + subs, + rank, + pools, + Symbol::NUM_NUM, + num_alias_variables, + ) + .unwrap(); + + let num_content = Content::Alias( + Symbol::NUM_NUM, + num_alias_variables, + num_content_var, + AliasKind::Structural, + ); + + Some(register(subs, rank, pools, num_content)) + } + _ => None, + } + } + + fn instantiate( + &mut self, + subs: &mut Subs, + rank: Rank, + pools: &mut Pools, + arena: &bumpalo::Bump, + symbol: Symbol, + alias_variables: AliasVariables, + ) -> Result { + // hardcoded instantiations for builtin aliases + if let Some(var) = + Self::instantiate_builtin_aliases(self, subs, rank, pools, symbol, alias_variables) + { + return Ok(var); + } + + let (typ, delayed_variables) = match self.aliases.iter_mut().find(|(s, _, _)| *s == symbol) + { + None => return Err(()), + Some((_, typ, delayed_variables)) => (typ, delayed_variables), + }; + + let mut substitutions: MutMap<_, _> = Default::default(); + + for rec_var in delayed_variables + .recursion_variables(&mut self.variables) + .iter_mut() + { + let new_var = subs.fresh_unnamed_flex_var(); + substitutions.insert(*rec_var, new_var); + *rec_var = new_var; + } + + let old_type_variables = delayed_variables.type_variables(&mut self.variables); + let new_type_variables = &subs.variables[alias_variables.type_variables().indices()]; + + for (old, new) in old_type_variables.iter_mut().zip(new_type_variables) { + // if constraint gen duplicated a type these variables could be the same + // (happens very often in practice) + if *old != *new { + substitutions.insert(*old, *new); + + *old = *new; + } + } + + let old_lambda_set_variables = delayed_variables.lambda_set_variables(&mut self.variables); + let new_lambda_set_variables = + &subs.variables[alias_variables.lambda_set_variables().indices()]; + + for (old, new) in old_lambda_set_variables + .iter_mut() + .zip(new_lambda_set_variables) + { + if *old != *new { + substitutions.insert(*old, *new); + *old = *new; + } + } + + if !substitutions.is_empty() { + typ.substitute_variables(&substitutions); + } + + // assumption: an alias does not (transitively) syntactically contain itself + // (if it did it would have to be a recursive tag union) + let mut t = Type::EmptyRec; + + std::mem::swap(typ, &mut t); + + let alias_variable = type_to_variable(subs, rank, pools, arena, self, &t); + + { + match self.aliases.iter_mut().find(|(s, _, _)| *s == symbol) { + None => unreachable!(), + Some((_, typ, _)) => { + // swap typ back + std::mem::swap(typ, &mut t); + } + } + } + + Ok(alias_variable) + } +} + #[derive(Clone, Debug, Default)] pub struct Env { symbols: Vec, @@ -173,9 +511,10 @@ pub fn run( env: &Env, problems: &mut Vec, mut subs: Subs, + aliases: &mut Aliases, constraint: &Constraint, ) -> (Solved, Env) { - let env = run_in_place(constraints, env, problems, &mut subs, constraint); + let env = run_in_place(constraints, env, problems, &mut subs, aliases, constraint); (Solved(subs), env) } @@ -186,9 +525,11 @@ pub fn run_in_place( env: &Env, problems: &mut Vec, subs: &mut Subs, + aliases: &mut Aliases, constraint: &Constraint, ) -> Env { let mut pools = Pools::default(); + let state = State { env: env.clone(), mark: Mark::NONE.next(), @@ -205,7 +546,7 @@ pub fn run_in_place( rank, &mut pools, problems, - &mut MutMap::default(), + aliases, subs, constraint, ); @@ -225,6 +566,12 @@ enum Work<'a> { env: &'a Env, rank: Rank, let_con: &'a LetConstraint, + + /// The variables used to store imported types in the Subs. + /// The `Contents` are copied from the source module, but to + /// mimic `type_to_var`, we must add these variables to `Pools` + /// at the correct rank + pool_variables: &'a [Variable], }, /// The ret_con part of a let constraint that introduces rigid and/or flex variables /// @@ -234,6 +581,12 @@ enum Work<'a> { env: &'a Env, rank: Rank, let_con: &'a LetConstraint, + + /// The variables used to store imported types in the Subs. + /// The `Contents` are copied from the source module, but to + /// mimic `type_to_var`, we must add these variables to `Pools` + /// at the correct rank + pool_variables: &'a [Variable], }, } @@ -246,7 +599,7 @@ fn solve( rank: Rank, pools: &mut Pools, problems: &mut Vec, - cached_aliases: &mut MutMap, + aliases: &mut Aliases, subs: &mut Subs, constraint: &Constraint, ) -> State { @@ -277,7 +630,12 @@ fn solve( continue; } - Work::LetConNoVariables { env, rank, let_con } => { + Work::LetConNoVariables { + env, + rank, + let_con, + pool_variables, + } => { // NOTE be extremely careful with shadowing here let offset = let_con.defs_and_ret_constraint.index(); let ret_constraint = &constraints.constraints[offset + 1]; @@ -287,11 +645,13 @@ fn solve( constraints, rank, pools, - cached_aliases, + aliases, subs, let_con.def_types, ); + pools.get_mut(rank).extend(pool_variables); + let mut new_env = env.clone(); for (symbol, loc_var) in local_def_vars.iter() { new_env.insert_symbol_var_if_vacant(*symbol, loc_var.value); @@ -306,7 +666,12 @@ fn solve( continue; } - Work::LetConIntroducesVariables { env, rank, let_con } => { + Work::LetConIntroducesVariables { + env, + rank, + let_con, + pool_variables, + } => { // NOTE be extremely careful with shadowing here let offset = let_con.defs_and_ret_constraint.index(); let ret_constraint = &constraints.constraints[offset + 1]; @@ -325,11 +690,13 @@ fn solve( constraints, next_rank, pools, - cached_aliases, + aliases, subs, let_con.def_types, ); + pools.get_mut(next_rank).extend(pool_variables); + debug_assert_eq!( { let offenders = pools @@ -414,18 +781,13 @@ fn solve( copy } Eq(type_index, expectation_index, category_index, region) => { - let typ = &constraints.types[type_index.index()]; - let expectation = &constraints.expectations[expectation_index.index()]; let category = &constraints.categories[category_index.index()]; - let actual = type_to_var(subs, rank, pools, cached_aliases, typ); - let expected = type_to_var( - subs, - rank, - pools, - cached_aliases, - expectation.get_type_ref(), - ); + let actual = + either_type_index_to_var(constraints, subs, rank, pools, aliases, *type_index); + + let expectation = &constraints.expectations[expectation_index.index()]; + let expected = type_to_var(subs, rank, pools, aliases, expectation.get_type_ref()); match unify(subs, actual, expected, Mode::EQ) { Success(vars) => { @@ -457,11 +819,16 @@ fn solve( } } Store(source_index, target, _filename, _linenr) => { - let source = &constraints.types[source_index.index()]; - // a special version of Eq that is used to store types in the AST. // IT DOES NOT REPORT ERRORS! - let actual = type_to_var(subs, rank, pools, cached_aliases, source); + let actual = either_type_index_to_var( + constraints, + subs, + rank, + pools, + aliases, + *source_index, + ); let target = *target; match unify(subs, actual, target, Mode::EQ) { @@ -513,13 +880,8 @@ fn solve( let actual = deep_copy_var_in(subs, rank, pools, var, arena); let expectation = &constraints.expectations[expectation_index.index()]; - let expected = type_to_var( - subs, - rank, - pools, - cached_aliases, - expectation.get_type_ref(), - ); + let expected = + type_to_var(subs, rank, pools, aliases, expectation.get_type_ref()); match unify(subs, actual, expected, Mode::EQ) { Success(vars) => { @@ -575,18 +937,13 @@ fn solve( } Pattern(type_index, expectation_index, category_index, region) | PatternPresence(type_index, expectation_index, category_index, region) => { - let typ = &constraints.types[type_index.index()]; - let expectation = &constraints.pattern_expectations[expectation_index.index()]; let category = &constraints.pattern_categories[category_index.index()]; - let actual = type_to_var(subs, rank, pools, cached_aliases, typ); - let expected = type_to_var( - subs, - rank, - pools, - cached_aliases, - expectation.get_type_ref(), - ); + let actual = + either_type_index_to_var(constraints, subs, rank, pools, aliases, *type_index); + + let expectation = &constraints.pattern_expectations[expectation_index.index()]; + let expected = type_to_var(subs, rank, pools, aliases, expectation.get_type_ref()); let mode = match constraint { PatternPresence(..) => Mode::PRESENT, @@ -622,7 +979,7 @@ fn solve( } } } - Let(index) => { + Let(index, pool_slice) => { let let_con = &constraints.let_constraints[index.index()]; let offset = let_con.defs_and_ret_constraint.index(); @@ -632,7 +989,11 @@ fn solve( let flex_vars = &constraints.variables[let_con.flex_vars.indices()]; let rigid_vars = &constraints.variables[let_con.rigid_vars.indices()]; + let pool_variables = &constraints.variables[pool_slice.indices()]; + if matches!(&ret_constraint, True) && let_con.rigid_vars.is_empty() { + debug_assert!(pool_variables.is_empty()); + introduce(subs, rank, pools, flex_vars); // If the return expression is guaranteed to solve, @@ -650,7 +1011,12 @@ fn solve( // // Note that the LetConSimple gets the current env and rank, // and not the env/rank from after solving the defs_constraint - stack.push(Work::LetConNoVariables { env, rank, let_con }); + stack.push(Work::LetConNoVariables { + env, + rank, + let_con, + pool_variables, + }); stack.push(Work::Constraint { env, rank, @@ -692,7 +1058,12 @@ fn solve( // // Note that the LetConSimple gets the current env and rank, // and not the env/rank from after solving the defs_constraint - stack.push(Work::LetConIntroducesVariables { env, rank, let_con }); + stack.push(Work::LetConIntroducesVariables { + env, + rank, + let_con, + pool_variables, + }); stack.push(Work::Constraint { env, rank: next_rank, @@ -703,9 +1074,9 @@ fn solve( } } IsOpenType(type_index) => { - let typ = &constraints.types[type_index.index()]; + let actual = + either_type_index_to_var(constraints, subs, rank, pools, aliases, *type_index); - let actual = type_to_var(subs, rank, pools, cached_aliases, typ); let mut new_desc = subs.get(actual); match new_desc.content { Content::Structure(FlatType::TagUnion(tags, _)) => { @@ -741,12 +1112,12 @@ fn solve( let tys = &constraints.types[types.indices()]; let pattern_category = &constraints.pattern_categories[pattern_category.index()]; - let actual = type_to_var(subs, rank, pools, cached_aliases, typ); + let actual = type_to_var(subs, rank, pools, aliases, typ); let tag_ty = Type::TagUnion( vec![(tag_name.clone(), tys.to_vec())], - Box::new(Type::EmptyTagUnion), + TypeExtension::Closed, ); - let includes = type_to_var(subs, rank, pools, cached_aliases, &tag_ty); + let includes = type_to_var(subs, rank, pools, aliases, &tag_ty); match unify(subs, actual, includes, Mode::PRESENT) { Success(vars) => { @@ -818,7 +1189,7 @@ impl LocalDefVarsVec<(Symbol, Loc)> { constraints: &Constraints, rank: Rank, pools: &mut Pools, - cached_aliases: &mut MutMap, + aliases: &mut Aliases, subs: &mut Subs, def_types_slice: roc_can::constraint::DefTypes, ) -> Self { @@ -828,7 +1199,7 @@ impl LocalDefVarsVec<(Symbol, Loc)> { let mut local_def_vars = Self::with_length(types_slice.len()); for ((symbol, region), typ) in loc_symbols_slice.iter().copied().zip(types_slice) { - let var = type_to_var(subs, rank, pools, cached_aliases, typ); + let var = type_to_var(subs, rank, pools, aliases, typ); local_def_vars.push((symbol, Loc { value: var, region })); } @@ -853,11 +1224,32 @@ fn put_scratchpad(scratchpad: bumpalo::Bump) { }); } +fn either_type_index_to_var( + constraints: &Constraints, + subs: &mut Subs, + rank: Rank, + pools: &mut Pools, + aliases: &mut Aliases, + either_type_index: roc_collections::soa::EitherIndex, +) -> Variable { + match either_type_index.split() { + Ok(type_index) => { + let typ = &constraints.types[type_index.index()]; + + type_to_var(subs, rank, pools, aliases, typ) + } + Err(var_index) => { + // we cheat, and store the variable directly in the index + unsafe { Variable::from_index(var_index.index() as _) } + } + } +} + fn type_to_var( subs: &mut Subs, rank: Rank, pools: &mut Pools, - _: &mut MutMap, + aliases: &mut Aliases, typ: &Type, ) -> Variable { if let Type::Variable(var) = typ { @@ -866,7 +1258,7 @@ fn type_to_var( let mut arena = take_scratchpad(); // let var = type_to_variable(subs, rank, pools, &arena, typ); - let var = type_to_variable(subs, rank, pools, &arena, typ); + let var = type_to_variable(subs, rank, pools, &arena, aliases, typ); arena.reset(); put_scratchpad(arena); @@ -897,6 +1289,20 @@ impl RegisterVariable { Variable(var) => Direct(*var), EmptyRec => Direct(Variable::EMPTY_RECORD), EmptyTagUnion => Direct(Variable::EMPTY_TAG_UNION), + Type::DelayedAlias(AliasCommon { symbol, .. }) => { + if let Some(reserved) = Variable::get_reserved(*symbol) { + if rank.is_none() { + // reserved variables are stored with rank NONE + return Direct(reserved); + } else { + // for any other rank, we need to copy; it takes care of adjusting the rank + let copied = deep_copy_var_in(subs, rank, pools, reserved, arena); + return Direct(copied); + } + } + + Deferred + } Type::Alias { symbol, .. } => { if let Some(reserved) = Variable::get_reserved(*symbol) { if rank.is_none() { @@ -945,6 +1351,7 @@ fn type_to_variable<'a>( rank: Rank, pools: &mut Pools, arena: &'a bumpalo::Bump, + aliases: &mut Aliases, typ: &Type, ) -> Variable { use bumpalo::collections::Vec; @@ -1022,7 +1429,7 @@ fn type_to_variable<'a>( Record(fields, ext) => { // An empty fields is inefficient (but would be correct) // If hit, try to turn the value into an EmptyRecord in canonicalization - debug_assert!(!fields.is_empty() || !ext.is_empty_record()); + debug_assert!(!fields.is_empty() || !ext.is_closed()); let mut field_vars = Vec::with_capacity_in(fields.len(), arena); @@ -1039,7 +1446,10 @@ fn type_to_variable<'a>( field_vars.push((field.clone(), field_var)); } - let temp_ext_var = helper!(ext); + let temp_ext_var = match ext { + TypeExtension::Open(ext) => helper!(ext), + TypeExtension::Closed => Variable::EMPTY_RECORD, + }; let (it, new_ext_var) = gather_fields_unsorted_iter(subs, RecordFields::empty(), temp_ext_var) @@ -1062,7 +1472,7 @@ fn type_to_variable<'a>( TagUnion(tags, ext) => { // An empty tags is inefficient (but would be correct) // If hit, try to turn the value into an EmptyTagUnion in canonicalization - debug_assert!(!tags.is_empty() || !ext.is_empty_tag_union()); + debug_assert!(!tags.is_empty() || !ext.is_closed()); let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext, &mut stack); @@ -1071,7 +1481,10 @@ fn type_to_variable<'a>( register_with_known_var(subs, destination, rank, pools, content) } FunctionOrTagUnion(tag_name, symbol, ext) => { - let temp_ext_var = helper!(ext); + let temp_ext_var = match ext { + TypeExtension::Open(ext) => helper!(ext), + TypeExtension::Closed => Variable::EMPTY_TAG_UNION, + }; let (it, ext) = roc_types::types::gather_tags_unsorted_iter( subs, @@ -1093,7 +1506,7 @@ fn type_to_variable<'a>( RecursiveTagUnion(rec_var, tags, ext) => { // An empty tags is inefficient (but would be correct) // If hit, try to turn the value into an EmptyTagUnion in canonicalization - debug_assert!(!tags.is_empty() || !ext.is_empty_tag_union()); + debug_assert!(!tags.is_empty() || !ext.is_closed()); let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext, &mut stack); @@ -1117,6 +1530,51 @@ fn type_to_variable<'a>( tag_union_var } + Type::DelayedAlias(AliasCommon { + symbol, + type_arguments, + lambda_set_variables, + }) => { + let kind = AliasKind::Structural; + + let alias_variables = { + let length = type_arguments.len() + lambda_set_variables.len(); + let new_variables = VariableSubsSlice::reserve_into_subs(subs, length); + + for (target_index, (_, arg_type)) in + (new_variables.indices()).zip(type_arguments) + { + let copy_var = helper!(arg_type); + subs.variables[target_index] = copy_var; + } + + let it = (new_variables.indices().skip(type_arguments.len())) + .zip(lambda_set_variables); + for (target_index, ls) in it { + let copy_var = helper!(&ls.0); + subs.variables[target_index] = copy_var; + } + + AliasVariables { + variables_start: new_variables.start, + type_variables_len: type_arguments.len() as _, + all_variables_len: length as _, + } + }; + + let instantiated = + aliases.instantiate(subs, rank, pools, arena, *symbol, alias_variables); + + let alias_variable = match instantiated { + Err(_) => unreachable!("Alias {:?} is not available", symbol), + Ok(alias_variable) => alias_variable, + }; + + let content = Content::Alias(*symbol, alias_variables, alias_variable, kind); + + register_with_known_var(subs, destination, rank, pools, content) + } + Type::Alias { symbol, type_arguments, @@ -1187,7 +1645,8 @@ fn type_to_variable<'a>( }; // cannot use helper! here because this variable may be involved in unification below - let alias_variable = type_to_variable(subs, rank, pools, arena, alias_type); + let alias_variable = + type_to_variable(subs, rank, pools, arena, aliases, alias_type); // TODO(opaques): I think host-exposed aliases should always be structural // (when does it make sense to give a host an opaque type?) let content = Content::Alias( @@ -1232,7 +1691,7 @@ fn roc_result_to_var<'a>( ) -> Variable { match result_type { Type::TagUnion(tags, ext) => { - debug_assert!(ext.is_empty_tag_union()); + debug_assert!(ext.is_closed()); debug_assert!(tags.len() == 2); if let [(err, err_args), (ok, ok_args)] = &tags[..] { @@ -1476,40 +1935,46 @@ fn type_to_union_tags<'a>( pools: &mut Pools, arena: &'_ bumpalo::Bump, tags: &'a [(TagName, Vec)], - ext: &'a Type, + ext: &'a TypeExtension, stack: &mut bumpalo::collections::Vec<'_, TypeToVar<'a>>, ) -> (UnionTags, Variable) { use bumpalo::collections::Vec; let sorted = tags.len() == 1 || sorted_no_duplicates(tags); - if ext.is_empty_tag_union() { - let ext = Variable::EMPTY_TAG_UNION; + match ext { + TypeExtension::Closed => { + let ext = Variable::EMPTY_TAG_UNION; - let union_tags = if sorted { - insert_tags_fast_path(subs, rank, pools, arena, tags, stack) - } else { - let tag_vars = Vec::with_capacity_in(tags.len(), arena); - insert_tags_slow_path(subs, rank, pools, arena, tags, tag_vars, stack) - }; + let union_tags = if sorted { + insert_tags_fast_path(subs, rank, pools, arena, tags, stack) + } else { + let tag_vars = Vec::with_capacity_in(tags.len(), arena); + insert_tags_slow_path(subs, rank, pools, arena, tags, tag_vars, stack) + }; - (union_tags, ext) - } else { - let mut tag_vars = Vec::with_capacity_in(tags.len(), arena); + (union_tags, ext) + } + TypeExtension::Open(ext) => { + let mut tag_vars = Vec::with_capacity_in(tags.len(), arena); - let temp_ext_var = RegisterVariable::with_stack(subs, rank, pools, arena, ext, stack); - let (it, ext) = - roc_types::types::gather_tags_unsorted_iter(subs, UnionTags::default(), temp_ext_var); + let temp_ext_var = RegisterVariable::with_stack(subs, rank, pools, arena, ext, stack); + let (it, ext) = roc_types::types::gather_tags_unsorted_iter( + subs, + UnionTags::default(), + temp_ext_var, + ); - tag_vars.extend(it.map(|(n, v)| (n.clone(), v))); + tag_vars.extend(it.map(|(n, v)| (n.clone(), v))); - let union_tags = if tag_vars.is_empty() && sorted { - insert_tags_fast_path(subs, rank, pools, arena, tags, stack) - } else { - insert_tags_slow_path(subs, rank, pools, arena, tags, tag_vars, stack) - }; + let union_tags = if tag_vars.is_empty() && sorted { + insert_tags_fast_path(subs, rank, pools, arena, tags, stack) + } else { + insert_tags_slow_path(subs, rank, pools, arena, tags, tag_vars, stack) + }; - (union_tags, ext) + (union_tags, ext) + } } } @@ -1821,7 +2286,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)); } @@ -1967,7 +2432,7 @@ fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, initial: Variable) { let var = *var; let args = *args; - stack.extend(var_slice!(args.variables())); + stack.extend(var_slice!(args.all_variables())); stack.push(var); } @@ -2216,7 +2681,9 @@ fn deep_copy_var_help( Alias(symbol, arguments, real_type_var, kind) => { let new_variables = SubsSlice::reserve_into_subs(subs, arguments.all_variables_len as _); - for (target_index, var_index) in (new_variables.indices()).zip(arguments.variables()) { + for (target_index, var_index) in + (new_variables.indices()).zip(arguments.all_variables()) + { let var = subs[var_index]; let copy_var = deep_copy_var_help(subs, max_rank, pools, visited, var); subs.variables[target_index] = copy_var; diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index 9e1f87e524..fb88e11430 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -10,7 +10,6 @@ mod helpers; #[cfg(test)] mod solve_expr { use crate::helpers::with_larger_debug_stack; - use roc_collections::all::MutMap; use roc_types::pretty_print::{content_to_string, name_all_type_vars}; // HELPERS @@ -47,7 +46,7 @@ mod solve_expr { module_src = &temp; } - let exposed_types = MutMap::default(); + let exposed_types = Default::default(); let loaded = { let dir = tempdir()?; let filename = PathBuf::from("Test.roc"); @@ -5270,6 +5269,7 @@ mod solve_expr { toI32: Num.toI32, toI64: Num.toI64, toI128: Num.toI128, + toNat: Num.toNat, toU8: Num.toU8, toU16: Num.toU16, toU32: Num.toU32, @@ -5278,7 +5278,7 @@ mod solve_expr { } "# ), - r#"{ toI128 : Int * -> I128, toI16 : Int * -> I16, toI32 : Int * -> I32, toI64 : Int * -> I64, toI8 : Int * -> I8, toU128 : Int * -> U128, toU16 : Int * -> U16, toU32 : Int * -> U32, toU64 : Int * -> U64, toU8 : Int * -> U8 }"#, + r#"{ toI128 : Int * -> I128, toI16 : Int * -> I16, toI32 : Int * -> I32, toI64 : Int * -> I64, toI8 : Int * -> I8, toNat : Int * -> Nat, toU128 : Int * -> U128, toU16 : Int * -> U16, toU32 : Int * -> U32, toU64 : Int * -> U64, toU8 : Int * -> U8 }"#, ) } @@ -5580,4 +5580,57 @@ mod solve_expr { r#"[ A, B, C ]"#, ) } + + #[test] + // https://github.com/rtfeldman/roc/issues/2702 + fn tag_inclusion_behind_opaque() { + infer_eq_without_problem( + indoc!( + r#" + Outer k := [ Empty, Wrapped k ] + + insert : Outer k, k -> Outer k + insert = \m, var -> + when m is + $Outer Empty -> $Outer (Wrapped var) + $Outer (Wrapped _) -> $Outer (Wrapped var) + + insert + "# + ), + r#"Outer k, k -> Outer k"#, + ) + } + + #[test] + fn tag_inclusion_behind_opaque_infer() { + infer_eq_without_problem( + indoc!( + r#" + Outer k := [ Empty, Wrapped k ] + + when ($Outer Empty) is + $Outer Empty -> $Outer (Wrapped "") + $Outer (Wrapped k) -> $Outer (Wrapped k) + "# + ), + r#"Outer Str"#, + ) + } + + #[test] + fn tag_inclusion_behind_opaque_infer_single_ctor() { + infer_eq_without_problem( + indoc!( + r#" + Outer := [ A, B ] + + when ($Outer A) is + $Outer A -> $Outer A + $Outer B -> $Outer B + "# + ), + r#"Outer"#, + ) + } } diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index 41635ea927..dd870a6192 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -798,6 +798,34 @@ fn list_walk_until_sum() { ); } +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn list_walk_imlements_position() { + assert_evals_to!( + r#" + Option a : [ Some a, None ] + + find : List a, a -> Option Nat + find = \list, needle -> + findHelp list needle + |> .v + + findHelp = \list, needle -> + List.walkUntil list { n: 0, v: None } \{ n, v }, element -> + if element == needle then + Stop { n, v: Some n } + else + Continue { n: n + 1, v } + + when find [ 1, 2, 3 ] 3 is + None -> 0 + Some v -> v + "#, + 2, + i64 + ); +} + #[test] #[cfg(any(feature = "gen-llvm"))] fn list_walk_until_even_prefix_sum() { diff --git a/compiler/test_gen/src/gen_num.rs b/compiler/test_gen/src/gen_num.rs index 1ce68d2524..8142e21636 100644 --- a/compiler/test_gen/src/gen_num.rs +++ b/compiler/test_gen/src/gen_num.rs @@ -2179,6 +2179,12 @@ to_int_tests! { to_u128_same_width, "15i128", 15 to_u128_extend, "15i8", 15 ) + "Num.toNat", usize, ( + to_nat_same_width, "15i64", 15, ["gen-wasm"] + to_nat_extend, "15i8", 15, ["gen-wasm"] + to_nat_truncate, "115i128", 115 + to_nat_truncate_wraps, "10_000_000_000_000_000_000_000i128", 1864712049423024128 + ) } macro_rules! to_int_checked_tests { @@ -2312,6 +2318,18 @@ to_int_checked_tests! { to_u128_checked_same_width_signed_fits, "15i128", 15 to_u128_checked_same_width_signed_oob, "-1i128", None ) + "Num.toNatChecked", usize, ( + to_nat_checked_smaller_width_pos, "15i8", 15 + to_nat_checked_smaller_width_neg_oob, "-15i8", None + to_nat_checked_same, "15u64", 15 + to_nat_checked_same_width_signed_fits, "15i64", 15 + to_nat_checked_same_width_signed_oob, "-1i64", None + to_nat_checked_larger_width_signed_fits_pos, "15i128", 15 + to_nat_checked_larger_width_signed_oob_pos, "18446744073709551616i128", None + to_nat_checked_larger_width_signed_oob_neg, "-1i128", None + to_nat_checked_larger_width_unsigned_fits_pos, "15u128", 15 + to_nat_checked_larger_width_unsigned_oob_pos, "18446744073709551616u128", None + ) } #[test] @@ -2781,3 +2799,63 @@ fn to_float_f64() { f64 ) } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +// https://github.com/rtfeldman/roc/issues/2696 +fn upcast_of_int_is_zext() { + assert_evals_to!( + indoc!( + r#" + Num.toU16 0b1000_0000u8 + "# + ), + 128, + u16 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +// https://github.com/rtfeldman/roc/issues/2696 +fn upcast_of_int_checked_is_zext() { + assert_evals_to!( + indoc!( + r#" + when Num.toU16Checked 0b1000_0000u8 is + Ok 128u16 -> 1u8 + _ -> 0u8 + "# + ), + 1, + u16 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn modulo_of_unsigned() { + assert_evals_to!( + indoc!( + r#" + 0b1111_1111u8 % 64 + "# + ), + 63, + u8 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn div_of_unsigned() { + assert_evals_to!( + indoc!( + r#" + 0b1111_1111u8 // 2 + "# + ), + 127, + u8 + ) +} diff --git a/compiler/test_gen/src/gen_primitives.rs b/compiler/test_gen/src/gen_primitives.rs index 30df72816a..04d8a122e3 100644 --- a/compiler/test_gen/src/gen_primitives.rs +++ b/compiler/test_gen/src/gen_primitives.rs @@ -3228,3 +3228,19 @@ fn issue_2322() { i64 ) } + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn box_and_unbox_string() { + assert_evals_to!( + indoc!( + r#" + Box.unbox (Box.box (Str.concat "Leverage " "agile frameworks to provide a robust synopsis for high level overviews")) + "# + ), + RocStr::from( + "Leverage agile frameworks to provide a robust synopsis for high level overviews" + ), + RocStr + ) +} diff --git a/compiler/test_gen/src/gen_tags.rs b/compiler/test_gen/src/gen_tags.rs index 4cab64ec87..eefcb272cf 100644 --- a/compiler/test_gen/src/gen_tags.rs +++ b/compiler/test_gen/src/gen_tags.rs @@ -1548,3 +1548,35 @@ fn issue_1162() { u8 ) } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn polymorphic_tag() { + assert_evals_to!( + indoc!( + r#" + x : [ Y U8 ]* + x = Y 3 + x + "# + ), + 3, // Y is a newtype, it gets unwrapped + u8 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn issue_2725_alias_polymorphic_lambda() { + assert_evals_to!( + indoc!( + r#" + wrap = \value -> Tag value + wrapIt = wrap + wrapIt 42 + "# + ), + 42, // Tag is a newtype, it gets unwrapped + i64 + ) +} diff --git a/compiler/test_gen/src/helpers/dev.rs b/compiler/test_gen/src/helpers/dev.rs index 8942c7356f..76d6179624 100644 --- a/compiler/test_gen/src/helpers/dev.rs +++ b/compiler/test_gen/src/helpers/dev.rs @@ -48,14 +48,13 @@ pub fn helper( module_src = &temp; } - let exposed_types = MutMap::default(); let loaded = roc_load::file::load_and_monomorphize_from_str( arena, filename, module_src, &stdlib, src_dir, - exposed_types, + Default::default(), roc_target::TargetInfo::default_x86_64(), ); diff --git a/compiler/test_gen/src/helpers/llvm.rs b/compiler/test_gen/src/helpers/llvm.rs index 35c4e7ab72..3708d5192a 100644 --- a/compiler/test_gen/src/helpers/llvm.rs +++ b/compiler/test_gen/src/helpers/llvm.rs @@ -3,7 +3,7 @@ use inkwell::module::Module; use libloading::Library; use roc_build::link::module_to_dylib; use roc_build::program::FunctionIterator; -use roc_collections::all::{MutMap, MutSet}; +use roc_collections::all::MutSet; use roc_gen_llvm::llvm::externs::add_default_roc_externs; use roc_mono::ir::OptLevel; use roc_region::all::LineInfo; @@ -51,14 +51,13 @@ fn create_llvm_module<'a>( module_src = &temp; } - let exposed_types = MutMap::default(); let loaded = roc_load::file::load_and_monomorphize_from_str( arena, filename, module_src, stdlib, src_dir, - exposed_types, + Default::default(), target_info, ); diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index 02a02bb8d7..ef22688018 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -86,14 +86,13 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32Result>( module_src = &temp; } - let exposed_types = MutMap::default(); let loaded = roc_load::file::load_and_monomorphize_from_str( arena, filename, module_src, stdlib, src_dir, - exposed_types, + Default::default(), roc_target::TargetInfo::default_wasm32(), ); diff --git a/compiler/test_mono/generated/issue_2725_alias_polymorphic_lambda.txt b/compiler/test_mono/generated/issue_2725_alias_polymorphic_lambda.txt new file mode 100644 index 0000000000..1eba5a003d --- /dev/null +++ b/compiler/test_mono/generated/issue_2725_alias_polymorphic_lambda.txt @@ -0,0 +1,7 @@ +procedure Test.2 (Test.3): + ret Test.3; + +procedure Test.0 (): + let Test.6 : I64 = 42i64; + let Test.5 : I64 = CallByName Test.2 Test.6; + ret Test.5; diff --git a/compiler/test_mono/generated/list_cannot_update_inplace.txt b/compiler/test_mono/generated/list_cannot_update_inplace.txt index 08123887b1..42ba0d2a8e 100644 --- a/compiler/test_mono/generated/list_cannot_update_inplace.txt +++ b/compiler/test_mono/generated/list_cannot_update_inplace.txt @@ -1,12 +1,25 @@ procedure List.4 (#Attr.2, #Attr.3, #Attr.4): - let Test.19 : U64 = lowlevel ListLen #Attr.2; - let Test.17 : Int1 = lowlevel NumLt #Attr.3 Test.19; + let Test.24 : U64 = lowlevel ListLen #Attr.2; + let Test.17 : Int1 = lowlevel NumLt #Attr.3 Test.24; if Test.17 then - let Test.18 : List I64 = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; + let Test.19 : {List I64, I64} = CallByName List.58 #Attr.2 #Attr.3 #Attr.4; + let Test.18 : List I64 = StructAtIndex 0 Test.19; + inc Test.18; + dec Test.19; ret Test.18; else ret #Attr.2; +procedure List.58 (#Attr.2, #Attr.3, #Attr.4): + let Test.23 : U64 = lowlevel ListLen #Attr.2; + let Test.21 : Int1 = lowlevel NumLt #Attr.3 Test.23; + if Test.21 then + let Test.22 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; + ret Test.22; + else + let Test.20 : {List I64, I64} = Struct {#Attr.2, #Attr.4}; + ret Test.20; + procedure List.7 (#Attr.2): let Test.9 : U64 = lowlevel ListLen #Attr.2; ret Test.9; diff --git a/compiler/test_mono/generated/list_pass_to_function.txt b/compiler/test_mono/generated/list_pass_to_function.txt index b235671dcf..99a6e73255 100644 --- a/compiler/test_mono/generated/list_pass_to_function.txt +++ b/compiler/test_mono/generated/list_pass_to_function.txt @@ -1,12 +1,25 @@ procedure List.4 (#Attr.2, #Attr.3, #Attr.4): - let Test.11 : U64 = lowlevel ListLen #Attr.2; - let Test.9 : Int1 = lowlevel NumLt #Attr.3 Test.11; + let Test.16 : U64 = lowlevel ListLen #Attr.2; + let Test.9 : Int1 = lowlevel NumLt #Attr.3 Test.16; if Test.9 then - let Test.10 : List I64 = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; + let Test.11 : {List I64, I64} = CallByName List.58 #Attr.2 #Attr.3 #Attr.4; + let Test.10 : List I64 = StructAtIndex 0 Test.11; + inc Test.10; + dec Test.11; ret Test.10; else ret #Attr.2; +procedure List.58 (#Attr.2, #Attr.3, #Attr.4): + let Test.15 : U64 = lowlevel ListLen #Attr.2; + let Test.13 : Int1 = lowlevel NumLt #Attr.3 Test.15; + if Test.13 then + let Test.14 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; + ret Test.14; + else + let Test.12 : {List I64, I64} = Struct {#Attr.2, #Attr.4}; + ret Test.12; + procedure Test.2 (Test.3): let Test.6 : U64 = 0i64; let Test.7 : I64 = 0i64; diff --git a/compiler/test_mono/generated/monomorphized_ints_aliased.txt b/compiler/test_mono/generated/monomorphized_ints_aliased.txt index c55c6b4f07..b461c2a04e 100644 --- a/compiler/test_mono/generated/monomorphized_ints_aliased.txt +++ b/compiler/test_mono/generated/monomorphized_ints_aliased.txt @@ -2,22 +2,20 @@ procedure Num.22 (#Attr.2, #Attr.3): let Test.12 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; ret Test.12; -procedure Test.4 (Test.7, Test.8): - let Test.17 : U64 = 1i64; - ret Test.17; - -procedure Test.4 (Test.7, Test.8): +procedure Test.5 (Test.7, Test.8): let Test.18 : U64 = 1i64; ret Test.18; +procedure Test.6 (Test.7, Test.8): + let Test.15 : U64 = 1i64; + ret Test.15; + procedure Test.0 (): - let Test.4 : {} = Struct {}; - let Test.4 : {} = Struct {}; - let Test.15 : U8 = 100i64; - let Test.16 : U32 = 100i64; - let Test.10 : U64 = CallByName Test.4 Test.15 Test.16; + let Test.16 : U8 = 100i64; + let Test.17 : U32 = 100i64; + let Test.10 : U64 = CallByName Test.5 Test.16 Test.17; let Test.13 : U32 = 100i64; let Test.14 : U8 = 100i64; - let Test.11 : U64 = CallByName Test.4 Test.13 Test.14; + let Test.11 : U64 = CallByName Test.6 Test.13 Test.14; let Test.9 : U64 = CallByName Num.22 Test.10 Test.11; ret Test.9; diff --git a/compiler/test_mono/generated/quicksort_swap.txt b/compiler/test_mono/generated/quicksort_swap.txt index d42217aaa2..467ec71cf3 100644 --- a/compiler/test_mono/generated/quicksort_swap.txt +++ b/compiler/test_mono/generated/quicksort_swap.txt @@ -1,59 +1,72 @@ procedure List.3 (#Attr.2, #Attr.3): - let Test.37 : U64 = lowlevel ListLen #Attr.2; - let Test.34 : Int1 = lowlevel NumLt #Attr.3 Test.37; - if Test.34 then - let Test.36 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - let Test.35 : [C {}, C I64] = Ok Test.36; - ret Test.35; + let Test.42 : U64 = lowlevel ListLen #Attr.2; + let Test.39 : Int1 = lowlevel NumLt #Attr.3 Test.42; + if Test.39 then + let Test.41 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + let Test.40 : [C {}, C I64] = Ok Test.41; + ret Test.40; else - let Test.33 : {} = Struct {}; - let Test.32 : [C {}, C I64] = Err Test.33; - ret Test.32; + let Test.38 : {} = Struct {}; + let Test.37 : [C {}, C I64] = Err Test.38; + ret Test.37; procedure List.4 (#Attr.2, #Attr.3, #Attr.4): - let Test.15 : U64 = lowlevel ListLen #Attr.2; - let Test.13 : Int1 = lowlevel NumLt #Attr.3 Test.15; + let Test.20 : U64 = lowlevel ListLen #Attr.2; + let Test.13 : Int1 = lowlevel NumLt #Attr.3 Test.20; if Test.13 then - let Test.14 : List I64 = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; + let Test.15 : {List I64, I64} = CallByName List.58 #Attr.2 #Attr.3 #Attr.4; + let Test.14 : List I64 = StructAtIndex 0 Test.15; + inc Test.14; + dec Test.15; ret Test.14; else ret #Attr.2; +procedure List.58 (#Attr.2, #Attr.3, #Attr.4): + let Test.19 : U64 = lowlevel ListLen #Attr.2; + let Test.17 : Int1 = lowlevel NumLt #Attr.3 Test.19; + if Test.17 then + let Test.18 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; + ret Test.18; + else + let Test.16 : {List I64, I64} = Struct {#Attr.2, #Attr.4}; + ret Test.16; + procedure Test.1 (Test.2): - let Test.38 : U64 = 0i64; - let Test.30 : [C {}, C I64] = CallByName List.3 Test.2 Test.38; - let Test.31 : U64 = 0i64; - let Test.29 : [C {}, C I64] = CallByName List.3 Test.2 Test.31; - let Test.8 : {[C {}, C I64], [C {}, C I64]} = Struct {Test.29, Test.30}; - joinpoint Test.26: - let Test.17 : List I64 = Array []; - ret Test.17; + let Test.43 : U64 = 0i64; + let Test.35 : [C {}, C I64] = CallByName List.3 Test.2 Test.43; + let Test.36 : U64 = 0i64; + let Test.34 : [C {}, C I64] = CallByName List.3 Test.2 Test.36; + let Test.8 : {[C {}, C I64], [C {}, C I64]} = Struct {Test.34, Test.35}; + joinpoint Test.31: + let Test.22 : List I64 = Array []; + ret Test.22; in - let Test.23 : [C {}, C I64] = StructAtIndex 1 Test.8; - let Test.24 : U8 = 1i64; - let Test.25 : U8 = GetTagId Test.23; - let Test.28 : Int1 = lowlevel Eq Test.24 Test.25; - if Test.28 then - let Test.20 : [C {}, C I64] = StructAtIndex 0 Test.8; - let Test.21 : U8 = 1i64; - let Test.22 : U8 = GetTagId Test.20; - let Test.27 : Int1 = lowlevel Eq Test.21 Test.22; - if Test.27 then - let Test.19 : [C {}, C I64] = StructAtIndex 0 Test.8; - let Test.4 : I64 = UnionAtIndex (Id 1) (Index 0) Test.19; - let Test.18 : [C {}, C I64] = StructAtIndex 1 Test.8; - let Test.5 : I64 = UnionAtIndex (Id 1) (Index 0) Test.18; - let Test.16 : U64 = 0i64; - let Test.10 : List I64 = CallByName List.4 Test.2 Test.16 Test.5; + let Test.28 : [C {}, C I64] = StructAtIndex 1 Test.8; + let Test.29 : U8 = 1i64; + let Test.30 : U8 = GetTagId Test.28; + let Test.33 : Int1 = lowlevel Eq Test.29 Test.30; + if Test.33 then + let Test.25 : [C {}, C I64] = StructAtIndex 0 Test.8; + let Test.26 : U8 = 1i64; + let Test.27 : U8 = GetTagId Test.25; + let Test.32 : Int1 = lowlevel Eq Test.26 Test.27; + if Test.32 then + let Test.24 : [C {}, C I64] = StructAtIndex 0 Test.8; + let Test.4 : I64 = UnionAtIndex (Id 1) (Index 0) Test.24; + let Test.23 : [C {}, C I64] = StructAtIndex 1 Test.8; + let Test.5 : I64 = UnionAtIndex (Id 1) (Index 0) Test.23; + let Test.21 : U64 = 0i64; + let Test.10 : List I64 = CallByName List.4 Test.2 Test.21 Test.5; let Test.11 : U64 = 0i64; let Test.9 : List I64 = CallByName List.4 Test.10 Test.11 Test.4; ret Test.9; else dec Test.2; - jump Test.26; + jump Test.31; else dec Test.2; - jump Test.26; + jump Test.31; procedure Test.0 (): let Test.7 : List I64 = Array [1i64, 2i64]; diff --git a/compiler/test_mono/generated/rigids.txt b/compiler/test_mono/generated/rigids.txt index 9a030e6c5e..bb5a06de40 100644 --- a/compiler/test_mono/generated/rigids.txt +++ b/compiler/test_mono/generated/rigids.txt @@ -1,55 +1,68 @@ procedure List.3 (#Attr.2, #Attr.3): - let Test.39 : U64 = lowlevel ListLen #Attr.2; - let Test.36 : Int1 = lowlevel NumLt #Attr.3 Test.39; - if Test.36 then - let Test.38 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - let Test.37 : [C {}, C I64] = Ok Test.38; - ret Test.37; + let Test.44 : U64 = lowlevel ListLen #Attr.2; + let Test.41 : Int1 = lowlevel NumLt #Attr.3 Test.44; + if Test.41 then + let Test.43 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + let Test.42 : [C {}, C I64] = Ok Test.43; + ret Test.42; else - let Test.35 : {} = Struct {}; - let Test.34 : [C {}, C I64] = Err Test.35; - ret Test.34; + let Test.40 : {} = Struct {}; + let Test.39 : [C {}, C I64] = Err Test.40; + ret Test.39; procedure List.4 (#Attr.2, #Attr.3, #Attr.4): - let Test.19 : U64 = lowlevel ListLen #Attr.2; - let Test.17 : Int1 = lowlevel NumLt #Attr.3 Test.19; + let Test.24 : U64 = lowlevel ListLen #Attr.2; + let Test.17 : Int1 = lowlevel NumLt #Attr.3 Test.24; if Test.17 then - let Test.18 : List I64 = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4; + let Test.19 : {List I64, I64} = CallByName List.58 #Attr.2 #Attr.3 #Attr.4; + let Test.18 : List I64 = StructAtIndex 0 Test.19; + inc Test.18; + dec Test.19; ret Test.18; else ret #Attr.2; -procedure Test.1 (Test.2, Test.3, Test.4): - let Test.33 : [C {}, C I64] = CallByName List.3 Test.4 Test.3; - let Test.32 : [C {}, C I64] = CallByName List.3 Test.4 Test.2; - let Test.13 : {[C {}, C I64], [C {}, C I64]} = Struct {Test.32, Test.33}; - joinpoint Test.29: - let Test.20 : List I64 = Array []; +procedure List.58 (#Attr.2, #Attr.3, #Attr.4): + let Test.23 : U64 = lowlevel ListLen #Attr.2; + let Test.21 : Int1 = lowlevel NumLt #Attr.3 Test.23; + if Test.21 then + let Test.22 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; + ret Test.22; + else + let Test.20 : {List I64, I64} = Struct {#Attr.2, #Attr.4}; ret Test.20; + +procedure Test.1 (Test.2, Test.3, Test.4): + let Test.38 : [C {}, C I64] = CallByName List.3 Test.4 Test.3; + let Test.37 : [C {}, C I64] = CallByName List.3 Test.4 Test.2; + let Test.13 : {[C {}, C I64], [C {}, C I64]} = Struct {Test.37, Test.38}; + joinpoint Test.34: + let Test.25 : List I64 = Array []; + ret Test.25; in - let Test.26 : [C {}, C I64] = StructAtIndex 1 Test.13; - let Test.27 : U8 = 1i64; - let Test.28 : U8 = GetTagId Test.26; - let Test.31 : Int1 = lowlevel Eq Test.27 Test.28; - if Test.31 then - let Test.23 : [C {}, C I64] = StructAtIndex 0 Test.13; - let Test.24 : U8 = 1i64; - let Test.25 : U8 = GetTagId Test.23; - let Test.30 : Int1 = lowlevel Eq Test.24 Test.25; - if Test.30 then - let Test.22 : [C {}, C I64] = StructAtIndex 0 Test.13; - let Test.6 : I64 = UnionAtIndex (Id 1) (Index 0) Test.22; - let Test.21 : [C {}, C I64] = StructAtIndex 1 Test.13; - let Test.7 : I64 = UnionAtIndex (Id 1) (Index 0) Test.21; + let Test.31 : [C {}, C I64] = StructAtIndex 1 Test.13; + let Test.32 : U8 = 1i64; + let Test.33 : U8 = GetTagId Test.31; + let Test.36 : Int1 = lowlevel Eq Test.32 Test.33; + if Test.36 then + let Test.28 : [C {}, C I64] = StructAtIndex 0 Test.13; + let Test.29 : U8 = 1i64; + let Test.30 : U8 = GetTagId Test.28; + let Test.35 : Int1 = lowlevel Eq Test.29 Test.30; + if Test.35 then + let Test.27 : [C {}, C I64] = StructAtIndex 0 Test.13; + let Test.6 : I64 = UnionAtIndex (Id 1) (Index 0) Test.27; + let Test.26 : [C {}, C I64] = StructAtIndex 1 Test.13; + let Test.7 : I64 = UnionAtIndex (Id 1) (Index 0) Test.26; let Test.15 : List I64 = CallByName List.4 Test.4 Test.2 Test.7; let Test.14 : List I64 = CallByName List.4 Test.15 Test.3 Test.6; ret Test.14; else dec Test.4; - jump Test.29; + jump Test.34; else dec Test.4; - jump Test.29; + jump Test.34; procedure Test.0 (): let Test.10 : U64 = 0i64; diff --git a/compiler/test_mono/generated/specialize_closures.txt b/compiler/test_mono/generated/specialize_closures.txt index 8d39d6b960..1d6423ffb7 100644 --- a/compiler/test_mono/generated/specialize_closures.txt +++ b/compiler/test_mono/generated/specialize_closures.txt @@ -36,9 +36,9 @@ procedure Test.8 (Test.11, #Attr.12): ret Test.11; procedure Test.0 (): + let Test.5 : I64 = 2i64; let Test.6 : Int1 = true; let Test.4 : I64 = 1i64; - let Test.5 : I64 = 2i64; joinpoint Test.22 Test.14: let Test.15 : I64 = 42i64; let Test.13 : I64 = CallByName Test.1 Test.14 Test.15; diff --git a/compiler/test_mono/generated/specialize_lowlevel.txt b/compiler/test_mono/generated/specialize_lowlevel.txt index 9706cdd1bf..8a15d461e3 100644 --- a/compiler/test_mono/generated/specialize_lowlevel.txt +++ b/compiler/test_mono/generated/specialize_lowlevel.txt @@ -17,8 +17,8 @@ procedure Test.7 (Test.9, #Attr.12): ret Test.20; procedure Test.0 (): - let Test.4 : I64 = 1i64; let Test.5 : I64 = 2i64; + let Test.4 : I64 = 1i64; let Test.12 : I64 = 42i64; joinpoint Test.19 Test.13: let Test.14 : U8 = GetTagId Test.13; diff --git a/compiler/test_mono/src/tests.rs b/compiler/test_mono/src/tests.rs index 28bb791955..9e12dcb901 100644 --- a/compiler/test_mono/src/tests.rs +++ b/compiler/test_mono/src/tests.rs @@ -96,15 +96,13 @@ fn compiles_to_ir(test_name: &str, src: &str) { module_src = &temp; } - let exposed_types = MutMap::default(); - let loaded = roc_load::file::load_and_monomorphize_from_str( arena, filename, module_src, &stdlib, src_dir, - exposed_types, + Default::default(), TARGET_INFO, ); @@ -1267,6 +1265,17 @@ fn issue_2535_polymorphic_fields_referenced_in_list() { ) } +#[mono_test] +fn issue_2725_alias_polymorphic_lambda() { + indoc!( + r#" + wrap = \value -> Tag value + wrapIt = wrap + wrapIt 42 + "# + ) +} + // #[ignore] // #[mono_test] // fn static_str_closure() { diff --git a/compiler/types/src/builtin_aliases.rs b/compiler/types/src/builtin_aliases.rs index 9b6264adda..16d8512e93 100644 --- a/compiler/types/src/builtin_aliases.rs +++ b/compiler/types/src/builtin_aliases.rs @@ -971,6 +971,11 @@ pub fn result_type(a: SolvedType, e: SolvedType) -> SolvedType { ) } +#[inline(always)] +pub fn box_type(a: SolvedType) -> SolvedType { + SolvedType::Apply(Symbol::BOX_BOX_TYPE, vec![a]) +} + #[inline(always)] fn result_alias_content(a: SolvedType, e: SolvedType) -> SolvedType { SolvedType::TagUnion( diff --git a/compiler/types/src/solved_types.rs b/compiler/types/src/solved_types.rs index 076be203d3..6516dace02 100644 --- a/compiler/types/src/solved_types.rs +++ b/compiler/types/src/solved_types.rs @@ -1,5 +1,5 @@ use crate::subs::{FlatType, GetSubsSlice, Subs, VarId, VarStore, Variable}; -use crate::types::{AliasKind, Problem, RecordField, Type}; +use crate::types::{AliasCommon, AliasKind, Problem, RecordField, Type, TypeExtension}; use roc_collections::all::{ImMap, MutSet, SendMap}; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; @@ -55,6 +55,8 @@ pub enum SolvedType { /// A type alias /// TODO transmit lambda sets! + DelayedAlias(Symbol, Vec<(Lowercase, SolvedType)>, Vec), + Alias( Symbol, Vec<(Lowercase, SolvedType)>, @@ -111,7 +113,11 @@ impl SolvedType { SolvedType::Func(solved_args, Box::new(solved_closure), Box::new(solved_ret)) } Record(fields, box_ext) => { - let solved_ext = Self::from_type(solved_subs, box_ext); + let solved_ext = match box_ext { + TypeExtension::Open(ext) => Self::from_type(solved_subs, ext), + TypeExtension::Closed => SolvedType::EmptyRecord, + }; + let mut solved_fields = Vec::with_capacity(fields.len()); for (label, field) in fields { @@ -137,7 +143,11 @@ impl SolvedType { SolvedType::TagUnion(solved_tags, Box::new(solved_ext)) } TagUnion(tags, box_ext) => { - let solved_ext = Self::from_type(solved_subs, box_ext); + let solved_ext = match box_ext { + TypeExtension::Open(ext) => Self::from_type(solved_subs, ext), + TypeExtension::Closed => SolvedType::EmptyTagUnion, + }; + let mut solved_tags = Vec::with_capacity(tags.len()); for (tag_name, types) in tags { let mut solved_types = Vec::with_capacity(types.len()); @@ -153,11 +163,19 @@ impl SolvedType { SolvedType::TagUnion(solved_tags, Box::new(solved_ext)) } FunctionOrTagUnion(tag_name, symbol, box_ext) => { - let solved_ext = Self::from_type(solved_subs, box_ext); + let solved_ext = match box_ext { + TypeExtension::Open(ext) => Self::from_type(solved_subs, ext), + TypeExtension::Closed => SolvedType::EmptyTagUnion, + }; + SolvedType::FunctionOrTagUnion(tag_name.clone(), *symbol, Box::new(solved_ext)) } RecursiveTagUnion(rec_var, tags, box_ext) => { - let solved_ext = Self::from_type(solved_subs, box_ext); + let solved_ext = match box_ext { + TypeExtension::Open(ext) => Self::from_type(solved_subs, ext), + TypeExtension::Closed => SolvedType::EmptyTagUnion, + }; + let mut solved_tags = Vec::with_capacity(tags.len()); for (tag_name, types) in tags { let mut solved_types = Vec::with_capacity(types.len()); @@ -177,6 +195,25 @@ impl SolvedType { ) } Erroneous(problem) => SolvedType::Erroneous(problem.clone()), + DelayedAlias(AliasCommon { + symbol, + type_arguments, + lambda_set_variables, + }) => { + let mut solved_args = Vec::with_capacity(type_arguments.len()); + + for (name, var) in type_arguments { + solved_args.push((name.clone(), Self::from_type(solved_subs, var))); + } + + let mut solved_lambda_sets = Vec::with_capacity(lambda_set_variables.len()); + + for var in lambda_set_variables { + solved_lambda_sets.push(SolvedLambdaSet(Self::from_type(solved_subs, &var.0))); + } + + SolvedType::DelayedAlias(*symbol, solved_args, solved_lambda_sets) + } Alias { symbol, type_arguments, @@ -502,7 +539,12 @@ pub fn to_type( new_fields.insert(label.clone(), field_val); } - Type::Record(new_fields, Box::new(to_type(ext, free_vars, var_store))) + let ext = match ext.as_ref() { + SolvedType::EmptyRecord => TypeExtension::Closed, + other => TypeExtension::Open(Box::new(to_type(other, free_vars, var_store))), + }; + + Type::Record(new_fields, ext) } EmptyRecord => Type::EmptyRec, EmptyTagUnion => Type::EmptyTagUnion, @@ -519,13 +561,21 @@ pub fn to_type( new_tags.push((tag_name.clone(), new_args)); } - Type::TagUnion(new_tags, Box::new(to_type(ext, free_vars, var_store))) + let ext = match ext.as_ref() { + SolvedType::EmptyTagUnion => TypeExtension::Closed, + other => TypeExtension::Open(Box::new(to_type(other, free_vars, var_store))), + }; + + Type::TagUnion(new_tags, ext) + } + FunctionOrTagUnion(tag_name, symbol, ext) => { + let ext = match ext.as_ref() { + SolvedType::EmptyTagUnion => TypeExtension::Closed, + other => TypeExtension::Open(Box::new(to_type(other, free_vars, var_store))), + }; + + Type::FunctionOrTagUnion(tag_name.clone(), *symbol, ext) } - FunctionOrTagUnion(tag_name, symbol, ext) => Type::FunctionOrTagUnion( - tag_name.clone(), - *symbol, - Box::new(to_type(ext, free_vars, var_store)), - ), RecursiveTagUnion(rec_var_id, tags, ext) => { let mut new_tags = Vec::with_capacity(tags.len()); @@ -539,16 +589,39 @@ pub fn to_type( new_tags.push((tag_name.clone(), new_args)); } + let ext = match ext.as_ref() { + SolvedType::EmptyTagUnion => TypeExtension::Closed, + other => TypeExtension::Open(Box::new(to_type(other, free_vars, var_store))), + }; + let rec_var = free_vars .unnamed_vars .get(rec_var_id) .expect("rec var not in unnamed vars"); - Type::RecursiveTagUnion( - *rec_var, - new_tags, - Box::new(to_type(ext, free_vars, var_store)), - ) + Type::RecursiveTagUnion(*rec_var, new_tags, ext) + } + DelayedAlias(symbol, solved_type_variables, solved_lambda_sets) => { + let mut type_variables = Vec::with_capacity(solved_type_variables.len()); + + for (lowercase, solved_arg) in solved_type_variables { + type_variables.push((lowercase.clone(), to_type(solved_arg, free_vars, var_store))); + } + + let mut lambda_set_variables = Vec::with_capacity(solved_lambda_sets.len()); + for solved_set in solved_lambda_sets { + lambda_set_variables.push(crate::types::LambdaSet(to_type( + &solved_set.0, + free_vars, + var_store, + ))) + } + + Type::DelayedAlias(AliasCommon { + symbol: *symbol, + type_arguments: type_variables, + lambda_set_variables, + }) } Alias(symbol, solved_type_variables, solved_lambda_sets, solved_actual, kind) => { let mut type_variables = Vec::with_capacity(solved_type_variables.len()); diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index fe3228a03c..31cbac6363 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -296,6 +296,16 @@ impl SubsSlice { _marker: std::marker::PhantomData, } } + + pub fn extend_new(vec: &mut Vec, it: impl IntoIterator) -> Self { + let start = vec.len(); + + vec.extend(it); + + let end = vec.len(); + + Self::new(start as u32, (end - start) as u16) + } } impl SubsSlice { @@ -427,7 +437,7 @@ fn subs_fmt_content(this: &Content, subs: &Subs, f: &mut fmt::Formatter) -> fmt: } => write!(f, "Recursion({:?}, {:?})", structure, opt_name), Content::Structure(flat_type) => subs_fmt_flat_type(flat_type, subs, f), Content::Alias(name, arguments, actual, kind) => { - let slice = subs.get_subs_slice(arguments.variables()); + let slice = subs.get_subs_slice(arguments.all_variables()); let wrap = match kind { AliasKind::Structural => "Alias", AliasKind::Opaque => "Opaque", @@ -789,7 +799,6 @@ impl Variable { /// /// It is not guaranteed that the variable is in bounds. pub unsafe fn from_index(v: u32) -> Self { - debug_assert!(v >= Self::NUM_RESERVED_VARS as u32); Variable(v) } @@ -816,6 +825,11 @@ impl Variable { Symbol::BOOL_BOOL => Some(Variable::BOOL), + Symbol::NUM_F64 => Some(Variable::F64), + Symbol::NUM_F32 => Some(Variable::F32), + + Symbol::NUM_DEC => Some(Variable::DEC), + _ => None, } } @@ -1277,6 +1291,9 @@ fn define_float_types(subs: &mut Subs) { impl Subs { pub const RESULT_TAG_NAMES: SubsSlice = SubsSlice::new(0, 2); + pub const NUM_AT_NUM: SubsSlice = SubsSlice::new(2, 1); + pub const NUM_AT_INTEGER: SubsSlice = SubsSlice::new(3, 1); + pub const NUM_AT_FLOATINGPOINT: SubsSlice = SubsSlice::new(4, 1); pub fn new() -> Self { Self::with_capacity(0) @@ -1290,6 +1307,10 @@ impl Subs { tag_names.push(TagName::Global("Err".into())); tag_names.push(TagName::Global("Ok".into())); + tag_names.push(TagName::Private(Symbol::NUM_AT_NUM)); + tag_names.push(TagName::Private(Symbol::NUM_AT_INTEGER)); + tag_names.push(TagName::Private(Symbol::NUM_AT_FLOATINGPOINT)); + let mut subs = Subs { utable: UnificationTable::default(), variables: Vec::new(), @@ -1650,10 +1671,22 @@ impl Rank { *self == Self::NONE } - pub fn toplevel() -> Self { + pub const fn toplevel() -> Self { Rank(1) } + /// the rank at which we introduce imports. + /// + /// Type checking starts at rank 1 aka toplevel. When there are rigid/flex variables introduced by a + /// constraint, then these must be generalized relative to toplevel, and hence are introduced at + /// rank 2. + /// + /// We always use: even if there are no rigids imported, introducing at rank 2 is correct + /// (if slightly inefficient) because there are no rigids anyway so generalization is trivial + pub const fn import() -> Self { + Rank(2) + } + pub fn next(self) -> Self { Rank(self.0 + 1) } @@ -1764,10 +1797,21 @@ pub struct AliasVariables { } impl AliasVariables { - pub const fn variables(&self) -> VariableSubsSlice { + pub const fn all_variables(&self) -> VariableSubsSlice { SubsSlice::new(self.variables_start, self.all_variables_len) } + pub const fn type_variables(&self) -> VariableSubsSlice { + SubsSlice::new(self.variables_start, self.type_variables_len) + } + + pub const fn lambda_set_variables(&self) -> VariableSubsSlice { + SubsSlice::new( + self.variables_start + self.type_variables_len as u32, + self.all_variables_len - self.type_variables_len, + ) + } + pub const fn len(&self) -> usize { self.type_variables_len as usize } @@ -1791,13 +1835,13 @@ impl AliasVariables { } pub fn named_type_arguments(&self) -> impl Iterator> { - self.variables() + self.all_variables() .into_iter() .take(self.type_variables_len as usize) } pub fn unnamed_type_arguments(&self) -> impl Iterator> { - self.variables() + self.all_variables() .into_iter() .skip(self.type_variables_len as usize) } @@ -1839,7 +1883,7 @@ impl IntoIterator for AliasVariables { type IntoIter = ::IntoIter; fn into_iter(self) -> Self::IntoIter { - self.variables().into_iter() + self.all_variables().into_iter() } } @@ -1979,7 +2023,13 @@ impl UnionTags { tag_names: SubsSlice, variables: SubsSlice, ) -> Self { - debug_assert_eq!(tag_names.len(), variables.len()); + debug_assert_eq!( + tag_names.len(), + variables.len(), + "tag name len != variables len: {:?} {:?}", + tag_names, + variables, + ); Self { length: tag_names.len() as u16, @@ -3287,7 +3337,7 @@ fn restore_help(subs: &mut Subs, initial: Variable) { Erroneous(_) => (), }, Alias(_, args, var, _) => { - stack.extend(var_slice(args.variables())); + stack.extend(var_slice(args.all_variables())); stack.push(*var); } @@ -3322,10 +3372,26 @@ impl StorageSubs { Self { subs } } + pub fn fresh_unnamed_flex_var(&mut self) -> Variable { + self.subs.fresh_unnamed_flex_var() + } + + pub fn as_inner_mut(&mut self) -> &mut Subs { + &mut self.subs + } + pub fn extend_with_variable(&mut self, source: &mut Subs, variable: Variable) -> Variable { deep_copy_var_to(source, &mut self.subs, variable) } + pub fn import_variable_from(&mut self, source: &mut Subs, variable: Variable) -> CopiedImport { + copy_import_to(source, &mut self.subs, variable, Rank::import()) + } + + pub fn export_variable_to(&mut self, target: &mut Subs, variable: Variable) -> CopiedImport { + copy_import_to(&mut self.subs, target, variable, Rank::import()) + } + pub fn merge_into(self, target: &mut Subs) -> impl Fn(Variable) -> Variable { let self_offsets = StorageSubsOffsets { utable: self.subs.utable.len() as u32, @@ -3538,21 +3604,16 @@ impl StorageSubs { use std::cell::RefCell; std::thread_local! { /// Scratchpad arena so we don't need to allocate a new one all the time - static SCRATCHPAD: RefCell = RefCell::new(bumpalo::Bump::with_capacity(4 * 1024)); + static SCRATCHPAD: RefCell> = RefCell::new(Some(bumpalo::Bump::with_capacity(4 * 1024))); } fn take_scratchpad() -> bumpalo::Bump { - let mut result = bumpalo::Bump::new(); - SCRATCHPAD.with(|f| { - result = f.replace(bumpalo::Bump::new()); - }); - - result + SCRATCHPAD.with(|f| f.take().unwrap()) } fn put_scratchpad(scratchpad: bumpalo::Bump) { SCRATCHPAD.with(|f| { - f.replace(scratchpad); + f.replace(Some(scratchpad)); }); } @@ -3565,21 +3626,32 @@ pub fn deep_copy_var_to( let mut arena = take_scratchpad(); - let mut visited = bumpalo::collections::Vec::with_capacity_in(256, &arena); + let copy = { + let visited = bumpalo::collections::Vec::with_capacity_in(256, &arena); - let copy = deep_copy_var_to_help(&arena, &mut visited, source, target, rank, var); + let mut env = DeepCopyVarToEnv { + visited, + source, + target, + max_rank: rank, + }; - // we have tracked all visited variables, and can now traverse them - // in one go (without looking at the UnificationTable) and clear the copy field - for var in visited { - let descriptor = source.get_ref_mut(var); + let copy = deep_copy_var_to_help(&mut env, var); - if descriptor.copy.is_some() { - descriptor.rank = Rank::NONE; - descriptor.mark = Mark::NONE; - descriptor.copy = OptVariable::NONE; + // we have tracked all visited variables, and can now traverse them + // in one go (without looking at the UnificationTable) and clear the copy field + for var in env.visited { + let descriptor = env.source.get_ref_mut(var); + + if descriptor.copy.is_some() { + descriptor.rank = Rank::NONE; + descriptor.mark = Mark::NONE; + descriptor.copy = OptVariable::NONE; + } } - } + + copy + }; arena.reset(); put_scratchpad(arena); @@ -3587,21 +3659,21 @@ pub fn deep_copy_var_to( copy } -fn deep_copy_var_to_help<'a>( - arena: &'a bumpalo::Bump, - visited: &mut bumpalo::collections::Vec<'a, Variable>, - source: &mut Subs, - target: &mut Subs, +struct DeepCopyVarToEnv<'a> { + visited: bumpalo::collections::Vec<'a, Variable>, + source: &'a mut Subs, + target: &'a mut Subs, max_rank: Rank, - var: Variable, -) -> Variable { +} + +fn deep_copy_var_to_help(env: &mut DeepCopyVarToEnv<'_>, var: Variable) -> Variable { use Content::*; use FlatType::*; - let desc = source.get_without_compacting(var); + let desc = env.source.get_without_compacting(var); if let Some(copy) = desc.copy.into_variable() { - debug_assert!(target.contains(copy)); + debug_assert!(env.target.contains(copy)); return copy; } else if desc.rank != Rank::NONE { // DO NOTHING, Fall through @@ -3610,10 +3682,13 @@ fn deep_copy_var_to_help<'a>( // return var; // // but we cannot, because this `var` is in the source, not the target, and we - // should only return variables in the target + // should only return variables in the target. so, we have to create a new + // variable in the target. } - visited.push(var); + env.visited.push(var); + + let max_rank = env.max_rank; let make_descriptor = |content| Descriptor { content, @@ -3622,13 +3697,13 @@ fn deep_copy_var_to_help<'a>( copy: OptVariable::NONE, }; - let copy = target.fresh_unnamed_flex_var(); + let copy = env.target.fresh_unnamed_flex_var(); // Link the original variable to the new variable. This lets us // avoid making multiple copies of the variable we are instantiating. // // Need to do this before recursively copying to avoid looping. - source.modify(var, |descriptor| { + env.source.modify(var, |descriptor| { descriptor.mark = Mark::NONE; descriptor.copy = copy.into(); }); @@ -3640,38 +3715,28 @@ fn deep_copy_var_to_help<'a>( Structure(flat_type) => { let new_flat_type = match flat_type { Apply(symbol, arguments) => { - let new_arguments = SubsSlice::reserve_into_subs(target, arguments.len()); + let new_arguments = SubsSlice::reserve_into_subs(env.target, arguments.len()); for (target_index, var_index) in (new_arguments.indices()).zip(arguments) { - let var = source[var_index]; - let copy_var = - deep_copy_var_to_help(arena, visited, source, target, max_rank, var); - target.variables[target_index] = copy_var; + let var = env.source[var_index]; + let copy_var = deep_copy_var_to_help(env, var); + env.target.variables[target_index] = copy_var; } Apply(symbol, new_arguments) } Func(arguments, closure_var, ret_var) => { - let new_ret_var = - deep_copy_var_to_help(arena, visited, source, target, max_rank, ret_var); + let new_ret_var = deep_copy_var_to_help(env, ret_var); - let new_closure_var = deep_copy_var_to_help( - arena, - visited, - source, - target, - max_rank, - closure_var, - ); + let new_closure_var = deep_copy_var_to_help(env, closure_var); - let new_arguments = SubsSlice::reserve_into_subs(target, arguments.len()); + let new_arguments = SubsSlice::reserve_into_subs(env.target, arguments.len()); for (target_index, var_index) in (new_arguments.indices()).zip(arguments) { - let var = source[var_index]; - let copy_var = - deep_copy_var_to_help(arena, visited, source, target, max_rank, var); - target.variables[target_index] = copy_var; + let var = env.source[var_index]; + let copy_var = deep_copy_var_to_help(env, var); + env.target.variables[target_index] = copy_var; } Func(new_arguments, new_closure_var, new_ret_var) @@ -3682,25 +3747,26 @@ fn deep_copy_var_to_help<'a>( Record(fields, ext_var) => { let record_fields = { let new_variables = - VariableSubsSlice::reserve_into_subs(target, fields.len()); + VariableSubsSlice::reserve_into_subs(env.target, fields.len()); let it = (new_variables.indices()).zip(fields.iter_variables()); for (target_index, var_index) in it { - let var = source[var_index]; - let copy_var = deep_copy_var_to_help( - arena, visited, source, target, max_rank, var, - ); - target.variables[target_index] = copy_var; + let var = env.source[var_index]; + let copy_var = deep_copy_var_to_help(env, var); + env.target.variables[target_index] = copy_var; } - let field_names_start = target.field_names.len() as u32; - let field_types_start = target.record_fields.len() as u32; + let field_names_start = env.target.field_names.len() as u32; + let field_types_start = env.target.record_fields.len() as u32; - let field_names = &source.field_names[fields.field_names().indices()]; - target.field_names.extend(field_names.iter().cloned()); + let field_names = &env.source.field_names[fields.field_names().indices()]; + env.target.field_names.extend(field_names.iter().cloned()); - let record_fields = &source.record_fields[fields.record_fields().indices()]; - target.record_fields.extend(record_fields.iter().copied()); + let record_fields = + &env.source.record_fields[fields.record_fields().indices()]; + env.target + .record_fields + .extend(record_fields.iter().copied()); RecordFields { length: fields.len() as _, @@ -3710,44 +3776,38 @@ fn deep_copy_var_to_help<'a>( } }; - Record( - record_fields, - deep_copy_var_to_help(arena, visited, source, target, max_rank, ext_var), - ) + Record(record_fields, deep_copy_var_to_help(env, ext_var)) } TagUnion(tags, ext_var) => { - let new_ext = - deep_copy_var_to_help(arena, visited, source, target, max_rank, ext_var); + let new_ext = deep_copy_var_to_help(env, ext_var); let new_variable_slices = - SubsSlice::reserve_variable_slices(target, tags.len()); + SubsSlice::reserve_variable_slices(env.target, tags.len()); let it = (new_variable_slices.indices()).zip(tags.variables()); for (target_index, index) in it { - let slice = source[index]; + let slice = env.source[index]; - let new_variables = SubsSlice::reserve_into_subs(target, slice.len()); + let new_variables = SubsSlice::reserve_into_subs(env.target, slice.len()); let it = (new_variables.indices()).zip(slice); for (target_index, var_index) in it { - let var = source[var_index]; - let copy_var = deep_copy_var_to_help( - arena, visited, source, target, max_rank, var, - ); - target.variables[target_index] = copy_var; + let var = env.source[var_index]; + let copy_var = deep_copy_var_to_help(env, var); + env.target.variables[target_index] = copy_var; } - target.variable_slices[target_index] = new_variables; + env.target.variable_slices[target_index] = new_variables; } let new_tag_names = { let tag_names = tags.tag_names(); - let slice = &source.tag_names[tag_names.indices()]; + let slice = &env.source.tag_names[tag_names.indices()]; - let start = target.tag_names.len() as u32; + let start = env.target.tag_names.len() as u32; let length = tag_names.len() as u16; - target.tag_names.extend(slice.iter().cloned()); + env.target.tag_names.extend(slice.iter().cloned()); SubsSlice::new(start, length) }; @@ -3758,78 +3818,80 @@ fn deep_copy_var_to_help<'a>( } FunctionOrTagUnion(tag_name, symbol, ext_var) => { - let new_tag_name = SubsIndex::new(target.tag_names.len() as u32); + let new_tag_name = SubsIndex::new(env.target.tag_names.len() as u32); - target.tag_names.push(source[tag_name].clone()); + env.target.tag_names.push(env.source[tag_name].clone()); - FunctionOrTagUnion( - new_tag_name, - symbol, - deep_copy_var_to_help(arena, visited, source, target, max_rank, ext_var), - ) + FunctionOrTagUnion(new_tag_name, symbol, deep_copy_var_to_help(env, ext_var)) } RecursiveTagUnion(rec_var, tags, ext_var) => { let new_variable_slices = - SubsSlice::reserve_variable_slices(target, tags.len()); + SubsSlice::reserve_variable_slices(env.target, tags.len()); let it = (new_variable_slices.indices()).zip(tags.variables()); for (target_index, index) in it { - let slice = source[index]; + let slice = env.source[index]; - let new_variables = SubsSlice::reserve_into_subs(target, slice.len()); + let new_variables = SubsSlice::reserve_into_subs(env.target, slice.len()); let it = (new_variables.indices()).zip(slice); for (target_index, var_index) in it { - let var = source[var_index]; - let copy_var = deep_copy_var_to_help( - arena, visited, source, target, max_rank, var, - ); - target.variables[target_index] = copy_var; + let var = env.source[var_index]; + let copy_var = deep_copy_var_to_help(env, var); + env.target.variables[target_index] = copy_var; } - target.variable_slices[target_index] = new_variables; + env.target.variable_slices[target_index] = new_variables; } let new_tag_names = { let tag_names = tags.tag_names(); - let slice = &source.tag_names[tag_names.indices()]; + let slice = &env.source.tag_names[tag_names.indices()]; - let start = target.tag_names.len() as u32; + let start = env.target.tag_names.len() as u32; let length = tag_names.len() as u16; - target.tag_names.extend(slice.iter().cloned()); + env.target.tag_names.extend(slice.iter().cloned()); SubsSlice::new(start, length) }; let union_tags = UnionTags::from_slices(new_tag_names, new_variable_slices); - let new_ext = - deep_copy_var_to_help(arena, visited, source, target, max_rank, ext_var); - let new_rec_var = - deep_copy_var_to_help(arena, visited, source, target, max_rank, rec_var); + let new_ext = deep_copy_var_to_help(env, ext_var); + let new_rec_var = deep_copy_var_to_help(env, rec_var); RecursiveTagUnion(new_rec_var, union_tags, new_ext) } }; - target.set(copy, make_descriptor(Structure(new_flat_type))); + env.target + .set(copy, make_descriptor(Structure(new_flat_type))); copy } - FlexVar(_) | Error => copy, + FlexVar(Some(name_index)) => { + let name = env.source.field_names[name_index.index as usize].clone(); + let new_name_index = SubsIndex::push_new(&mut env.target.field_names, name); + + let content = FlexVar(Some(new_name_index)); + env.target.set_content(copy, content); + + copy + } + + FlexVar(None) | Error => copy, RecursionVar { opt_name, structure, } => { - let new_structure = - deep_copy_var_to_help(arena, visited, source, target, max_rank, structure); + let new_structure = deep_copy_var_to_help(env, structure); - debug_assert!((new_structure.index() as usize) < target.len()); + debug_assert!((new_structure.index() as usize) < env.target.len()); - target.set( + env.target.set( copy, make_descriptor(RecursionVar { opt_name, @@ -3840,19 +3902,24 @@ fn deep_copy_var_to_help<'a>( copy } - RigidVar(name) => { - target.set(copy, make_descriptor(FlexVar(Some(name)))); + RigidVar(name_index) => { + let name = env.source.field_names[name_index.index as usize].clone(); + let new_name_index = SubsIndex::push_new(&mut env.target.field_names, name); + env.target + .set(copy, make_descriptor(FlexVar(Some(new_name_index)))); copy } Alias(symbol, arguments, real_type_var, kind) => { let new_variables = - SubsSlice::reserve_into_subs(target, arguments.all_variables_len as _); - for (target_index, var_index) in (new_variables.indices()).zip(arguments.variables()) { - let var = source[var_index]; - let copy_var = deep_copy_var_to_help(arena, visited, source, target, max_rank, var); - target.variables[target_index] = copy_var; + SubsSlice::reserve_into_subs(env.target, arguments.all_variables_len as _); + for (target_index, var_index) in + (new_variables.indices()).zip(arguments.all_variables()) + { + let var = env.source[var_index]; + let copy_var = deep_copy_var_to_help(env, var); + env.target.variables[target_index] = copy_var; } let new_arguments = AliasVariables { @@ -3860,29 +3927,447 @@ fn deep_copy_var_to_help<'a>( ..arguments }; - let new_real_type_var = - deep_copy_var_to_help(arena, visited, source, target, max_rank, real_type_var); + let new_real_type_var = deep_copy_var_to_help(env, real_type_var); let new_content = Alias(symbol, new_arguments, new_real_type_var, kind); - target.set(copy, make_descriptor(new_content)); + env.target.set(copy, make_descriptor(new_content)); copy } RangedNumber(typ, vars) => { - let new_typ = deep_copy_var_to_help(arena, visited, source, target, max_rank, typ); + let new_typ = deep_copy_var_to_help(env, typ); - let new_vars = SubsSlice::reserve_into_subs(target, vars.len()); + let new_vars = SubsSlice::reserve_into_subs(env.target, vars.len()); for (target_index, var_index) in (new_vars.indices()).zip(vars) { - let var = source[var_index]; - let copy_var = deep_copy_var_to_help(arena, visited, source, target, max_rank, var); - target.variables[target_index] = copy_var; + let var = env.source[var_index]; + let copy_var = deep_copy_var_to_help(env, var); + env.target.variables[target_index] = copy_var; } let new_content = RangedNumber(new_typ, new_vars); - target.set(copy, make_descriptor(new_content)); + env.target.set(copy, make_descriptor(new_content)); + copy + } + } +} + +/// Bookkeeping to correctly move these types into the target subs +/// +/// We track the rigid/flex variables because they need to be part of a `Let` +/// constraint, introducing these variables at the right rank +/// +/// We also track `registered` variables. An import should be equivalent to +/// a call to `type_to_var` (solve.rs). The `copy_import_to` function puts +/// the right `Contents` into the target `Subs` at the right locations, +/// but `type_to_var` furthermore adds the variables used to store those `Content`s +/// to `Pools` at the right rank. Here we remember the variables used to store `Content`s +/// so that we can later add them to `Pools` +#[derive(Debug)] +pub struct CopiedImport { + pub variable: Variable, + pub flex: Vec, + pub rigid: Vec, + pub translations: Vec<(Variable, Variable)>, + pub registered: Vec, +} + +struct CopyImportEnv<'a> { + visited: bumpalo::collections::Vec<'a, Variable>, + source: &'a mut Subs, + target: &'a mut Subs, + flex: Vec, + rigid: Vec, + translations: Vec<(Variable, Variable)>, + registered: Vec, +} + +pub fn copy_import_to( + source: &mut Subs, // mut to set the copy + target: &mut Subs, + var: Variable, + rank: Rank, +) -> CopiedImport { + let mut arena = take_scratchpad(); + + let copied_import = { + let visited = bumpalo::collections::Vec::with_capacity_in(256, &arena); + + let mut env = CopyImportEnv { + visited, + source, + target, + flex: Vec::new(), + rigid: Vec::new(), + translations: Vec::new(), + registered: Vec::new(), + }; + + let copy = copy_import_to_help(&mut env, rank, var); + + let CopyImportEnv { + visited, + source, + flex, + rigid, + translations, + registered, + target: _, + } = env; + + // we have tracked all visited variables, and can now traverse them + // in one go (without looking at the UnificationTable) and clear the copy field + + for var in visited { + let descriptor = source.get_ref_mut(var); + + if descriptor.copy.is_some() { + descriptor.rank = Rank::NONE; + descriptor.mark = Mark::NONE; + descriptor.copy = OptVariable::NONE; + } + } + + CopiedImport { + variable: copy, + flex, + rigid, + translations, + registered, + } + }; + + arena.reset(); + put_scratchpad(arena); + + copied_import +} + +/// is this content registered (in the current pool) by type_to_variable? +/// TypeToVar skips registering for flex and rigid variables, and +/// also for the empty records and tag unions (they used the Variable::EMPTY_RECORD/...) +/// standard variables +fn is_registered(content: &Content) -> bool { + match content { + Content::FlexVar(_) | Content::RigidVar(_) => false, + Content::Structure(FlatType::EmptyRecord | FlatType::EmptyTagUnion) => false, + + Content::Structure(_) + | Content::RecursionVar { .. } + | Content::Alias(_, _, _, _) + | Content::RangedNumber(_, _) + | Content::Error => true, + } +} + +fn copy_import_to_help(env: &mut CopyImportEnv<'_>, max_rank: Rank, var: Variable) -> Variable { + use Content::*; + use FlatType::*; + + let desc = env.source.get_without_compacting(var); + + if let Some(copy) = desc.copy.into_variable() { + debug_assert!(env.target.contains(copy)); + return copy; + } else if desc.rank != Rank::NONE { + // DO NOTHING, Fall through + // + // The original copy_import can do + // return var; + // + // but we cannot, because this `var` is in the source, not the target, and we + // should only return variables in the target. so, we have to create a new + // variable in the target. + } + + env.visited.push(var); + + let make_descriptor = |content| Descriptor { + content, + rank: max_rank, + mark: Mark::NONE, + copy: OptVariable::NONE, + }; + + // let copy = env.target.fresh_unnamed_flex_var(); + let copy = env.target.fresh(make_descriptor(unnamed_flex_var())); + + // is this content registered (in the current pool) by type_to_variable? + if is_registered(&desc.content) { + env.registered.push(copy); + } + + // Link the original variable to the new variable. This lets us + // avoid making multiple copies of the variable we are instantiating. + // + // Need to do this before recursively copying to avoid looping. + env.source.modify(var, |descriptor| { + descriptor.mark = Mark::NONE; + descriptor.copy = copy.into(); + }); + + // Now we recursively copy the content of the variable. + // We have already marked the variable as copied, so we + // will not repeat this work or crawl this variable again. + match desc.content { + Structure(flat_type) => { + let new_flat_type = match flat_type { + Apply(symbol, arguments) => { + let new_arguments = SubsSlice::reserve_into_subs(env.target, arguments.len()); + + for (target_index, var_index) in (new_arguments.indices()).zip(arguments) { + let var = env.source[var_index]; + let copy_var = copy_import_to_help(env, max_rank, var); + env.target.variables[target_index] = copy_var; + } + + Apply(symbol, new_arguments) + } + + Func(arguments, closure_var, ret_var) => { + let new_ret_var = copy_import_to_help(env, max_rank, ret_var); + + let new_closure_var = copy_import_to_help(env, max_rank, closure_var); + + let new_arguments = SubsSlice::reserve_into_subs(env.target, arguments.len()); + + for (target_index, var_index) in (new_arguments.indices()).zip(arguments) { + let var = env.source[var_index]; + let copy_var = copy_import_to_help(env, max_rank, var); + env.target.variables[target_index] = copy_var; + } + + Func(new_arguments, new_closure_var, new_ret_var) + } + + same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same, + + Record(fields, ext_var) => { + let record_fields = { + let new_variables = + VariableSubsSlice::reserve_into_subs(env.target, fields.len()); + + let it = (new_variables.indices()).zip(fields.iter_variables()); + for (target_index, var_index) in it { + let var = env.source[var_index]; + let copy_var = copy_import_to_help(env, max_rank, var); + env.target.variables[target_index] = copy_var; + } + + let field_names_start = env.target.field_names.len() as u32; + let field_types_start = env.target.record_fields.len() as u32; + + let field_names = &env.source.field_names[fields.field_names().indices()]; + env.target.field_names.extend(field_names.iter().cloned()); + + let record_fields = + &env.source.record_fields[fields.record_fields().indices()]; + env.target + .record_fields + .extend(record_fields.iter().copied()); + + RecordFields { + length: fields.len() as _, + field_names_start, + variables_start: new_variables.start, + field_types_start, + } + }; + + Record(record_fields, copy_import_to_help(env, max_rank, ext_var)) + } + + TagUnion(tags, ext_var) => { + let new_ext = copy_import_to_help(env, max_rank, ext_var); + + let new_variable_slices = + SubsSlice::reserve_variable_slices(env.target, tags.len()); + + let it = (new_variable_slices.indices()).zip(tags.variables()); + for (target_index, index) in it { + let slice = env.source[index]; + + let new_variables = SubsSlice::reserve_into_subs(env.target, slice.len()); + let it = (new_variables.indices()).zip(slice); + for (target_index, var_index) in it { + let var = env.source[var_index]; + let copy_var = copy_import_to_help(env, max_rank, var); + env.target.variables[target_index] = copy_var; + } + + env.target.variable_slices[target_index] = new_variables; + } + + let new_tag_names = { + let tag_names = tags.tag_names(); + let slice = &env.source.tag_names[tag_names.indices()]; + + let start = env.target.tag_names.len() as u32; + let length = tag_names.len() as u16; + + env.target.tag_names.extend(slice.iter().cloned()); + + SubsSlice::new(start, length) + }; + + let union_tags = UnionTags::from_slices(new_tag_names, new_variable_slices); + + TagUnion(union_tags, new_ext) + } + + FunctionOrTagUnion(tag_name, symbol, ext_var) => { + let new_tag_name = SubsIndex::new(env.target.tag_names.len() as u32); + + env.target.tag_names.push(env.source[tag_name].clone()); + + FunctionOrTagUnion( + new_tag_name, + symbol, + copy_import_to_help(env, max_rank, ext_var), + ) + } + + RecursiveTagUnion(rec_var, tags, ext_var) => { + let new_variable_slices = + SubsSlice::reserve_variable_slices(env.target, tags.len()); + + let it = (new_variable_slices.indices()).zip(tags.variables()); + for (target_index, index) in it { + let slice = env.source[index]; + + let new_variables = SubsSlice::reserve_into_subs(env.target, slice.len()); + let it = (new_variables.indices()).zip(slice); + for (target_index, var_index) in it { + let var = env.source[var_index]; + let copy_var = copy_import_to_help(env, max_rank, var); + env.target.variables[target_index] = copy_var; + } + + env.target.variable_slices[target_index] = new_variables; + } + + let new_tag_names = { + let tag_names = tags.tag_names(); + let slice = &env.source.tag_names[tag_names.indices()]; + + let start = env.target.tag_names.len() as u32; + let length = tag_names.len() as u16; + + env.target.tag_names.extend(slice.iter().cloned()); + + SubsSlice::new(start, length) + }; + + let union_tags = UnionTags::from_slices(new_tag_names, new_variable_slices); + + let new_ext = copy_import_to_help(env, max_rank, ext_var); + let new_rec_var = copy_import_to_help(env, max_rank, rec_var); + + RecursiveTagUnion(new_rec_var, union_tags, new_ext) + } + }; + + env.target + .set(copy, make_descriptor(Structure(new_flat_type))); + + copy + } + + FlexVar(opt_name_index) => { + if let Some(name_index) = opt_name_index { + let name = env.source.field_names[name_index.index as usize].clone(); + let new_name_index = SubsIndex::push_new(&mut env.target.field_names, name); + + let content = FlexVar(Some(new_name_index)); + env.target.set_content(copy, content); + } + + env.flex.push(copy); + + copy + } + + Error => { + // Open question: should this return Error, or a Flex var? + + env.target.set(copy, make_descriptor(Error)); + + copy + } + + RigidVar(name_index) => { + let name = env.source.field_names[name_index.index as usize].clone(); + let new_name_index = SubsIndex::push_new(&mut env.target.field_names, name); + + env.target + .set(copy, make_descriptor(RigidVar(new_name_index))); + + env.rigid.push(copy); + + env.translations.push((var, copy)); + + copy + } + + RecursionVar { + opt_name, + structure, + } => { + let new_structure = copy_import_to_help(env, max_rank, structure); + + debug_assert!((new_structure.index() as usize) < env.target.len()); + + env.target.set( + copy, + make_descriptor(RecursionVar { + opt_name, + structure: new_structure, + }), + ); + + copy + } + + Alias(symbol, arguments, real_type_var, kind) => { + let new_variables = + SubsSlice::reserve_into_subs(env.target, arguments.all_variables_len as _); + for (target_index, var_index) in + (new_variables.indices()).zip(arguments.all_variables()) + { + let var = env.source[var_index]; + let copy_var = copy_import_to_help(env, max_rank, var); + env.target.variables[target_index] = copy_var; + } + + let new_arguments = AliasVariables { + variables_start: new_variables.start, + ..arguments + }; + + let new_real_type_var = copy_import_to_help(env, max_rank, real_type_var); + let new_content = Alias(symbol, new_arguments, new_real_type_var, kind); + + env.target.set(copy, make_descriptor(new_content)); + + copy + } + + RangedNumber(typ, vars) => { + let new_typ = copy_import_to_help(env, max_rank, typ); + + let new_vars = SubsSlice::reserve_into_subs(env.target, vars.len()); + + for (target_index, var_index) in (new_vars.indices()).zip(vars) { + let var = env.source[var_index]; + let copy_var = copy_import_to_help(env, max_rank, var); + env.target.variables[target_index] = copy_var; + } + + let new_content = RangedNumber(new_typ, new_vars); + + env.target.set(copy, make_descriptor(new_content)); copy } } @@ -3955,7 +4440,7 @@ where Erroneous(_) | EmptyRecord | EmptyTagUnion => {} }, Alias(_, arguments, real_type_var, _) => { - push_var_slice!(arguments.variables()); + push_var_slice!(arguments.all_variables()); stack.push(*real_type_var); } RangedNumber(typ, vars) => { diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index c6f4f360f3..fd8d9e12f0 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -2,7 +2,7 @@ use crate::pretty_print::Parens; use crate::subs::{ GetSubsSlice, RecordFields, Subs, UnionTags, VarStore, Variable, VariableSubsSlice, }; -use roc_collections::all::{HumanIndex, ImMap, ImSet, MutSet, SendMap}; +use roc_collections::all::{HumanIndex, ImMap, ImSet, MutMap, MutSet, SendMap}; use roc_error_macros::internal_error; use roc_module::called_via::CalledVia; use roc_module::ident::{ForeignSymbol, Ident, Lowercase, TagName}; @@ -71,6 +71,16 @@ impl RecordField { } } + pub fn as_inner_mut(&mut self) -> &mut T { + use RecordField::*; + + match self { + Optional(t) => t, + Required(t) => t, + Demanded(t) => t, + } + } + pub fn map(&self, mut f: F) -> RecordField where F: FnMut(&T) -> U, @@ -150,8 +160,12 @@ impl RecordField { pub struct LambdaSet(pub Type); impl LambdaSet { - fn substitute(&mut self, substitutions: &ImMap) { - self.0.substitute(substitutions); + pub fn as_inner(&self) -> &Type { + &self.0 + } + + fn as_inner_mut(&mut self) -> &mut Type { + &mut self.0 } fn instantiate_aliases( @@ -167,19 +181,27 @@ impl LambdaSet { } #[derive(PartialEq, Eq, Clone)] +pub struct AliasCommon { + pub symbol: Symbol, + pub type_arguments: Vec<(Lowercase, Type)>, + pub lambda_set_variables: Vec, +} + +#[derive(PartialEq, Eq)] pub enum Type { EmptyRec, EmptyTagUnion, /// A function. The types of its arguments, size of its closure, then the type of its return value. Function(Vec, Box, Box), - Record(SendMap>, Box), - TagUnion(Vec<(TagName, Vec)>, Box), - FunctionOrTagUnion(TagName, Symbol, Box), + Record(SendMap>, TypeExtension), + TagUnion(Vec<(TagName, Vec)>, TypeExtension), + FunctionOrTagUnion(TagName, Symbol, TypeExtension), /// A function name that is used in our defunctionalization algorithm ClosureTag { name: Symbol, ext: Variable, }, + DelayedAlias(AliasCommon), Alias { symbol: Symbol, type_arguments: Vec<(Lowercase, Type)>, @@ -194,7 +216,7 @@ pub enum Type { actual_var: Variable, actual: Box, }, - RecursiveTagUnion(Variable, Vec<(TagName, Vec)>, Box), + RecursiveTagUnion(Variable, Vec<(TagName, Vec)>, TypeExtension), /// Applying a type to some arguments (e.g. Dict.Dict String Int) Apply(Symbol, Vec, Region), Variable(Variable), @@ -203,6 +225,106 @@ pub enum Type { Erroneous(Problem), } +static mut TYPE_CLONE_COUNT: std::sync::atomic::AtomicUsize = + std::sync::atomic::AtomicUsize::new(0); + +pub fn get_type_clone_count() -> usize { + unsafe { TYPE_CLONE_COUNT.load(std::sync::atomic::Ordering::SeqCst) } +} + +impl Clone for Type { + fn clone(&self) -> Self { + unsafe { TYPE_CLONE_COUNT.fetch_add(1, std::sync::atomic::Ordering::SeqCst) }; + match self { + Self::EmptyRec => Self::EmptyRec, + Self::EmptyTagUnion => Self::EmptyTagUnion, + Self::Function(arg0, arg1, arg2) => { + Self::Function(arg0.clone(), arg1.clone(), arg2.clone()) + } + Self::Record(arg0, arg1) => Self::Record(arg0.clone(), arg1.clone()), + Self::TagUnion(arg0, arg1) => Self::TagUnion(arg0.clone(), arg1.clone()), + Self::FunctionOrTagUnion(arg0, arg1, arg2) => { + Self::FunctionOrTagUnion(arg0.clone(), *arg1, arg2.clone()) + } + Self::ClosureTag { name, ext } => Self::ClosureTag { + name: *name, + ext: *ext, + }, + Self::DelayedAlias(arg0) => Self::DelayedAlias(arg0.clone()), + Self::Alias { + symbol, + type_arguments, + lambda_set_variables, + actual, + kind, + } => Self::Alias { + symbol: *symbol, + type_arguments: type_arguments.clone(), + lambda_set_variables: lambda_set_variables.clone(), + actual: actual.clone(), + kind: *kind, + }, + Self::HostExposedAlias { + name, + type_arguments, + lambda_set_variables, + actual_var, + actual, + } => Self::HostExposedAlias { + name: *name, + type_arguments: type_arguments.clone(), + lambda_set_variables: lambda_set_variables.clone(), + actual_var: *actual_var, + actual: actual.clone(), + }, + Self::RecursiveTagUnion(arg0, arg1, arg2) => { + Self::RecursiveTagUnion(*arg0, arg1.clone(), arg2.clone()) + } + Self::Apply(arg0, arg1, arg2) => Self::Apply(*arg0, arg1.clone(), *arg2), + Self::Variable(arg0) => Self::Variable(*arg0), + Self::RangedNumber(arg0, arg1) => Self::RangedNumber(arg0.clone(), arg1.clone()), + Self::Erroneous(arg0) => Self::Erroneous(arg0.clone()), + } + } +} + +#[derive(PartialEq, Eq, Clone)] +pub enum TypeExtension { + Open(Box), + Closed, +} + +impl TypeExtension { + #[inline(always)] + pub fn from_type(typ: Type) -> Self { + match typ { + Type::EmptyTagUnion | Type::EmptyRec => Self::Closed, + _ => Self::Open(Box::new(typ)), + } + } + + #[inline(always)] + pub fn is_closed(&self) -> bool { + match self { + TypeExtension::Open(_) => false, + TypeExtension::Closed => true, + } + } +} + +impl<'a> IntoIterator for &'a TypeExtension { + type Item = &'a Type; + + type IntoIter = std::option::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + match self { + TypeExtension::Open(ext) => Some(ext.as_ref()).into_iter(), + TypeExtension::Closed => None.into_iter(), + } + } +} + impl fmt::Debug for Type { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { @@ -244,6 +366,28 @@ impl fmt::Debug for Type { write!(f, ")") } + Type::DelayedAlias(AliasCommon { + symbol, + type_arguments, + lambda_set_variables, + }) => { + write!(f, "(DelayedAlias {:?}", symbol)?; + + for (_, arg) in type_arguments { + write!(f, " {:?}", arg)?; + } + + for (lambda_set, greek_letter) in + lambda_set_variables.iter().zip(GREEK_LETTERS.iter()) + { + write!(f, " {}@{:?}", greek_letter, lambda_set.0)?; + } + + write!(f, ")")?; + + Ok(()) + } + Type::Alias { symbol, type_arguments, @@ -315,12 +459,12 @@ impl fmt::Debug for Type { write!(f, "}}")?; - match *ext.clone() { - Type::EmptyRec => { + match ext { + TypeExtension::Closed => { // This is a closed record. We're done! Ok(()) } - other => { + TypeExtension::Open(other) => { // This is an open record, so print the variable // right after the '}' // @@ -356,12 +500,12 @@ impl fmt::Debug for Type { write!(f, "]")?; - match *ext.clone() { - Type::EmptyTagUnion => { + match ext { + TypeExtension::Closed => { // This is a closed variant. We're done! Ok(()) } - other => { + TypeExtension::Open(other) => { // This is an open tag union, so print the variable // right after the ']' // @@ -376,12 +520,12 @@ impl fmt::Debug for Type { write!(f, "{:?}", tag_name)?; write!(f, "]")?; - match *ext.clone() { - Type::EmptyTagUnion => { + match ext { + TypeExtension::Closed => { // This is a closed variant. We're done! Ok(()) } - other => { + TypeExtension::Open(other) => { // This is an open tag union, so print the variable // right after the ']' // @@ -426,19 +570,20 @@ impl fmt::Debug for Type { write!(f, "]")?; - match *ext.clone() { - Type::EmptyTagUnion => { + match ext { + TypeExtension::Closed => { // This is a closed variant. We're done! + Ok(()) } - other => { + TypeExtension::Open(other) => { // This is an open tag union, so print the variable // right after the ']' // // e.g. the "*" at the end of `[ Foo ]*` // or the "r" at the end of `[ DivByZero ]r` - other.fmt(f)?; + other.fmt(f) } - } + }?; write!(f, " as <{:?}>", rec) } @@ -486,80 +631,214 @@ impl Type { pub fn substitute(&mut self, substitutions: &ImMap) { use Type::*; - match self { - ClosureTag { ext: v, .. } | Variable(v) => { - if let Some(replacement) = substitutions.get(v) { - *self = replacement.clone(); - } - } - Function(args, closure, ret) => { - for arg in args { - arg.substitute(substitutions); - } - closure.substitute(substitutions); - ret.substitute(substitutions); - } - TagUnion(tags, ext) => { - for (_, args) in tags { - for x in args { - x.substitute(substitutions); + let mut stack = vec![self]; + + while let Some(typ) = stack.pop() { + match typ { + ClosureTag { ext: v, .. } | Variable(v) => { + if let Some(replacement) = substitutions.get(v) { + *typ = replacement.clone(); } } - ext.substitute(substitutions); - } - FunctionOrTagUnion(_, _, ext) => { - ext.substitute(substitutions); - } - RecursiveTagUnion(_, tags, ext) => { - for (_, args) in tags { - for x in args { - x.substitute(substitutions); + Function(args, closure, ret) => { + stack.extend(args); + stack.push(closure); + stack.push(ret); + } + TagUnion(tags, ext) => { + for (_, args) in tags { + stack.extend(args.iter_mut()); + } + + if let TypeExtension::Open(ext) = ext { + stack.push(ext); } } - ext.substitute(substitutions); - } - Record(fields, ext) => { - for (_, x) in fields.iter_mut() { - x.substitute(substitutions); + FunctionOrTagUnion(_, _, ext) => { + if let TypeExtension::Open(ext) = ext { + stack.push(ext); + } } - ext.substitute(substitutions); - } - Alias { - type_arguments, - lambda_set_variables, - actual, - .. - } => { - for (_, value) in type_arguments.iter_mut() { - value.substitute(substitutions); + RecursiveTagUnion(_, tags, ext) => { + for (_, args) in tags { + stack.extend(args.iter_mut()); + } + + if let TypeExtension::Open(ext) = ext { + stack.push(ext); + } + } + Record(fields, ext) => { + for (_, x) in fields.iter_mut() { + stack.push(x.as_inner_mut()); + } + + if let TypeExtension::Open(ext) = ext { + stack.push(ext); + } + } + Type::DelayedAlias(AliasCommon { + type_arguments, + lambda_set_variables, + .. + }) => { + for (_, value) in type_arguments.iter_mut() { + stack.push(value); + } + + for lambda_set in lambda_set_variables.iter_mut() { + stack.push(lambda_set.as_inner_mut()); + } + } + Alias { + type_arguments, + lambda_set_variables, + actual, + .. + } => { + for (_, value) in type_arguments.iter_mut() { + stack.push(value); + } + + for lambda_set in lambda_set_variables.iter_mut() { + stack.push(lambda_set.as_inner_mut()); + } + + stack.push(actual); + } + HostExposedAlias { + type_arguments, + lambda_set_variables, + actual: actual_type, + .. + } => { + for (_, value) in type_arguments.iter_mut() { + stack.push(value); + } + + for lambda_set in lambda_set_variables.iter_mut() { + stack.push(lambda_set.as_inner_mut()); + } + + stack.push(actual_type); + } + Apply(_, args, _) => { + stack.extend(args); + } + RangedNumber(typ, _) => { + stack.push(typ); } - for lambda_set in lambda_set_variables.iter_mut() { - lambda_set.substitute(substitutions); + EmptyRec | EmptyTagUnion | Erroneous(_) => {} + } + } + } + + pub fn substitute_variables(&mut self, substitutions: &MutMap) { + use Type::*; + + let mut stack = vec![self]; + + while let Some(typ) = stack.pop() { + match typ { + ClosureTag { ext: v, .. } | Variable(v) => { + if let Some(replacement) = substitutions.get(v) { + *v = *replacement; + } + } + Function(args, closure, ret) => { + stack.extend(args); + stack.push(closure); + stack.push(ret); + } + TagUnion(tags, ext) => { + for (_, args) in tags { + stack.extend(args.iter_mut()); + } + + if let TypeExtension::Open(ext) = ext { + stack.push(ext); + } + } + FunctionOrTagUnion(_, _, ext) => { + if let TypeExtension::Open(ext) = ext { + stack.push(ext); + } + } + RecursiveTagUnion(rec_var, tags, ext) => { + if let Some(replacement) = substitutions.get(rec_var) { + *rec_var = *replacement; + } + + for (_, args) in tags { + stack.extend(args.iter_mut()); + } + + if let TypeExtension::Open(ext) = ext { + stack.push(ext); + } + } + Record(fields, ext) => { + for (_, x) in fields.iter_mut() { + stack.push(x.as_inner_mut()); + } + if let TypeExtension::Open(ext) = ext { + stack.push(ext); + } + } + Type::DelayedAlias(AliasCommon { + type_arguments, + lambda_set_variables, + .. + }) => { + for (_, value) in type_arguments.iter_mut() { + stack.push(value); + } + + for lambda_set in lambda_set_variables.iter_mut() { + stack.push(lambda_set.as_inner_mut()); + } + } + Alias { + type_arguments, + lambda_set_variables, + actual, + .. + } => { + for (_, value) in type_arguments.iter_mut() { + stack.push(value); + } + for lambda_set in lambda_set_variables.iter_mut() { + stack.push(lambda_set.as_inner_mut()); + } + + stack.push(actual); + } + HostExposedAlias { + type_arguments, + lambda_set_variables, + actual: actual_type, + .. + } => { + for (_, value) in type_arguments.iter_mut() { + stack.push(value); + } + + for lambda_set in lambda_set_variables.iter_mut() { + stack.push(lambda_set.as_inner_mut()); + } + + stack.push(actual_type); + } + Apply(_, args, _) => { + stack.extend(args); + } + RangedNumber(typ, _) => { + stack.push(typ); } - actual.substitute(substitutions); + EmptyRec | EmptyTagUnion | Erroneous(_) => {} } - HostExposedAlias { - type_arguments: arguments, - actual: actual_type, - .. - } => { - for (_, value) in arguments.iter_mut() { - value.substitute(substitutions); - } - actual_type.substitute(substitutions); - } - Apply(_, args, _) => { - for arg in args { - arg.substitute(substitutions); - } - } - RangedNumber(typ, _) => { - typ.substitute(substitutions); - } - - EmptyRec | EmptyTagUnion | Erroneous(_) => {} } } @@ -581,20 +860,38 @@ impl Type { closure.substitute_alias(rep_symbol, rep_args, actual)?; ret.substitute_alias(rep_symbol, rep_args, actual) } - FunctionOrTagUnion(_, _, ext) => ext.substitute_alias(rep_symbol, rep_args, actual), + FunctionOrTagUnion(_, _, ext) => match ext { + TypeExtension::Open(ext) => ext.substitute_alias(rep_symbol, rep_args, actual), + TypeExtension::Closed => Ok(()), + }, RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => { for (_, args) in tags { for x in args { x.substitute_alias(rep_symbol, rep_args, actual)?; } } - ext.substitute_alias(rep_symbol, rep_args, actual) + + match ext { + TypeExtension::Open(ext) => ext.substitute_alias(rep_symbol, rep_args, actual), + TypeExtension::Closed => Ok(()), + } } Record(fields, ext) => { for (_, x) in fields.iter_mut() { x.substitute_alias(rep_symbol, rep_args, actual)?; } - ext.substitute_alias(rep_symbol, rep_args, actual) + + match ext { + TypeExtension::Open(ext) => ext.substitute_alias(rep_symbol, rep_args, actual), + TypeExtension::Closed => Ok(()), + } + } + DelayedAlias(AliasCommon { type_arguments, .. }) => { + for (_, ta) in type_arguments { + ta.substitute_alias(rep_symbol, rep_args, actual)?; + } + + Ok(()) } Alias { type_arguments, @@ -636,6 +933,13 @@ impl Type { } } + fn contains_symbol_ext(ext: &TypeExtension, rep_symbol: Symbol) -> bool { + match ext { + TypeExtension::Open(ext) => ext.contains_symbol(rep_symbol), + TypeExtension::Closed => false, + } + } + pub fn contains_symbol(&self, rep_symbol: Symbol) -> bool { use Type::*; @@ -645,9 +949,9 @@ impl Type { || closure.contains_symbol(rep_symbol) || args.iter().any(|arg| arg.contains_symbol(rep_symbol)) } - FunctionOrTagUnion(_, _, ext) => ext.contains_symbol(rep_symbol), + FunctionOrTagUnion(_, _, ext) => Self::contains_symbol_ext(ext, rep_symbol), RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => { - ext.contains_symbol(rep_symbol) + Self::contains_symbol_ext(ext, rep_symbol) || tags .iter() .map(|v| v.1.iter()) @@ -656,9 +960,19 @@ impl Type { } Record(fields, ext) => { - ext.contains_symbol(rep_symbol) + Self::contains_symbol_ext(ext, rep_symbol) || fields.values().any(|arg| arg.contains_symbol(rep_symbol)) } + DelayedAlias(AliasCommon { + symbol, + type_arguments, + .. + }) => { + symbol == &rep_symbol + || type_arguments + .iter() + .any(|v| v.1.contains_symbol(rep_symbol)) + } Alias { symbol: alias_symbol, actual: actual_type, @@ -674,6 +988,13 @@ impl Type { } } + fn contains_variable_ext(ext: &TypeExtension, rep_variable: Variable) -> bool { + match ext { + TypeExtension::Open(ext) => ext.contains_variable(rep_variable), + TypeExtension::Closed => false, + } + } + pub fn contains_variable(&self, rep_variable: Variable) -> bool { use Type::*; @@ -684,9 +1005,9 @@ impl Type { || closure.contains_variable(rep_variable) || args.iter().any(|arg| arg.contains_variable(rep_variable)) } - FunctionOrTagUnion(_, _, ext) => ext.contains_variable(rep_variable), + FunctionOrTagUnion(_, _, ext) => Self::contains_variable_ext(ext, rep_variable), RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => { - ext.contains_variable(rep_variable) + Self::contains_variable_ext(ext, rep_variable) || tags .iter() .map(|v| v.1.iter()) @@ -695,11 +1016,14 @@ impl Type { } Record(fields, ext) => { - ext.contains_variable(rep_variable) + Self::contains_variable_ext(ext, rep_variable) || fields .values() .any(|arg| arg.contains_variable(rep_variable)) } + DelayedAlias(AliasCommon { .. }) => { + todo!() + } Alias { actual: actual_type, .. @@ -713,11 +1037,8 @@ impl Type { } } - pub fn symbols(&self) -> ImSet { - let mut found_symbols = ImSet::default(); - symbols_help(self, &mut found_symbols); - - found_symbols + pub fn symbols(&self) -> Vec { + symbols_help(self) } /// a shallow dealias, continue until the first constructor is not an alias. @@ -747,7 +1068,9 @@ impl Type { ret.instantiate_aliases(region, aliases, var_store, introduced); } FunctionOrTagUnion(_, _, ext) => { - ext.instantiate_aliases(region, aliases, var_store, introduced); + if let TypeExtension::Open(ext) = ext { + ext.instantiate_aliases(region, aliases, var_store, introduced); + } } RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => { for (_, args) in tags { @@ -755,13 +1078,22 @@ impl Type { x.instantiate_aliases(region, aliases, var_store, introduced); } } - ext.instantiate_aliases(region, aliases, var_store, introduced); + + if let TypeExtension::Open(ext) = ext { + ext.instantiate_aliases(region, aliases, var_store, introduced); + } } Record(fields, ext) => { for (_, x) in fields.iter_mut() { x.instantiate_aliases(region, aliases, var_store, introduced); } - ext.instantiate_aliases(region, aliases, var_store, introduced); + + if let TypeExtension::Open(ext) = ext { + ext.instantiate_aliases(region, aliases, var_store, introduced); + } + } + DelayedAlias(AliasCommon { .. }) => { + // do nothing, yay } HostExposedAlias { type_arguments: type_args, @@ -845,7 +1177,10 @@ impl Type { for typ in tags.iter_mut().map(|v| v.1.iter_mut()).flatten() { typ.substitute(&substitution); } - ext.substitute(&substitution); + + if let TypeExtension::Open(ext) = &mut ext { + ext.substitute(&substitution); + } actual = Type::RecursiveTagUnion(new_rec_var, tags, ext); } @@ -906,14 +1241,17 @@ impl Type { pub fn is_narrow(&self) -> bool { match self.shallow_dealias() { Type::TagUnion(tags, ext) | Type::RecursiveTagUnion(_, tags, ext) => { - ext.is_empty_tag_union() + matches!(ext, TypeExtension::Closed) && tags.len() == 1 && tags[0].1.len() == 1 && tags[0].1[0].is_narrow() } - Type::Record(fields, ext) => { - fields.values().all(|field| field.as_inner().is_narrow()) && ext.is_narrow() - } + Type::Record(fields, ext) => match ext { + TypeExtension::Open(ext) => { + fields.values().all(|field| field.as_inner().is_narrow()) && ext.is_narrow() + } + TypeExtension::Closed => fields.values().all(|field| field.as_inner().is_narrow()), + }, Type::Function(args, clos, ret) => { args.iter().all(|a| a.is_narrow()) && clos.is_narrow() && ret.is_narrow() } @@ -934,62 +1272,73 @@ impl Type { } } -fn symbols_help(tipe: &Type, accum: &mut ImSet) { +fn symbols_help(initial: &Type) -> Vec { use Type::*; - match tipe { - Function(args, closure, ret) => { - symbols_help(ret, accum); - symbols_help(closure, accum); - args.iter().for_each(|arg| symbols_help(arg, accum)); - } - FunctionOrTagUnion(_, _, ext) => { - symbols_help(ext, accum); - } - RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => { - symbols_help(ext, accum); - tags.iter() - .map(|v| v.1.iter()) - .flatten() - .for_each(|arg| symbols_help(arg, accum)); - } + let mut output = vec![]; + let mut stack = vec![initial]; - Record(fields, ext) => { - symbols_help(ext, accum); - fields.values().for_each(|field| { - use RecordField::*; + while let Some(tipe) = stack.pop() { + match tipe { + Function(args, closure, ret) => { + stack.push(ret); + stack.push(closure); + stack.extend(args); + } + FunctionOrTagUnion(_, _, ext) => { + stack.extend(ext); + } + RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => { + stack.extend(ext); + stack.extend(tags.iter().map(|v| v.1.iter()).flatten()); + } - match field { - Optional(arg) => symbols_help(arg, accum), - Required(arg) => symbols_help(arg, accum), - Demanded(arg) => symbols_help(arg, accum), - } - }); + Record(fields, ext) => { + stack.extend(ext); + stack.extend(fields.values().map(|field| field.as_inner())); + } + DelayedAlias(AliasCommon { + symbol, + type_arguments, + .. + }) => { + output.push(*symbol); + stack.extend(type_arguments.iter().map(|v| &v.1)); + } + Alias { + symbol: alias_symbol, + actual: actual_type, + .. + } => { + // because the type parameters are inlined in the actual type, we don't need to look + // at the type parameters here + output.push(*alias_symbol); + stack.push(actual_type); + } + HostExposedAlias { name, actual, .. } => { + // because the type parameters are inlined in the actual type, we don't need to look + // at the type parameters here + output.push(*name); + stack.push(actual); + } + Apply(symbol, args, _) => { + output.push(*symbol); + stack.extend(args); + } + Erroneous(Problem::CyclicAlias(alias, _, _)) => { + output.push(*alias); + } + RangedNumber(typ, _) => { + stack.push(typ); + } + EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => {} } - Alias { - symbol: alias_symbol, - actual: actual_type, - .. - } => { - accum.insert(*alias_symbol); - symbols_help(actual_type, accum); - } - HostExposedAlias { name, actual, .. } => { - accum.insert(*name); - symbols_help(actual, accum); - } - Apply(symbol, args, _) => { - accum.insert(*symbol); - args.iter().for_each(|arg| symbols_help(arg, accum)); - } - Erroneous(Problem::CyclicAlias(alias, _, _)) => { - accum.insert(*alias); - } - RangedNumber(typ, _) => { - symbols_help(typ, accum); - } - EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => {} } + + output.sort(); + output.dedup(); + + output } fn variables_help(tipe: &Type, accum: &mut ImSet) { @@ -1019,7 +1368,10 @@ fn variables_help(tipe: &Type, accum: &mut ImSet) { Demanded(x) => variables_help(x, accum), }; } - variables_help(ext, accum); + + if let TypeExtension::Open(ext) = ext { + variables_help(ext, accum); + } } TagUnion(tags, ext) => { for (_, args) in tags { @@ -1027,10 +1379,15 @@ fn variables_help(tipe: &Type, accum: &mut ImSet) { variables_help(x, accum); } } - variables_help(ext, accum); + + if let TypeExtension::Open(ext) = ext { + variables_help(ext, accum); + } } FunctionOrTagUnion(_, _, ext) => { - variables_help(ext, accum); + if let TypeExtension::Open(ext) = ext { + variables_help(ext, accum); + } } RecursiveTagUnion(rec, tags, ext) => { for (_, args) in tags { @@ -1038,7 +1395,10 @@ fn variables_help(tipe: &Type, accum: &mut ImSet) { variables_help(x, accum); } } - variables_help(ext, accum); + + if let TypeExtension::Open(ext) = ext { + variables_help(ext, accum); + } // just check that this is actually a recursive type debug_assert!(accum.contains(rec)); @@ -1046,6 +1406,11 @@ fn variables_help(tipe: &Type, accum: &mut ImSet) { // this rec var doesn't need to be in flex_vars or rigid_vars accum.remove(rec); } + DelayedAlias(AliasCommon { type_arguments, .. }) => { + for (_, arg) in type_arguments { + variables_help(arg, accum); + } + } Alias { type_arguments, actual, @@ -1125,7 +1490,10 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) { Demanded(x) => variables_help_detailed(x, accum), }; } - variables_help_detailed(ext, accum); + + if let TypeExtension::Open(ext) = ext { + variables_help_detailed(ext, accum); + } } TagUnion(tags, ext) => { for (_, args) in tags { @@ -1133,10 +1501,15 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) { variables_help_detailed(x, accum); } } - variables_help_detailed(ext, accum); + + if let TypeExtension::Open(ext) = ext { + variables_help_detailed(ext, accum); + } } FunctionOrTagUnion(_, _, ext) => { - variables_help_detailed(ext, accum); + if let TypeExtension::Open(ext) = ext { + variables_help_detailed(ext, accum); + } } RecursiveTagUnion(rec, tags, ext) => { for (_, args) in tags { @@ -1144,16 +1517,24 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) { variables_help_detailed(x, accum); } } - variables_help_detailed(ext, accum); + + if let TypeExtension::Open(ext) = ext { + variables_help_detailed(ext, accum); + } // just check that this is actually a recursive type - debug_assert!(accum.type_variables.contains(rec)); + // debug_assert!(accum.type_variables.contains(rec)); // this rec var doesn't need to be in flex_vars or rigid_vars accum.type_variables.remove(rec); accum.recursion_variables.insert(*rec); } + DelayedAlias(AliasCommon { type_arguments, .. }) => { + for (_, arg) in type_arguments { + variables_help_detailed(arg, accum); + } + } Alias { type_arguments, actual, diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index 88d0e0da98..bfa36d4293 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -180,10 +180,12 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome { // println!("\n --------------- \n"); let content_1 = subs.get(ctx.first).content; let content_2 = subs.get(ctx.second).content; + let mode = if ctx.mode.is_eq() { "~" } else { "+=" }; println!( - "{:?} {:?} ~ {:?} {:?}", + "{:?} {:?} {} {:?} {:?}", ctx.first, roc_types::subs::SubsFmtContent(&content_1, subs), + mode, ctx.second, roc_types::subs::SubsFmtContent(&content_2, subs), ); @@ -320,9 +322,9 @@ fn unify_alias( if args.len() == other_args.len() { let mut problems = Vec::new(); let it = args - .variables() + .all_variables() .into_iter() - .zip(other_args.variables().into_iter()); + .zip(other_args.all_variables().into_iter()); for (l, r) in it { let l_var = subs[l]; @@ -334,7 +336,13 @@ fn unify_alias( problems.extend(merge(subs, ctx, *other_content)); } - // if problems.is_empty() { problems.extend(unify_pool(subs, pool, real_var, *other_real_var)); } + // THEORY: if two aliases or opaques have the same name and arguments, their + // real_var is the same and we don't need to check it. + // See https://github.com/rtfeldman/roc/pull/1510 + // + // if problems.is_empty() && either_is_opaque { + // problems.extend(unify_pool(subs, pool, real_var, *other_real_var, ctx.mode)); + // } problems } else { diff --git a/docs/Cargo.toml b/docs/Cargo.toml index 8df7f76be8..d377031ac9 100644 --- a/docs/Cargo.toml +++ b/docs/Cargo.toml @@ -20,8 +20,10 @@ roc_types = { path = "../compiler/types" } roc_parse = { path = "../compiler/parse" } roc_target = { path = "../compiler/roc_target" } roc_collections = { path = "../compiler/collections" } +roc_highlight = { path = "../highlight"} bumpalo = { version = "3.8.0", features = ["collections"] } snafu = { version = "0.6.10", features = ["backtraces"] } +peg = "0.8.0" [dev-dependencies] pretty_assertions = "1.0.0" diff --git a/docs/src/def.rs b/docs/src/def.rs deleted file mode 100644 index 0de951abdc..0000000000 --- a/docs/src/def.rs +++ /dev/null @@ -1,80 +0,0 @@ -use bumpalo::{collections::String as BumpString, Bump}; -use roc_ast::{ - ast_error::ASTResult, - lang::{self, core::def::def_to_def2::def_to_def2}, - mem_pool::pool::Pool, -}; -use roc_code_markup::{markup::convert::from_def2::def2_to_markup, slow_pool::SlowPool}; -use roc_module::symbol::{IdentIds, Interns, ModuleId}; -use roc_region::all::Region; -use roc_types::subs::VarStore; - -use crate::{docs_error::DocsResult, html::mark_node_to_html}; - -// html is written to buf -pub fn defs_to_html<'a>( - buf: &mut BumpString<'a>, - defs: Vec>, - env_module_id: ModuleId, - interns: &mut Interns, -) -> DocsResult<()> { - let mut env_pool = Pool::with_capacity(1024); - let env_arena = Bump::new(); - - let mut var_store = VarStore::default(); - let dep_idents = IdentIds::exposed_builtins(8); - let exposed_ident_ids = IdentIds::default(); - - let def_arena = Bump::new(); - - let mut env = lang::env::Env::new( - env_module_id, - &env_arena, - &mut env_pool, - &mut var_store, - dep_idents, - &interns.module_ids, - exposed_ident_ids, - ); - - let mut scope = lang::scope::Scope::new(env.home, env.pool, env.var_store); - scope.fill_scope(&env, &mut interns.all_ident_ids)?; - - let region = Region::zero(); - - for def in defs.iter() { - write_def_to_bump_str_html(&def_arena, &mut env, &mut scope, region, def, interns, buf)?; - } - - Ok(()) -} - -fn write_def_to_bump_str_html<'a, 'b>( - arena: &'a Bump, - env: &mut lang::env::Env<'a>, - scope: &mut lang::scope::Scope, - region: Region, - def: &'a roc_parse::ast::Def<'a>, - interns: &Interns, - buf: &mut BumpString<'b>, -) -> ASTResult<()> { - let def2 = def_to_def2(arena, env, scope, def, region); - - let def2_id = env.pool.add(def2); - - let mut mark_node_pool = SlowPool::default(); - - let def2_markup_id = def2_to_markup( - env, - env.pool.get(def2_id), - def2_id, - &mut mark_node_pool, - interns, - )?; - - let def2_markup_node = mark_node_pool.get(def2_markup_id); - - mark_node_to_html(def2_markup_node, &mark_node_pool, buf); - - Ok(()) -} diff --git a/docs/src/docs_error.rs b/docs/src/docs_error.rs index e554cd00a0..7ee2f06ced 100644 --- a/docs/src/docs_error.rs +++ b/docs/src/docs_error.rs @@ -1,7 +1,8 @@ +use peg::error::ParseError; use roc_ast::ast_error::ASTError; use roc_module::module_err::ModuleError; use roc_parse::parser::SyntaxError; -use snafu::{NoneError, ResultExt, Snafu}; +use snafu::Snafu; #[derive(Debug, Snafu)] #[snafu(visibility(pub))] @@ -18,17 +19,18 @@ pub enum DocsError { WrapSyntaxError { msg: String, }, + WrapPegParseError { + source: ParseError, + }, } pub type DocsResult = std::result::Result; impl<'a> From> for DocsError { fn from(syntax_err: SyntaxError) -> Self { - let msg = format!("{:?}", syntax_err); - - // hack to handle MarkError derive - let dummy_res: Result<(), NoneError> = Err(NoneError {}); - dummy_res.context(WrapSyntaxError { msg }).unwrap_err() + Self::WrapSyntaxError { + msg: format!("{:?}", syntax_err), + } } } @@ -43,3 +45,11 @@ impl From for DocsError { Self::WrapModuleError { source: module_err } } } + +impl From> for DocsError { + fn from(peg_parse_err: ParseError) -> Self { + Self::WrapPegParseError { + source: peg_parse_err, + } + } +} diff --git a/docs/src/expr.rs b/docs/src/expr.rs deleted file mode 100644 index 677e05c72f..0000000000 --- a/docs/src/expr.rs +++ /dev/null @@ -1,74 +0,0 @@ -use crate::html::mark_node_to_html; -use bumpalo::{collections::String as BumpString, Bump}; -use roc_ast::{ - ast_error::ASTResult, - lang::{self, core::expr::expr_to_expr2::expr_to_expr2}, - mem_pool::pool::Pool, -}; -use roc_code_markup::{markup::convert::from_expr2::expr2_to_markup, slow_pool::SlowPool}; -use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds}; -use roc_parse::ast::Expr; -use roc_region::all::Region; -use roc_types::subs::VarStore; - -// html is written to buf -pub fn expr_to_html<'a>( - buf: &mut BumpString<'a>, - expr: Expr<'a>, - env_module_id: ModuleId, - env_module_ids: &'a ModuleIds, - interns: &Interns, -) { - let mut env_pool = Pool::with_capacity(1024); - let env_arena = Bump::new(); - - let mut var_store = VarStore::default(); - let dep_idents = IdentIds::exposed_builtins(8); - let exposed_ident_ids = IdentIds::default(); - - let mut env = lang::env::Env::new( - env_module_id, - &env_arena, - &mut env_pool, - &mut var_store, - dep_idents, - env_module_ids, - exposed_ident_ids, - ); - - let mut scope = lang::scope::Scope::new(env.home, env.pool, env.var_store); - let region = Region::zero(); - - // TODO remove unwrap - write_expr_to_bump_str_html(&mut env, &mut scope, region, &expr, interns, buf).unwrap(); -} - -fn write_expr_to_bump_str_html<'a, 'b>( - env: &mut lang::env::Env<'a>, - scope: &mut lang::scope::Scope, - region: Region, - expr: &'a Expr, - interns: &Interns, - buf: &mut BumpString<'b>, -) -> ASTResult<()> { - let (expr2, _) = expr_to_expr2(env, scope, expr, region); - - let expr2_id = env.pool.add(expr2); - - let mut mark_node_pool = SlowPool::default(); - - let expr2_markup_id = expr2_to_markup( - env, - env.pool.get(expr2_id), - expr2_id, - &mut mark_node_pool, - interns, - 0, - )?; - - let expr2_markup_node = mark_node_pool.get(expr2_markup_id); - - mark_node_to_html(expr2_markup_node, &mark_node_pool, buf); - - Ok(()) -} diff --git a/docs/src/html.rs b/docs/src/html.rs index 38eb45b55d..baad3715db 100644 --- a/docs/src/html.rs +++ b/docs/src/html.rs @@ -1,12 +1,7 @@ -use bumpalo::collections::String as BumpString; use roc_code_markup::{markup::nodes::MarkupNode, slow_pool::SlowPool}; // determine appropriate css class for MarkupNode -pub fn mark_node_to_html<'a>( - mark_node: &MarkupNode, - mark_node_pool: &SlowPool, - buf: &mut BumpString<'a>, -) { +pub fn mark_node_to_html(mark_node: &MarkupNode, mark_node_pool: &SlowPool, buf: &mut String) { let mut additional_newlines = 0; match mark_node { @@ -31,7 +26,6 @@ pub fn mark_node_to_html<'a>( let css_class = match syn_high_style { Operator => "operator", - Comma => "comma", String => "string", FunctionName => "function-name", FunctionArgName => "function-arg-name", @@ -46,6 +40,9 @@ pub fn mark_node_to_html<'a>( Blank => "blank", Comment => "comment", DocsComment => "docs-comment", + UppercaseIdent => "uppercase-ident", + LowercaseIdent => "lowercase-ident", + Keyword => "keyword-ident", }; write_html_to_buf(content, css_class, buf); @@ -77,7 +74,7 @@ pub fn mark_node_to_html<'a>( } } -fn write_html_to_buf<'a>(content: &str, css_class: &'static str, buf: &mut BumpString<'a>) { +fn write_html_to_buf(content: &str, css_class: &'static str, buf: &mut String) { let opening_tag: String = [""].concat(); buf.push_str(opening_tag.as_str()); diff --git a/docs/src/lib.rs b/docs/src/lib.rs index 5749caf14c..0591c88121 100644 --- a/docs/src/lib.rs +++ b/docs/src/lib.rs @@ -1,27 +1,26 @@ extern crate pulldown_cmark; extern crate roc_load; -use bumpalo::{collections::String as BumpString, Bump}; -use def::defs_to_html; -use docs_error::DocsResult; -use expr::expr_to_html; +use bumpalo::Bump; +use docs_error::{DocsError, DocsResult}; +use html::mark_node_to_html; use roc_builtins::std::StdLib; use roc_can::scope::Scope; +use roc_code_markup::markup::nodes::MarkupNode; +use roc_code_markup::slow_pool::SlowPool; use roc_collections::all::MutMap; +use roc_highlight::highlight_parser::{highlight_defs, highlight_expr}; use roc_load::docs::DocEntry::DocDef; use roc_load::docs::{DocEntry, TypeAnnotation}; use roc_load::docs::{ModuleDocumentation, RecordField}; use roc_load::file::{LoadedModule, LoadingProblem}; -use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds}; +use roc_module::symbol::{IdentIds, Interns, ModuleId}; use roc_parse::ident::{parse_ident, Ident}; -use roc_parse::parser::SyntaxError; use roc_parse::state::State; -use roc_region::all::{Position, Region}; +use roc_region::all::Region; use std::fs; use std::path::{Path, PathBuf}; -mod def; mod docs_error; -mod expr; mod html; pub fn generate_docs_html(filenames: Vec, std_lib: StdLib, build_dir: &Path) { @@ -110,46 +109,45 @@ pub fn generate_docs_html(filenames: Vec, std_lib: StdLib, build_dir: & } // converts plain-text code to highlighted html -pub fn syntax_highlight_expr<'a>( - arena: &'a Bump, - buf: &mut BumpString<'a>, - code_str: &'a str, - env_module_id: ModuleId, - env_module_ids: &'a ModuleIds, - interns: &Interns, -) -> Result> { +pub fn syntax_highlight_expr(code_str: &str) -> DocsResult { let trimmed_code_str = code_str.trim_end().trim(); - let state = State::new(trimmed_code_str.as_bytes()); + let mut mark_node_pool = SlowPool::default(); - match roc_parse::expr::test_parse_expr(0, arena, state) { - Ok(loc_expr) => { - expr_to_html(buf, loc_expr.value, env_module_id, env_module_ids, interns); + let mut highlighted_html_str = String::new(); - Ok(buf.to_string()) + match highlight_expr(trimmed_code_str, &mut mark_node_pool) { + Ok(root_mark_node_id) => { + let root_mark_node = mark_node_pool.get(root_mark_node_id); + mark_node_to_html(root_mark_node, &mark_node_pool, &mut highlighted_html_str); + + Ok(highlighted_html_str) } - Err(fail) => Err(SyntaxError::Expr(fail, Position::default())), + Err(err) => Err(DocsError::from(err)), } } // converts plain-text code to highlighted html -pub fn syntax_highlight_top_level_defs<'a>( - arena: &'a Bump, - buf: &mut BumpString<'a>, - code_str: &'a str, - env_module_id: ModuleId, - interns: &mut Interns, -) -> DocsResult { +pub fn syntax_highlight_top_level_defs(code_str: &str) -> DocsResult { let trimmed_code_str = code_str.trim_end().trim(); - match roc_parse::test_helpers::parse_defs_with(arena, trimmed_code_str) { - Ok(vec_loc_def) => { - let vec_def = vec_loc_def.iter().map(|loc| loc.value).collect(); + let mut mark_node_pool = SlowPool::default(); - defs_to_html(buf, vec_def, env_module_id, interns)?; + let mut highlighted_html_str = String::new(); - Ok(buf.to_string()) + match highlight_defs(trimmed_code_str, &mut mark_node_pool) { + Ok(mark_node_id_vec) => { + let def_mark_nodes: Vec<&MarkupNode> = mark_node_id_vec + .iter() + .map(|mn_id| mark_node_pool.get(*mn_id)) + .collect(); + + for mn in def_mark_nodes { + mark_node_to_html(mn, &mark_node_pool, &mut highlighted_html_str) + } + + Ok(highlighted_html_str) } - Err(err) => Err(err.into()), + Err(err) => Err(DocsError::from(err)), } } @@ -426,7 +424,7 @@ pub fn load_modules_for_files(filenames: Vec, std_lib: StdLib) -> Vec modules.push(loaded), @@ -957,17 +955,9 @@ fn markdown_to_html( (0, 0) } Event::Text(CowStr::Borrowed(code_str)) if expecting_code_block => { - let code_block_arena = Bump::new(); - - let mut code_block_buf = BumpString::new_in(&code_block_arena); match syntax_highlight_expr( - &code_block_arena, - &mut code_block_buf, - code_str, - loaded_module.module_id, - &loaded_module.interns.module_ids, - &loaded_module.interns + code_str ) { Ok(highlighted_code_str) => { diff --git a/docs/tests/insert_syntax_highlighting.rs b/docs/tests/insert_syntax_highlighting.rs index 8cf05cdde5..e3ecc67195 100644 --- a/docs/tests/insert_syntax_highlighting.rs +++ b/docs/tests/insert_syntax_highlighting.rs @@ -1,38 +1,17 @@ #[macro_use] extern crate pretty_assertions; -#[macro_use] -extern crate indoc; +// Keep this around until the commented out tests can be enabled again. +/*#[macro_use] +extern crate indoc;*/ #[cfg(test)] mod insert_doc_syntax_highlighting { - use std::{fs::File, io::Write, path::PathBuf}; - use bumpalo::{collections::String as BumpString, Bump}; - use roc_ast::module::load_module; use roc_docs::{syntax_highlight_expr, syntax_highlight_top_level_defs}; - use roc_load::file::LoadedModule; - use tempfile::tempdir; - use uuid::Uuid; fn expect_html(code_str: &str, want: &str, use_expr: bool) { - let mut loaded_module = if use_expr { - make_mock_module("") - } else { - make_mock_module(code_str) - }; - - let code_block_arena = Bump::new(); - let mut code_block_buf = BumpString::new_in(&code_block_arena); - if use_expr { - match syntax_highlight_expr( - &code_block_arena, - &mut code_block_buf, - code_str, - loaded_module.module_id, - &loaded_module.interns.module_ids, - &loaded_module.interns, - ) { + match syntax_highlight_expr(code_str) { Ok(highlighted_code_str) => { assert_eq!(highlighted_code_str, want); } @@ -41,13 +20,7 @@ mod insert_doc_syntax_highlighting { } }; } else { - match syntax_highlight_top_level_defs( - &code_block_arena, - &mut code_block_buf, - code_str, - loaded_module.module_id, - &mut loaded_module.interns, - ) { + match syntax_highlight_top_level_defs(code_str) { Ok(highlighted_code_str) => { assert_eq!(highlighted_code_str, want); } @@ -58,36 +31,6 @@ mod insert_doc_syntax_highlighting { } } - pub const HELLO_WORLD: &str = r#"interface Test exposes [ ] imports [ ] - -main = "Hello, world!" - - -"#; - - fn make_mock_module(code_str: &str) -> LoadedModule { - let temp_dir = tempdir().expect("Failed to create temporary directory for test."); - let temp_file_path_buf = - PathBuf::from([Uuid::new_v4().to_string(), ".roc".to_string()].join("")); - let temp_file_full_path = temp_dir.path().join(temp_file_path_buf); - - let mut file = File::create(temp_file_full_path.clone()).unwrap_or_else(|_| { - panic!( - "Failed to create temporary file for path {:?}", - temp_file_full_path - ) - }); - - let mut full_code_str = HELLO_WORLD.to_owned(); - full_code_str.push_str("\n\n"); - full_code_str.push_str(code_str); - - writeln!(file, "{}", full_code_str) - .unwrap_or_else(|_| panic!("Failed to write {:?} to file: {:?}", HELLO_WORLD, file)); - - load_module(&temp_file_full_path) - } - fn expect_html_expr(code_str: &str, want: &str) { expect_html(code_str, want, true) } @@ -101,7 +44,9 @@ main = "Hello, world!" expect_html_expr("2", r#"2"#); } - #[test] + // These tests have been commented out due to introduction of a new syntax highlighting approach. + // You can make these tests work by following the instructions at the top of this file here: roc/highlight/src/highlight_parser.rs + /*#[test] fn string_expr() { expect_html_expr(r#""abc""#, r#""abc""#); } @@ -144,10 +89,18 @@ main = "Hello, world!" r#"{ a: { bB: "WoRlD" } }"#, "{ a: { bB: \"WoRlD\" } }", ); - } + }*/ #[test] - fn top_level_def_value() { + fn top_level_def_val_num() { + expect_html_def( + r#"myVal = 0"#, + "myVal = 0\n\n", + ); + } + + /*#[test] + fn top_level_def_val_str() { expect_html_def( r#"myVal = "Hello, World!""#, "myVal = \"Hello, World!\"\n\n\n", @@ -198,7 +151,7 @@ main = "Hello, world!" ), "# COMMENT\nmyVal = \"Hello, World!\"\n\n\n\n\n", ); - } + }*/ // TODO see issue #2134 /*#[test] diff --git a/editor/src/editor/grid_node_map.rs b/editor/src/editor/grid_node_map.rs index e342d624c1..04bc197d17 100644 --- a/editor/src/editor/grid_node_map.rs +++ b/editor/src/editor/grid_node_map.rs @@ -9,6 +9,7 @@ use crate::ui::text::text_pos::TextPos; use crate::ui::ui_error::{LineInsertionFailed, OutOfBounds, UIResult}; use crate::ui::util::{slice_get, slice_get_mut}; use roc_ast::lang::core::ast::ASTNodeId; +use roc_code_markup::markup::mark_id_ast_id_map::MarkIdAstIdMap; use roc_code_markup::markup::nodes::get_root_mark_node_id; use roc_code_markup::slow_pool::MarkNodeId; use roc_code_markup::slow_pool::SlowPool; @@ -210,18 +211,23 @@ impl GridNodeMap { ed_model: &EdModel, ) -> EdResult<(TextPos, TextPos, ASTNodeId, MarkNodeId)> { let line = slice_get(caret_pos.line, &self.lines)?; - let node_id = slice_get(caret_pos.column, line)?; - let node = ed_model.mark_node_pool.get(*node_id); + let node_id = *slice_get(caret_pos.column, line)?; + let node = ed_model.mark_node_pool.get(node_id); if node.is_nested() { - let (start_pos, end_pos) = self.get_nested_start_end_pos(*node_id, ed_model)?; + let (start_pos, end_pos) = self.get_nested_start_end_pos(node_id, ed_model)?; - Ok((start_pos, end_pos, node.get_ast_node_id(), *node_id)) + Ok(( + start_pos, + end_pos, + ed_model.mark_id_ast_id_map.get(node_id)?, + node_id, + )) } else { - let (first_node_index, last_node_index) = first_last_index_of(*node_id, line)?; + let (first_node_index, last_node_index) = first_last_index_of(node_id, line)?; - let curr_node_id = slice_get(first_node_index, line)?; - let curr_ast_node_id = ed_model.mark_node_pool.get(*curr_node_id).get_ast_node_id(); + let curr_node_id = *slice_get(first_node_index, line)?; + let curr_ast_node_id = ed_model.mark_id_ast_id_map.get(curr_node_id)?; let mut expr_start_index = first_node_index; let mut expr_end_index = last_node_index; @@ -230,11 +236,8 @@ impl GridNodeMap { let mut pos_extra_subtract = 0; for i in (0..first_node_index).rev() { - let prev_pos_node_id = slice_get(i, line)?; - let prev_ast_node_id = ed_model - .mark_node_pool - .get(*prev_pos_node_id) - .get_ast_node_id(); + let prev_pos_node_id = *slice_get(i, line)?; + let prev_ast_node_id = ed_model.mark_id_ast_id_map.get(prev_pos_node_id)?; if prev_ast_node_id == curr_ast_node_id { if pos_extra_subtract > 0 { @@ -253,10 +256,7 @@ impl GridNodeMap { for i in last_node_index..line.len() { let next_pos_node_id = slice_get(i, line)?; - let next_ast_node_id = ed_model - .mark_node_pool - .get(*next_pos_node_id) - .get_ast_node_id(); + let next_ast_node_id = ed_model.mark_id_ast_id_map.get(*next_pos_node_id)?; if next_ast_node_id == curr_ast_node_id { if pos_extra_add > 0 { @@ -270,8 +270,11 @@ impl GridNodeMap { } } - let correct_mark_node_id = - GridNodeMap::get_top_node_with_expr_id(*curr_node_id, &ed_model.mark_node_pool); + let correct_mark_node_id = GridNodeMap::get_top_node_with_expr_id( + curr_node_id, + &ed_model.mark_node_pool, + &ed_model.mark_id_ast_id_map, + )?; Ok(( TextPos { @@ -293,19 +296,18 @@ impl GridNodeMap { fn get_top_node_with_expr_id( curr_node_id: MarkNodeId, mark_node_pool: &SlowPool, - ) -> MarkNodeId { + mark_id_ast_id_map: &MarkIdAstIdMap, + ) -> EdResult { let curr_node = mark_node_pool.get(curr_node_id); if let Some(parent_id) = curr_node.get_parent_id_opt() { - let parent = mark_node_pool.get(parent_id); - - if parent.get_ast_node_id() == curr_node.get_ast_node_id() { - parent_id + if mark_id_ast_id_map.get(parent_id)? == mark_id_ast_id_map.get(curr_node_id)? { + Ok(parent_id) } else { - curr_node_id + Ok(curr_node_id) } } else { - curr_node_id + Ok(curr_node_id) } } @@ -388,6 +390,7 @@ impl GridNodeMap { &self, line_nr: usize, mark_node_pool: &SlowPool, + mark_id_ast_id_map: &MarkIdAstIdMap, ) -> EdResult { for curr_line_nr in (0..line_nr).rev() { let first_col_pos = TextPos { @@ -399,7 +402,7 @@ impl GridNodeMap { let mark_node_id = self.get_id_at_row_col(first_col_pos)?; let root_mark_node_id = get_root_mark_node_id(mark_node_id, mark_node_pool); - let ast_node_id = mark_node_pool.get(root_mark_node_id).get_ast_node_id(); + let ast_node_id = mark_id_ast_id_map.get(root_mark_node_id)?; if let ASTNodeId::ADefId(_) = ast_node_id { return Ok(root_mark_node_id); diff --git a/editor/src/editor/mvc/break_line.rs b/editor/src/editor/mvc/break_line.rs index 9b27106ab3..c5264d7a9e 100644 --- a/editor/src/editor/mvc/break_line.rs +++ b/editor/src/editor/mvc/break_line.rs @@ -1,4 +1,5 @@ use roc_ast::lang::core::def::def2::Def2; +use roc_code_markup::markup::common_nodes::NEW_LINES_AFTER_DEF; use crate::editor::ed_error::EdResult; use crate::editor::mvc::app_update::InputOutcome; @@ -21,38 +22,33 @@ pub fn break_line(ed_model: &mut EdModel) -> EdResult { column: caret_pos.column - 1, }) { - let new_blank_line_nr = caret_line_nr + 3; + let new_blank_line_nr = caret_line_nr + NEW_LINES_AFTER_DEF; // if there already is a blank line at new_blank_line_nr just move the caret there, don't add extra lines // safe unwrap, we already checked the nr_of_lines if !(ed_model.code_lines.nr_of_lines() >= new_blank_line_nr && ed_model.code_lines.line_len(new_blank_line_nr).unwrap() == 0) { - // two blank lines between top level definitions - EdModel::insert_empty_line(caret_line_nr + 1, &mut ed_model.grid_node_map)?; - EdModel::insert_empty_line(caret_line_nr + 2, &mut ed_model.grid_node_map)?; - // third "empty" line will be filled by the blank - EdModel::insert_empty_line(caret_line_nr + 3, &mut ed_model.grid_node_map)?; + for i in 1..=NEW_LINES_AFTER_DEF { + EdModel::insert_empty_line(caret_line_nr + i, &mut ed_model.grid_node_map)?; + } - insert_new_blank(ed_model, caret_pos.line + 3)?; + insert_new_blank(ed_model, caret_pos.line + NEW_LINES_AFTER_DEF + 1)?; } } } - ed_model.simple_move_carets_down(3); // two blank lines between top level definitions + ed_model.simple_move_carets_down(NEW_LINES_AFTER_DEF); // one blank lines between top level definitions Ok(InputOutcome::Accepted) } pub fn insert_new_blank(ed_model: &mut EdModel, insert_on_line_nr: usize) -> EdResult<()> { - println!( - "{}", - ed_model.module.ast.ast_to_string(ed_model.module.env.pool) - ); - // find position of the previous ASTNode to figure out where to add this new Blank ASTNode - let def_mark_node_id = ed_model - .grid_node_map - .get_def_mark_node_id_before_line(insert_on_line_nr, &ed_model.mark_node_pool)?; + let def_mark_node_id = ed_model.grid_node_map.get_def_mark_node_id_before_line( + insert_on_line_nr, + &ed_model.mark_node_pool, + &ed_model.mark_id_ast_id_map, + )?; let new_line_blank = Def2::Blank; let new_line_blank_id = ed_model.module.env.pool.add(new_line_blank); diff --git a/editor/src/editor/mvc/ed_model.rs b/editor/src/editor/mvc/ed_model.rs index 12a9d44564..f8a3c26d7d 100644 --- a/editor/src/editor/mvc/ed_model.rs +++ b/editor/src/editor/mvc/ed_model.rs @@ -16,6 +16,7 @@ use roc_ast::lang::env::Env; use roc_ast::mem_pool::pool_str::PoolStr; use roc_ast::parse::parse_ast; use roc_code_markup::markup::convert::from_ast::ast_to_mark_nodes; +use roc_code_markup::markup::mark_id_ast_id_map::MarkIdAstIdMap; use roc_code_markup::markup::nodes; use roc_code_markup::slow_pool::{MarkNodeId, SlowPool}; use roc_load::file::LoadedModule; @@ -31,6 +32,7 @@ pub struct EdModel<'a> { pub grid_node_map: GridNodeMap, // allows us to map window coordinates to MarkNodeId's pub markup_ids: Vec, // one root node for every top level definition pub mark_node_pool: SlowPool, // all MarkupNodes for this file are saved into this pool and can be retrieved using their MarkNodeId + pub mark_id_ast_id_map: MarkIdAstIdMap, // To find the ASTNode that is represented by a MarkNode pub glyph_dim_rect_opt: Option, // represents the width and height of single monospace glyph(char) pub has_focus: bool, pub caret_w_select_vec: NonEmpty<(CaretWSelect, Option)>, // the editor supports multiple carets/cursors and multiple selections @@ -64,7 +66,7 @@ pub fn init_model<'a>( let mut mark_node_pool = SlowPool::default(); - let markup_ids = if code_str.is_empty() { + let (markup_ids, mark_id_ast_id_map) = if code_str.is_empty() { EmptyCodeString {}.fail() } else { Ok(ast_to_mark_nodes( @@ -107,6 +109,7 @@ pub fn init_model<'a>( grid_node_map, markup_ids, mark_node_pool, + mark_id_ast_id_map, glyph_dim_rect_opt: None, has_focus: true, caret_w_select_vec: NonEmpty::new((caret, None)), @@ -160,7 +163,12 @@ impl<'a> EdModel<'a> { if let Some(parent_id) = curr_mark_node.get_parent_id_opt() { let parent = self.mark_node_pool.get(parent_id); - Ok(parent.get_child_indices(curr_mark_node_id, &self.mark_node_pool)?) + let ast_node_id = self.mark_id_ast_id_map.get(curr_mark_node_id)?; + Ok(parent.get_child_indices( + curr_mark_node_id, + ast_node_id, + &self.mark_id_ast_id_map, + )?) } else { MissingParent { node_id: curr_mark_node_id, @@ -212,7 +220,7 @@ impl<'a> EdModule<'a> { pub mod test_ed_model { use crate::editor::ed_error::EdResult; use crate::editor::mvc::ed_model; - use crate::editor::resources::strings::{HELLO_WORLD, PLATFORM_STR}; + use crate::editor::resources::strings::{nr_hello_world_lines, HELLO_WORLD, PLATFORM_STR}; use crate::ui::text::caret_w_select::test_caret_w_select::convert_dsl_to_selection; use crate::ui::text::caret_w_select::test_caret_w_select::convert_selection_to_dsl; use crate::ui::text::caret_w_select::CaretPos; @@ -331,10 +339,9 @@ pub mod test_ed_model { )?; // adjust caret for header and main function - let nr_hello_world_lines = HELLO_WORLD.matches('\n').count() - 1; let caret_w_select = convert_dsl_to_selection(&code_lines)?; let adjusted_caret_pos = TextPos { - line: caret_w_select.caret_pos.line + nr_hello_world_lines, + line: caret_w_select.caret_pos.line + nr_hello_world_lines(), column: caret_w_select.caret_pos.column, }; diff --git a/editor/src/editor/mvc/ed_update.rs b/editor/src/editor/mvc/ed_update.rs index 0f5e6e3a2f..ba546e85d1 100644 --- a/editor/src/editor/mvc/ed_update.rs +++ b/editor/src/editor/mvc/ed_update.rs @@ -377,8 +377,7 @@ impl<'a> EdModel<'a> { let expr2_level_mark_node = self.mark_node_pool.get(selected_block.mark_node_id); if let Some(parent_id) = expr2_level_mark_node.get_parent_id_opt() { - let parent_mark_node = self.mark_node_pool.get(parent_id); - let ast_node_id = parent_mark_node.get_ast_node_id(); + let ast_node_id = self.mark_id_ast_id_map.get(parent_id)?; let (expr_start_pos, expr_end_pos) = self .grid_node_map @@ -568,7 +567,6 @@ impl<'a> EdModel<'a> { let newlines_at_end = expr2_level_mark_node.get_newlines_at_end(); let blank_replacement = MarkupNode::Blank { - ast_node_id: sel_block.ast_node_id, attributes: Attributes::default(), parent_id_opt: expr2_level_mark_node.get_parent_id_opt(), newlines_at_end, @@ -658,13 +656,16 @@ impl<'a> EdModel<'a> { fn post_process_ast_update(&mut self) -> EdResult<()> { //dbg!("{}",self.module.ast.ast_to_string(self.module.env.pool)); - self.markup_ids = ast_to_mark_nodes( + let markup_ids_tup = ast_to_mark_nodes( &mut self.module.env, &self.module.ast, &mut self.mark_node_pool, &self.loaded_module.interns, )?; + self.markup_ids = markup_ids_tup.0; + self.mark_id_ast_id_map = markup_ids_tup.1; + self.code_lines = CodeLines::from_str(&nodes::mark_nodes_to_string( &self.markup_ids, &self.mark_node_pool, @@ -839,7 +840,7 @@ pub fn get_node_context<'a>(ed_model: &'a EdModel) -> EdResult> .get_id_at_row_col(ed_model.get_caret())?; let curr_mark_node = ed_model.mark_node_pool.get(curr_mark_node_id); let parent_id_opt = curr_mark_node.get_parent_id_opt(); - let ast_node_id = curr_mark_node.get_ast_node_id(); + let ast_node_id = ed_model.mark_id_ast_id_map.get(curr_mark_node_id)?; Ok(NodeContext { old_caret_pos, @@ -1002,10 +1003,7 @@ pub fn handle_new_char_expr( match expr_ref { Expr2::SmallInt { .. } => update_int(ed_model, curr_mark_node_id, ch)?, _ => { - let prev_ast_node_id = ed_model - .mark_node_pool - .get(prev_mark_node_id) - .get_ast_node_id(); + let prev_ast_node_id = ed_model.mark_id_ast_id_map.get(prev_mark_node_id)?; match prev_ast_node_id { ASTNodeId::ADefId(_) => InputOutcome::Ignored, @@ -1026,10 +1024,7 @@ pub fn handle_new_char_expr( let mark_parent_id_opt = curr_mark_node.get_parent_id_opt(); if let Some(mark_parent_id) = mark_parent_id_opt { - let parent_ast_id = ed_model - .mark_node_pool - .get(mark_parent_id) - .get_ast_node_id(); + let parent_ast_id = ed_model.mark_id_ast_id_map.get(mark_parent_id)?; match parent_ast_id { ASTNodeId::ADefId(_) => InputOutcome::Ignored, @@ -1047,10 +1042,7 @@ pub fn handle_new_char_expr( let mark_parent_id_opt = curr_mark_node.get_parent_id_opt(); if let Some(mark_parent_id) = mark_parent_id_opt { - let parent_ast_id = ed_model - .mark_node_pool - .get(mark_parent_id) - .get_ast_node_id(); + let parent_ast_id = ed_model.mark_id_ast_id_map.get(mark_parent_id)?; match parent_ast_id { ASTNodeId::ADefId(_) => InputOutcome::Ignored, @@ -1215,8 +1207,7 @@ pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult let outcome = if ed_model.node_exists_at_caret() { let curr_mark_node_id = ed_model.get_curr_mark_node_id()?; - let curr_mark_node = ed_model.mark_node_pool.get(curr_mark_node_id); - let ast_node_id = curr_mark_node.get_ast_node_id(); + let ast_node_id = ed_model.mark_id_ast_id_map.get(curr_mark_node_id)?; match ast_node_id { ASTNodeId::ADefId(def_id) => { @@ -1233,9 +1224,9 @@ pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult } else { let prev_mark_node_id_opt = ed_model.get_prev_mark_node_id()?; if let Some(prev_mark_node_id) = prev_mark_node_id_opt { - let prev_mark_node = ed_model.mark_node_pool.get(prev_mark_node_id); - let prev_ast_node = ed_model.module.env.pool.get(prev_mark_node.get_ast_node_id().to_expr_id()?); + let prev_ast_node_id = ed_model.mark_id_ast_id_map.get(prev_mark_node_id)?.to_expr_id()?; + let prev_ast_node = ed_model.module.env.pool.get(prev_ast_node_id); match prev_ast_node { Expr2::SmallInt{ .. } => { @@ -1284,6 +1275,8 @@ pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult #[cfg(test)] pub mod test_ed_update { + use std::iter; + use crate::editor::ed_error::print_err; use crate::editor::mvc::ed_model::test_ed_model::ed_model_from_dsl; use crate::editor::mvc::ed_model::test_ed_model::ed_model_to_dsl; @@ -1291,13 +1284,14 @@ pub mod test_ed_update { use crate::editor::mvc::ed_update::handle_new_char; use crate::editor::mvc::ed_update::EdModel; use crate::editor::mvc::ed_update::EdResult; - use crate::editor::resources::strings::HELLO_WORLD; + use crate::editor::resources::strings::nr_hello_world_lines; use crate::ui::text::lines::SelectableLines; use crate::ui::ui_error::UIResult; use crate::window::keyboard_input::no_mods; use crate::window::keyboard_input::test_modifiers::ctrl_cmd_shift; use crate::window::keyboard_input::Modifiers; use bumpalo::Bump; + use roc_code_markup::markup::common_nodes::NEW_LINES_AFTER_DEF; use roc_module::symbol::ModuleIds; use threadpool::ThreadPool; use winit::event::VirtualKeyCode::*; @@ -1432,8 +1426,7 @@ pub mod test_ed_update { } fn strip_header(lines: &mut Vec) { - let nr_hello_world_lines = HELLO_WORLD.matches('\n').count() - 1; - lines.drain(0..nr_hello_world_lines); + lines.drain(0..nr_hello_world_lines()); } pub fn assert_insert_seq_nls( @@ -1492,8 +1485,11 @@ pub mod test_ed_update { // add newlines like the editor's formatting would add them fn add_nls(lines: Vec) -> Vec { let mut new_lines = lines; - //Two lines between TLD's, extra newline so the user can go to third line add new def there - new_lines.append(&mut vec!["".to_owned(), "".to_owned(), "".to_owned()]); + //line(s) between TLD's, extra newline so the user can go to last line add new def there + let mut extra_empty_lines = iter::repeat("".to_owned()) + .take(NEW_LINES_AFTER_DEF) + .collect(); + new_lines.append(&mut extra_empty_lines); new_lines } @@ -2585,7 +2581,7 @@ pub mod test_ed_update { fn test_enter() -> Result<(), String> { assert_insert_seq( ovec!["┃"], - ovec!["ab = 5", "", "", "cd = \"good┃\"", "", "", ""], + add_nls(ovec!["ab = 5", "", "cd = \"good┃\""]), "ab🡲🡲🡲5\rcd🡲🡲🡲\"good", )?; diff --git a/editor/src/editor/mvc/int_update.rs b/editor/src/editor/mvc/int_update.rs index d32befdf3c..a30c9be1c0 100644 --- a/editor/src/editor/mvc/int_update.rs +++ b/editor/src/editor/mvc/int_update.rs @@ -65,7 +65,7 @@ pub fn update_int( .get_offset_to_node_id(old_caret_pos, int_mark_node_id)?; let int_mark_node = ed_model.mark_node_pool.get_mut(int_mark_node_id); - let int_ast_node_id = int_mark_node.get_ast_node_id(); + let int_ast_node_id = ed_model.mark_id_ast_id_map.get(int_mark_node_id)?; let content_str_mut = int_mark_node.get_content_mut()?; diff --git a/editor/src/editor/mvc/list_update.rs b/editor/src/editor/mvc/list_update.rs index a438017c6f..2df46bc55e 100644 --- a/editor/src/editor/mvc/list_update.rs +++ b/editor/src/editor/mvc/list_update.rs @@ -58,9 +58,7 @@ pub fn add_blank_child( let trip_result: EdResult<(ExprId, ExprId, MarkNodeId)> = if let Some(parent_id) = parent_id_opt { - let parent = ed_model.mark_node_pool.get(parent_id); - - let list_ast_node_id = parent.get_ast_node_id(); + let list_ast_node_id = ed_model.mark_id_ast_id_map.get(parent_id)?; let list_ast_node = ed_model.module.env.pool.get(list_ast_node_id.to_expr_id()?); match list_ast_node { diff --git a/editor/src/editor/mvc/record_update.rs b/editor/src/editor/mvc/record_update.rs index e92299d79c..ce172acbb3 100644 --- a/editor/src/editor/mvc/record_update.rs +++ b/editor/src/editor/mvc/record_update.rs @@ -91,7 +91,6 @@ pub fn update_empty_record( let record_field_node = MarkupNode::Text { content: new_input.to_owned(), - ast_node_id, syn_high_style: HighlightStyle::RecordField, attributes: Attributes::default(), parent_id_opt, @@ -149,9 +148,9 @@ pub fn update_record_colon( let prev_mark_node_id_opt = ed_model.get_prev_mark_node_id()?; if let Some(prev_mark_node_id) = prev_mark_node_id_opt { - let prev_mark_node = ed_model.mark_node_pool.get(prev_mark_node_id); + let prev_mn_ast_node_id = ed_model.mark_id_ast_id_map.get(prev_mark_node_id)?; - match prev_mark_node.get_ast_node_id() { + match prev_mn_ast_node_id { ASTNodeId::ADefId(_) => Ok(InputOutcome::Ignored), ASTNodeId::AExprId(prev_expr_id) => { let prev_expr = ed_model.module.env.pool.get(prev_expr_id); diff --git a/editor/src/editor/render_ast.rs b/editor/src/editor/render_ast.rs index d66457a79e..f19ed38736 100644 --- a/editor/src/editor/render_ast.rs +++ b/editor/src/editor/render_ast.rs @@ -105,7 +105,6 @@ fn markup_to_wgpu_helper<'a>( match markup_node { MarkupNode::Nested { - ast_node_id: _, children_ids, parent_id_opt: _, newlines_at_end, @@ -131,7 +130,6 @@ fn markup_to_wgpu_helper<'a>( } MarkupNode::Text { content, - ast_node_id: _, syn_high_style, attributes, parent_id_opt: _, @@ -183,7 +181,6 @@ fn markup_to_wgpu_helper<'a>( wgpu_texts.push(glyph_text); } MarkupNode::Blank { - ast_node_id: _, attributes: _, parent_id_opt: _, newlines_at_end, diff --git a/editor/src/editor/render_debug.rs b/editor/src/editor/render_debug.rs index 2fc1f23863..dec6950f60 100644 --- a/editor/src/editor/render_debug.rs +++ b/editor/src/editor/render_debug.rs @@ -45,9 +45,13 @@ pub fn build_debug_graphics( .with_color(colors::to_slice(from_hsb(266, 31, 96))) .with_scale(config.debug_font_size); - let mark_node_pool_text = glyph_brush::OwnedText::new(format!("{}", ed_model.mark_node_pool)) - .with_color(colors::to_slice(from_hsb(110, 45, 82))) - .with_scale(config.debug_font_size); + let mark_node_pool_text = glyph_brush::OwnedText::new( + ed_model + .mark_node_pool + .debug_string(&ed_model.mark_id_ast_id_map), + ) + .with_color(colors::to_slice(from_hsb(110, 45, 82))) + .with_scale(config.debug_font_size); let mut ast_node_text_str = "AST:\n".to_owned(); diff --git a/editor/src/editor/resources/strings.rs b/editor/src/editor/resources/strings.rs index c99234e626..13ad146536 100644 --- a/editor/src/editor/resources/strings.rs +++ b/editor/src/editor/resources/strings.rs @@ -23,9 +23,12 @@ app "test-app" main = "Hello, world!" - "#; +pub fn nr_hello_world_lines() -> usize { + HELLO_WORLD.matches('\n').count() - 1 +} + pub const PLATFORM_STR: &str = r#" platform "test-platform" requires {} { main : Str } diff --git a/editor/src/graphics/lowlevel/buffer.rs b/editor/src/graphics/lowlevel/buffer.rs index fb508b1ce3..76f26e0480 100644 --- a/editor/src/graphics/lowlevel/buffer.rs +++ b/editor/src/graphics/lowlevel/buffer.rs @@ -1,5 +1,5 @@ // Adapted from https://github.com/sotrh/learn-wgpu -// by Benjamin Hansen - license information can be found in the COPYRIGHT +// by Benjamin Hansen - license information can be found in the LEGAL_DETAILS // file in the root directory of this distribution. // // Thank you, Benjamin! @@ -159,7 +159,7 @@ impl StagingBuffer { } // Taken from https://github.com/sotrh/learn-wgpu -// by Benjamin Hansen - license information can be found in the COPYRIGHT +// by Benjamin Hansen - license information can be found in the LEGAL_DETAILS // file in the root directory of this distribution. // // Thank you, Benjamin! diff --git a/editor/src/graphics/lowlevel/vertex.rs b/editor/src/graphics/lowlevel/vertex.rs index f17a840bb2..85f40bce46 100644 --- a/editor/src/graphics/lowlevel/vertex.rs +++ b/editor/src/graphics/lowlevel/vertex.rs @@ -1,5 +1,5 @@ // Taken from https://github.com/sotrh/learn-wgpu -// by Benjamin Hansen - license information can be found in the COPYRIGHT +// by Benjamin Hansen - license information can be found in the LEGAL_DETAILS // file in the root directory of this distribution. // // Thank you, Benjamin! diff --git a/editor/src/graphics/primitives/text.rs b/editor/src/graphics/primitives/text.rs index bfd57c2fe7..57cd6d2ef9 100644 --- a/editor/src/graphics/primitives/text.rs +++ b/editor/src/graphics/primitives/text.rs @@ -1,5 +1,5 @@ // Adapted from https://github.com/sotrh/learn-wgpu -// by Benjamin Hansen - license information can be found in the COPYRIGHT +// by Benjamin Hansen - license information can be found in the LEGAL_DETAILS // file in the root directory of this distribution. // // Thank you, Benjamin! diff --git a/examples/benchmarks/.gitignore b/examples/benchmarks/.gitignore index 66394864b9..bb8dcb472f 100644 --- a/examples/benchmarks/.gitignore +++ b/examples/benchmarks/.gitignore @@ -9,3 +9,4 @@ rbtree-del rbtree-insert test-astar test-base64 +*.wasm diff --git a/examples/benchmarks/AStar.roc b/examples/benchmarks/AStar.roc index f4bd5ed452..4f5a8e0fa1 100644 --- a/examples/benchmarks/AStar.roc +++ b/examples/benchmarks/AStar.roc @@ -25,14 +25,14 @@ cheapestOpen = \costFn, model -> model.openSet |> Set.toList |> List.keepOks - (\position -> - when Dict.get model.costs position is - Err _ -> - Err {} + (\position -> + when Dict.get model.costs position is + Err _ -> + Err {} - Ok cost -> - Ok { cost: cost + costFn position, position } - ) + Ok cost -> + Ok { cost: cost + costFn position, position } + ) |> Quicksort.sortBy .cost |> List.first |> Result.map .position diff --git a/examples/gui/platform/src/graphics/lowlevel/buffer.rs b/examples/gui/platform/src/graphics/lowlevel/buffer.rs index dbe0270e57..a5a7f54161 100644 --- a/examples/gui/platform/src/graphics/lowlevel/buffer.rs +++ b/examples/gui/platform/src/graphics/lowlevel/buffer.rs @@ -39,7 +39,7 @@ const QUAD_VERTS: [Vertex; 4] = [ }, ]; -pub const MAX_QUADS: usize = 100_000; +pub const MAX_QUADS: usize = 1_000; pub fn create_rect_buffers( gpu_device: &wgpu::Device, @@ -59,7 +59,7 @@ pub fn create_rect_buffers( }); let quad_buffer = gpu_device.create_buffer(&wgpu::BufferDescriptor { - label: Some("iced_wgpu::quad instance buffer"), + label: None, size: mem::size_of::() as u64 * MAX_QUADS as u64, usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, diff --git a/examples/gui/platform/src/gui.rs b/examples/gui/platform/src/gui.rs index 0e6bc8b24b..29caa01404 100644 --- a/examples/gui/platform/src/gui.rs +++ b/examples/gui/platform/src/gui.rs @@ -178,7 +178,7 @@ fn run_event_loop(title: &str, root: RocElem) -> Result<(), Box> { // } // } // } - todo!("TODO handle keyboard input"); + // TODO todo!("TODO handle keyboard input"); } //Modifiers Changed Event::WindowEvent { diff --git a/examples/interactive/echo.roc b/examples/interactive/echo.roc index 4eefae59f5..84ca8c668d 100644 --- a/examples/interactive/echo.roc +++ b/examples/interactive/echo.roc @@ -23,11 +23,11 @@ echo = \shout -> shout |> Str.toUtf8 |> List.mapWithIndex - (\_, i -> - length = (List.len (Str.toUtf8 shout) - i) - phrase = (List.split (Str.toUtf8 shout) length).before + (\_, i -> + length = (List.len (Str.toUtf8 shout) - i) + phrase = (List.split (Str.toUtf8 shout) length).before - List.concat (silence (if i == 0 then 2 * length else length)) phrase) + List.concat (silence (if i == 0 then 2 * length else length)) phrase) |> List.join |> Str.fromUtf8 |> Result.withDefault "" diff --git a/highlight/Cargo.toml b/highlight/Cargo.toml new file mode 100644 index 0000000000..aef00a2c33 --- /dev/null +++ b/highlight/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "roc_highlight" +version = "0.1.0" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +edition = "2018" +description = "For syntax highlighting, starts with a string and returns our markup nodes." + +[dependencies] +peg = "0.8.0" +roc_code_markup = { path = "../code_markup"} diff --git a/highlight/src/highlight_parser.rs b/highlight/src/highlight_parser.rs new file mode 100644 index 0000000000..f096cf7cec --- /dev/null +++ b/highlight/src/highlight_parser.rs @@ -0,0 +1,244 @@ +use peg::error::ParseError; +use roc_code_markup::markup::attribute::Attributes; +use roc_code_markup::markup::common_nodes::{ + else_mn, if_mn, new_assign_mn, new_dot_mn, new_equals_mn, new_if_expr_mn, + new_module_name_mn_id, new_module_var_mn, then_mn, +}; +use roc_code_markup::markup::nodes::MarkupNode; +use roc_code_markup::slow_pool::{MarkNodeId, SlowPool}; +use roc_code_markup::syntax_highlight::HighlightStyle; + +use crate::tokenizer::{full_tokenize, Token, TokenTable}; + +type T = Token; + +// Inspired by https://ziglang.org/documentation/0.7.1/#Grammar +// license information can be found in the LEGAL_DETAILS file in +// the root directory of this distribution. +// Thank you zig contributors! + +/* +HOW TO ADD NEW RULES: +- go to highlight/tests/peg_grammar.rs +- find for example a variant of common_expr that is not implemented yet, like `if_expr` +- we add `if_expr()` to the `common_expr` rule, in the same order as in peg_grammar::common_expr() +- we copy the if_expr rule from `peg_grammar.rs` +- we add ` -> MarkNodeId` to the if_expr rule +- we change the first full_expr in if_expr() to cond_e_id:full_expr(), the second to then_e_id:full_expr()... +- we add if_mn(), else_mn(), then_mn() and new_if_expr_mn() to common_nodes.rs +- we replace [T::KeywordIf],[T::KeywordThen]... with a new if(),... rule that adds an if,... node to the mark_node_pool. +- we bundle everything together in a nested node and save it in the mn_pool: +``` +{ + mn_pool.add( + new_if_expr_mn(if_id, cond_e_id, then_id, then_e_id, else_id, else_e_id) + ) +} +- we finsih up by adding a test: `test_highlight_if_expr` +``` +*/ +peg::parser! { + grammar highlightparser(t_table: &TokenTable, code_str: &str, mn_pool: &mut SlowPool) for [T] { + + pub rule full_expr() -> MarkNodeId = + common_expr() + + pub rule full_exprs() -> Vec = + opt_same_indent_expr()* + + rule opt_same_indent_expr() -> MarkNodeId = + [T::SameIndent]? e_id:full_expr() {e_id} + + rule opt_same_indent_def() -> MarkNodeId = + [T::SameIndent]? d_id:def() {d_id} + + rule common_expr() -> MarkNodeId = + if_expr() + / p:position!() [T::Number] { add_new_mn(t_table.extract_str(p, code_str), HighlightStyle::Number, mn_pool) } + / module_var() + / lowercase_ident() + + rule if_expr() -> MarkNodeId = + if_id:if() cond_e_id:full_expr() then_id:then() then_e_id:full_expr() else_id:else_rule() else_e_id:full_expr() + { + mn_pool.add( + new_if_expr_mn(if_id, cond_e_id, then_id, then_e_id, else_id, else_e_id) + ) + } + + rule if() -> MarkNodeId = + [T::KeywordIf] {mn_pool.add(if_mn())} + + rule then() -> MarkNodeId = + [T::KeywordThen] {mn_pool.add(then_mn())} + + rule else_rule() -> MarkNodeId = + [T::KeywordElse] {mn_pool.add(else_mn())} + + pub rule def() -> MarkNodeId = + // annotated_body() + // annotation() + /* / */ body() + // alias() + // expect() + + pub rule module_defs() -> Vec = + opt_same_indent_def()+ + + rule body() -> MarkNodeId = + ident_id:ident() as_id:assign() [T::OpenIndent] e_id:full_expr() /*TODO not sure when this is needed> es:full_exprs()*/ ([T::CloseIndent] / end_of_file()) + { + mn_pool.add( + new_assign_mn(ident_id, as_id, e_id) + ) + } + / + ident_id:ident() as_id:assign() e_id:full_expr() end_of_file()? + { + mn_pool.add( + new_assign_mn(ident_id, as_id, e_id) + ) + } + + rule module_var() -> MarkNodeId = + mod_name_id:module_name() dot_id:dot() ident_id:lowercase_ident() { + mn_pool.add( + new_module_var_mn(mod_name_id, dot_id, ident_id) + ) + } + + rule module_name() -> MarkNodeId = + first_ident_id:uppercase_ident() rest_ids:dot_idents() { + new_module_name_mn_id( + merge_ids(first_ident_id, rest_ids), + mn_pool + ) + } + + rule assign() -> MarkNodeId = + [T::OpAssignment] { mn_pool.add(new_equals_mn()) } + + rule dot() -> MarkNodeId = + [T::Dot] { mn_pool.add(new_dot_mn()) } + + rule dot_ident() -> (MarkNodeId, MarkNodeId) = + dot_id:dot() ident_id:uppercase_ident() { (dot_id, ident_id) } + + rule dot_idents() -> Vec = + di:dot_ident()* {flatten_tups(di)} + + rule ident() -> MarkNodeId = + uppercase_ident() + / lowercase_ident() + + rule uppercase_ident() -> MarkNodeId = + p:position!() [T::UppercaseIdent] { add_new_mn(t_table.extract_str(p, code_str), HighlightStyle::UppercaseIdent, mn_pool) } + + rule lowercase_ident() -> MarkNodeId = + p:position!() [T::LowercaseIdent] { add_new_mn(t_table.extract_str(p, code_str), HighlightStyle::LowercaseIdent, mn_pool) } + + rule end_of_file() = + ![_] + + } +} +fn merge_ids(mn_id: MarkNodeId, other_mn_id: Vec) -> Vec { + let mut ids = vec![mn_id]; + let mut rest_ids: Vec = other_mn_id; + + ids.append(&mut rest_ids); + + ids +} + +fn flatten_tups(tup_vec: Vec<(MarkNodeId, MarkNodeId)>) -> Vec { + tup_vec.iter().flat_map(|(a, b)| vec![*a, *b]).collect() +} + +fn add_new_mn( + text: &str, + highlight_style: HighlightStyle, + mark_node_pool: &mut SlowPool, +) -> MarkNodeId { + let m_node = MarkupNode::Text { + content: text.to_owned(), + syn_high_style: highlight_style, + attributes: Attributes::default(), + parent_id_opt: None, + newlines_at_end: 0, + }; + mark_node_pool.add(m_node) +} + +pub fn highlight_expr( + code_str: &str, + mark_node_pool: &mut SlowPool, +) -> Result> { + let token_table = full_tokenize(code_str); + + highlightparser::full_expr(&token_table.tokens, &token_table, code_str, mark_node_pool) +} + +pub fn highlight_defs( + code_str: &str, + mark_node_pool: &mut SlowPool, +) -> Result, ParseError> { + let token_table = full_tokenize(code_str); + + highlightparser::module_defs(&token_table.tokens, &token_table, code_str, mark_node_pool) +} + +#[cfg(test)] +pub mod highlight_tests { + use roc_code_markup::{markup::nodes::node_to_string_w_children, slow_pool::SlowPool}; + + use crate::highlight_parser::{highlight_defs, highlight_expr}; + + fn test_highlight_expr(input: &str, expected_output: &str) { + let mut mark_node_pool = SlowPool::default(); + + let mark_id = highlight_expr(input, &mut mark_node_pool).unwrap(); + + let mut str_buffer = String::new(); + + node_to_string_w_children(mark_id, &mut str_buffer, &mark_node_pool); + + assert_eq!(&str_buffer, expected_output); + } + + #[test] + fn test_highlight() { + test_highlight_expr("0", "0"); + } + + #[test] + fn test_highlight_module_var() { + test_highlight_expr("Foo.Bar.var", "Foo.Bar.var"); + } + + #[test] + fn test_highlight_if_expr() { + test_highlight_expr( + "if booly then 42 else 31415", + "if booly then 42 else 31415\n", + ) + } + + #[test] + fn test_highlight_defs() { + let mut mark_node_pool = SlowPool::default(); + + let mut str_buffer = String::new(); + + node_to_string_w_children( + *highlight_defs("a = 0", &mut mark_node_pool) + .unwrap() + .get(0) + .unwrap(), + &mut str_buffer, + &mark_node_pool, + ); + + assert_eq!(&str_buffer, "a = 0\n\n"); + } +} diff --git a/highlight/src/lib.rs b/highlight/src/lib.rs new file mode 100644 index 0000000000..9562ca2ab3 --- /dev/null +++ b/highlight/src/lib.rs @@ -0,0 +1,2 @@ +pub mod highlight_parser; +pub mod tokenizer; diff --git a/highlight/src/tokenizer.rs b/highlight/src/tokenizer.rs new file mode 100644 index 0000000000..9eddb99da1 --- /dev/null +++ b/highlight/src/tokenizer.rs @@ -0,0 +1,664 @@ +use std::cmp::Ordering; + +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// Tokens are full of very dense information to make checking properties about them +/// very fast. +/// Some bits have specific meanings: +/// * 0b_001*_****: "Identifier-like" things +/// * 0b_01**_****: "Punctuation" +/// * 0b_0100_1***: []{}() INDENT/DEDENT +/// * 0b_0100_1**0 [{(INDENT +/// * 0b_0100_1**1 ]})DEDENT +/// * 0b_011*_**** Operators +pub enum Token { + LowercaseIdent = 0b_0010_0000, + UppercaseIdent = 0b_0011_0011, + MalformedIdent = 0b_0010_0001, + + KeywordIf = 0b_0010_0010, + KeywordThen = 0b_0010_0011, + KeywordElse = 0b_0010_0100, + KeywordWhen = 0b_0010_0101, + KeywordAs = 0b_0010_0110, + KeywordIs = 0b_0010_0111, + KeywordExpect = 0b_0010_1000, + KeywordApp = 0b_0010_1001, + KeywordInterface = 0b_0010_1010, + KeywordPackages = 0b_0010_1011, + KeywordImports = 0b_0010_1100, + KeywordProvides = 0b_0010_1101, + KeywordTo = 0b_0010_1110, + KeywordExposes = 0b_0010_1111, + KeywordEffects = 0b_0011_0000, + KeywordPlatform = 0b_0011_0001, + KeywordRequires = 0b_0011_0010, + + Comma = 0b_0100_0000, + Colon = 0b_0100_0001, + + OpenParen = 0b_0100_1000, + CloseParen = 0b_0100_1001, + OpenCurly = 0b_0100_1010, + CloseCurly = 0b_0100_1011, + OpenSquare = 0b_0100_1100, + CloseSquare = 0b_0100_1101, + OpenIndent = 0b_0100_1110, + CloseIndent = 0b_0100_1111, + SameIndent = 0b_0101_0000, + + OpPlus = 0b_0110_0000, + OpMinus = 0b_0110_0001, + OpSlash = 0b_0110_0010, + OpPercent = 0b_0110_0011, + OpCaret = 0b_0110_0100, + OpGreaterThan = 0b_0110_0101, + OpLessThan = 0b_0110_0110, + OpAssignment = 0b_0110_0111, + OpPizza = 0b_0110_1000, + OpEquals = 0b_0110_1001, + OpNotEquals = 0b_0110_1010, + OpGreaterThanOrEq = 0b_0110_1011, + OpLessThanOrEq = 0b_0110_1100, + OpAnd = 0b_0110_1101, + OpOr = 0b_0110_1110, + OpDoubleSlash = 0b_0110_1111, + OpDoublePercent = 0b_0111_0001, + OpBackpassing = 0b_0111_1010, + + TodoNextThing = 0b_1000_0000, + + Malformed, + MalformedOperator, + + PrivateTag, + + String, + + NumberBase, + Number, + + QuestionMark, + + Underscore, + + Ampersand, + Pipe, + Dot, + SpaceDot, // ` .` necessary to know difference between `Result.map .position` and `Result.map.position` + Bang, + LambdaStart, + Arrow, + FatArrow, + Asterisk, +} + +#[derive(Default)] +pub struct TokenTable { + pub tokens: Vec, + pub offsets: Vec, + pub lengths: Vec, +} + +#[derive(Default)] +pub struct LexState { + indents: Vec, +} + +trait ConsumeToken { + fn token(&mut self, token: Token, _offset: usize, _length: usize); +} + +#[derive(Default)] +struct TokenConsumer { + token_table: TokenTable, +} + +impl ConsumeToken for TokenConsumer { + fn token(&mut self, token: Token, offset: usize, length: usize) { + self.token_table.tokens.push(token); + self.token_table.offsets.push(offset); + self.token_table.lengths.push(length); + } +} + +pub fn tokenize(code_str: &str) -> Vec { + full_tokenize(code_str).tokens +} + +pub fn full_tokenize(code_str: &str) -> TokenTable { + let mut lex_state = LexState::default(); + let mut consumer = TokenConsumer::default(); + + consume_all_tokens(&mut lex_state, code_str.as_bytes(), &mut consumer); + + consumer.token_table +} + +fn consume_all_tokens(state: &mut LexState, bytes: &[u8], consumer: &mut impl ConsumeToken) { + let mut i = 0; + + while i < bytes.len() { + let bytes = &bytes[i..]; + + let (token, len) = match bytes[0] { + b'(' => (Token::OpenParen, 1), + b')' => (Token::CloseParen, 1), + b'{' => (Token::OpenCurly, 1), + b'}' => (Token::CloseCurly, 1), + b'[' => (Token::OpenSquare, 1), + b']' => (Token::CloseSquare, 1), + b',' => (Token::Comma, 1), + b'_' => lex_underscore(bytes), + b'@' => lex_private_tag(bytes), + b'a'..=b'z' => lex_ident(false, bytes), + b'A'..=b'Z' => lex_ident(true, bytes), + b'0'..=b'9' => lex_number(bytes), + b'-' | b':' | b'!' | b'.' | b'*' | b'/' | b'&' | b'%' | b'^' | b'+' | b'<' | b'=' + | b'>' | b'|' | b'\\' => lex_operator(bytes), + b' ' => match skip_whitespace(bytes) { + SpaceDotOrSpaces::SpacesWSpaceDot(skip) => { + i += skip; + (Token::SpaceDot, 1) + } + SpaceDotOrSpaces::Spaces(skip) => { + i += skip; + continue; + } + }, + b'\n' => { + // TODO: add newline to side_table + let skip_newline_return = skip_newlines_and_comments(bytes); + + match skip_newline_return { + SkipNewlineReturn::SkipWIndent(skipped_lines, curr_line_indent) => { + add_indents(skipped_lines, curr_line_indent, state, consumer, &mut i); + continue; + } + SkipNewlineReturn::WSpaceDot(skipped_lines, curr_line_indent) => { + add_indents(skipped_lines, curr_line_indent, state, consumer, &mut i); + (Token::SpaceDot, 1) + } + } + } + b'#' => { + // TODO: add comment to side_table + i += skip_comment(bytes); + continue; + } + b'"' => lex_string(bytes), + b => todo!("handle {:?}", b as char), + }; + + consumer.token(token, i, len); + i += len; + } +} + +fn add_indents( + skipped_lines: usize, + curr_line_indent: usize, + state: &mut LexState, + consumer: &mut impl ConsumeToken, + curr_byte_ctr: &mut usize, +) { + *curr_byte_ctr += skipped_lines; + + if let Some(&prev_indent) = state.indents.last() { + if curr_line_indent > prev_indent { + state.indents.push(curr_line_indent); + consumer.token(Token::OpenIndent, *curr_byte_ctr, 0); + } else { + *curr_byte_ctr += curr_line_indent; + + match prev_indent.cmp(&curr_line_indent) { + Ordering::Equal => { + consumer.token(Token::SameIndent, *curr_byte_ctr, 0); + } + Ordering::Greater => { + while state.indents.last().is_some() + && curr_line_indent < *state.indents.last().unwrap() + // safe unwrap because we check first + { + state.indents.pop(); + consumer.token(Token::CloseIndent, *curr_byte_ctr, 0); + } + } + Ordering::Less => {} + } + } + } else if curr_line_indent > 0 { + state.indents.push(curr_line_indent); + consumer.token(Token::OpenIndent, *curr_byte_ctr, 0); + } else { + consumer.token(Token::SameIndent, *curr_byte_ctr, 0); + } +} + +impl TokenTable { + pub fn extract_str<'a>(&self, index: usize, content: &'a str) -> &'a str { + // Not returning a result here because weaving it through highlight_parser makes it more difficult to expand and understand. + // The only way I think this can panic is by calling position! in highlight_parser after the last element, which does not make sense to begin with. + let len = *self.lengths.get(index).unwrap_or_else(|| { + panic!( + "Index {:?} was out of bounds for TokenTable.lengths with len {:?}", + index, + self.lengths.len() + ) + }); + let offset = *self.offsets.get(index).unwrap_or_else(|| { + panic!( + "Index {:?} was out of bounds for TokenTable.offsets with len {:?}", + index, + self.lengths.len() + ) + }); + + &content[offset..(offset + len)] + } +} + +fn skip_comment(bytes: &[u8]) -> usize { + let mut skip = 0; + while skip < bytes.len() && bytes[skip] != b'\n' { + skip += 1; + } + if (skip + 1) < bytes.len() && bytes[skip] == b'\n' && bytes[skip + 1] == b'#' { + skip += 1; + } + + skip +} + +#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord)] +struct Indent(usize); + +enum SpaceDotOrSpaces { + SpacesWSpaceDot(usize), + Spaces(usize), +} + +fn skip_whitespace(bytes: &[u8]) -> SpaceDotOrSpaces { + debug_assert!(bytes[0] == b' '); + + let mut skip = 0; + while skip < bytes.len() && bytes[skip] == b' ' { + skip += 1; + } + + if skip < bytes.len() && bytes[skip] == b'.' { + SpaceDotOrSpaces::SpacesWSpaceDot(skip) + } else { + SpaceDotOrSpaces::Spaces(skip) + } +} + +enum SkipNewlineReturn { + SkipWIndent(usize, usize), + WSpaceDot(usize, usize), +} + +// also skips lines that contain only whitespace +fn skip_newlines_and_comments(bytes: &[u8]) -> SkipNewlineReturn { + let mut skip = 0; + let mut indent = 0; + + while skip < bytes.len() && bytes[skip] == b'\n' { + skip += indent + 1; + + if bytes.len() > skip { + if bytes[skip] == b' ' { + let space_dot_or_spaces = skip_whitespace(&bytes[skip..]); + + match space_dot_or_spaces { + SpaceDotOrSpaces::SpacesWSpaceDot(spaces) => { + return SkipNewlineReturn::WSpaceDot(skip, spaces) + } + SpaceDotOrSpaces::Spaces(spaces) => { + if bytes.len() > (skip + spaces) { + if bytes[skip + spaces] == b'\n' { + indent = 0; + skip += spaces; + } else if bytes[skip + spaces] == b'#' { + let comment_skip = skip_comment(&bytes[(skip + spaces)..]); + + indent = 0; + skip += spaces + comment_skip; + } else { + indent = spaces; + } + } else { + indent = spaces; + } + } + } + } else { + while bytes[skip] == b'#' { + let comment_skip = skip_comment(&bytes[skip..]); + + indent = 0; + skip += comment_skip; + } + } + } + } + + SkipNewlineReturn::SkipWIndent(skip, indent) +} + +fn is_op_continue(ch: u8) -> bool { + matches!( + ch, + b'-' | b':' + | b'!' + | b'.' + | b'*' + | b'/' + | b'&' + | b'%' + | b'^' + | b'+' + | b'<' + | b'=' + | b'>' + | b'|' + | b'\\' + ) +} + +fn lex_operator(bytes: &[u8]) -> (Token, usize) { + let mut i = 0; + while i < bytes.len() && is_op_continue(bytes[i]) { + i += 1; + } + let tok = match &bytes[0..i] { + b"+" => Token::OpPlus, + b"-" => Token::OpMinus, + b"*" => Token::Asterisk, + b"/" => Token::OpSlash, + b"%" => Token::OpPercent, + b"^" => Token::OpCaret, + b">" => Token::OpGreaterThan, + b"<" => Token::OpLessThan, + b"." => Token::Dot, + b"=" => Token::OpAssignment, + b":" => Token::Colon, + b"|" => Token::Pipe, + b"\\" => Token::LambdaStart, + b"|>" => Token::OpPizza, + b"==" => Token::OpEquals, + b"!" => Token::Bang, + b"!=" => Token::OpNotEquals, + b">=" => Token::OpGreaterThanOrEq, + b"<=" => Token::OpLessThanOrEq, + b"&&" => Token::OpAnd, + b"&" => Token::Ampersand, + b"||" => Token::OpOr, + b"//" => Token::OpDoubleSlash, + b"%%" => Token::OpDoublePercent, + b"->" => Token::Arrow, + b"<-" => Token::OpBackpassing, + op => { + dbg!(std::str::from_utf8(op).unwrap()); + Token::MalformedOperator + } + }; + (tok, i) +} + +fn is_ident_continue(ch: u8) -> bool { + matches!(ch, b'a'..=b'z'|b'A'..=b'Z'|b'0'..=b'9'|b'_') +} + +fn lex_private_tag(bytes: &[u8]) -> (Token, usize) { + debug_assert!(bytes[0] == b'@'); + let mut i = 1; + while i < bytes.len() && is_ident_continue(bytes[i]) { + i += 1; + } + (Token::PrivateTag, i) +} + +fn lex_ident(uppercase: bool, bytes: &[u8]) -> (Token, usize) { + let mut i = 0; + while i < bytes.len() && is_ident_continue(bytes[i]) { + i += 1; + } + let tok = match &bytes[0..i] { + b"if" => Token::KeywordIf, + b"then" => Token::KeywordThen, + b"else" => Token::KeywordElse, + b"when" => Token::KeywordWhen, + b"as" => Token::KeywordAs, + b"is" => Token::KeywordIs, + b"expect" => Token::KeywordExpect, + b"app" => Token::KeywordApp, + b"interface" => Token::KeywordInterface, + b"packages" => Token::KeywordPackages, + b"imports" => Token::KeywordImports, + b"provides" => Token::KeywordProvides, + b"to" => Token::KeywordTo, + b"exposes" => Token::KeywordExposes, + b"effects" => Token::KeywordEffects, + b"platform" => Token::KeywordPlatform, + b"requires" => Token::KeywordRequires, + ident => { + if ident.contains(&b'_') { + Token::MalformedIdent + } else if uppercase { + Token::UppercaseIdent + } else { + Token::LowercaseIdent + } + } + }; + (tok, i) +} + +fn lex_underscore(bytes: &[u8]) -> (Token, usize) { + let mut i = 0; + while i < bytes.len() && is_ident_continue(bytes[i]) { + i += 1; + } + (Token::Underscore, i) +} + +fn is_int_continue(ch: u8) -> bool { + matches!(ch, b'0'..=b'9' | b'_') +} + +fn lex_number(bytes: &[u8]) -> (Token, usize) { + let mut i = 0; + while i < bytes.len() && is_int_continue(bytes[i]) { + i += 1; + } + + if i < bytes.len() && bytes[i] == b'.' { + i += 1; + while i < bytes.len() && is_int_continue(bytes[i]) { + i += 1; + } + } + + (Token::Number, i) +} + +fn lex_string(bytes: &[u8]) -> (Token, usize) { + let mut i = 0; + assert_eq!(bytes[i], b'"'); + i += 1; + + while i < bytes.len() { + match bytes[i] { + b'"' => break, + // TODO: escapes + _ => i += 1, + } + } + + assert_eq!(bytes[i], b'"'); + i += 1; + + (Token::String, i) +} + +#[cfg(test)] +mod tokenizer { + use super::Token; + use crate::tokenizer::tokenize; + + type T = Token; + + #[test] + fn test_indent_tokenization_1() { + let tokens = tokenize( + r#"showBool = \b -> + when b is + True -> + "True""#, + ); + + assert_eq!( + tokens, + [ + T::LowercaseIdent, + T::OpAssignment, + T::LambdaStart, + T::LowercaseIdent, + T::Arrow, + T::OpenIndent, + T::KeywordWhen, + T::LowercaseIdent, + T::KeywordIs, + T::OpenIndent, + T::UppercaseIdent, + T::Arrow, + T::OpenIndent, + T::String + ] + ); + } + + #[test] + fn test_indent_tokenization_2() { + let tokens = tokenize( + r#"showBool = \b -> + when b is + True -> + "True" + "#, + ); + + assert_eq!( + tokens, + [ + T::LowercaseIdent, + T::OpAssignment, + T::LambdaStart, + T::LowercaseIdent, + T::Arrow, + T::OpenIndent, + T::KeywordWhen, + T::LowercaseIdent, + T::KeywordIs, + T::OpenIndent, + T::UppercaseIdent, + T::Arrow, + T::OpenIndent, + T::String, + T::CloseIndent, + T::CloseIndent, + T::CloseIndent + ] + ); + } + + #[test] + fn test_tokenization_line_with_only_spaces() { + let tokens = tokenize( + r#"\key -> + when dict is + Empty -> + 4 + + Node -> + 5"#, + ); + + assert_eq!( + tokens, + [ + T::LambdaStart, + T::LowercaseIdent, + T::Arrow, + T::OpenIndent, + T::KeywordWhen, + T::LowercaseIdent, + T::KeywordIs, + T::OpenIndent, + T::UppercaseIdent, + T::Arrow, + T::OpenIndent, + T::Number, + T::CloseIndent, + T::UppercaseIdent, + T::Arrow, + T::OpenIndent, + T::Number + ] + ); + } + + #[test] + fn test_tokenization_empty_lines_and_comments() { + let tokens = tokenize( + r#"a = 5 + +# com1 +# com2 +b = 6"#, + ); + + assert_eq!( + tokens, + [ + T::LowercaseIdent, + T::OpAssignment, + T::Number, + T::SameIndent, + T::LowercaseIdent, + T::OpAssignment, + T::Number + ] + ); + } + + #[test] + fn test_tokenization_when_branch_comments() { + let tokens = tokenize( + r#"when errorCode is + # A -> Task.fail InvalidCharacter + # B -> Task.fail IOError + _ -> + Task.succeed -1"#, + ); + + assert_eq!( + tokens, + [ + T::KeywordWhen, + T::LowercaseIdent, + T::KeywordIs, + T::OpenIndent, + T::Underscore, + T::Arrow, + T::OpenIndent, + T::UppercaseIdent, + T::Dot, + T::LowercaseIdent, + T::OpMinus, + T::Number + ] + ); + } +} diff --git a/highlight/tests/peg_grammar.rs b/highlight/tests/peg_grammar.rs new file mode 100644 index 0000000000..7b8bde04c5 --- /dev/null +++ b/highlight/tests/peg_grammar.rs @@ -0,0 +1,1438 @@ +/* +To debug grammar, add `dbg!(&tokens);`, execute with: +``` +cargo test --features peg/trace test_fibo -- --nocapture +``` +With visualization, see necessary format for temp_trace.txt [here](https://github.com/fasterthanlime/pegviz): +``` +cat highlight/temp_trace.txt | pegviz --output ./pegviz.html +``` +*/ + +#[cfg(test)] +mod test_peg_grammar { + use roc_highlight::tokenizer::{tokenize, Token}; + + type T = Token; + + // Inspired by https://ziglang.org/documentation/0.7.1/#Grammar + // license information can be found in the LEGAL_DETAILS file in + // the root directory of this distribution. + // Thank you zig contributors! + peg::parser! { + grammar tokenparser() for [T] { + + pub rule module() = + header() module_defs()? indented_end() + + pub rule full_expr() = + op_expr() + / [T::OpenIndent] op_expr() close_or_end() + + pub rule op_expr() = pizza_expr() + + rule common_expr() = + closure() + / expect() + / if_expr() + / when() + / backpass() + / list() + / record() + / record_update() + / parens_around() + / [T::Number] + / [T::NumberBase] + / [T::String] + / module_var() + / tag() + / accessor_function() + / defs() + / annotation() + / [T::LowercaseIdent] + pub rule expr() = + access() + / apply() + / common_expr() + + pub rule closure() = + [T::LambdaStart] args() [T::Arrow] closure_body() + + rule closure_body() = + [T::OpenIndent] full_expr() ([T::CloseIndent] / end_of_file() / &[T::CloseParen]) + / [T::SameIndent]? full_expr() + + rule args() = + (arg() [T::Comma])* arg() + + rule arg() = + [T::Underscore] + / ident() + / record_destructure() + + + rule tag() = + private_tag() + / [T::UppercaseIdent] + + rule private_tag() = [T::PrivateTag] {} + + + rule list() = empty_list() + / [T::OpenSquare] (expr() [T::Comma])* expr()? [T::Comma]? [T::CloseSquare] { } + rule empty_list() = [T::OpenSquare] [T::CloseSquare] + + + rule record() = + empty_record() + / [T::OpenCurly] assigned_fields_i() [T::CloseCurly] + + rule assigned_fields() = + (assigned_field() [T::SameIndent]? [T::Comma] [T::SameIndent]?)* [T::SameIndent]? assigned_field()? [T::Comma]? + + rule assigned_fields_i() = + [T::OpenIndent] assigned_fields() [T::CloseIndent] + / [T::SameIndent]? assigned_fields() [T::SameIndent]? + + + rule assigned_field() = + required_value() + / [T::LowercaseIdent] + + rule required_value() = + [T::LowercaseIdent] [T::Colon] full_expr() + + rule empty_record() = [T::OpenCurly] [T::CloseCurly] + + rule record_update() = [T::OpenCurly] expr() [T::Ampersand] assigned_fields_i() [T::CloseCurly] + + rule record_type() = + empty_record() + / [T::OpenCurly] record_field_types_i() [T::CloseCurly] + + rule record_type_i() = + [T::OpenIndent] record_type() [T::CloseIndent]? + / record_type() + + rule record_field_types_i() = + [T::OpenIndent] record_field_types() [T::CloseIndent] + / record_field_types() + + rule record_field_types() = + ([T::SameIndent]? record_field_type() [T::SameIndent]? [T::Comma])* ([T::SameIndent]? record_field_type() [T::Comma]?)? + + rule record_field_type() = + ident() [T::Colon] type_annotation() + + + pub rule parens_around() = [T::OpenParen] full_expr() [T::CloseParen] + + rule if_expr() = [T::KeywordIf] full_expr() [T::KeywordThen] full_expr() + [T::KeywordElse] full_expr() + + rule expect() = [T::KeywordExpect] expr() + + pub rule backpass() = + single_backpass() ([T::SameIndent] single_backpass())* [T::SameIndent] full_expr() + + pub rule single_backpass() = + backpass_pattern() [T::OpBackpassing] full_expr() + + rule common_pattern() = + [T::LowercaseIdent] + / [T::Underscore] + / module_var() + / concrete_type() + / parens_around() + / tag() + + rule backpass_pattern() = + common_pattern() + / record_destructure() + / [T::Number] + / [T::NumberBase] + / [T::String] + / list() + + // for applies without line breaks between args: Node color rK rV + rule apply_arg_pattern() = + accessor_function() + / access() + / record() + / record_update() + / closure() + / common_pattern() + / [T::Number] + / [T::NumberBase] + / [T::String] + / list() + / parens_around() + + pub rule when_match_pattern() = + record() + / [T::Number] + / [T::NumberBase] + / [T::String] + / list() + / parens_around() + / apply() + / common_pattern() + + + // for applies where the arg is on its own line, for example: + // Effect.after + // transform a + rule apply_arg_line_pattern() = + record() + / closure() + / apply() + / common_pattern() + + rule apply_start_pattern() = + access() + / common_pattern() + + rule record_destructure() = + empty_record() + / [T::OpenCurly] (ident() [T::Comma])* ident() [T::Comma]? [T::CloseCurly] + + rule access() = + access_start() [T::Dot] ident() + + rule access_start() = + [T::LowercaseIdent] + / record() + / parens_around() + + rule accessor_function() = + [T::SpaceDot] ident() + / [T::Dot] ident() + + pub rule header() = + __ almost_header() header_end() + + pub rule almost_header() = + app_header() + / interface_header() + / platform_header() + + rule app_header() = + [T::KeywordApp] [T::String] [T::OpenIndent]? packages() imports() provides()// check String to be non-empty? + + rule interface_header() = + [T::KeywordInterface] module_name() [T::OpenIndent]? exposes() imports() + + rule platform_header() = + [T::KeywordPlatform] [T::String] [T::OpenIndent]? requires() exposes() packages() imports() provides() effects()// check String to be nonempty? + + rule header_end() = + ([T::CloseIndent] + / &[T::SameIndent])? // & to not consume the SameIndent + rule packages() = + __ [T::KeywordPackages] record() + + rule imports() = + __ [T::KeywordImports] imports_list() + + rule imports_list() = + empty_list() + / [T::OpenSquare] (imports_entry() [T::Comma])* imports_entry()? [T::Comma]? [T::CloseSquare] + + rule imports_entry() = + ([T::LowercaseIdent] [T::Dot])? + module_name() + ([T::Dot] exposes_list() )? + + rule exposes_list() = + [T::OpenCurly] (exposes_entry() [T::Comma])* exposes_entry()? [T::Comma]? [T::CloseCurly] + rule exposes_entry() = + ident() + + rule provides() = + __ [T::KeywordProvides] provides_list() ([T::KeywordTo] provides_to())? + + rule provides_to() = + [T::String] + / ident() + + rule provides_list() = + empty_list() + / [T::OpenSquare] exposed_names() [T::CloseSquare] + + rule exposes() = + __ [T::KeywordExposes] [T::OpenSquare] exposed_names() [T::CloseSquare] + + rule exposed_names() = + (ident() [T::Comma])* ident()? [T::Comma]? + + rule requires() = + [T::KeywordRequires] requires_rigids() [T::OpenCurly] typed_ident() [T::CloseCurly] + + rule requires_rigids() = + empty_record() + / [T::OpenCurly] (requires_rigid() [T::Comma])* requires_rigid() [T::Comma]? [T::CloseCurly] + + rule requires_rigid() = + [T::LowercaseIdent] ([T::FatArrow] [T::UppercaseIdent])? + + pub rule typed_ident() = + [T::LowercaseIdent] [T::Colon] type_annotation() + + pub rule effects() = + __ [T::KeywordEffects] effect_name() record_type_i() + + rule effect_name() = + [T::LowercaseIdent] [T::Dot] [T::UppercaseIdent] + + + rule module_name() = + [T::UppercaseIdent] ([T::Dot] [T::UppercaseIdent])* + + rule ident() = + [T::UppercaseIdent] + / [T::LowercaseIdent] + + + // content of type_annotation without Colon(:) + pub rule type_annotation() = + function_type() + / type_annotation_no_fun() + + rule type_annotation_no_fun() = + [T::OpenParen] type_annotation_no_fun() [T::CloseParen] + / [T::OpenIndent] type_annotation_no_fun() close_or_end() + / tag_union() + / apply_type() + / bound_variable() + / record_type() + / inferred() + / wildcard() + // TODO inline type alias + + rule type_annotation_paren_fun() = + type_annotation_no_fun() + / [T::OpenParen] function_type() [T::CloseParen] + + rule tag_union() = + empty_list() + / [T::OpenSquare] tags() [T::CloseSquare] type_variable()? + + rule tags() = + [T::OpenIndent] tags_only() [T::CloseIndent] + / tags_only() + + rule tags_only() = + ([T::SameIndent]? apply_type() [T::SameIndent]? [T::Comma] [T::SameIndent]? )* ([T::SameIndent]? apply_type() [T::Comma]?)? + + rule type_variable() = + [T::Underscore] + / bound_variable() + + rule bound_variable() = + [T::LowercaseIdent] + + // The `*` type variable, e.g. in (List *) + rule wildcard() = + [T::Asterisk] + + // '_', indicating the compiler should infer the type + rule inferred() = + [T::Underscore] + + rule function_type() = + ( type_annotation_paren_fun() ([T::Comma] type_annotation_paren_fun())* [T::Arrow])? type_annotation_paren_fun() + + pub rule apply_type() = + concrete_type() apply_type_args()? + rule concrete_type() = + [T::UppercaseIdent] ([T::Dot] [T::UppercaseIdent])* + rule apply_type_args() = + apply_type_arg() apply_type_arg()* + + rule apply_type_arg() = + type_annotation_no_fun() + / record_destructure() + + rule _() = + ([T::SameIndent])? + + // the rules below allow us to set assoicativity and precedence + rule unary_op() = + [T::OpMinus] + / [T::Bang] + rule unary_expr() = + unary_op()* expr() + + rule mul_level_op() = + [T::Asterisk] + / [T::OpSlash] + / [T::OpDoubleSlash] + / [T::OpPercent] + / [T::OpDoublePercent] + rule mul_level_expr() = + unary_expr() (mul_level_op() unary_expr())* + + rule add_level_op() = + [T::OpPlus] + / [T::OpMinus] + rule add_level_expr() = + mul_level_expr() (add_level_op() mul_level_expr())* + + rule compare_op() = + [T::OpEquals] // == + / [T::OpNotEquals] + / [T::OpLessThan] + / [T::OpGreaterThan] + / [T::OpLessThanOrEq] + / [T::OpGreaterThanOrEq] + rule compare_expr() = + add_level_expr() (compare_op() add_level_expr())? + + rule bool_and_expr() = + compare_expr() ([T::OpAnd] compare_expr())* + + rule bool_or_expr() = + bool_and_expr() ([T::OpOr] bool_and_expr())* + + + rule pizza_expr() = + bool_or_expr() pizza_end()? + + rule pizza_end() = + [T::SameIndent]? [T::OpPizza] [T::SameIndent]? bool_or_expr() pizza_end()* + / [T::SameIndent]? [T::OpPizza] [T::OpenIndent] bool_or_expr() pizza_end()* close_or_end() + / [T::OpenIndent] [T::OpPizza] [T::SameIndent]? bool_or_expr() pizza_end()* close_or_end() + / [T::OpenIndent] [T::OpPizza] [T::OpenIndent] bool_or_expr() pizza_end()* close_double_or_end() + + rule close_or_end() = + [T::CloseIndent] + / end_of_file() + + rule close_double_or_end() = + [T::CloseIndent] [T::CloseIndent] + / [T::CloseIndent] end_of_file() + / end_of_file() + + //TODO support right assoicative caret(^), for example: 2^2 + + pub rule defs() = + def() ([T::SameIndent]? def())* [T::SameIndent]? full_expr() + + pub rule def() = + annotated_body() + / annotation() + / body() + / alias() + / expect() + + pub rule module_defs() = + ([T::SameIndent]? def())+ + + rule annotation() = + annotation_pre_colon() [T::Colon] type_annotation() + + rule annotation_pre_colon() = + apply() + / tag() + / ident() + + rule body() = + ident() [T::OpAssignment] [T::OpenIndent] full_expr() ([T::SameIndent]? full_expr())* ([T::CloseIndent] / end_of_file()) + / ident() [T::OpAssignment] full_expr() end_of_file()? + + rule annotated_body() = + annotation() [T::SameIndent] body() + + rule alias() = + apply_type() [T::Colon] type_annotation() + + pub rule when() = + [T::KeywordWhen] expr() [T::KeywordIs] when_branches() + + rule when_branches() = + [T::OpenIndent] when_branch()+ close_or_end() + / when_branch()+ + + pub rule when_branch() = + when_match_pattern() ([T::Pipe] full_expr())* ([T::KeywordIf] full_expr())? [T::Arrow] when_branch_body() + + rule when_branch_body() = + [T::OpenIndent] full_expr() ([T::CloseIndent] / end_of_file()) + / full_expr() + + rule var() = + [T::LowercaseIdent] + / module_var() + + rule module_var() = + module_name() [T::Dot] [T::LowercaseIdent] + + pub rule apply() = + apply_start_pattern() apply_args() + + pub rule apply_args() = + [T::OpenIndent] apply_arg_line_pattern() single_line_apply_args()? ([T::CloseIndent]/indented_end()) + / apply_arg_pattern()+ + + rule single_line_apply_args() = + [T::SameIndent] apply_arg_line_pattern() ( (single_line_apply_args()*) / indented_end() ) + / ([T::OpenIndent] apply_arg_line_pattern() single_line_apply_args()* ([T::CloseIndent] / indented_end())) + + rule apply_expr() = + var() + / tag() + + rule end() = + [T::CloseIndent] + / &[T::SameIndent] // & to not consume the SameIndent + / end_of_file() + + rule indented_end() = + ([T::OpenIndent] / [T::CloseIndent] / [T::SameIndent])* end_of_file() + + // for optionalindents + // underscore rules do not require parentheses + rule __() = + ( + [T::OpenIndent] + / [T::CloseIndent] + / [T::SameIndent] + )? + + rule end_of_file() = + ![_] + + } + } + + #[test] + fn test_basic_expr() { + assert_eq!(tokenparser::expr(&[T::OpenSquare, T::CloseSquare]), Ok(())); + assert_eq!(tokenparser::expr(&[T::OpenCurly, T::CloseCurly]), Ok(())); + + assert_eq!( + tokenparser::expr(&[T::OpenParen, T::OpenSquare, T::CloseSquare, T::CloseParen]), + Ok(()) + ); + + assert_eq!(tokenparser::expr(&[T::Number]), Ok(())); + assert_eq!(tokenparser::expr(&[T::String]), Ok(())); + + assert_eq!( + tokenparser::expr(&[ + T::KeywordIf, + T::Number, + T::KeywordThen, + T::Number, + T::KeywordElse, + T::Number + ]), + Ok(()) + ); + + assert_eq!(tokenparser::expr(&[T::KeywordExpect, T::Number]), Ok(())); + } + + #[test] + fn test_app_header_1() { + let tokens = tokenize(r#"app "test-app" packages {} imports [] provides [] to blah"#); + + assert_eq!(tokenparser::header(&tokens), Ok(())); + } + + #[test] + fn test_app_header_2() { + let tokens = tokenize( + r#" + app "test-app" + packages { pf: "platform" } + imports [] + provides [ main ] to pf + "#, + ); + + assert_eq!(tokenparser::header(&tokens), Ok(())); + } + + #[test] + fn test_interface_header() { + let tokens = tokenize( + r#" + interface Foo.Bar.Baz exposes [] imports []"#, + ); + + assert_eq!(tokenparser::header(&tokens), Ok(())); + } + + #[test] + fn test_interface_header_2() { + let tokens = tokenize( + r#" + + interface Base64.Encode + exposes [ toBytes ] + imports [ Bytes.Encode.{ Encoder } ]"#, + ); + + assert_eq!(tokenparser::header(&tokens), Ok(())); + } + + #[test] + fn test_platform_header_1() { + let tokens = tokenize( + r#"platform "rtfeldman/blah" requires {} { main : {} } exposes [] packages {} imports [] provides [] effects fx.Blah {}"#, + ); + + assert_eq!(tokenparser::header(&tokens), Ok(())); + } + + #[test] + fn test_platform_header_2() { + let tokens = tokenize( + r#"platform "examples/cli" + requires {}{ main : Task {} [] } # TODO FIXME + exposes [] + packages {} + imports [ Task.{ Task } ] + provides [ mainForHost ] + effects fx.Effect + { + getLine : Effect Str, + putLine : Str -> Effect {}, + twoArguments : Int, Int -> Effect {} + }"#, + ); + + assert_eq!(tokenparser::header(&tokens), Ok(())); + } + + #[test] + fn test_annotated_def() { + let tokens = tokenize( + r#"test1 : Bool +test1 = + example1 == [ 2, 4 ]"#, + ); + + assert_eq!(tokenparser::def(&tokens), Ok(())); + } + + #[test] + fn test_record_def_1() { + let tokens = tokenize(r#"x = { content: 4 }"#); + + assert_eq!(tokenparser::def(&tokens), Ok(())); + } + + #[test] + fn test_record_def_2() { + let tokens = tokenize( + r#"x = + { content: 4 }"#, + ); + + assert_eq!(tokenparser::def(&tokens), Ok(())); + } + + #[test] + fn test_record_def_3() { + let tokens = tokenize( + r#"x = + { + a: 4, + b: 5 + }"#, + ); + + assert_eq!(tokenparser::def(&tokens), Ok(())); + } + + #[test] + fn test_record_def_4() { + let tokens = tokenize( + r#"x = + { + a: 4, + b: 5, + c: 6, + }"#, + ); + + assert_eq!(tokenparser::def(&tokens), Ok(())); + } + + #[test] + fn test_record_def_5() { + let tokens = tokenize( + r#"x = + { + a: 4, + }"#, + ); + + assert_eq!(tokenparser::def(&tokens), Ok(())); + } + + #[test] + fn test_record_def_6() { + let tokens = tokenize( + r#"a = { + b: c, + d: { + e: f, + }, + }"#, + ); + + assert_eq!(tokenparser::def(&tokens), Ok(())); + } + + #[test] + fn test_typed_ident() { + // main : Task {} [] + assert_eq!( + tokenparser::typed_ident(&[ + T::LowercaseIdent, + T::Colon, + T::UppercaseIdent, + T::OpenCurly, + T::CloseCurly, + T::OpenSquare, + T::CloseSquare + ]), + Ok(()) + ); + } + + #[test] + fn test_order_of_ops() { + // True || False && True || False + assert_eq!( + tokenparser::full_expr(&[ + T::UppercaseIdent, + T::OpOr, + T::UppercaseIdent, + T::OpAnd, + T::UppercaseIdent, + T::OpOr, + T::UppercaseIdent + ]), + Ok(()) + ); + } + + fn file_to_string(file_path: &str) -> String { + // it's ok to panic in a test + std::fs::read_to_string(file_path).unwrap() + } + + fn example_path(sub_path: &str) -> String { + let examples_dir = "../examples/".to_string(); + + let file_path = examples_dir + sub_path; + + file_to_string(&file_path) + } + + #[test] + fn test_hello() { + let tokens = tokenize(&example_path("hello-world/helloWorld.roc")); + + assert_eq!(tokenparser::module(&tokens), Ok(())); + } + + #[test] + fn test_fibo() { + let tokens = tokenize(&example_path("algorithms/fibonacci.roc")); + + assert_eq!(tokenparser::module(&tokens), Ok(())); + } + + #[test] + fn test_annotation() { + let tokens = tokenize(r#"ConsList a : [ Cons a (ConsList a), Nil ]"#); + + assert_eq!(tokenparser::def(&tokens), Ok(())); + } + + #[test] + fn test_apply_type() { + let tokens = tokenize(r#"Cons a (ConsList a)"#); + + assert_eq!(tokenparser::apply_type(&tokens), Ok(())); + } + + #[test] + fn test_apply_expect_fail_1() { + assert!(tokenparser::apply(&[ + T::LowercaseIdent, + T::LowercaseIdent, + T::CloseIndent, + T::UppercaseIdent + ]) + .is_err()); + } + + #[test] + fn test_apply_expect_fail_2() { + let tokens = tokenize( + r#"eval a + b"#, + ); + + assert!(tokenparser::apply(&tokens).is_err()); + } + + #[test] + fn test_when_1() { + let tokens = tokenize( + r#"when list is + Cons _ rest -> + 1 + len rest + + Nil -> + 0"#, + ); + + assert_eq!(tokenparser::when(&tokens), Ok(())); + } + + #[test] + fn test_when_2() { + let tokens = tokenize( + r#"when list is + Nil -> + Cons a + + Nil -> + Nil"#, + ); + + assert_eq!(tokenparser::when(&tokens), Ok(())); + } + + #[test] + fn test_when_in_defs() { + let tokens = tokenize( + r#"fromBytes = \bytes -> + when bytes is + Ok v -> v + "#, + ); + + assert_eq!(tokenparser::module_defs(&tokens), Ok(())); + } + + #[test] + fn test_base64() { + let tokens = tokenize(&example_path("benchmarks/Base64.roc")); + + assert_eq!(tokenparser::module(&tokens), Ok(())); + } + + #[test] + fn test_base64_test() { + let tokens = tokenize(&example_path("benchmarks/TestBase64.roc")); + + assert_eq!(tokenparser::module(&tokens), Ok(())); + } + + #[test] + fn test_when_branch() { + let tokens = tokenize(r#"Ok path -> path"#); + + assert_eq!(tokenparser::when_branch(&tokens), Ok(())); + } + + #[test] + fn test_def_in_def() { + let tokens = tokenize( + r#"example = + cost = 1 + + cost"#, + ); + assert_eq!(tokenparser::def(&tokens), Ok(())); + } + + #[test] + fn test_backpass_in_def() { + let tokens = tokenize( + r#"main = + lastName <- 4 + Stdout.line "Hi!""#, + ); + + assert_eq!(tokenparser::def(&tokens), Ok(())); + } + + #[test] + fn test_astar_test() { + let tokens = tokenize(&example_path("benchmarks/TestAStar.roc")); + + assert_eq!(tokenparser::module(&tokens), Ok(())); + } + + #[test] + fn test_cli_echo() { + let tokens = tokenize(&example_path("interactive/echo.roc")); + + assert_eq!(tokenparser::module(&tokens), Ok(())); + } + + #[test] + fn test_pizza_1() { + let tokens = tokenize( + r#"closure = \_ -> + Task.succeed {} + |> Task.map (\_ -> x)"#, + ); + + assert_eq!(tokenparser::def(&tokens), Ok(())); + } + + #[test] + fn test_pizza_one_line() { + let tokens = tokenize(r#"5 |> fun"#); + + assert_eq!(tokenparser::op_expr(&tokens), Ok(())); + } + + #[test] + fn test_pizza_same_indent_1() { + let tokens = tokenize( + r#"5 + |> fun"#, + ); + + assert_eq!(tokenparser::op_expr(&tokens), Ok(())); + } + + #[test] + fn test_pizza_same_indent_2() { + let tokens = tokenize( + r#"5 + |> + fun"#, + ); + + assert_eq!(tokenparser::op_expr(&tokens), Ok(())); + } + + #[test] + fn test_pizza_indented_1_a() { + let tokens = tokenize( + r#"5 + |> fun"#, + ); + + assert_eq!(tokenparser::op_expr(&tokens), Ok(())); + } + + #[test] + fn test_pizza_indented_1_b() { + let tokens = tokenize( + r#"5 + |> fun + "#, + ); + + assert_eq!(tokenparser::op_expr(&tokens), Ok(())); + } + + #[test] + fn test_pizza_indented_2_a() { + let tokens = tokenize( + r#"5 + |> + fun"#, + ); + + assert_eq!(tokenparser::op_expr(&tokens), Ok(())); + } + + #[test] + fn test_pizza_indented_2_b() { + let tokens = tokenize( + r#"5 + |> + fun + "#, + ); + + assert_eq!(tokenparser::op_expr(&tokens), Ok(())); + } + + #[test] + fn test_pizza_indented_2_c() { + let tokens = tokenize( + r#"5 + |> + fun + + "#, + ); + + assert_eq!(tokenparser::op_expr(&tokens), Ok(())); + } + + #[test] + fn test_pizza_mixed_indent_1_a() { + let tokens = tokenize( + r#"5 + |> + fun"#, + ); + + assert_eq!(tokenparser::op_expr(&tokens), Ok(())); + } + + #[test] + fn test_pizza_mixed_indent_1_b() { + let tokens = tokenize( + r#"5 + |> + fun + "#, + ); + + assert_eq!(tokenparser::op_expr(&tokens), Ok(())); + } + + #[test] + fn test_pizza_mixed_indent_2_a() { + let tokens = tokenize( + r#"5 + |> + fun"#, + ); + + assert_eq!(tokenparser::op_expr(&tokens), Ok(())); + } + + #[test] + fn test_pizza_mixed_indent_2_b() { + let tokens = tokenize( + r#"5 + |> + fun"#, + ); + + assert_eq!(tokenparser::op_expr(&tokens), Ok(())); + } + + #[test] + fn test_longer_pizza() { + let tokens = tokenize(r#"5 |> fun a |> fun b"#); + + assert_eq!(tokenparser::op_expr(&tokens), Ok(())); + } + + #[test] + fn test_deeper_pizza() { + let tokens = tokenize( + r#"5 + |> fun a + |> fun b"#, + ); + + assert_eq!(tokenparser::op_expr(&tokens), Ok(())); + } + + #[test] + fn test_deeper_indented_pizza_a() { + let tokens = tokenize( + r#"5 + |> fun a + |> fun b"#, + ); + + assert_eq!(tokenparser::op_expr(&tokens), Ok(())); + } + + #[test] + fn test_deeper_indented_pizza_b() { + let tokens = tokenize( + r#"5 + |> fun a + |> fun b + "#, + ); + + assert_eq!(tokenparser::op_expr(&tokens), Ok(())); + } + + #[test] + fn test_deep_mixed_indent_pizza_a() { + let tokens = tokenize( + r#"5 + |> fun a |> fun b + |> fun c d + |> fun "test" + |> List.map Str.toI64 + |> g (1 + 1)"#, + ); + + assert_eq!(tokenparser::op_expr(&tokens), Ok(())); + } + + #[test] + fn test_deep_mixed_indent_pizza_b() { + let tokens = tokenize( + r#"5 + |> fun a |> fun b + |> fun c d + |> fun "test" + |> List.map Str.toI64 + |> g (1 + 1) + "#, + ); + + assert_eq!(tokenparser::op_expr(&tokens), Ok(())); + } + + #[test] + fn test_bool_or() { + let tokens = tokenize(r#"a || True || b || False"#); + + assert_eq!(tokenparser::op_expr(&tokens), Ok(())); + } + + #[test] + fn test_closure_file() { + let tokens = tokenize(&example_path("benchmarks/Closure.roc")); + + assert_eq!(tokenparser::module(&tokens), Ok(())); + } + + #[test] + fn test_def_with_indents() { + let tokens = tokenize( + r#"main = + Task.after + Task.getInt + \n -> + queens n"#, + ); + + assert_eq!(tokenparser::def(&tokens), Ok(())); + } + + #[test] + fn test_nqueens() { + let tokens = tokenize(&example_path("benchmarks/NQueens.roc")); + + assert_eq!(tokenparser::module(&tokens), Ok(())); + } + + #[test] + fn test_quicksort_help() { + let tokens = tokenize( + r#"quicksortHelp = \list, low, high -> + if low < high then + when partition low is + Pair -> + partitioned + |> quicksortHelp low + else + list"#, + ); + + assert_eq!(tokenparser::def(&tokens), Ok(())); + } + + #[test] + fn test_quicksort() { + let tokens = tokenize(&example_path("benchmarks/Quicksort.roc")); + + assert_eq!(tokenparser::module(&tokens), Ok(())); + } + + #[test] + fn test_indented_closure_apply() { + let tokens = tokenize( + r#" + effect + \result -> result"#, + ); + + assert_eq!(tokenparser::apply_args(&tokens), Ok(())); + } + + #[test] + fn test_parens_closure_indent() { + let tokens = tokenize( + r#"(\i -> + i)"#, + ); + assert_eq!(tokenparser::parens_around(&tokens), Ok(())); + } + + #[test] + fn test_task() { + let tokens = tokenize(&example_path("benchmarks/platform/Task.roc")); + + assert_eq!(tokenparser::module(&tokens), Ok(())); + } + + #[test] + fn test_pizza_line() { + let tokens = tokenize( + r#"unoptimized + |> Num.toStr + |> Task.putLine"#, + ); + + assert_eq!(tokenparser::full_expr(&tokens), Ok(())); + } + + #[test] + fn test_defs_w_apply() { + let tokens = tokenize( + r#"unoptimized = eval e + + 42"#, + ); + + assert_eq!(tokenparser::defs(&tokens), Ok(())); + } + + #[test] + fn test_indented_apply_defs() { + let tokens = tokenize( + r#"main = + after + \n -> + e = 5 + + 4 + + Expr : I64"#, + ); + + assert_eq!(tokenparser::module_defs(&tokens), Ok(())); + } + + #[test] + fn test_cfold() { + let tokens = tokenize(&example_path("benchmarks/CFold.roc")); + + assert_eq!(tokenparser::module(&tokens), Ok(())); + } + + #[test] + fn test_apply_with_comment() { + let tokens = tokenize( + r#"main = + Task.after + \n -> + e = mkExpr n 1 # comment + unoptimized = eval e + optimized = eval (constFolding (reassoc e)) + + optimized"#, + ); + + assert_eq!(tokenparser::def(&tokens), Ok(())); + } + + #[test] + fn test_multi_defs() { + let tokens = tokenize( + r#"main = + tree : I64 + tree = 0 + + tree"#, + ); + + assert_eq!(tokenparser::def(&tokens), Ok(())); + } + + // TODO fix slow execution; likely a problem with apply + #[test] + fn test_perf_issue() { + let tokens = tokenize( + r#"main = + tree = insert 0 {} Empty + + tree + |> Task.putLine + +nodeInParens : RedBlackTree k v, (k -> Str), (v -> Str) -> Str +nodeInParens = \tree, showKey, showValue -> + when tree is + _ -> + "(\(inner))" + +RedBlackTree k v : [ Node NodeColor k v (RedBlackTree k v) (RedBlackTree k v), Empty ] + +Key k : Num k + +balance = \color -> + when right is + Node Red -> + when left is + _ -> + Node color rK rV (Node Red key value left) + + _ -> + 5"#, + ); + + assert_eq!(tokenparser::module_defs(&tokens), Ok(())); + } + + #[test] + fn test_rbtree_insert() { + let tokens = tokenize(&example_path("benchmarks/RBTreeInsert.roc")); + + assert_eq!(tokenparser::module(&tokens), Ok(())); + } + + #[test] + fn test_closure_1() { + let tokens = tokenize( + r#"\key -> + when dict is + Empty -> + 4 + + Node -> + 5"#, + ); + + assert_eq!(tokenparser::closure(&tokens), Ok(())); + } + + #[test] + fn test_closure_2() { + let tokens = tokenize( + r#"\key -> + when dict is + Empty -> + Node Red + + Node nColor -> + when key is + GT -> + balance nColor"#, + ); + + assert_eq!(tokenparser::closure(&tokens), Ok(())); + } + + #[test] + fn test_nested_apply() { + let tokens = tokenize( + r#"after = \effect -> + Effect.after + transform a + + map : Str"#, + ); + + assert_eq!(tokenparser::module_defs(&tokens), Ok(())); + } + + #[test] + fn test_deep_indented_defs() { + let tokens = tokenize( + r#"after = \effect -> + after + \result -> + transform a + + map : Str"#, + ); + + assert_eq!(tokenparser::module_defs(&tokens), Ok(())); + } + + #[test] + fn test_rbtree_ck() { + let tokens = tokenize(&example_path("benchmarks/RBTreeCk.roc")); + + assert_eq!(tokenparser::module(&tokens), Ok(())); + } + + #[test] + fn test_record_type_def() { + let tokens = tokenize( + r#"Model position : + { + evaluated : Set, + }"#, + ); + + assert_eq!(tokenparser::def(&tokens), Ok(())); + } + + #[test] + fn test_apply_with_acces() { + let tokens = tokenize(r#"Dict.get model.costs"#); + + assert_eq!(tokenparser::apply(&tokens), Ok(())); + } + + #[test] + fn test_space_dot() { + let tokens = tokenize(r#"Result.map .position"#); + + assert_eq!(tokenparser::op_expr(&tokens), Ok(())); + } + + #[test] + fn test_astar() { + let tokens = tokenize(&example_path("benchmarks/AStar.roc")); + + assert_eq!(tokenparser::module(&tokens), Ok(())); + } + + #[test] + fn test_backpass_in_def_2() { + let tokens = tokenize( + r#"with = \path -> + handle <- withOpen + + 4"#, + ); + + assert_eq!(tokenparser::def(&tokens), Ok(())); + } + + #[test] + fn test_false_interpreter_context() { + let tokens = tokenize(&example_path("false-interpreter/Context.roc")); + + assert_eq!(tokenparser::module(&tokens), Ok(())); + } + + #[test] + fn test_when_match_apply() { + let tokens = tokenize(r#"Pair (Val 0) f"#); + + assert_eq!(tokenparser::when_match_pattern(&tokens), Ok(())); + } + + #[test] + fn test_when_match_apply_2() { + let tokens = tokenize(r#"Pair (Val 0) f"#); + + assert_eq!(tokenparser::when_match_pattern(&tokens), Ok(())); + } + + #[test] + fn test_apply_with_closure() { + let tokens = tokenize(r#"Task.after \w -> nestHelp s"#); + + assert_eq!(tokenparser::apply(&tokens), Ok(())); + } + + #[test] + fn test_deriv() { + let tokens = tokenize(&example_path("benchmarks/Deriv.roc")); + + assert_eq!(tokenparser::module(&tokens), Ok(())); + } +} diff --git a/repl_cli/Cargo.toml b/repl_cli/Cargo.toml index b56556c5e4..c6b6dc2011 100644 --- a/repl_cli/Cargo.toml +++ b/repl_cli/Cargo.toml @@ -7,14 +7,14 @@ version = "0.1.0" [features] # pipe target to roc_build -target-arm = ["roc_build/target-arm"] target-aarch64 = ["roc_build/target-aarch64"] +target-arm = ["roc_build/target-arm"] +target-wasm32 = ["roc_build/target-wasm32"] target-x86 = ["roc_build/target-x86"] target-x86_64 = ["roc_build/target-x86_64"] -target-wasm32 = ["roc_build/target-wasm32"] [dependencies] -bumpalo = { version = "3.8.0", features = ["collections"] } +bumpalo = {version = "3.8.0", features = ["collections"]} const_format = "0.2.22" inkwell = {path = "../vendor/inkwell"} libloading = "0.7.1" @@ -31,6 +31,7 @@ roc_load = {path = "../compiler/load"} roc_mono = {path = "../compiler/mono"} roc_parse = {path = "../compiler/parse"} roc_repl_eval = {path = "../repl_eval"} +roc_reporting = {path = "../reporting"} roc_std = {path = "../roc_std", default-features = false} roc_target = {path = "../compiler/roc_target"} roc_types = {path = "../compiler/types"} diff --git a/repl_cli/src/lib.rs b/repl_cli/src/lib.rs index 76070a6e69..0deaeebb8d 100644 --- a/repl_cli/src/lib.rs +++ b/repl_cli/src/lib.rs @@ -20,6 +20,7 @@ use roc_parse::parser::{EExpr, ELambda, SyntaxError}; use roc_repl_eval::eval::jit_to_ast; use roc_repl_eval::gen::{compile_to_mono, format_answer, ReplOutput}; use roc_repl_eval::{ReplApp, ReplAppMemory}; +use roc_reporting::report::DEFAULT_PALETTE; use roc_std::RocStr; use roc_target::TargetInfo; use roc_types::pretty_print::{content_to_string, name_all_type_vars}; @@ -197,7 +198,7 @@ fn gen_and_eval_llvm<'a>( let arena = Bump::new(); let target_info = TargetInfo::from(&target); - let loaded = match compile_to_mono(&arena, src, target_info) { + let loaded = match compile_to_mono(&arena, src, target_info, DEFAULT_PALETTE) { Ok(x) => x, Err(prob_strings) => { return Ok(ReplOutput::Problems(prob_strings)); diff --git a/repl_eval/src/eval.rs b/repl_eval/src/eval.rs index f353638dd2..edaa0ec06b 100644 --- a/repl_eval/src/eval.rs +++ b/repl_eval/src/eval.rs @@ -282,7 +282,7 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( let (newtype_containers, content) = unroll_newtypes(env, content); let content = unroll_aliases(env, content); - macro_rules! helper { + macro_rules! num_helper { ($ty:ty) => { app.call_function(main_fn_name, |_, num: $ty| { num_to_ast(env, number_literal_to_ast(env.arena, num), content) @@ -298,21 +298,24 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( Layout::Builtin(Builtin::Int(int_width)) => { use IntWidth::*; - let result = match int_width { - U8 | I8 => { - // NOTE: `helper!` does not handle 8-bit numbers yet + let result = match (content, int_width) { + (Content::Structure(FlatType::Apply(Symbol::NUM_NUM, _)), U8) => num_helper!(u8), + (_, U8) => { + // This is not a number, it's a tag union or something else app.call_function(main_fn_name, |mem: &A::Memory, num: u8| { byte_to_ast(env, mem, num, content) }) } - U16 => helper!(u16), - U32 => helper!(u32), - U64 => helper!(u64), - U128 => helper!(u128), - I16 => helper!(i16), - I32 => helper!(i32), - I64 => helper!(i64), - I128 => helper!(i128), + // The rest are numbers... for now + (_, U16) => num_helper!(u16), + (_, U32) => num_helper!(u32), + (_, U64) => num_helper!(u64), + (_, U128) => num_helper!(u128), + (_, I8) => num_helper!(i8), + (_, I16) => num_helper!(i16), + (_, I32) => num_helper!(i32), + (_, I64) => num_helper!(i64), + (_, I128) => num_helper!(i128), }; Ok(result) @@ -321,14 +324,14 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( use FloatWidth::*; let result = match float_width { - F32 => helper!(f32), - F64 => helper!(f64), + F32 => num_helper!(f32), + F64 => num_helper!(f64), F128 => todo!("F128 not implemented"), }; Ok(result) } - Layout::Builtin(Builtin::Decimal) => Ok(helper!(RocDec)), + Layout::Builtin(Builtin::Decimal) => Ok(num_helper!(RocDec)), Layout::Builtin(Builtin::Str) => { let size = layout.stack_size(env.target_info) as usize; Ok( @@ -439,6 +442,16 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( unreachable!("RecursivePointers can only be inside structures") } Layout::LambdaSet(_) => Ok(OPAQUE_FUNCTION), + Layout::Boxed(_) => { + let size = layout.stack_size(env.target_info); + Ok(app.call_function_dynamic_size( + main_fn_name, + size as usize, + |mem: &A::Memory, addr| { + addr_to_ast(env, mem, addr, layout, WhenRecursive::Unreachable, content) + }, + )) + } }; result.map(|e| apply_newtypes(env, newtype_containers, e)) } @@ -730,6 +743,25 @@ fn addr_to_ast<'a, M: ReplAppMemory>( ) } } + (Content::Structure(FlatType::Apply(Symbol::BOX_BOX_TYPE, args)), Layout::Boxed(inner_layout)) => { + debug_assert_eq!(args.len(), 1); + + let inner_var_index = args.into_iter().next().unwrap(); + let inner_var = env.subs[inner_var_index]; + let inner_content = env.subs.get_content_without_compacting(inner_var); + + let addr_of_inner = mem.deref_usize(addr); + let inner_expr = addr_to_ast(env, mem, addr_of_inner, inner_layout, WhenRecursive::Unreachable, inner_content); + + let box_box = env.arena.alloc(Loc::at_zero(Expr::Var { + module_name: "Box", ident: "box" + })); + let box_box_arg = &*env.arena.alloc(Loc::at_zero(inner_expr)); + let box_box_args = env.arena.alloc([box_box_arg]); + + Expr::Apply(box_box, box_box_args, CalledVia::Space) + } + (_, Layout::Boxed(_)) => unreachable!("Box layouts can only be behind a `Box.Box` application"), other => { todo!( "TODO add support for rendering pointer to {:?} in the REPL", diff --git a/repl_eval/src/gen.rs b/repl_eval/src/gen.rs index a11ba966b8..c9e067447c 100644 --- a/repl_eval/src/gen.rs +++ b/repl_eval/src/gen.rs @@ -1,12 +1,13 @@ use bumpalo::Bump; +use roc_reporting::report::Palette; use std::path::{Path, PathBuf}; -use roc_collections::all::MutMap; use roc_fmt::annotation::Formattable; use roc_fmt::annotation::{Newlines, Parens}; use roc_load::file::{LoadingProblem, MonomorphizedModule}; use roc_parse::ast::Expr; use roc_region::all::LineInfo; +use roc_reporting::report::{can_problem, mono_problem, type_problem, RocDocAllocator}; use roc_target::TargetInfo; use crate::eval::ToAstProblem; @@ -44,18 +45,15 @@ pub fn compile_to_mono<'a>( arena: &'a Bump, src: &str, target_info: TargetInfo, + palette: Palette, ) -> Result, Vec> { - use roc_reporting::report::{ - can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE, - }; - let stdlib = arena.alloc(roc_builtins::std::standard_stdlib()); let filename = PathBuf::from("REPL.roc"); let src_dir = Path::new("fake/test/path"); let module_src = arena.alloc(promote_expr_to_module(src)); - let exposed_types = MutMap::default(); + let exposed_types = Default::default(); let loaded = roc_load::file::load_and_monomorphize_from_str( arena, filename, @@ -100,7 +98,6 @@ pub fn compile_to_mono<'a>( let line_info = LineInfo::new(module_src); let src_lines: Vec<&str> = src.split('\n').collect(); - let palette = DEFAULT_PALETTE; // Report parsing and canonicalization problems let alloc = RocDocAllocator::new(&src_lines, *home, interns); diff --git a/repl_test/src/tests.rs b/repl_test/src/tests.rs index 059168b610..5abff88496 100644 --- a/repl_test/src/tests.rs +++ b/repl_test/src/tests.rs @@ -1051,3 +1051,46 @@ fn dec_in_repl() { r#"1.23 : Dec"#, ) } + +#[test] +fn print_i8_issue_2710() { + expect_success( + indoc!( + r#" + a : I8 + a = -1 + a + "# + ), + r#"-1 : I8"#, + ) +} + +#[cfg(not(feature = "wasm"))] +#[test] +fn box_box() { + expect_success( + indoc!( + r#" + Box.box "container store" + "# + ), + r#"Box.box "container store" : Box Str"#, + ) +} + +#[cfg(not(feature = "wasm"))] +#[test] +fn box_box_type_alias() { + expect_success( + indoc!( + r#" + HeapStr : Box Str + helloHeap : HeapStr + helloHeap = Box.box "bye stacks" + helloHeap + "# + ), + r#"Box.box "bye stacks" : HeapStr"#, + ) +} diff --git a/repl_test/test_wasm.sh b/repl_test/test_wasm.sh index d50c676bd7..41ded30ab4 100755 --- a/repl_test/test_wasm.sh +++ b/repl_test/test_wasm.sh @@ -4,7 +4,7 @@ set -eux # We *could* write a build.rs to do this but we'd have nested cargo processes with different targets. # That can be solved using two separate target direcories with --target-dir but there isn't a huge win. # We need to clear RUSTFLAGS for this command, as CI sets normally some flags that are specific to CPU targets. -RUSTFLAGS="" cargo build --target wasm32-unknown-unknown -p roc_repl_wasm --features wasmer --release +RUSTFLAGS="" cargo build --target wasm32-unknown-unknown -p roc_repl_wasm --no-default-features --features wasmer --release # Build & run the test code on *native* target, not WebAssembly cargo test -p repl_test --features wasm -- --test-threads=1 diff --git a/repl_wasm/Cargo.toml b/repl_wasm/Cargo.toml index 27f9ab6830..7acb6b2e89 100644 --- a/repl_wasm/Cargo.toml +++ b/repl_wasm/Cargo.toml @@ -10,8 +10,9 @@ crate-type = ["cdylib"] roc_builtins = {path = "../compiler/builtins"} [dependencies] -bumpalo = { version = "3.8.0", features = ["collections"] } -futures = { version = "0.3.17", optional = true } +bumpalo = {version = "3.8.0", features = ["collections"]} +console_error_panic_hook = {version = "0.1.7", optional = true} +futures = {version = "0.3.17", optional = true} js-sys = "0.3.56" wasm-bindgen = "0.2.79" wasm-bindgen-futures = "0.4.29" @@ -21,8 +22,12 @@ roc_gen_wasm = {path = "../compiler/gen_wasm"} roc_load = {path = "../compiler/load"} roc_parse = {path = "../compiler/parse"} roc_repl_eval = {path = "../repl_eval"} +roc_reporting = {path = "../reporting"} roc_target = {path = "../compiler/roc_target"} roc_types = {path = "../compiler/types"} [features] wasmer = ["futures"] + +[package.metadata.wasm-pack.profile.profiling] +wasm-opt = ['-Os', '-g'] # `wasm-pack --profiling` is supposed to have debug info `-g` by default, but doesn't diff --git a/repl_wasm/src/lib.rs b/repl_wasm/src/lib.rs index 5d7edd3881..a1d66e6f8d 100644 --- a/repl_wasm/src/lib.rs +++ b/repl_wasm/src/lib.rs @@ -3,6 +3,8 @@ mod repl; // // Interface with external JS in the browser // +#[cfg(feature = "console_error_panic_hook")] +extern crate console_error_panic_hook; #[cfg(not(feature = "wasmer"))] mod externs_js; #[cfg(not(feature = "wasmer"))] diff --git a/repl_wasm/src/repl.rs b/repl_wasm/src/repl.rs index c4c39feba9..08cfc5965a 100644 --- a/repl_wasm/src/repl.rs +++ b/repl_wasm/src/repl.rs @@ -10,6 +10,7 @@ use roc_repl_eval::{ gen::{compile_to_mono, format_answer, ReplOutput}, ReplApp, ReplAppMemory, }; +use roc_reporting::report::DEFAULT_PALETTE_HTML; use roc_target::TargetInfo; use roc_types::pretty_print::{content_to_string, name_all_type_vars}; @@ -155,12 +156,15 @@ impl<'a> ReplApp<'a> for WasmReplApp<'a> { } pub async fn entrypoint_from_js(src: String) -> Result { + #[cfg(feature = "console_error_panic_hook")] + console_error_panic_hook::set_once(); + let arena = &Bump::new(); let pre_linked_binary: &'static [u8] = include_bytes!("../data/pre_linked_binary.o"); // Compile the app let target_info = TargetInfo::default_wasm32(); - let mono = match compile_to_mono(arena, &src, target_info) { + let mono = match compile_to_mono(arena, &src, target_info, DEFAULT_PALETTE_HTML) { Ok(m) => m, Err(messages) => return Err(messages.join("\n\n")), }; diff --git a/repl_www/.gitignore b/repl_www/.gitignore index 796b96d1c4..3bf393d175 100644 --- a/repl_www/.gitignore +++ b/repl_www/.gitignore @@ -1 +1 @@ -/build +/public/roc_repl_wasm* diff --git a/repl_www/build.sh b/repl_www/build.sh index 419369c689..9e762ddfd4 100755 --- a/repl_www/build.sh +++ b/repl_www/build.sh @@ -15,21 +15,18 @@ then fi # output directory is first argument or default -WWW_ROOT="${1:-repl_www/build}" +WWW_ROOT="${1:-repl_www/public}" mkdir -p $WWW_ROOT -# End up with `repl/index.html` and everything else at the root directory. -# We want all the assets to be at the root because the files auto-generated by `cargo` expect them to be there. -cp -r repl_www/public/* $WWW_ROOT - # When debugging the REPL, use `REPL_DEBUG=1 repl_www/build.sh` if [ -n "${REPL_DEBUG:-}" ] then # Leave out wasm-opt since it takes too long when debugging, and provide some debug options - cargo build --target wasm32-unknown-unknown -p roc_repl_wasm --release + cargo build --target wasm32-unknown-unknown -p roc_repl_wasm --release --features console_error_panic_hook wasm-bindgen --target web --keep-debug target/wasm32-unknown-unknown/release/roc_repl_wasm.wasm --out-dir repl_wasm/pkg/ else - wasm-pack build --target web repl_wasm + # A `--profiling` build is optimized and has debug info, so we get stack traces for compiler `todo!()` + wasm-pack build --profiling --target web repl_wasm -- --features console_error_panic_hook fi cp repl_wasm/pkg/*.wasm $WWW_ROOT diff --git a/repl_www/public/repl.css b/repl_www/public/repl.css index 2c39fc8a3d..014fa5276a 100644 --- a/repl_www/public/repl.css +++ b/repl_www/public/repl.css @@ -53,11 +53,11 @@ section.history { .history-item .output { margin: 0; } -.history-item .output-ok { - color: #0f8; +.panic { + color: red; } -.history-item .output-error { - color: #f00; +.input-line-prefix { + color: cyan; } .code { font-family: "Courier New", Courier, monospace; diff --git a/repl_www/public/repl.js b/repl_www/public/repl.js index 4ad0323e15..d23347518e 100644 --- a/repl_www/public/repl.js +++ b/repl_www/public/repl.js @@ -1,7 +1,17 @@ -// wasm_bindgen treats our `extern` declarations as JS globals, so let's keep it happy -window.js_create_app = js_create_app; -window.js_run_app = js_run_app; -window.js_get_result_and_memory = js_get_result_and_memory; +// The only way we can provide values to wasm_bindgen's generated code is to set globals +function setGlobalsForWasmBindgen() { + window.js_create_app = js_create_app; + window.js_run_app = js_run_app; + window.js_get_result_and_memory = js_get_result_and_memory; + + // The only place we use console.error is in wasm_bindgen, where it gets a single string argument. + console.error = function displayErrorInHistoryPanel(string) { + const html = `
${string}
`; + updateHistoryEntry(repl.inputHistoryIndex, false, html); + }; +} +setGlobalsForWasmBindgen(); + import * as roc_repl_wasm from "/roc_repl_wasm.js"; import { getMockWasiImports } from "/wasi.js"; @@ -170,8 +180,16 @@ function createHistoryEntry(inputText) { const historyIndex = repl.inputHistory.length; repl.inputHistory.push(inputText); + const firstLinePrefix = '» '; + const otherLinePrefix = '\n'; + const inputLines = inputText.split("\n"); + if (inputLines[inputLines.length - 1] === "") { + inputLines.pop(); + } + const inputWithPrefixes = firstLinePrefix + inputLines.join(otherLinePrefix); + const inputElem = document.createElement("pre"); - inputElem.textContent = inputText; + inputElem.innerHTML = inputWithPrefixes; inputElem.classList.add("input"); const historyItem = document.createElement("div"); @@ -186,7 +204,7 @@ function createHistoryEntry(inputText) { function updateHistoryEntry(index, ok, outputText) { const outputElem = document.createElement("pre"); - outputElem.textContent = outputText; + outputElem.innerHTML = outputText; outputElem.classList.add("output"); outputElem.classList.add(ok ? "output-ok" : "output-error"); diff --git a/reporting/src/error/canonicalize.rs b/reporting/src/error/canonicalize.rs index 6473db7388..83e35f6ade 100644 --- a/reporting/src/error/canonicalize.rs +++ b/reporting/src/error/canonicalize.rs @@ -5,6 +5,7 @@ use roc_problem::can::{ BadPattern, ExtensionTypeKind, FloatErrorKind, IntErrorKind, Problem, RuntimeError, }; use roc_region::all::{LineColumn, LineColumnRegion, LineInfo, Loc, Region}; +use roc_types::types::AliasKind; use std::path::PathBuf; use crate::error::r#type::suggest; @@ -17,6 +18,7 @@ const UNRECOGNIZED_NAME: &str = "UNRECOGNIZED NAME"; const UNUSED_DEF: &str = "UNUSED DEFINITION"; const UNUSED_IMPORT: &str = "UNUSED IMPORT"; const UNUSED_ALIAS_PARAM: &str = "UNUSED TYPE ALIAS PARAMETER"; +const UNBOUND_TYPE_VARIABLE: &str = "UNBOUND TYPE VARIABLE"; const UNUSED_ARG: &str = "UNUSED ARGUMENT"; const MISSING_DEFINITION: &str = "MISSING DEFINITION"; const UNKNOWN_GENERATES_WITH: &str = "UNKNOWN GENERATES FUNCTION"; @@ -252,6 +254,43 @@ pub fn can_problem<'b>( title = UNUSED_ALIAS_PARAM.to_string(); severity = Severity::RuntimeError; } + Problem::UnboundTypeVariable { + typ: alias, + num_unbound, + one_occurrence, + kind, + } => { + let mut stack = Vec::with_capacity(4); + if num_unbound == 1 { + stack.push(alloc.concat(vec![ + alloc.reflow("The definition of "), + alloc.symbol_unqualified(alias), + alloc.reflow(" has an unbound type variable:"), + ])); + } else { + stack.push(alloc.concat(vec![ + alloc.reflow("The definition of "), + alloc.symbol_unqualified(alias), + alloc.reflow(" has "), + alloc.text(format!("{}", num_unbound)), + alloc.reflow(" unbound type variables."), + ])); + stack.push(alloc.reflow("Here is one occurrence:")); + } + stack.push(alloc.region(lines.convert_region(one_occurrence))); + stack.push(alloc.tip().append(alloc.concat(vec![ + alloc.reflow("Type variables must be bound before the "), + alloc.keyword(match kind { + AliasKind::Structural => ":", + AliasKind::Opaque => ":=", + }), + alloc.reflow(". Perhaps you intended to add a type parameter to this type?"), + ]))); + doc = alloc.stack(stack); + + title = UNBOUND_TYPE_VARIABLE.to_string(); + severity = Severity::RuntimeError; + } Problem::BadRecursion(entries) => { doc = to_circular_def_doc(alloc, lines, &entries); title = CIRCULAR_DEF.to_string(); diff --git a/reporting/src/error/parse.rs b/reporting/src/error/parse.rs index 6f5aa33346..e29c6140fc 100644 --- a/reporting/src/error/parse.rs +++ b/reporting/src/error/parse.rs @@ -540,6 +540,8 @@ fn to_expr_report<'a>( to_malformed_number_literal_report(alloc, lines, filename, pos) } + EExpr::Ability(err, pos) => to_ability_def_report(alloc, lines, filename, err, *pos), + _ => todo!("unhandled parse error: {:?}", parse_problem), } } @@ -3647,6 +3649,83 @@ fn to_space_report<'a>( } } +fn to_ability_def_report<'a>( + alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, + filename: PathBuf, + problem: &roc_parse::parser::EAbility<'a>, + start: Position, +) -> Report<'a> { + use roc_parse::parser::EAbility; + + match problem { + EAbility::Space(error, pos) => to_space_report(alloc, lines, filename, error, *pos), + EAbility::Type(tipe, pos) => to_type_report(alloc, lines, filename, tipe, *pos), + EAbility::DemandAlignment(over_under_indent, pos) => { + let over_under_msg = if *over_under_indent > 0 { + alloc.reflow("indented too much") + } else { + alloc.reflow("not indented enough") + }; + + let msg = alloc.concat(vec![ + alloc.reflow("I suspect this line is "), + over_under_msg, + alloc.reflow(" (by "), + alloc.string(over_under_indent.abs().to_string()), + alloc.reflow(" spaces)"), + ]); + + to_unfinished_ability_report(alloc, lines, filename, *pos, start, msg) + } + EAbility::DemandName(pos) => to_unfinished_ability_report( + alloc, + lines, + filename, + *pos, + start, + alloc.reflow("I was expecting to see a value signature next."), + ), + EAbility::DemandColon(pos) => to_unfinished_ability_report( + alloc, + lines, + filename, + *pos, + start, + alloc.concat(vec![ + alloc.reflow("I was expecting to see a "), + alloc.parser_suggestion(":"), + alloc.reflow(" annotating the signature of this value next."), + ]), + ), + } +} + +fn to_unfinished_ability_report<'a>( + alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, + filename: PathBuf, + pos: Position, + start: Position, + message: RocDocBuilder<'a>, +) -> Report<'a> { + let surroundings = Region::new(start, pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); + + let doc = alloc.stack(vec![ + alloc.reflow(r"I was partway through parsing an ability definition, but I got stuck here:"), + alloc.region_with_subregion(lines.convert_region(surroundings), region), + message, + ]); + + Report { + filename, + doc, + title: "UNFINISHED ABILITY".to_string(), + severity: Severity::RuntimeError, + } +} + #[derive(Debug)] enum Next<'a> { Keyword(&'a str), diff --git a/reporting/src/error/type.rs b/reporting/src/error/type.rs index 9fd69a8153..ce0eec7674 100644 --- a/reporting/src/error/type.rs +++ b/reporting/src/error/type.rs @@ -102,14 +102,6 @@ pub fn type_problem<'b>( report(title, doc, filename) } - CyclicAlias(..) => { - // We'll also report cyclic aliases as a canonicalization problem, no need to - // re-report them. - None - } - - SolvedTypeError => None, // Don't re-report cascading errors - see https://github.com/rtfeldman/roc/pull/1711 - Shadowed(original_region, shadow) => { let doc = report_shadowing(alloc, lines, original_region, shadow); let title = DUPLICATE_NAME.to_string(); @@ -117,6 +109,12 @@ pub fn type_problem<'b>( report(title, doc, filename) } + SolvedTypeError => None, // Don't re-report cascading errors - see https://github.com/rtfeldman/roc/pull/1711 + + // We'll also report these as a canonicalization problem, no need to re-report them. + CyclicAlias(..) => None, + UnrecognizedIdent(..) => None, + other => panic!("unhandled bad type: {:?}", other), } } diff --git a/reporting/src/report.rs b/reporting/src/report.rs index 201a06ad90..4dc7c46d3e 100644 --- a/reporting/src/report.rs +++ b/reporting/src/report.rs @@ -128,59 +128,110 @@ impl<'b> Report<'b> { } } -pub struct Palette<'a> { - pub primary: &'a str, - pub code_block: &'a str, - pub keyword: &'a str, - pub variable: &'a str, - pub type_variable: &'a str, - pub structure: &'a str, - pub alias: &'a str, - pub opaque: &'a str, - pub error: &'a str, - pub line_number: &'a str, - pub header: &'a str, - pub gutter_bar: &'a str, - pub module_name: &'a str, - pub binop: &'a str, - pub typo: &'a str, - pub typo_suggestion: &'a str, - pub parser_suggestion: &'a str, +/// This struct is a combination of several things +/// 1. A set of StyleCodes suitable for the platform we're running on (web or terminal) +/// 2. A set of colors we decided to use +/// 3. A mapping from UI elements to the styles we use for them +/// Note: This should really be called Theme! Usually a "palette" is just (2). +pub struct Palette { + pub primary: &'static str, + pub code_block: &'static str, + pub keyword: &'static str, + pub variable: &'static str, + pub type_variable: &'static str, + pub structure: &'static str, + pub alias: &'static str, + pub opaque: &'static str, + pub error: &'static str, + pub line_number: &'static str, + pub header: &'static str, + pub gutter_bar: &'static str, + pub module_name: &'static str, + pub binop: &'static str, + pub typo: &'static str, + pub typo_suggestion: &'static str, + pub parser_suggestion: &'static str, + pub bold: &'static str, + pub underline: &'static str, + pub reset: &'static str, } -pub const DEFAULT_PALETTE: Palette = Palette { - primary: WHITE_CODE, - code_block: WHITE_CODE, - keyword: GREEN_CODE, - variable: BLUE_CODE, - type_variable: YELLOW_CODE, - structure: GREEN_CODE, - alias: YELLOW_CODE, - opaque: YELLOW_CODE, - error: RED_CODE, - line_number: CYAN_CODE, - header: CYAN_CODE, - gutter_bar: CYAN_CODE, - module_name: GREEN_CODE, - binop: GREEN_CODE, - typo: YELLOW_CODE, - typo_suggestion: GREEN_CODE, - parser_suggestion: YELLOW_CODE, +/// Set the default styles for various semantic elements, +/// given a set of StyleCodes for a platform (web or terminal). +const fn default_palette_from_style_codes(codes: StyleCodes) -> Palette { + Palette { + primary: codes.white, + code_block: codes.white, + keyword: codes.green, + variable: codes.blue, + type_variable: codes.yellow, + structure: codes.green, + alias: codes.yellow, + opaque: codes.yellow, + error: codes.red, + line_number: codes.cyan, + header: codes.cyan, + gutter_bar: codes.cyan, + module_name: codes.green, + binop: codes.green, + typo: codes.yellow, + typo_suggestion: codes.green, + parser_suggestion: codes.yellow, + bold: codes.bold, + underline: codes.underline, + reset: codes.reset, + } +} + +pub const DEFAULT_PALETTE: Palette = default_palette_from_style_codes(ANSI_STYLE_CODES); + +pub const DEFAULT_PALETTE_HTML: Palette = default_palette_from_style_codes(HTML_STYLE_CODES); + +/// A machine-readable format for text styles (colors and other styles) +pub struct StyleCodes { + pub red: &'static str, + pub green: &'static str, + pub yellow: &'static str, + pub blue: &'static str, + pub magenta: &'static str, + pub cyan: &'static str, + pub white: &'static str, + pub bold: &'static str, + pub underline: &'static str, + pub reset: &'static str, +} + +pub const ANSI_STYLE_CODES: StyleCodes = StyleCodes { + red: "\u{001b}[31m", + green: "\u{001b}[32m", + yellow: "\u{001b}[33m", + blue: "\u{001b}[34m", + magenta: "\u{001b}[35m", + cyan: "\u{001b}[36m", + white: "\u{001b}[37m", + bold: "\u{001b}[1m", + underline: "\u{001b}[4m", + reset: "\u{001b}[0m", }; -pub const RED_CODE: &str = "\u{001b}[31m"; -pub const GREEN_CODE: &str = "\u{001b}[32m"; -pub const YELLOW_CODE: &str = "\u{001b}[33m"; -pub const BLUE_CODE: &str = "\u{001b}[34m"; -pub const MAGENTA_CODE: &str = "\u{001b}[35m"; -pub const CYAN_CODE: &str = "\u{001b}[36m"; -pub const WHITE_CODE: &str = "\u{001b}[37m"; +macro_rules! html_color { + ($name: expr) => { + concat!("") + }; +} -pub const BOLD_CODE: &str = "\u{001b}[1m"; - -pub const UNDERLINE_CODE: &str = "\u{001b}[4m"; - -pub const RESET_CODE: &str = "\u{001b}[0m"; +pub const HTML_STYLE_CODES: StyleCodes = StyleCodes { + red: html_color!("red"), + green: html_color!("green"), + yellow: html_color!("yellow"), + blue: html_color!("blue"), + magenta: html_color!("magenta"), + cyan: html_color!("cyan"), + white: html_color!("white"), + bold: "", + underline: "", + reset: "", +}; // define custom allocator struct so we can `impl RocDocAllocator` custom helpers pub struct RocDocAllocator<'a> { @@ -534,11 +585,8 @@ impl<'a> RocDocAllocator<'a> { // debug_assert!(region.contains(&sub_region)); // If the outer region takes more than 1 full screen (~60 lines), only show the inner region - match region.end().line.checked_sub(region.start().line) { - Some(v) if v > 60 => { - return self.region_with_subregion(sub_region, sub_region); - } - _ => {} + if region.end().line.saturating_sub(region.start().line) > 60 { + return self.region_with_subregion(sub_region, sub_region); } // if true, the final line of the snippet will be some ^^^ that point to the region where @@ -745,7 +793,7 @@ impl CiWrite { /// Render with fancy formatting pub struct ColorWrite<'a, W> { style_stack: Vec, - palette: &'a Palette<'a>, + palette: &'a Palette, upstream: W, } @@ -861,10 +909,10 @@ where use Annotation::*; match annotation { Emphasized => { - self.write_str(BOLD_CODE)?; + self.write_str(self.palette.bold)?; } Url | Tip => { - self.write_str(UNDERLINE_CODE)?; + self.write_str(self.palette.underline)?; } PlainText => { self.write_str(self.palette.primary)?; @@ -932,7 +980,7 @@ where Emphasized | Url | TypeVariable | Alias | Symbol | BinOp | Error | GutterBar | Typo | TypoSuggestion | ParserSuggestion | Structure | CodeBlock | PlainText | LineNumber | Tip | Module | Header | Keyword => { - self.write_str(RESET_CODE)?; + self.write_str(self.palette.reset)?; } TypeBlock | GlobalTag | PrivateTag | Opaque | RecordField => { /* nothing yet */ } diff --git a/reporting/tests/helpers/mod.rs b/reporting/tests/helpers/mod.rs index 9563cea7b0..eda767542e 100644 --- a/reporting/tests/helpers/mod.rs +++ b/reporting/tests/helpers/mod.rs @@ -9,12 +9,12 @@ use roc_can::operator; use roc_can::scope::Scope; use roc_collections::all::{ImMap, MutMap, SendSet}; use roc_constrain::expr::constrain_expr; -use roc_constrain::module::{constrain_imported_values, Import}; +use roc_constrain::module::introduce_builtin_imports; use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds}; use roc_parse::parser::{SourceError, SyntaxError}; use roc_problem::can::Problem; use roc_region::all::Loc; -use roc_solve::solve; +use roc_solve::solve::{self, Aliases}; use roc_types::subs::{Content, Subs, VarStore, Variable}; use roc_types::types::Type; use std::hash::Hash; @@ -30,10 +30,11 @@ pub fn infer_expr( problems: &mut Vec, constraints: &Constraints, constraint: &Constraint, + aliases: &mut Aliases, expr_var: Variable, ) -> (Content, Subs) { let env = solve::Env::default(); - let (solved, _) = solve::run(constraints, &env, problems, subs, constraint); + let (solved, _) = solve::run(constraints, &env, problems, subs, aliases, constraint); let content = *solved.inner().get_content_without_compacting(expr_var); @@ -163,19 +164,14 @@ pub fn can_expr_with<'a>( expected, ); - let types = roc_builtins::std::types(); - - let imports: Vec<_> = types - .into_iter() - .map(|(symbol, (solved_type, region))| Import { - loc_symbol: Loc::at(region, symbol), - solved_type, - }) + let imports = roc_builtins::std::borrow_stdlib() + .types + .keys() + .copied() .collect(); - //load builtin values - let (_introduced_rigids, constraint) = - constrain_imported_values(&mut constraints, imports, constraint, &mut var_store); + let constraint = + introduce_builtin_imports(&mut constraints, imports, constraint, &mut var_store); let mut all_ident_ids = MutMap::default(); diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 247db7a0ec..644f14a14d 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -12,14 +12,14 @@ mod test_reporting { use crate::helpers::test_home; use crate::helpers::{can_expr, infer_expr, CanExprOut, ParseErrOut}; use bumpalo::Bump; + use indoc::indoc; use roc_module::symbol::{Interns, ModuleId}; use roc_mono::ir::{Procs, Stmt, UpdateModeIds}; use roc_mono::layout::LayoutCache; use roc_region::all::LineInfo; use roc_reporting::report::{ - can_problem, mono_problem, parse_problem, type_problem, Report, Severity, BLUE_CODE, - BOLD_CODE, CYAN_CODE, DEFAULT_PALETTE, GREEN_CODE, MAGENTA_CODE, RED_CODE, RESET_CODE, - UNDERLINE_CODE, WHITE_CODE, YELLOW_CODE, + can_problem, mono_problem, parse_problem, type_problem, Report, Severity, ANSI_STYLE_CODES, + DEFAULT_PALETTE, }; use roc_reporting::report::{RocDocAllocator, RocDocBuilder}; use roc_solve::solve; @@ -76,12 +76,24 @@ mod test_reporting { } for var in output.introduced_variables.wildcards { - subs.rigid_var(var, "*".into()); + subs.rigid_var(var.value, "*".into()); + } + + let mut solve_aliases = roc_solve::solve::Aliases::default(); + + for (name, alias) in output.aliases { + solve_aliases.insert(name, alias); } let mut unify_problems = Vec::new(); - let (_content, mut subs) = - infer_expr(subs, &mut unify_problems, &constraints, &constraint, var); + let (_content, mut subs) = infer_expr( + subs, + &mut unify_problems, + &constraints, + &constraint, + &mut solve_aliases, + var, + ); name_all_type_vars(var, &mut subs); @@ -288,16 +300,16 @@ mod test_reporting { } fn human_readable(str: &str) -> String { - 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, "") + str.replace(ANSI_STYLE_CODES.red, "") + .replace(ANSI_STYLE_CODES.white, "") + .replace(ANSI_STYLE_CODES.blue, "") + .replace(ANSI_STYLE_CODES.yellow, "") + .replace(ANSI_STYLE_CODES.green, "") + .replace(ANSI_STYLE_CODES.cyan, "") + .replace(ANSI_STYLE_CODES.magenta, "") + .replace(ANSI_STYLE_CODES.reset, "") + .replace(ANSI_STYLE_CODES.bold, "") + .replace(ANSI_STYLE_CODES.underline, "") } #[test] @@ -426,8 +438,8 @@ mod test_reporting { `Booly` is not used anywhere in your code. - 3│ Booly : [ Yes, No, Maybe ] - ^^^^^^^^^^^^^^^^^^^^^^^^^^ + 1│ Booly : [ Yes, No ] + ^^^^^^^^^^^^^^^^^^^ If you didn't intend on using `Booly` then remove it so future readers of your code don't wonder why it is there. @@ -436,8 +448,8 @@ mod test_reporting { `Booly` is not used anywhere in your code. - 1│ Booly : [ Yes, No ] - ^^^^^^^^^^^^^^^^^^^ + 3│ Booly : [ Yes, No, Maybe ] + ^^^^^^^^^^^^^^^^^^^^^^^^^^ If you didn't intend on using `Booly` then remove it so future readers of your code don't wonder why it is there. @@ -1544,10 +1556,10 @@ mod test_reporting { Did you mean one of these? + Box Bool U8 F64 - Nat "# ), ) @@ -2003,8 +2015,8 @@ mod test_reporting { Ok U8 + Box f - I8 "# ), ) @@ -3727,10 +3739,10 @@ mod test_reporting { Is there an import missing? Perhaps there is a typo. Did you mean one of these? + Box Bool Num Set - Str "# ), ) @@ -7983,21 +7995,21 @@ I need all branches in an `if` to have the same type! indoc!( r#" ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── - - The `Bar` alias is recursive in an invalid way: - - 2│ Bar a : [ Stuff (Foo a) ] - ^^^^^ - - The `Bar` alias depends on itself through the following chain of + + The `Foo` alias is recursive in an invalid way: + + 1│ Foo a : [ Thing (Bar a) ] + ^^^ + + The `Foo` alias depends on itself through the following chain of definitions: - + ┌─────┐ - │ Bar - │ ↓ │ Foo + │ ↓ + │ Bar └─────┘ - + Recursion in aliases is only allowed if recursion happens behind a tagged union, at least one variant of which is not recursive. "# @@ -8185,7 +8197,7 @@ I need all branches in an `if` to have the same type! Test List Num - Set + Box "# ), ) @@ -8369,7 +8381,7 @@ I need all branches in an `if` to have the same type! But all the previous branches match: - F [ A ] + F [ A ]a "# ), ) @@ -8565,4 +8577,298 @@ I need all branches in an `if` to have the same type! ), ) } + + #[test] + fn unknown_type() { + report_problem_as( + indoc!( + r#" + Type : [ Constructor UnknownType ] + + insertHelper : UnknownType, Type -> Type + insertHelper = \h, m -> + when m is + Constructor _ -> Constructor h + + insertHelper + "# + ), + indoc!( + r#" + ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + + I cannot find a `UnknownType` value + + 1│ Type : [ Constructor UnknownType ] + ^^^^^^^^^^^ + + Did you mean one of these? + + Type + Unsigned8 + Unsigned32 + Unsigned16 + + ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + + I cannot find a `UnknownType` value + + 3│ insertHelper : UnknownType, Type -> Type + ^^^^ + + Did you mean one of these? + + Type + Unsigned8 + Unsigned32 + Unsigned16 + "# + ), + ) + } + + #[test] + fn ability_first_demand_not_indented_enough() { + report_problem_as( + indoc!( + r#" + Eq has + eq : a, a -> U64 | a has Eq + + 1 + "# + ), + indoc!( + r#" + ── UNFINISHED ABILITY ────────────────────────────────────────────────────────── + + I was partway through parsing an ability definition, but I got stuck + here: + + 1│ Eq has + 2│ eq : a, a -> U64 | a has Eq + ^ + + I suspect this line is not indented enough (by 1 spaces) + "# + ), + ) + } + + #[test] + fn ability_demands_not_indented_with_first() { + report_problem_as( + indoc!( + r#" + Eq has + eq : a, a -> U64 | a has Eq + neq : a, a -> U64 | a has Eq + + 1 + "# + ), + indoc!( + r#" + ── UNFINISHED ABILITY ────────────────────────────────────────────────────────── + + I was partway through parsing an ability definition, but I got stuck + here: + + 2│ eq : a, a -> U64 | a has Eq + 3│ neq : a, a -> U64 | a has Eq + ^ + + I suspect this line is indented too much (by 4 spaces) + "# + ), + ) + } + + #[test] + fn ability_demand_value_has_args() { + report_problem_as( + indoc!( + r#" + Eq has + eq b c : a, a -> U64 | a has Eq + + 1 + "# + ), + indoc!( + r#" + ── UNFINISHED ABILITY ────────────────────────────────────────────────────────── + + I was partway through parsing an ability definition, but I got stuck + here: + + 2│ eq b c : a, a -> U64 | a has Eq + ^ + + I was expecting to see a : annotating the signature of this value + next. + "# + ), + ) + } + + #[test] + fn ability_non_signature_expression() { + report_problem_as( + indoc!( + r#" + Eq has + 123 + + 1 + "# + ), + indoc!( + r#" + ── UNFINISHED ABILITY ────────────────────────────────────────────────────────── + + I was partway through parsing an ability definition, but I got stuck + here: + + 1│ Eq has + 2│ 123 + ^ + + I was expecting to see a value signature next. + "# + ), + ) + } + + #[test] + fn wildcard_in_alias() { + report_problem_as( + indoc!( + r#" + I : Int * + a : I + a + "# + ), + indoc!( + r#" + ── UNBOUND TYPE VARIABLE ─────────────────────────────────────────────────────── + + The definition of `I` has an unbound type variable: + + 1│ I : Int * + ^ + + Tip: Type variables must be bound before the `:`. Perhaps you intended + to add a type parameter to this type? + "# + ), + ) + } + + #[test] + fn wildcard_in_opaque() { + report_problem_as( + indoc!( + r#" + I := Int * + a : I + a + "# + ), + indoc!( + r#" + ── UNBOUND TYPE VARIABLE ─────────────────────────────────────────────────────── + + The definition of `I` has an unbound type variable: + + 1│ I := Int * + ^ + + Tip: Type variables must be bound before the `:=`. Perhaps you intended + to add a type parameter to this type? + "# + ), + ) + } + + #[test] + fn multiple_wildcards_in_alias() { + report_problem_as( + indoc!( + r#" + I : [ A (Int *), B (Int *) ] + a : I + a + "# + ), + indoc!( + r#" + ── UNBOUND TYPE VARIABLE ─────────────────────────────────────────────────────── + + The definition of `I` has 2 unbound type variables. + + Here is one occurrence: + + 1│ I : [ A (Int *), B (Int *) ] + ^ + + Tip: Type variables must be bound before the `:`. Perhaps you intended + to add a type parameter to this type? + "# + ), + ) + } + + #[test] + fn inference_var_in_alias() { + report_problem_as( + indoc!( + r#" + I : Int _ + a : I + a + "# + ), + indoc!( + r#" + ── UNBOUND TYPE VARIABLE ─────────────────────────────────────────────────────── + + The definition of `I` has an unbound type variable: + + 1│ I : Int _ + ^ + + Tip: Type variables must be bound before the `:`. Perhaps you intended + to add a type parameter to this type? + "# + ), + ) + } + + #[test] + fn unbound_var_in_alias() { + report_problem_as( + indoc!( + r#" + I : Int a + a : I + a + "# + ), + indoc!( + r#" + ── UNBOUND TYPE VARIABLE ─────────────────────────────────────────────────────── + + The definition of `I` has an unbound type variable: + + 1│ I : Int a + ^ + + Tip: Type variables must be bound before the `:`. Perhaps you intended + to add a type parameter to this type? + "# + ), + ) + } } diff --git a/utils/src/lib.rs b/utils/src/lib.rs index bbab75e59b..25db29a929 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -33,7 +33,7 @@ pub fn index_of(elt: T, slice: &[T]) -> Uti Ok(index) } -// replace slice method that return Option with one that return Result and proper Error +// replaces slice method that return Option with one that return Result and proper Error pub fn slice_get(index: usize, slice: &[T]) -> UtilResult<&>::Output> { let elt_ref = slice.get(index).context(OutOfBounds { index, diff --git a/vendor/inkwell/Cargo.toml b/vendor/inkwell/Cargo.toml index 5894370eab..46feb8bc37 100644 --- a/vendor/inkwell/Cargo.toml +++ b/vendor/inkwell/Cargo.toml @@ -23,7 +23,7 @@ edition = "2018" # commit of TheDan64/inkwell, push a new tag which points to the latest commit, # change the tag value in this Cargo.toml to point to that tag, and `cargo update`. # This way, GitHub Actions works and nobody's builds get broken. -inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm13-0.release1", features = [ "llvm12-0" ] } +inkwell = { git = "https://github.com/rtfeldman/inkwell", branch = "master", features = [ "llvm12-0" ] } [features] target-arm = [] diff --git a/www/build.sh b/www/build.sh index 86db1d0134..fbe0bdfbf4 100755 --- a/www/build.sh +++ b/www/build.sh @@ -16,6 +16,9 @@ pushd build # grab the source code and copy it to Netlify's server; if it's not there, fail the build. wget https://github.com/rtfeldman/elm-css/files/8037422/roc-source-code.zip +# Copy REPL webpage source files +cp -r ../../repl_www/public/* . + # grab the pre-compiled REPL and copy it to Netlify's server; if it's not there, fail the build. wget https://github.com/brian-carroll/mock-repl/archive/refs/heads/deploy.zip unzip deploy.zip @@ -23,9 +26,6 @@ mv mock-repl-deploy/* . rmdir mock-repl-deploy rm deploy.zip -# Copy REPL webpage source files -cp -r ../../repl_www/public/* . - popd # pushd ..