diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index c5f2a8d123..dbc7005e80 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -2,6 +2,10 @@ on: [pull_request] name: Benchmarks +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: RUST_BACKTRACE: 1 ROC_NUM_WORKERS: 1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 72bfdc7151..845a8e9425 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,6 +2,10 @@ on: [pull_request] name: CI +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/spellcheck.yml b/.github/workflows/spellcheck.yml index 0a44b6576e..0a1eee7af9 100644 --- a/.github/workflows/spellcheck.yml +++ b/.github/workflows/spellcheck.yml @@ -2,6 +2,10 @@ on: [pull_request] name: SpellCheck +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: RUST_BACKTRACE: 1 diff --git a/AUTHORS b/AUTHORS index 88baae4952..2c21c5fb3d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -76,3 +76,5 @@ Nikita Mounier <36044205+nikitamounier@users.noreply.github.com> Cai Bingjun <62678643+C-BJ@users.noreply.github.com> Jared Cone Sean Hagstrom +Kas Buunk +Oskar Hahn diff --git a/BUILDING_FROM_SOURCE.md b/BUILDING_FROM_SOURCE.md index 4182939e77..cae2627685 100644 --- a/BUILDING_FROM_SOURCE.md +++ b/BUILDING_FROM_SOURCE.md @@ -107,7 +107,7 @@ To run the test suite (via `cargo test`), you additionally need to install: * [`valgrind`](https://www.valgrind.org/) (needs special treatment to [install on macOS](https://stackoverflow.com/a/61359781) Alternatively, you can use `cargo test --no-fail-fast` or `cargo test -p specific_tests` to skip over the valgrind failures & tests. -For debugging LLVM IR, we use [DebugIR](https://github.com/vaivaswatha/debugir). This dependency is only required to build with the `--debug` flag, and for normal developtment you should be fine without it. +For debugging LLVM IR, we use [DebugIR](https://github.com/vaivaswatha/debugir). This dependency is only required to build with the `--debug` flag, and for normal development you should be fine without it. ### libxcb libraries diff --git a/Cargo.lock b/Cargo.lock index 51a0e2d9c6..ca60eae4eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -242,10 +242,22 @@ version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527" dependencies = [ - "funty", - "radium", + "funty 1.2.0", + "radium 0.6.2", "tap", - "wyz", + "wyz 0.4.0", +] + +[[package]] +name = "bitvec" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b" +dependencies = [ + "funty 2.0.0", + "radium 0.7.0", + "tap", + "wyz 0.5.0", ] [[package]] @@ -1393,6 +1405,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.17" @@ -2645,7 +2663,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c48e482b9a59ad6c2cdb06f7725e7bd33fe3525baaf4699fde7bfea6a5b77b1" dependencies = [ - "bitvec", + "bitvec 0.22.3", "packed_struct_codegen", "serde", ] @@ -3092,6 +3110,12 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "radix_trie" version = "0.2.1" @@ -3229,9 +3253,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.4" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" dependencies = [ "aho-corasick", "memchr", @@ -3271,6 +3295,19 @@ dependencies = [ "winapi", ] +[[package]] +name = "remove_dir_all" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882f368737489ea543bc5c340e6f3d34a28c39980bd9a979e47322b26f60ac40" +dependencies = [ + "libc", + "log", + "num_cpus", + "rayon", + "winapi", +] + [[package]] name = "renderdoc-sys" version = "0.7.1" @@ -3409,12 +3446,14 @@ dependencies = [ name = "roc_can" version = "0.1.0" dependencies = [ + "bitvec 1.0.0", "bumpalo", "indoc", "pretty_assertions", "roc_builtins", "roc_collections", "roc_error_macros", + "roc_exhaustive", "roc_module", "roc_parse", "roc_problem", @@ -3735,6 +3774,7 @@ dependencies = [ "roc_reporting", "roc_solve", "roc_target", + "roc_test_utils", "roc_types", "roc_unify", "tempfile", @@ -3897,7 +3937,6 @@ dependencies = [ "roc_exhaustive", "roc_load", "roc_module", - "roc_mono", "roc_parse", "roc_problem", "roc_region", @@ -3916,11 +3955,14 @@ dependencies = [ "arrayvec 0.7.2", "bumpalo", "indoc", + "lazy_static", "pretty_assertions", + "regex", "roc_builtins", "roc_can", "roc_collections", "roc_error_macros", + "roc_exhaustive", "roc_load", "roc_module", "roc_parse", @@ -3953,6 +3995,7 @@ name = "roc_test_utils" version = "0.1.0" dependencies = [ "pretty_assertions", + "remove_dir_all 0.7.0", ] [[package]] @@ -4449,9 +4492,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "target-lexicon" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bffcddbc2458fa3e6058414599e3c838a022abae82e5c67b4f7f80298d5bff" +checksum = "d7fa7e55043acb85fca6b3c01485a2eeb6b69c5d21002e273c79e465f43b7ac1" [[package]] name = "tempfile" @@ -4463,7 +4506,7 @@ dependencies = [ "libc", "rand", "redox_syscall", - "remove_dir_all", + "remove_dir_all 0.5.3", "winapi", ] @@ -5580,6 +5623,15 @@ dependencies = [ "tap", ] +[[package]] +name = "wyz" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +dependencies = [ + "tap", +] + [[package]] name = "x11-clipboard" version = "0.5.3" diff --git a/README.md b/README.md index 2b094900b3..c0c5f98702 100644 --- a/README.md +++ b/README.md @@ -32,11 +32,13 @@ For NQueens, input 10 in the terminal and press enter. **Tip:** when programming in roc, we recommend to execute `./roc check myproject/Foo.roc` before `./roc myproject/Foo.roc` or `./roc build myproject/Foo.roc`. `./roc check` can produce clear error messages in cases where building/running may panic. -## Sponsor +## Sponsors -We are very grateful for our sponsor [NoRedInk](https://www.noredink.com/). +We are very grateful for our sponsors [NoRedInk](https://www.noredink.com/) and [rwx](https://www.rwx.com). -NoRedInk logo +[NoRedInk logo](https://www.noredink.com/) +     +[rwx logo](https://www.rwx.com) ## Applications and Platforms diff --git a/TUTORIAL.md b/TUTORIAL.md index e8820da585..9885bb8e50 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -115,18 +115,19 @@ Create a new file called `Hello.roc` and put this inside it: ```coffee app "hello" - packages { pf: "examples/cli/platform" } + packages { pf: "examples/interactive/cli-platform" } imports [ pf.Stdout ] provides [ main ] to pf main = Stdout.line "I'm a Roc application!" ``` -> **NOTE:** This assumes you've put Hello.roc in the root directory of the -> Roc source code. If you'd like to put it somewhere else, you'll need to replace -> `"examples/cli/"` with the path to the `examples/cli/` folder in -> that source code. In the future, Roc will have the tutorial built in, and this -> aside will no longer be necessary! +> **NOTE:** This assumes you've put Hello.roc in the root directory of the Roc +> source code. If you'd like to put it somewhere else, you'll need to replace +> `"examples/interactive/cli-platform"` with the path to the +> `examples/interactive/cli-platform` folder in that source code. In the future, +> Roc will have the tutorial built in, and this aside will no longer be +> necessary! Try running this with: @@ -1202,6 +1203,24 @@ and also `Num.cos 1` and have them all work as expected; the number literal `1` `Num *`, which is compatible with the more constrained types `Int` and `Frac`. For the same reason, you can pass number literals to functions expecting even more constrained types, like `I32` or `F64`. +### Typed Number Literals +When writing a number literal in Roc you can specify the numeric type as a suffix of the literal. +`1u8` specifies `1` as an unsigned 8-bit integer, `5i32` specifies `5` as a signed 32-bit integer, etc. +The full list of possible suffixes includes: +`i8`, `u8`, `i16`, `u16`, `i32`, `u32`, `i64`, `u64`, `i128`, `u128`, `nat`, `f32`, `f64`, `dec` + +### Hexadecimal Integer Literals +Integer literals can be written in hexadecimal form by prefixing with `0x` followed by hexadecimal characters. +`0xFE` evaluates to decimal `254` +The integer type can be specified as a suffix to the hexadecimal literal, +so `0xC8u8` evaluates to decimal `200` as an unsigned 8-bit integer. + +### Binary Integer Literals +Integer literals can be written in binary form by prefixing with `0b` followed by the 1's and 0's representing +each bit. `0b0000_1000` evaluates to decimal `8` +The integer type can be specified as a suffix to the binary literal, +so `0b0100u8` evaluates to decimal `4` as an unsigned 8-bit integer. + ## Interface modules [ This part of the tutorial has not been written yet. Coming soon! ] @@ -1236,7 +1255,7 @@ Let's take a closer look at the part of `Hello.roc` above `main`: ```coffee app "hello" - packages { pf: "examples/cli/platform" } + packages { pf: "examples/interactive/cli-platform" } imports [ pf.Stdout ] provides main to pf ``` @@ -1254,14 +1273,14 @@ without running it by running `roc build Hello.roc`. The remaining lines all involve the *platform* this application is built on: ```coffee -packages { pf: "examples/cli/platform" } +packages { pf: "examples/interactive/cli-platform" } imports [ pf.Stdout ] provides main to pf ``` -The `packages { pf: "examples/cli/platform" }` part says two things: +The `packages { pf: "examples/interactive/cli-platform" }` part says two things: -- We're going to be using a *package* (that is, a collection of modules) called `"examples/cli/platform"` +- We're going to be using a *package* (that is, a collection of modules) called `"examples/interactive/cli-platform"` - We're going to name that package `pf` so we can refer to it more concisely in the future. The `imports [ pf.Stdout ]` line says that we want to import the `Stdout` module @@ -1281,17 +1300,18 @@ calling a function named `line` which is exposed by a module named When we write `imports [ pf.Stdout ]`, it specifies that the `Stdout` module comes from the `pf` package. -Since `pf` was the name we chose for the `examples/cli/platform` package -(when we wrote `packages { pf: "examples/cli/platform" }`), this `imports` line -tells the Roc compiler that when we call `Stdout.line`, it should look for that -`line` function in the `Stdout` module of the `examples/cli/platform` package. +Since `pf` was the name we chose for the `examples/interactive/cli-platform` +package (when we wrote `packages { pf: "examples/interactive/cli-platform" }`), +this `imports` line tells the Roc compiler that when we call `Stdout.line`, it +should look for that `line` function in the `Stdout` module of the +`examples/interactive/cli-platform` package. # Building a Command-Line Interface (CLI) ## Tasks Tasks are technically not part of the Roc language, but they're very common in -platforms. Let's use the CLI platform in `examples/cli` as an example! +platforms. Let's use the CLI platform in `examples/interactive/cli-platform` as an example! In the CLI platform, we have four operations we can do: @@ -1306,7 +1326,7 @@ First, let's do a basic "Hello World" using the tutorial app. ```coffee app "cli-tutorial" - packages { pf: "examples/cli/platform" } + packages { pf: "examples/interactive/cli-platform" } imports [ pf.Stdout ] provides [ main ] to pf @@ -1343,7 +1363,7 @@ Let's change `main` to read a line from `stdin`, and then print it back out agai ```swift app "cli-tutorial" - packages { pf: "examples/cli/platform" } + packages { pf: "examples/interactive/cli-platform" } imports [ pf.Stdout, pf.Stdin, pf.Task ] provides [ main ] to pf @@ -1393,7 +1413,7 @@ This works, but we can make it a little nicer to read. Let's change it to the fo ```haskell app "cli-tutorial" - packages { pf: "examples/cli/platform" } + packages { pf: "examples/interactive/cli-platform" } imports [ pf.Stdout, pf.Stdin, pf.Task.{ await } ] provides [ main ] to pf @@ -1941,10 +1961,9 @@ Here are various Roc expressions involving operators, and what they desugar to. | `a - b` | `Num.sub a b` | | `a * b` | `Num.mul a b` | | `a / b` | `Num.div a b` | -| `a // b` | `Num.divFloor a b` | +| `a // b` | `Num.divTrunc a b` | | `a ^ b` | `Num.pow a b` | | `a % b` | `Num.rem a b` | -| `a %% b` | `Num.mod a b` | | `a >> b` | `Num.shr a b` | | `a << b` | `Num.shl a b` | | `-a` | `Num.neg a` | diff --git a/ast/src/canonicalization/module.rs b/ast/src/canonicalization/module.rs index 64c568af5f..634e7e881c 100644 --- a/ast/src/canonicalization/module.rs +++ b/ast/src/canonicalization/module.rs @@ -7,6 +7,7 @@ use roc_can::operator::desugar_def; use roc_collections::all::{default_hasher, ImMap, ImSet, MutMap, MutSet, SendMap}; use roc_module::ident::Ident; use roc_module::ident::Lowercase; +use roc_module::symbol::IdentIdsByModule; use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; use roc_parse::ast; use roc_parse::pattern::PatternType; @@ -48,7 +49,7 @@ pub fn canonicalize_module_defs<'a>( home: ModuleId, module_ids: &ModuleIds, exposed_ident_ids: IdentIds, - dep_idents: MutMap, + dep_idents: IdentIdsByModule, aliases: MutMap, exposed_imports: MutMap, mut exposed_symbols: MutSet, diff --git a/ast/src/constrain.rs b/ast/src/constrain.rs index bd00bbbda5..de68e4b6cb 100644 --- a/ast/src/constrain.rs +++ b/ast/src/constrain.rs @@ -245,32 +245,13 @@ pub fn constrain_expr<'a>( exists(arena, field_vars, And(constraints)) } } - Expr2::GlobalTag { + Expr2::Tag { variant_var, ext_var, name, arguments, } => { - let tag_name = TagName::Global(name.as_str(env.pool).into()); - - constrain_tag( - arena, - env, - expected, - region, - tag_name, - arguments, - *ext_var, - *variant_var, - ) - } - Expr2::PrivateTag { - name, - arguments, - ext_var, - variant_var, - } => { - let tag_name = TagName::Private(*name); + let tag_name = TagName::Tag(name.as_str(env.pool).into()); constrain_tag( arena, @@ -678,24 +659,28 @@ pub fn constrain_expr<'a>( for (index, when_branch_id) in branches.iter_node_ids().enumerate() { let when_branch = env.pool.get(when_branch_id); - let pattern_region = region; // let pattern_region = Region::across_all( // when_branch.patterns.iter(env.pool).map(|v| &v.region), // ); + let pattern_expected = |sub_pattern, sub_region| { + PExpected::ForReason( + PReason::WhenMatch { + index: HumanIndex::zero_based(index), + sub_pattern, + }, + cond_type.shallow_clone(), + sub_region, + ) + }; + let branch_con = constrain_when_branch( arena, env, // TODO: when_branch.value.region, region, when_branch, - PExpected::ForReason( - PReason::WhenMatch { - index: HumanIndex::zero_based(index), - }, - cond_type.shallow_clone(), - pattern_region, - ), + pattern_expected, Expected::FromAnnotation( name.clone(), *arity, @@ -722,22 +707,26 @@ pub fn constrain_expr<'a>( for (index, when_branch_id) in branches.iter_node_ids().enumerate() { let when_branch = env.pool.get(when_branch_id); - let pattern_region = region; // let pattern_region = // Region::across_all(when_branch.patterns.iter().map(|v| &v.region)); + let pattern_expected = |sub_pattern, sub_region| { + PExpected::ForReason( + PReason::WhenMatch { + index: HumanIndex::zero_based(index), + sub_pattern, + }, + cond_type.shallow_clone(), + sub_region, + ) + }; + let branch_con = constrain_when_branch( arena, env, region, when_branch, - PExpected::ForReason( - PReason::WhenMatch { - index: HumanIndex::zero_based(index), - }, - cond_type.shallow_clone(), - pattern_region, - ), + pattern_expected, Expected::ForReason( Reason::WhenBranch { index: HumanIndex::zero_based(index), @@ -1296,7 +1285,7 @@ fn constrain_when_branch<'a>( env: &mut Env, region: Region, when_branch: &WhenBranch, - pattern_expected: PExpected, + pattern_expected: impl Fn(HumanIndex, Region) -> PExpected, expr_expected: Expected, ) -> Constraint<'a> { let when_expr = env.pool.get(when_branch.body); @@ -1311,16 +1300,22 @@ fn constrain_when_branch<'a>( // TODO investigate for error messages, is it better to unify all branches with a variable, // then unify that variable with the expectation? - for pattern_id in when_branch.patterns.iter_node_ids() { + for (sub_pattern, pattern_id) in when_branch.patterns.iter_node_ids().enumerate() { let pattern = env.pool.get(pattern_id); + let pattern_expected = pattern_expected( + HumanIndex::zero_based(sub_pattern), + // TODO: use the proper subpattern region. Not available to us right now. + region, + ); + constrain_pattern( arena, env, pattern, // loc_pattern.region, region, - pattern_expected.shallow_clone(), + pattern_expected, &mut state, true, ); @@ -1609,34 +1604,13 @@ pub fn constrain_pattern<'a>( state.constraints.push(whole_con); state.constraints.push(record_con); } - GlobalTag { + Tag { whole_var, ext_var, tag_name: name, arguments, } => { - let tag_name = TagName::Global(name.as_str(env.pool).into()); - - constrain_tag_pattern( - arena, - env, - region, - expected, - state, - *whole_var, - *ext_var, - arguments, - tag_name, - destruct_position, - ); - } - PrivateTag { - whole_var, - ext_var, - tag_name: name, - arguments, - } => { - let tag_name = TagName::Private(*name); + let tag_name = TagName::Tag(name.as_str(env.pool).into()); constrain_tag_pattern( arena, @@ -1881,19 +1855,9 @@ fn num_float(pool: &mut Pool, range: TypeId) -> Type2 { fn num_floatingpoint(pool: &mut Pool, range: TypeId) -> Type2 { let range_type = pool.get(range); - let alias_content = Type2::TagUnion( - PoolVec::new( - vec![( - TagName::Private(Symbol::NUM_AT_FLOATINGPOINT), - PoolVec::new(vec![range_type.shallow_clone()].into_iter(), pool), - )] - .into_iter(), - pool, - ), - pool.add(Type2::EmptyTagUnion), - ); + let alias_content = range_type.shallow_clone(); - Type2::Alias( + Type2::Opaque( Symbol::NUM_FLOATINGPOINT, PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool), pool.add(alias_content), @@ -1917,37 +1881,16 @@ fn num_int(pool: &mut Pool, range: TypeId) -> Type2 { #[inline(always)] fn _num_signed64(pool: &mut Pool) -> Type2 { - let alias_content = Type2::TagUnion( - PoolVec::new( - vec![( - TagName::Private(Symbol::NUM_AT_SIGNED64), - PoolVec::empty(pool), - )] - .into_iter(), - pool, - ), - pool.add(Type2::EmptyTagUnion), - ); - Type2::Alias( Symbol::NUM_SIGNED64, PoolVec::empty(pool), - pool.add(alias_content), + pool.add(Type2::EmptyTagUnion), ) } #[inline(always)] fn num_unsigned32(pool: &mut Pool) -> Type2 { - let alias_content = Type2::TagUnion( - PoolVec::new( - std::iter::once(( - TagName::Private(Symbol::NUM_UNSIGNED32), - PoolVec::empty(pool), - )), - pool, - ), - pool.add(Type2::EmptyTagUnion), - ); + let alias_content = Type2::EmptyTagUnion; Type2::Alias( Symbol::NUM_UNSIGNED32, @@ -1960,19 +1903,9 @@ fn num_unsigned32(pool: &mut Pool) -> Type2 { fn _num_integer(pool: &mut Pool, range: TypeId) -> Type2 { let range_type = pool.get(range); - let alias_content = Type2::TagUnion( - PoolVec::new( - vec![( - TagName::Private(Symbol::NUM_AT_INTEGER), - PoolVec::new(vec![range_type.shallow_clone()].into_iter(), pool), - )] - .into_iter(), - pool, - ), - pool.add(Type2::EmptyTagUnion), - ); + let alias_content = range_type.shallow_clone(); - Type2::Alias( + Type2::Opaque( Symbol::NUM_INTEGER, PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool), pool.add(alias_content), @@ -1983,19 +1916,9 @@ fn _num_integer(pool: &mut Pool, range: TypeId) -> Type2 { fn num_num(pool: &mut Pool, type_id: TypeId) -> Type2 { let range_type = pool.get(type_id); - let alias_content = Type2::TagUnion( - PoolVec::new( - vec![( - TagName::Private(Symbol::NUM_AT_NUM), - PoolVec::new(vec![range_type.shallow_clone()].into_iter(), pool), - )] - .into_iter(), - pool, - ), - pool.add(Type2::EmptyTagUnion), - ); + let alias_content = range_type.shallow_clone(); - Type2::Alias( + Type2::Opaque( Symbol::NUM_NUM, PoolVec::new( vec![(PoolStr::new("range", pool), type_id)].into_iter(), @@ -2275,7 +2198,7 @@ pub mod test_constrain { } #[test] - fn constrain_global_tag() { + fn constrain_tag() { infer_eq( indoc!( r#" @@ -2286,18 +2209,6 @@ pub mod test_constrain { ) } - #[test] - fn constrain_private_tag() { - infer_eq( - indoc!( - r#" - @Foo - "# - ), - "[ @Foo ]*", - ) - } - #[test] fn constrain_call_and_accessor() { infer_eq( diff --git a/ast/src/lang/core/expr/expr2.rs b/ast/src/lang/core/expr/expr2.rs index de03d27dde..c6e5068b26 100644 --- a/ast/src/lang/core/expr/expr2.rs +++ b/ast/src/lang/core/expr/expr2.rs @@ -148,18 +148,12 @@ pub enum Expr2 { }, // Sum Types - GlobalTag { + Tag { name: PoolStr, // 4B variant_var: Variable, // 4B ext_var: Variable, // 4B arguments: PoolVec<(Variable, ExprId)>, // 8B }, - PrivateTag { - name: Symbol, // 8B - variant_var: Variable, // 4B - ext_var: Variable, // 4B - arguments: PoolVec<(Variable, ExprId)>, // 8B - }, Blank, // Rendered as empty box in editor // Compiles, but will crash if reached diff --git a/ast/src/lang/core/expr/expr_to_expr2.rs b/ast/src/lang/core/expr/expr_to_expr2.rs index 126f885499..3d7230589d 100644 --- a/ast/src/lang/core/expr/expr_to_expr2.rs +++ b/ast/src/lang/core/expr/expr_to_expr2.rs @@ -173,10 +173,10 @@ pub fn expr_to_expr2<'a>( (expr, output) } - GlobalTag(tag) => { - // a global tag without any arguments + Tag(tag) => { + // a tag without any arguments ( - Expr2::GlobalTag { + Expr2::Tag { name: PoolStr::new(tag, env.pool), variant_var: env.var_store.fresh(), ext_var: env.var_store.fresh(), @@ -185,20 +185,6 @@ pub fn expr_to_expr2<'a>( Output::default(), ) } - PrivateTag(name) => { - // a private tag without any arguments - let ident_id = env.ident_ids.get_or_insert(&(*name).into()); - let name = Symbol::new(env.home, ident_id); - ( - Expr2::PrivateTag { - name, - variant_var: env.var_store.fresh(), - ext_var: env.var_store.fresh(), - arguments: PoolVec::empty(env.pool), - }, - Output::default(), - ) - } RecordUpdate { fields, @@ -557,23 +543,12 @@ pub fn expr_to_expr2<'a>( // We can't call a runtime error; bail out by propagating it! return (fn_expr, output); } - Expr2::GlobalTag { + Expr2::Tag { variant_var, ext_var, name, .. - } => Expr2::GlobalTag { - variant_var, - ext_var, - name, - arguments: args, - }, - Expr2::PrivateTag { - variant_var, - ext_var, - name, - .. - } => Expr2::PrivateTag { + } => Expr2::Tag { variant_var, ext_var, name, diff --git a/ast/src/lang/core/pattern.rs b/ast/src/lang/core/pattern.rs index 13d91a9850..d217fbb937 100644 --- a/ast/src/lang/core/pattern.rs +++ b/ast/src/lang/core/pattern.rs @@ -41,18 +41,12 @@ pub enum Pattern2 { StrLiteral(PoolStr), // 8B CharacterLiteral(char), // 4B Underscore, // 0B - GlobalTag { + Tag { whole_var: Variable, // 4B ext_var: Variable, // 4B tag_name: PoolStr, // 8B arguments: PoolVec<(Variable, PatternId)>, // 8B }, - PrivateTag { - whole_var: Variable, // 4B - ext_var: Variable, // 4B - tag_name: Symbol, // 8B - arguments: PoolVec<(Variable, PatternId)>, // 8B - }, RecordDestructure { whole_var: Variable, // 4B ext_var: Variable, // 4B @@ -271,26 +265,15 @@ pub fn to_pattern2<'a>( ptype => unsupported_pattern(env, ptype, region), }, - GlobalTag(name) => { + Tag(name) => { // Canonicalize the tag's name. - Pattern2::GlobalTag { + Pattern2::Tag { whole_var: env.var_store.fresh(), ext_var: env.var_store.fresh(), tag_name: PoolStr::new(name, env.pool), arguments: PoolVec::empty(env.pool), } } - PrivateTag(name) => { - let ident_id = env.ident_ids.get_or_insert(&(*name).into()); - - // Canonicalize the tag's name. - Pattern2::PrivateTag { - whole_var: env.var_store.fresh(), - ext_var: env.var_store.fresh(), - tag_name: Symbol::new(env.home, ident_id), - arguments: PoolVec::empty(env.pool), - } - } OpaqueRef(..) => todo_opaques!(), @@ -313,22 +296,12 @@ pub fn to_pattern2<'a>( } match tag.value { - GlobalTag(name) => Pattern2::GlobalTag { + Tag(name) => Pattern2::Tag { whole_var: env.var_store.fresh(), ext_var: env.var_store.fresh(), tag_name: PoolStr::new(name, env.pool), arguments: can_patterns, }, - PrivateTag(name) => { - let ident_id = env.ident_ids.get_or_insert(&name.into()); - - Pattern2::PrivateTag { - whole_var: env.var_store.fresh(), - ext_var: env.var_store.fresh(), - tag_name: Symbol::new(env.home, ident_id), - arguments: can_patterns, - } - } _ => unreachable!("Other patterns cannot be applied"), } } @@ -506,7 +479,7 @@ pub fn symbols_from_pattern(pool: &Pool, initial: &Pattern2) -> Vec { symbols.push(*symbol); } - GlobalTag { arguments, .. } | PrivateTag { arguments, .. } => { + Tag { arguments, .. } => { for (_, pat_id) in arguments.iter(pool) { let pat = pool.get(*pat_id); stack.push(pat); @@ -543,7 +516,7 @@ pub fn symbols_from_pattern(pool: &Pool, initial: &Pattern2) -> Vec { pub fn get_identifier_string(pattern: &Pattern2, interns: &Interns) -> ASTResult { match pattern { - Pattern2::Identifier(symbol) => Ok(symbol.ident_str(interns).to_string()), + Pattern2::Identifier(symbol) => Ok(symbol.as_str(interns).to_string()), other => UnexpectedPattern2Variant { required_pattern2: "Identifier".to_string(), encountered_pattern2: format!("{:?}", other), @@ -567,7 +540,7 @@ pub fn symbols_and_variables_from_pattern( symbols.push((*symbol, variable)); } - GlobalTag { arguments, .. } | PrivateTag { arguments, .. } => { + Tag { arguments, .. } => { for (var, pat_id) in arguments.iter(pool) { let pat = pool.get(*pat_id); stack.push((*var, pat)); diff --git a/ast/src/lang/core/types.rs b/ast/src/lang/core/types.rs index 59f727d2f7..3536222bf5 100644 --- a/ast/src/lang/core/types.rs +++ b/ast/src/lang/core/types.rs @@ -24,6 +24,7 @@ pub enum Type2 { Variable(Variable), // 4B Alias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 8B + 4B + pad + Opaque(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 8B + 4B + pad AsAlias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 8B + 4B + pad // 24B @@ -74,6 +75,9 @@ impl ShallowClone for Type2 { Self::Alias(symbol, args, alias_type_id) => { Self::Alias(*symbol, args.shallow_clone(), alias_type_id.clone()) } + Self::Opaque(symbol, args, alias_type_id) => { + Self::Opaque(*symbol, args.shallow_clone(), alias_type_id.clone()) + } Self::Record(fields, ext_id) => Self::Record(fields.shallow_clone(), ext_id.clone()), Self::Function(args, closure_type_id, ret_type_id) => Self::Function( args.shallow_clone(), @@ -101,7 +105,7 @@ impl Type2 { Variable(v) => { result.insert(*v); } - Alias(_, _, actual) | AsAlias(_, _, actual) => { + Alias(_, _, actual) | AsAlias(_, _, actual) | Opaque(_, _, actual) => { stack.push(pool.get(*actual)); } HostExposedAlias { @@ -690,29 +694,14 @@ fn can_tags<'a>( // a duplicate let new_name = 'inner: loop { match tag { - Tag::Global { name, args } => { + Tag::Apply { name, args } => { let arg_types = PoolVec::with_capacity(args.len() as u32, env.pool); for (type_id, loc_arg) in arg_types.iter_node_ids().zip(args.iter()) { as_type_id(env, scope, rigids, type_id, &loc_arg.value, loc_arg.region); } - let tag_name = TagName::Global(name.value.into()); - tag_types.push((tag_name.clone(), arg_types)); - - break 'inner tag_name; - } - Tag::Private { name, args } => { - let ident_id = env.ident_ids.get_or_insert(&name.value.into()); - let symbol = Symbol::new(env.home, ident_id); - - let arg_types = PoolVec::with_capacity(args.len() as u32, env.pool); - - for (type_id, loc_arg) in arg_types.iter_node_ids().zip(args.iter()) { - as_type_id(env, scope, rigids, type_id, &loc_arg.value, loc_arg.region); - } - - let tag_name = TagName::Private(symbol); + let tag_name = TagName::Tag(name.value.into()); tag_types.push((tag_name.clone(), arg_types)); break 'inner tag_name; diff --git a/ast/src/lang/env.rs b/ast/src/lang/env.rs index 7e7f5a1670..6afee73494 100644 --- a/ast/src/lang/env.rs +++ b/ast/src/lang/env.rs @@ -2,7 +2,7 @@ use crate::mem_pool::pool::{NodeId, Pool}; use bumpalo::{collections::Vec as BumpVec, Bump}; use roc_collections::all::{MutMap, MutSet}; use roc_module::ident::{Ident, Lowercase, ModuleName}; -use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; +use roc_module::symbol::{IdentIds, IdentIdsByModule, ModuleId, ModuleIds, Symbol}; use roc_problem::can::{Problem, RuntimeError}; use roc_region::all::{Loc, Region}; use roc_types::subs::VarStore; @@ -19,7 +19,7 @@ pub struct Env<'a> { pub problems: BumpVec<'a, Problem>, - pub dep_idents: MutMap, + pub dep_idents: IdentIdsByModule, pub module_ids: &'a ModuleIds, pub ident_ids: IdentIds, pub exposed_ident_ids: IdentIds, @@ -41,7 +41,7 @@ impl<'a> Env<'a> { arena: &'a Bump, pool: &'a mut Pool, var_store: &'a mut VarStore, - dep_idents: MutMap, + dep_idents: IdentIdsByModule, module_ids: &'a ModuleIds, exposed_ident_ids: IdentIds, ) -> Env<'a> { @@ -129,8 +129,8 @@ impl<'a> Env<'a> { region, }, self.ident_ids - .idents() - .map(|(_, string)| string.as_ref().into()) + .ident_strs() + .map(|(_, string)| string.into()) .collect(), )), } @@ -146,11 +146,11 @@ impl<'a> Env<'a> { } None => { let exposed_values = exposed_ids - .idents() + .ident_strs() .filter(|(_, ident)| { - ident.as_ref().starts_with(|c: char| c.is_lowercase()) + ident.starts_with(|c: char| c.is_lowercase()) }) - .map(|(_, ident)| Lowercase::from(ident.as_ref())) + .map(|(_, ident)| Lowercase::from(ident)) .collect(); Err(RuntimeError::ValueNotExposed { module_name, diff --git a/ast/src/lang/scope.rs b/ast/src/lang/scope.rs index b01c818064..ced87957e4 100644 --- a/ast/src/lang/scope.rs +++ b/ast/src/lang/scope.rs @@ -12,7 +12,8 @@ use crate::mem_pool::shallow_clone::ShallowClone; use roc_collections::all::{MutMap, MutSet}; use roc_module::ident::{Ident, Lowercase}; use roc_module::symbol::{ - get_module_ident_ids, get_module_ident_ids_mut, IdentIds, Interns, ModuleId, Symbol, + get_module_ident_ids, get_module_ident_ids_mut, IdentIds, IdentIdsByModule, Interns, ModuleId, + Symbol, }; use roc_problem::can::RuntimeError; use roc_region::all::{Loc, Region}; @@ -245,7 +246,7 @@ impl Scope { // use that existing IdentId. Otherwise, create a fresh one. let ident_id = match exposed_ident_ids.get_id(&ident) { Some(ident_id) => ident_id, - None => all_ident_ids.add(ident.clone().into()), + None => all_ident_ids.add_ident(&ident), }; let symbol = Symbol::new(self.home, ident_id); @@ -262,7 +263,7 @@ impl Scope { /// /// Used for record guards like { x: Just _ } pub fn ignore(&mut self, ident: Ident, all_ident_ids: &mut IdentIds) -> Symbol { - let ident_id = all_ident_ids.add(ident.into()); + let ident_id = all_ident_ids.add_ident(&ident); Symbol::new(self.home, ident_id) } @@ -320,16 +321,12 @@ impl Scope { self.aliases.contains_key(&name) } - pub fn fill_scope( - &mut self, - env: &Env, - all_ident_ids: &mut MutMap, - ) -> ASTResult<()> { + pub fn fill_scope(&mut self, env: &Env, all_ident_ids: &mut IdentIdsByModule) -> ASTResult<()> { let ident_ids = get_module_ident_ids(all_ident_ids, &env.home)?.clone(); - for (_, ident_ref) in ident_ids.idents() { + for (_, ident_ref) in ident_ids.ident_strs() { self.introduce( - ident_ref.as_inline_str().as_str().into(), + ident_ref.into(), &env.exposed_ident_ids, get_module_ident_ids_mut(all_ident_ids, &env.home)?, Region::zero(), diff --git a/ast/src/solve_type.rs b/ast/src/solve_type.rs index f15b128827..eac6ae712e 100644 --- a/ast/src/solve_type.rs +++ b/ast/src/solve_type.rs @@ -3,6 +3,7 @@ use bumpalo::Bump; use roc_can::expected::{Expected, PExpected}; use roc_collections::all::{BumpMap, BumpMapDefault, MutMap}; +use roc_error_macros::internal_error; use roc_module::ident::TagName; use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; @@ -75,7 +76,7 @@ use crate::mem_pool::shallow_clone::ShallowClone; // Ranks are used to limit the number of type variables considered for generalization. Only those inside // of the let (so those used in inferring the type of `\x -> x`) are considered. -#[derive(PartialEq, Debug, Clone)] +#[derive(Debug, Clone)] pub enum TypeError { BadExpr(Region, Category, ErrorType, Expected), BadPattern(Region, PatternCategory, ErrorType, PExpected), @@ -868,7 +869,7 @@ fn type_to_variable<'a>( register(subs, rank, pools, content) } - Alias(symbol, args, alias_type_id) => { + Alias(symbol, args, alias_type_id) | Opaque(symbol, args, alias_type_id) => { // TODO cache in uniqueness inference gives problems! all Int's get the same uniqueness var! // Cache aliases without type arguments. Commonly used aliases like `Int` would otherwise get O(n) // different variables (once for each occurrence). The recursion restriction is required @@ -910,8 +911,12 @@ fn type_to_variable<'a>( let alias_var = type_to_variable(arena, mempool, subs, rank, pools, cached, alias_type); - // TODO(opaques): take opaques into account - let content = Content::Alias(*symbol, arg_vars, alias_var, AliasKind::Structural); + let kind = match typ { + Alias(..) => AliasKind::Structural, + Opaque(..) => AliasKind::Opaque, + _ => internal_error!(), + }; + let content = Content::Alias(*symbol, arg_vars, alias_var, kind); let result = register(subs, rank, pools, content); diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 4b5d13d1c1..65921d9495 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -66,7 +66,7 @@ const_format = "0.2.22" bumpalo = { version = "3.8.0", features = ["collections"] } mimalloc = { version = "0.1.26", default-features = false } -target-lexicon = "0.12.2" +target-lexicon = "0.12.3" tempfile = "3.2.0" wasmer-wasi = { version = "2.0.0", optional = true } diff --git a/cli/src/build.rs b/cli/src/build.rs index 0ccf118178..be2bd0736e 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -1,7 +1,7 @@ use bumpalo::Bump; use roc_build::{ link::{link, rebuild_host, LinkType}, - program, + program::{self, Problems}, }; use roc_builtins::bitcode; use roc_load::LoadingProblem; @@ -21,25 +21,9 @@ fn report_timing(buf: &mut String, label: &str, duration: Duration) { )); } -pub enum BuildOutcome { - NoProblems, - OnlyWarnings, - Errors, -} - -impl BuildOutcome { - pub fn status_code(&self) -> i32 { - match self { - Self::NoProblems => 0, - Self::OnlyWarnings => 1, - Self::Errors => 2, - } - } -} - pub struct BuiltFile { pub binary_path: PathBuf, - pub outcome: BuildOutcome, + pub problems: Problems, pub total_time: Duration, } @@ -184,7 +168,7 @@ pub fn build_file<'a>( // This only needs to be mutable for report_problems. This can't be done // inside a nested scope without causing a borrow error! let mut loaded = loaded; - program::report_problems_monomorphized(&mut loaded); + let problems = program::report_problems_monomorphized(&mut loaded); let loaded = loaded; let code_gen_timing = program::gen_from_mono_module( @@ -243,7 +227,7 @@ pub fn build_file<'a>( // Step 2: link the precompiled host and compiled app let link_start = SystemTime::now(); - let outcome = if surgically_link { + let problems = if surgically_link { roc_linker::link_preprocessed_host(target, &host_input_path, app_o_file, &binary_path) .map_err(|err| { todo!( @@ -251,12 +235,12 @@ pub fn build_file<'a>( err ); })?; - BuildOutcome::NoProblems + problems } else if matches!(link_type, LinkType::None) { // Just copy the object file to the output folder. binary_path.set_extension(app_extension); std::fs::copy(app_o_file, &binary_path).unwrap(); - BuildOutcome::NoProblems + problems } else { let mut inputs = vec![ host_input_path.as_path().to_str().unwrap(), @@ -281,11 +265,15 @@ pub fn build_file<'a>( todo!("gracefully handle error after `ld` spawned"); })?; - // TODO change this to report whether there were errors or warnings! if exit_status.success() { - BuildOutcome::NoProblems + problems } else { - BuildOutcome::Errors + let mut problems = problems; + + // Add an error for `ld` failing + problems.errors += 1; + + problems } }; let linking_time = link_start.elapsed().unwrap(); @@ -298,7 +286,7 @@ pub fn build_file<'a>( Ok(BuiltFile { binary_path, - outcome, + problems, total_time, }) } @@ -318,7 +306,7 @@ fn spawn_rebuild_thread( let thread_local_target = target.clone(); std::thread::spawn(move || { if !precompiled { - print!("πŸ”¨ Rebuilding host... "); + println!("πŸ”¨ Rebuilding host..."); } let rebuild_host_start = SystemTime::now(); @@ -350,10 +338,6 @@ fn spawn_rebuild_thread( } let rebuild_host_end = rebuild_host_start.elapsed().unwrap(); - if !precompiled { - println!("Done!"); - } - rebuild_host_end.as_millis() }) } @@ -364,7 +348,7 @@ pub fn check_file( src_dir: PathBuf, roc_file_path: PathBuf, emit_timings: bool, -) -> Result { +) -> Result<(program::Problems, Duration), LoadingProblem> { let compilation_start = SystemTime::now(); // only used for generating errors. We don't do code generation, so hardcoding should be fine @@ -437,5 +421,8 @@ pub fn check_file( println!("Finished checking in {} ms\n", compilation_end.as_millis(),); } - Ok(program::report_problems_typechecked(&mut loaded)) + Ok(( + program::report_problems_typechecked(&mut loaded), + compilation_end, + )) } diff --git a/cli/src/format.rs b/cli/src/format.rs index ea77c5f1dc..28da7a3735 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -617,8 +617,7 @@ impl<'a> RemoveSpaces<'a> for Expr<'a> { Expr::Record(a) => Expr::Record(a.remove_spaces(arena)), Expr::Var { module_name, ident } => Expr::Var { module_name, ident }, Expr::Underscore(a) => Expr::Underscore(a), - Expr::GlobalTag(a) => Expr::GlobalTag(a), - Expr::PrivateTag(a) => Expr::PrivateTag(a), + Expr::Tag(a) => Expr::Tag(a), Expr::OpaqueRef(a) => Expr::OpaqueRef(a), Expr::Closure(a, b) => Expr::Closure( arena.alloc(a.remove_spaces(arena)), @@ -669,8 +668,7 @@ impl<'a> RemoveSpaces<'a> for Pattern<'a> { fn remove_spaces(&self, arena: &'a Bump) -> Self { match *self { Pattern::Identifier(a) => Pattern::Identifier(a), - Pattern::GlobalTag(a) => Pattern::GlobalTag(a), - Pattern::PrivateTag(a) => Pattern::PrivateTag(a), + Pattern::Tag(a) => Pattern::Tag(a), Pattern::OpaqueRef(a) => Pattern::OpaqueRef(a), Pattern::Apply(a, b) => Pattern::Apply( arena.alloc(a.remove_spaces(arena)), @@ -753,11 +751,7 @@ impl<'a> RemoveSpaces<'a> for HasClause<'a> { impl<'a> RemoveSpaces<'a> for Tag<'a> { fn remove_spaces(&self, arena: &'a Bump) -> Self { match *self { - Tag::Global { name, args } => Tag::Global { - name: name.remove_spaces(arena), - args: args.remove_spaces(arena), - }, - Tag::Private { name, args } => Tag::Private { + Tag::Apply { name, args } => Tag::Apply { name: name.remove_spaces(arena), args: args.remove_spaces(arena), }, diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 38a52097ed..25f831d1f0 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -1,7 +1,7 @@ #[macro_use] extern crate const_format; -use build::{BuildOutcome, BuiltFile}; +use build::BuiltFile; use bumpalo::Bump; use clap::{App, AppSettings, Arg, ArgMatches}; use roc_build::link::LinkType; @@ -24,6 +24,7 @@ mod format; pub use format::format; pub const CMD_BUILD: &str = "build"; +pub const CMD_RUN: &str = "run"; pub const CMD_REPL: &str = "repl"; pub const CMD_EDIT: &str = "edit"; pub const CMD_DOCS: &str = "docs"; @@ -49,10 +50,12 @@ pub const ROC_DIR: &str = "ROC_DIR"; pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES"; pub const ARGS_FOR_APP: &str = "ARGS_FOR_APP"; +const VERSION: &str = include_str!("../../version.txt"); + pub fn build_app<'a>() -> App<'a> { let app = App::new("roc") - .version(concatcp!(include_str!("../../version.txt"), "\n")) - .about("Runs the given .roc file. Use one of the SUBCOMMANDS below to do something else!") + .version(concatcp!(VERSION, "\n")) + .about("Runs the given .roc file, if there are no compilation errors.\nUse one of the SUBCOMMANDS below to do something else!") .subcommand(App::new(CMD_BUILD) .about("Build a binary from the given .roc file, but don't run it") .arg( @@ -128,7 +131,6 @@ pub fn build_app<'a>() -> App<'a> { .long(FLAG_PRECOMPILED) .about("Assumes the host has been precompiled and skips recompiling the host. (Enabled by default when using a --target other than `--target host`)") .possible_values(["true", "false"]) - .default_value("false") .required(false), ) .arg( @@ -141,8 +143,16 @@ pub fn build_app<'a>() -> App<'a> { .subcommand(App::new(CMD_REPL) .about("Launch the interactive Read Eval Print Loop (REPL)") ) + .subcommand(App::new(CMD_RUN) + .about("Run a .roc file even if it has build errors") + .arg( + Arg::new(ROC_FILE) + .about("The .roc file of an app to run") + .required(true), + ) + ) .subcommand(App::new(CMD_FORMAT) - .about("Format Roc code") + .about("Format a .roc file using standard Roc formatting") .arg( Arg::new(DIRECTORY_OR_FILES) .index(1) @@ -156,10 +166,9 @@ pub fn build_app<'a>() -> App<'a> { ) ) .subcommand(App::new(CMD_VERSION) - .about("Print version information") - ) + .about(concatcp!("Print the Roc compiler’s version, which is currently ", VERSION))) .subcommand(App::new(CMD_CHECK) - .about("When developing, it's recommended to run `check` before `build`. It may provide a useful error message in cases where `build` panics") + .about("Check the code for problems, but doesn’t build or run it") .arg( Arg::new(FLAG_TIME) .long(FLAG_TIME) @@ -168,7 +177,7 @@ pub fn build_app<'a>() -> App<'a> { ) .arg( Arg::new(ROC_FILE) - .about("The .roc file of an app to run") + .about("The .roc file of an app to check") .required(true), ) ) @@ -194,19 +203,19 @@ pub fn build_app<'a>() -> App<'a> { .arg( Arg::new(FLAG_OPT_SIZE) .long(FLAG_OPT_SIZE) - .about("Optimize your compiled Roc program to have a small binary size. (Optimization takes time to complete.)") + .about("Optimize the compiled program to have a small binary size. (Optimization takes time to complete.)") .required(false), ) .arg( Arg::new(FLAG_DEV) .long(FLAG_DEV) - .about("Make compilation as fast as possible. (Runtime performance may suffer)") + .about("Make compilation finish as soon as possible, at the expense of runtime performance.") .required(false), ) .arg( Arg::new(FLAG_DEBUG) .long(FLAG_DEBUG) - .about("Store LLVM debug information in the generated program") + .about("Store LLVM debug information in the generated program.") .requires(ROC_FILE) .required(false), ) @@ -232,17 +241,8 @@ pub fn build_app<'a>() -> App<'a> { .arg( Arg::new(FLAG_PRECOMPILED) .long(FLAG_PRECOMPILED) - .about("Assumes the host has been precompiled and skips recompiling the host. (Enabled by default when using a --target other than `--target host`)") + .about("Assumes the host has been precompiled and skips recompiling the host. (Enabled by default when using `roc build` with a --target other than `--target host`)") .possible_values(["true", "false"]) - .default_value("false") - .required(false), - ) - .arg( - Arg::new(FLAG_TARGET) - .long(FLAG_TARGET) - .about("Choose a different target") - .default_value(Target::default().as_str()) - .possible_values(Target::OPTIONS) .required(false), ) .arg( @@ -282,6 +282,7 @@ pub fn docs(files: Vec) { pub enum BuildConfig { BuildOnly, BuildAndRun { roc_file_arg_index: usize }, + BuildAndRunIfNoErrors { roc_file_arg_index: usize }, } pub enum FormatMode { @@ -389,7 +390,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { match res_binary_path { Ok(BuiltFile { binary_path, - outcome, + problems, total_time, }) => { match config { @@ -404,56 +405,128 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { std::mem::forget(arena); println!( - "πŸŽ‰ Built {} in {} ms", - generated_filename.to_str().unwrap(), - total_time.as_millis() + "\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms while successfully building:\n\n {}", + if problems.errors == 0 { + 32 // green + } else { + 33 // yellow + }, + problems.errors, + if problems.errors == 1 { + "error" + } else { + "errors" + }, + if problems.warnings == 0 { + 32 // green + } else { + 33 // yellow + }, + problems.warnings, + if problems.warnings == 1 { + "warning" + } else { + "warnings" + }, + total_time.as_millis(), + generated_filename.to_str().unwrap() ); // Return a nonzero exit code if there were problems - Ok(outcome.status_code()) + Ok(problems.exit_code()) } BuildAndRun { roc_file_arg_index } => { - let mut cmd = match triple.architecture { - Architecture::Wasm32 => { - // If possible, report the generated executable name relative to the current dir. - let generated_filename = binary_path - .strip_prefix(env::current_dir().unwrap()) - .unwrap_or(&binary_path); - - // No need to waste time freeing this memory, - // since the process is about to exit anyway. - std::mem::forget(arena); - - let args = std::env::args() - .skip(roc_file_arg_index) - .collect::>(); - - run_with_wasmer(generated_filename, &args); - return Ok(0); - } - _ => Command::new(&binary_path), - }; - - if let Architecture::Wasm32 = triple.architecture { - cmd.arg(binary_path); + if problems.errors > 0 || problems.warnings > 0 { + println!( + "\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.\n\nRunning program anyway…\n\n\x1B[36m{}\x1B[39m", + if problems.errors == 0 { + 32 // green + } else { + 33 // yellow + }, + problems.errors, + if problems.errors == 1 { + "error" + } else { + "errors" + }, + if problems.warnings == 0 { + 32 // green + } else { + 33 // yellow + }, + problems.warnings, + if problems.warnings == 1 { + "warning" + } else { + "warnings" + }, + total_time.as_millis(), + "─".repeat(80) + ); } - // Forward all the arguments after the .roc file argument - // to the new process. This way, you can do things like: - // - // roc app.roc foo bar baz - // - // ...and have it so that app.roc will receive only `foo`, - // `bar`, and `baz` as its arguments. - for (index, arg) in std::env::args().enumerate() { - if index > roc_file_arg_index { - cmd.arg(arg); + roc_run( + arena, + &original_cwd, + triple, + roc_file_arg_index, + &binary_path, + ) + } + BuildAndRunIfNoErrors { roc_file_arg_index } => { + if problems.errors == 0 { + if problems.warnings > 0 { + println!( + "\x1B[32m0\x1B[39m errors and \x1B[33m{}\x1B[39m {} found in {} ms.\n\nRunning program…\n\n\x1B[36m{}\x1B[39m", + problems.warnings, + if problems.warnings == 1 { + "warning" + } else { + "warnings" + }, + total_time.as_millis(), + "─".repeat(80) + ); } - } - match outcome { - BuildOutcome::Errors => Ok(outcome.status_code()), - _ => roc_run(cmd.current_dir(original_cwd)), + roc_run( + arena, + &original_cwd, + triple, + roc_file_arg_index, + &binary_path, + ) + } else { + println!( + "\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.\n\nYou can run the program anyway with: \x1B[32mroc run {}\x1B[39m", + if problems.errors == 0 { + 32 // green + } else { + 33 // yellow + }, + problems.errors, + if problems.errors == 1 { + "error" + } else { + "errors" + }, + if problems.warnings == 0 { + 32 // green + } else { + 33 // yellow + }, + problems.warnings, + if problems.warnings == 1 { + "warning" + } else { + "warnings" + }, + total_time.as_millis(), + filename + ); + + Ok(problems.exit_code()) } } } @@ -470,11 +543,55 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { } #[cfg(target_family = "unix")] -fn roc_run(cmd: &mut Command) -> io::Result { +fn roc_run( + arena: Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it! + cwd: &Path, + triple: Triple, + roc_file_arg_index: usize, + binary_path: &Path, +) -> io::Result { use std::os::unix::process::CommandExt; + let mut cmd = match triple.architecture { + Architecture::Wasm32 => { + // If possible, report the generated executable name relative to the current dir. + let generated_filename = binary_path + .strip_prefix(env::current_dir().unwrap()) + .unwrap_or(binary_path); + + // No need to waste time freeing this memory, + // since the process is about to exit anyway. + std::mem::forget(arena); + + let args = std::env::args() + .skip(roc_file_arg_index) + .collect::>(); + + run_with_wasmer(generated_filename, &args); + return Ok(0); + } + _ => Command::new(&binary_path), + }; + + if let Architecture::Wasm32 = triple.architecture { + cmd.arg(binary_path); + } + + // Forward all the arguments after the .roc file argument + // to the new process. This way, you can do things like: + // + // roc app.roc foo bar baz + // + // ...and have it so that app.roc will receive only `foo`, + // `bar`, and `baz` as its arguments. + for (index, arg) in std::env::args().enumerate() { + if index > roc_file_arg_index { + cmd.arg(arg); + } + } + // This is much faster than spawning a subprocess if we're on a UNIX system! - let err = cmd.exec(); + let err = cmd.current_dir(cwd).exec(); // If exec actually returned, it was definitely an error! (Otherwise, // this process would have been replaced by the other one, and we'd diff --git a/cli/src/main.rs b/cli/src/main.rs index 6a5c984ef3..b38445980b 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,7 +1,8 @@ use roc_cli::build::check_file; use roc_cli::{ build_app, docs, format, BuildConfig, FormatMode, CMD_BUILD, CMD_CHECK, CMD_DOCS, CMD_EDIT, - CMD_FORMAT, CMD_REPL, CMD_VERSION, DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_TIME, ROC_FILE, + CMD_FORMAT, CMD_REPL, CMD_RUN, CMD_VERSION, DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_TIME, + ROC_FILE, }; use roc_load::LoadingProblem; use std::fs::{self, FileType}; @@ -27,7 +28,10 @@ fn main() -> io::Result<()> { Some(arg_index) => { let roc_file_arg_index = arg_index + 1; // Not sure why this +1 is necessary, but it is! - build(&matches, BuildConfig::BuildAndRun { roc_file_arg_index }) + build( + &matches, + BuildConfig::BuildAndRunIfNoErrors { roc_file_arg_index }, + ) } None => { @@ -37,6 +41,21 @@ fn main() -> io::Result<()> { } } } + Some((CMD_RUN, matches)) => { + match matches.index_of(ROC_FILE) { + Some(arg_index) => { + let roc_file_arg_index = arg_index + 1; // Not sure why this +1 is necessary, but it is! + + build(matches, BuildConfig::BuildAndRun { roc_file_arg_index }) + } + + None => { + eprintln!("What .roc file do you want to run? Specify it at the end of the `roc run` command."); + + Ok(1) + } + } + } Some((CMD_BUILD, matches)) => Ok(build(matches, BuildConfig::BuildOnly)?), Some((CMD_CHECK, matches)) => { let arena = bumpalo::Bump::new(); @@ -47,9 +66,35 @@ fn main() -> io::Result<()> { let src_dir = roc_file_path.parent().unwrap().to_owned(); match check_file(&arena, src_dir, roc_file_path, emit_timings) { - Ok(number_of_errors) => { - let exit_code = if number_of_errors != 0 { 1 } else { 0 }; - Ok(exit_code) + Ok((problems, total_time)) => { + println!( + "\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.", + if problems.errors == 0 { + 32 // green + } else { + 33 // yellow + }, + problems.errors, + if problems.errors == 1 { + "error" + } else { + "errors" + }, + if problems.warnings == 0 { + 32 // green + } else { + 33 // yellow + }, + problems.warnings, + if problems.warnings == 1 { + "warning" + } else { + "warnings" + }, + total_time.as_millis(), + ); + + Ok(problems.exit_code()) } Err(LoadingProblem::FormattedReport(report)) => { diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 2cc15a2d96..c2ca6d9be9 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -63,13 +63,19 @@ mod cli_run { .replace(ANSI_STYLE_CODES.bold, "") .replace(ANSI_STYLE_CODES.underline, "") .replace(ANSI_STYLE_CODES.reset, "") + .replace(ANSI_STYLE_CODES.color_reset, "") } fn check_compile_error(file: &Path, flags: &[&str], expected: &str) { let compile_out = run_roc(&[&["check", file.to_str().unwrap()], flags].concat()); let err = compile_out.stdout.trim(); let err = strip_colors(err); - assert_multiline_str_eq!(err, expected.into()); + + // e.g. "1 error and 0 warnings found in 123 ms." + let (before_first_digit, _) = err.split_at(err.rfind("found in ").unwrap()); + let err = format!("{}found in ms.", before_first_digit); + + assert_multiline_str_eq!(err.as_str(), expected.into()); } fn check_format_check_as_expected(file: &Path, expects_success_exit_code: bool) { @@ -180,8 +186,8 @@ mod cli_run { }; if !&out.stdout.ends_with(expected_ending) { panic!( - "expected output to end with {:?} but instead got {:#?}", - expected_ending, out.stdout + "expected output to end with {:?} but instead got {:#?} - stderr was: {:#?}", + expected_ending, out.stdout, out.stderr ); } assert!(out.status.success()); @@ -255,8 +261,8 @@ mod cli_run { return; } } - "hello-gui" => { - // Since this one requires opening a window, we do `roc build` on it but don't run it. + "hello-gui" | "breakout" => { + // Since these require opening a window, we do `roc build` on them but don't run them. build_example(&file_name, &["--optimize"]); return; @@ -393,6 +399,14 @@ mod cli_run { expected_ending: "", use_valgrind: false, }, + breakout:"breakout" => Example { + filename: "breakout.roc", + executable_filename: "breakout", + stdin: &[], + input_file: None, + expected_ending: "", + use_valgrind: false, + }, quicksort:"algorithms" => Example { filename: "quicksort.roc", executable_filename: "quicksort", @@ -849,7 +863,7 @@ mod cli_run { &[], indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + ── UNRECOGNIZED NAME ─────────────────────────── tests/known_bad/TypeError.roc ─ I cannot find a `d` value @@ -863,7 +877,9 @@ mod cli_run { I8 F64 - ────────────────────────────────────────────────────────────────────────────────"# + ──────────────────────────────────────────────────────────────────────────────── + + 1 error and 0 warnings found in ms."# ), ); } @@ -875,14 +891,16 @@ mod cli_run { &[], indoc!( r#" - ── MISSING DEFINITION ────────────────────────────────────────────────────────── + ── MISSING DEFINITION ────────────────── tests/known_bad/ExposedNotDefined.roc ─ bar is listed as exposed, but it isn't defined in this module. You can fix this by adding a definition for bar, or by removing it from exposes. - ────────────────────────────────────────────────────────────────────────────────"# + ──────────────────────────────────────────────────────────────────────────────── + + 1 error and 0 warnings found in ms."# ), ); } @@ -894,7 +912,7 @@ mod cli_run { &[], indoc!( r#" - ── UNUSED IMPORT ─────────────────────────────────────────────────────────────── + ── UNUSED IMPORT ──────────────────────────── tests/known_bad/UnusedImport.roc ─ Nothing from Symbol is used in this module. @@ -903,7 +921,9 @@ mod cli_run { Since Symbol isn't used, you don't need to import it. - ────────────────────────────────────────────────────────────────────────────────"# + ──────────────────────────────────────────────────────────────────────────────── + + 0 errors and 1 warning found in ms."# ), ); } @@ -915,7 +935,7 @@ mod cli_run { &[], indoc!( r#" - ── UNKNOWN GENERATES FUNCTION ────────────────────────────────────────────────── + ── UNKNOWN GENERATES FUNCTION ─────── tests/known_bad/UnknownGeneratesWith.roc ─ I don't know how to generate the foobar function. @@ -925,7 +945,9 @@ mod cli_run { Only specific functions like `after` and `map` can be generated.Learn more about hosted modules at TODO. - ────────────────────────────────────────────────────────────────────────────────"# + ──────────────────────────────────────────────────────────────────────────────── + + 1 error and 0 warnings found in ms."# ), ); } diff --git a/code_markup/src/colors.rs b/code_markup/src/colors.rs index b2c4c8a298..bf64fd7b95 100644 --- a/code_markup/src/colors.rs +++ b/code_markup/src/colors.rs @@ -1,4 +1,4 @@ -use palette::{FromColor, Hsv, Srgb}; +use palette::{FromColor, Hsv, LinSrgb, Srgb}; pub type RgbaTup = (f32, f32, f32, f32); pub const WHITE: RgbaTup = (1.0, 1.0, 1.0, 1.0); @@ -12,11 +12,11 @@ pub fn from_hsb(hue: usize, saturation: usize, brightness: usize) -> RgbaTup { } pub fn from_hsba(hue: usize, saturation: usize, brightness: usize, alpha: f32) -> RgbaTup { - let rgb = Srgb::from_color(Hsv::new( + let rgb = LinSrgb::from(Srgb::from_color(Hsv::new( hue as f32, (saturation as f32) / 100.0, (brightness as f32) / 100.0, - )); + ))); (rgb.red, rgb.green, rgb.blue, alpha) } diff --git a/code_markup/src/markup/convert/from_expr2.rs b/code_markup/src/markup/convert/from_expr2.rs index 5987541868..c859e5b8af 100644 --- a/code_markup/src/markup/convert/from_expr2.rs +++ b/code_markup/src/markup/convert/from_expr2.rs @@ -88,7 +88,7 @@ pub fn expr2_to_markup<'a>( mark_id_ast_id_map, ) } - Expr2::GlobalTag { name, .. } => new_markup_node( + Expr2::Tag { name, .. } => new_markup_node( with_indent(indent_level, &get_string(env, name)), ast_node_id, HighlightStyle::Type, diff --git a/compiler/README.md b/compiler/README.md index e57984562d..d26c7e862e 100644 --- a/compiler/README.md +++ b/compiler/README.md @@ -167,6 +167,20 @@ For a more detailed understanding of the compilation phases, see the `Phase`, `B ## Debugging intermediate representations +### Debugging the typechecker + +Setting the following environment variables: + +- `ROC_PRINT_UNIFICATIONS` prints all type unifications that are done, + before and after the unification. +- `ROC_PRINT_MISMATCHES` prints all type mismatches hit during unification. +- `ROC_PRETTY_PRINT_ALIAS_CONTENTS` expands the contents of aliases during + pretty-printing of types. + +Note that this is only relevant during debug builds. Eventually we should have +some better debugging tools here, see https://github.com/rtfeldman/roc/issues/2486 +for one. + ### The mono IR If you observe a miscomplication, you may first want to check the generated mono diff --git a/compiler/alias_analysis/src/lib.rs b/compiler/alias_analysis/src/lib.rs index d492f3a12a..572a438847 100644 --- a/compiler/alias_analysis/src/lib.rs +++ b/compiler/alias_analysis/src/lib.rs @@ -279,7 +279,8 @@ fn build_entry_point( let block = builder.add_block(); // to the modelling language, the arguments appear out of thin air - let argument_type = build_tuple_type(&mut builder, layout.arguments)?; + let argument_type = + build_tuple_type(&mut builder, layout.arguments, &WhenRecursive::Unreachable)?; // does not make any assumptions about the input // let argument = builder.add_unknown_with(block, &[], argument_type)?; @@ -308,7 +309,11 @@ fn build_entry_point( let block = builder.add_block(); - let type_id = layout_spec(&mut builder, &Layout::struct_no_name_order(layouts))?; + let type_id = layout_spec( + &mut builder, + &Layout::struct_no_name_order(layouts), + &WhenRecursive::Unreachable, + )?; let argument = builder.add_unknown_with(block, &[], type_id)?; @@ -352,8 +357,9 @@ fn proc_spec<'a>(proc: &Proc<'a>) -> Result<(FuncDef, MutSet>)> let arg_type_id = layout_spec( &mut builder, &Layout::struct_no_name_order(&argument_layouts), + &WhenRecursive::Unreachable, )?; - let ret_type_id = layout_spec(&mut builder, &proc.ret_layout)?; + let ret_type_id = layout_spec(&mut builder, &proc.ret_layout, &WhenRecursive::Unreachable)?; let spec = builder.build(arg_type_id, ret_type_id, root)?; @@ -457,10 +463,14 @@ fn stmt_spec<'a>( let mut type_ids = Vec::new(); for p in parameters.iter() { - type_ids.push(layout_spec(builder, &p.layout)?); + type_ids.push(layout_spec( + builder, + &p.layout, + &WhenRecursive::Unreachable, + )?); } - let ret_type_id = layout_spec(builder, layout)?; + let ret_type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?; let jp_arg_type_id = builder.add_tuple_type(&type_ids)?; @@ -500,14 +510,14 @@ fn stmt_spec<'a>( builder.add_sub_block(block, BlockExpr(cont_block, cont_value_id)) } Jump(id, symbols) => { - let ret_type_id = layout_spec(builder, layout)?; + let ret_type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?; let argument = build_tuple_value(builder, env, block, symbols)?; let jpid = env.join_points[id]; builder.add_jump(block, jpid, argument, ret_type_id) } RuntimeError(_) => { - let type_id = layout_spec(builder, layout)?; + let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?; builder.add_terminate(block, type_id) } @@ -556,11 +566,15 @@ fn build_recursive_tuple_type( builder.add_tuple_type(&field_types) } -fn build_tuple_type(builder: &mut impl TypeContext, layouts: &[Layout]) -> Result { +fn build_tuple_type( + builder: &mut impl TypeContext, + layouts: &[Layout], + when_recursive: &WhenRecursive, +) -> Result { let mut field_types = Vec::new(); for field in layouts.iter() { - field_types.push(layout_spec(builder, field)?); + field_types.push(layout_spec(builder, field, when_recursive)?); } builder.add_tuple_type(&field_types) @@ -691,7 +705,7 @@ fn call_spec( .map(|symbol| env.symbols[symbol]) .collect(); - let result_type = layout_spec(builder, ret_layout)?; + let result_type = layout_spec(builder, ret_layout, &WhenRecursive::Unreachable)?; builder.add_unknown_with(block, &arguments, result_type) } @@ -761,7 +775,8 @@ fn call_spec( }; let state_layout = argument_layouts[0]; - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = state; add_loop(builder, block, state_type, init_state, loop_body) @@ -782,7 +797,8 @@ fn call_spec( }; let state_layout = argument_layouts[0]; - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = state; add_loop(builder, block, state_type, init_state, loop_body) @@ -806,7 +822,8 @@ fn call_spec( }; let state_layout = argument_layouts[0]; - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = state; add_loop(builder, block, state_type, init_state, loop_body) @@ -828,10 +845,12 @@ fn call_spec( list_append(builder, block, update_mode_var, state, new_element) }; - let output_element_type = layout_spec(builder, return_layout)?; + let output_element_type = + layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?; let state_layout = Layout::Builtin(Builtin::List(return_layout)); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = new_list(builder, block, output_element_type)?; @@ -851,10 +870,12 @@ fn call_spec( list_append(builder, block, update_mode_var, state, new_element) }; - let output_element_type = layout_spec(builder, return_layout)?; + let output_element_type = + layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?; let state_layout = Layout::Builtin(Builtin::List(return_layout)); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = new_list(builder, block, output_element_type)?; @@ -879,7 +900,8 @@ fn call_spec( }; let state_layout = Layout::Builtin(Builtin::List(&argument_layouts[0])); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = list; add_loop(builder, block, state_type, init_state, loop_body) @@ -903,10 +925,12 @@ fn call_spec( list_append(builder, block, update_mode_var, state, new_element) }; - let output_element_type = layout_spec(builder, return_layout)?; + let output_element_type = + layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?; let state_layout = Layout::Builtin(Builtin::List(return_layout)); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = new_list(builder, block, output_element_type)?; @@ -936,10 +960,12 @@ fn call_spec( list_append(builder, block, update_mode_var, state, new_element) }; - let output_element_type = layout_spec(builder, return_layout)?; + let output_element_type = + layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?; let state_layout = Layout::Builtin(Builtin::List(return_layout)); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = new_list(builder, block, output_element_type)?; @@ -975,10 +1001,12 @@ fn call_spec( list_append(builder, block, update_mode_var, state, new_element) }; - let output_element_type = layout_spec(builder, return_layout)?; + let output_element_type = + layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?; let state_layout = Layout::Builtin(Builtin::List(return_layout)); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = new_list(builder, block, output_element_type)?; @@ -1010,7 +1038,8 @@ fn call_spec( }; let state_layout = Layout::Builtin(Builtin::List(&argument_layouts[0])); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = list; add_loop(builder, block, state_type, init_state, loop_body) @@ -1087,11 +1116,13 @@ fn call_spec( ) }; - let output_element_type = layout_spec(builder, &output_element_layout)?; + let output_element_type = + layout_spec(builder, &output_element_layout, &WhenRecursive::Unreachable)?; let init_state = new_list(builder, block, output_element_type)?; let state_layout = Layout::Builtin(Builtin::List(&output_element_layout)); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; add_loop(builder, block, state_type, init_state, loop_body) } @@ -1108,7 +1139,8 @@ fn call_spec( }; let state_layout = Layout::Builtin(Builtin::Bool); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = new_num(builder, block)?; @@ -1127,7 +1159,8 @@ fn call_spec( }; let state_layout = Layout::Builtin(Builtin::Bool); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = new_num(builder, block)?; @@ -1139,7 +1172,8 @@ fn call_spec( // ListFindUnsafe returns { value: v, found: Bool=Int1 } let output_layouts = vec![argument_layouts[0], Layout::Builtin(Builtin::Bool)]; let output_layout = Layout::struct_no_name_order(&output_layouts); - let output_type = layout_spec(builder, &output_layout)?; + let output_type = + layout_spec(builder, &output_layout, &WhenRecursive::Unreachable)?; let loop_body = |builder: &mut FuncDefBuilder, block, output| { let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; @@ -1201,7 +1235,7 @@ fn lowlevel_spec( ) -> Result { use LowLevel::*; - let type_id = layout_spec(builder, layout)?; + let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?; let mode = update_mode.to_bytes(); let update_mode_var = UpdateModeVar(&mode); @@ -1323,8 +1357,8 @@ fn lowlevel_spec( } DictEmpty => match layout { Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => { - let key_id = layout_spec(builder, key_layout)?; - let value_id = layout_spec(builder, value_layout)?; + let key_id = layout_spec(builder, key_layout, &WhenRecursive::Unreachable)?; + let value_id = layout_spec(builder, value_layout, &WhenRecursive::Unreachable)?; new_dict(builder, block, key_id, value_id) } _ => unreachable!("empty array does not have a list layout"), @@ -1367,7 +1401,7 @@ fn lowlevel_spec( // TODO overly pessimstic let arguments: Vec<_> = arguments.iter().map(|symbol| env.symbols[symbol]).collect(); - let result_type = layout_spec(builder, layout)?; + let result_type = layout_spec(builder, layout, &WhenRecursive::Unreachable)?; builder.add_unknown_with(block, &arguments, result_type) } @@ -1478,7 +1512,8 @@ fn expr_spec<'a>( let value_id = match tag_layout { UnionLayout::NonRecursive(tags) => { - let variant_types = non_recursive_variant_types(builder, tags)?; + let variant_types = + non_recursive_variant_types(builder, tags, &WhenRecursive::Unreachable)?; let value_id = build_tuple_value(builder, env, block, arguments)?; return builder.add_make_union(block, &variant_types, *tag_id as u32, value_id); } @@ -1592,7 +1627,7 @@ fn expr_spec<'a>( builder.add_get_tuple_field(block, value_id, *index as u32) } Array { elem_layout, elems } => { - let type_id = layout_spec(builder, elem_layout)?; + let type_id = layout_spec(builder, elem_layout, &WhenRecursive::Unreachable)?; let list = new_list(builder, block, type_id)?; @@ -1619,19 +1654,19 @@ fn expr_spec<'a>( EmptyArray => match layout { Layout::Builtin(Builtin::List(element_layout)) => { - let type_id = layout_spec(builder, element_layout)?; + let type_id = layout_spec(builder, element_layout, &WhenRecursive::Unreachable)?; new_list(builder, block, type_id) } _ => unreachable!("empty array does not have a list layout"), }, Reset { symbol, .. } => { - let type_id = layout_spec(builder, layout)?; + let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?; let value_id = env.symbols[symbol]; builder.add_unknown_with(block, &[value_id], type_id) } RuntimeErrorFunction(_) => { - let type_id = layout_spec(builder, layout)?; + let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?; builder.add_terminate(block, type_id) } @@ -1658,18 +1693,24 @@ fn literal_spec( } } -fn layout_spec(builder: &mut impl TypeContext, layout: &Layout) -> Result { - layout_spec_help(builder, layout, &WhenRecursive::Unreachable) +fn layout_spec( + builder: &mut impl TypeContext, + layout: &Layout, + when_recursive: &WhenRecursive, +) -> Result { + layout_spec_help(builder, layout, when_recursive) } fn non_recursive_variant_types( builder: &mut impl TypeContext, tags: &[&[Layout]], + // If there is a recursive pointer latent within this layout, coming from a containing layout. + when_recursive: &WhenRecursive, ) -> Result> { let mut result = Vec::with_capacity(tags.len()); for tag in tags.iter() { - result.push(build_tuple_type(builder, tag)?); + result.push(build_tuple_type(builder, tag, when_recursive)?); } Ok(result) @@ -1701,7 +1742,7 @@ fn layout_spec_help( builder.add_tuple_type(&[]) } UnionLayout::NonRecursive(tags) => { - let variant_types = non_recursive_variant_types(builder, tags)?; + let variant_types = non_recursive_variant_types(builder, tags, when_recursive)?; builder.add_union_type(&variant_types) } UnionLayout::Recursive(_) diff --git a/compiler/build/Cargo.toml b/compiler/build/Cargo.toml index 323a820d46..218f783af5 100644 --- a/compiler/build/Cargo.toml +++ b/compiler/build/Cargo.toml @@ -30,7 +30,7 @@ bumpalo = { version = "3.8.0", features = ["collections"] } libloading = "0.7.1" tempfile = "3.2.0" inkwell = { path = "../../vendor/inkwell", optional = true } -target-lexicon = "0.12.2" +target-lexicon = "0.12.3" [target.'cfg(target_os = "macos")'.dependencies] serde_json = "1.0.69" diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index a7e32d0328..3acb21720d 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -1072,7 +1072,7 @@ pub fn module_to_dylib( opt_level: OptLevel, ) -> Result { use crate::target::{self, convert_opt_level}; - use inkwell::targets::{CodeModel, FileType, RelocMode}; + use inkwell::targets::{FileType, RelocMode}; let dir = tempfile::tempdir().unwrap(); let filename = PathBuf::from("Test.roc"); @@ -1083,9 +1083,8 @@ pub fn module_to_dylib( // Emit the .o file using position-independent code (PIC) - needed for dylibs let reloc = RelocMode::PIC; - let model = CodeModel::Default; let target_machine = - target::target_machine(target, convert_opt_level(opt_level), reloc, model).unwrap(); + target::target_machine(target, convert_opt_level(opt_level), reloc).unwrap(); target_machine .write_to_file(module, FileType::Object, &app_o_file) @@ -1105,6 +1104,21 @@ pub fn module_to_dylib( // Load the dylib let path = dylib_path.as_path().to_str().unwrap(); + if matches!(target.architecture, Architecture::Aarch64(_)) { + // On AArch64 darwin machines, calling `ldopen` on Roc-generated libs from multiple threads + // sometimes fails with + // cannot dlopen until fork() handlers have completed + // This may be due to codesigning. In any case, spinning until we are able to dlopen seems + // to be okay. + loop { + match unsafe { Library::new(path) } { + Ok(lib) => return Ok(lib), + Err(Error::DlOpen { .. }) => continue, + Err(other) => return Err(other), + } + } + } + unsafe { Library::new(path) } } diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index 518bc0c3e4..a9d595bdf0 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -24,43 +24,52 @@ pub struct CodeGenTiming { #[cfg(feature = "llvm")] const LLVM_VERSION: &str = "12"; -// TODO instead of finding exhaustiveness problems in monomorphization, find -// them after type checking (like Elm does) so we can complete the entire -// `roc check` process without needing to monomorphize. -/// Returns the number of problems reported. -pub fn report_problems_monomorphized(loaded: &mut MonomorphizedModule) -> usize { +pub fn report_problems_monomorphized(loaded: &mut MonomorphizedModule) -> Problems { report_problems_help( loaded.total_problems(), &loaded.sources, &loaded.interns, &mut loaded.can_problems, &mut loaded.type_problems, - &mut loaded.mono_problems, ) } -pub fn report_problems_typechecked(loaded: &mut LoadedModule) -> usize { +pub fn report_problems_typechecked(loaded: &mut LoadedModule) -> Problems { report_problems_help( loaded.total_problems(), &loaded.sources, &loaded.interns, &mut loaded.can_problems, &mut loaded.type_problems, - &mut Default::default(), ) } +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +pub struct Problems { + pub errors: usize, + pub warnings: usize, +} + +impl Problems { + pub fn exit_code(&self) -> i32 { + // 0 means no problems, 1 means errors, 2 means warnings + if self.errors > 0 { + 1 + } else { + self.warnings.min(1) as i32 + } + } +} + fn report_problems_help( total_problems: usize, sources: &MutMap)>, interns: &Interns, can_problems: &mut MutMap>, type_problems: &mut MutMap>, - mono_problems: &mut MutMap>, -) -> usize { +) -> Problems { use roc_reporting::report::{ - can_problem, mono_problem, type_problem, Report, RocDocAllocator, Severity::*, - DEFAULT_PALETTE, + can_problem, type_problem, Report, RocDocAllocator, Severity::*, DEFAULT_PALETTE, }; let palette = DEFAULT_PALETTE; @@ -117,25 +126,6 @@ fn report_problems_help( } } } - - let problems = mono_problems.remove(home).unwrap_or_default(); - - for problem in problems { - let report = mono_problem(&alloc, &lines, module_path.clone(), problem); - let severity = report.severity; - let mut buf = String::new(); - - report.render_color_terminal(&mut buf, &alloc, &palette); - - match severity { - Warning => { - warnings.push(buf); - } - RuntimeError => { - errors.push(buf); - } - } - } } let problems_reported; @@ -144,13 +134,13 @@ fn report_problems_help( if errors.is_empty() { problems_reported = warnings.len(); - for warning in warnings { + for warning in warnings.iter() { println!("\n{}\n", warning); } } else { problems_reported = errors.len(); - for error in errors { + for error in errors.iter() { println!("\n{}\n", error); } } @@ -165,7 +155,10 @@ fn report_problems_help( println!("{}\u{001B}[0m\n", Report::horizontal_rule(&palette)); } - problems_reported + Problems { + errors: errors.len(), + warnings: warnings.len(), + } } #[cfg(not(feature = "llvm"))] @@ -229,7 +222,7 @@ pub fn gen_from_mono_module_llvm( use inkwell::attributes::{Attribute, AttributeLoc}; use inkwell::context::Context; use inkwell::module::Linkage; - use inkwell::targets::{CodeModel, FileType, RelocMode}; + use inkwell::targets::{FileType, RelocMode}; let code_gen_start = SystemTime::now(); @@ -417,10 +410,8 @@ pub fn gen_from_mono_module_llvm( match target.architecture { Architecture::X86_64 | Architecture::X86_32(_) | Architecture::Aarch64(_) => { let reloc = RelocMode::PIC; - let model = CodeModel::Default; let target_machine = - target::target_machine(target, convert_opt_level(opt_level), reloc, model) - .unwrap(); + target::target_machine(target, convert_opt_level(opt_level), reloc).unwrap(); target_machine .write_to_file(env.module, FileType::Object, app_o_file) diff --git a/compiler/build/src/target.rs b/compiler/build/src/target.rs index 6beee37300..096ccbc867 100644 --- a/compiler/build/src/target.rs +++ b/compiler/build/src/target.rs @@ -147,19 +147,28 @@ pub fn target_machine( target: &Triple, opt: OptimizationLevel, reloc: RelocMode, - model: CodeModel, ) -> Option { let arch = arch_str(target); init_arch(target); + let code_model = match target.architecture { + // LLVM 12 will not compile our programs without a large code model. + // The reason is not totally clear to me, but my guess is a few special-cases in + // llvm/lib/Target/AArch64/AArch64ISelLowering.cpp (instructions) + // llvm/lib/Target/AArch64/AArch64Subtarget.cpp (GoT tables) + // Revisit when upgrading to LLVM 13. + Architecture::Aarch64(..) => CodeModel::Large, + _ => CodeModel::Default, + }; + Target::from_name(arch).unwrap().create_target_machine( &TargetTriple::create(target_triple_str(target)), "generic", "", // TODO: this probably should be TargetMachine::get_host_cpu_features() to enable all features. opt, reloc, - model, + code_model, ) } diff --git a/compiler/builtins/README.md b/compiler/builtins/README.md index 2ab95cd260..9954fc4c87 100644 --- a/compiler/builtins/README.md +++ b/compiler/builtins/README.md @@ -4,7 +4,7 @@ Builtins are the functions and modules that are implicitly imported into every m ### module/src/symbol.rs -Towards the bottom of `symbol.rs` there is a `define_builtins!` macro being used that takes many modules and function names. The first level (`List`, `Int` ..) is the module name, and the second level is the function or value name (`reverse`, `mod` ..). If you wanted to add a `Int` function called `addTwo` go to `2 Int: "Int" => {` and inside that case add to the bottom `38 INT_ADD_TWO: "addTwo"` (assuming there are 37 existing ones). +Towards the bottom of `symbol.rs` there is a `define_builtins!` macro being used that takes many modules and function names. The first level (`List`, `Int` ..) is the module name, and the second level is the function or value name (`reverse`, `rem` ..). If you wanted to add a `Int` function called `addTwo` go to `2 Int: "Int" => {` and inside that case add to the bottom `38 INT_ADD_TWO: "addTwo"` (assuming there are 37 existing ones). Some of these have `#` inside their name (`first#list`, `#lt` ..). This is a trick we are doing to hide implementation details from Roc programmers. To a Roc programmer, a name with `#` in it is invalid, because `#` means everything after it is parsed to a comment. We are constructing these functions manually, so we are circumventing the parsing step and dont have such restrictions. We get to make functions and values with `#` which as a consequence are not accessible to Roc programmers. Roc programmers simply cannot reference them. diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index 0c0dfa3b22..85f35fbca2 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const math = std.math; const utils = @import("utils.zig"); const expect = @import("expect.zig"); @@ -161,6 +162,32 @@ comptime { exportExpectFn(expect.deinitFailuresC, "deinit_failures"); @export(utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak }); + + if (builtin.target.cpu.arch == .aarch64) { + @export(__roc_force_setjmp, .{ .name = "__roc_force_setjmp", .linkage = .Weak }); + @export(__roc_force_longjmp, .{ .name = "__roc_force_longjmp", .linkage = .Weak }); + } +} + +// Utils continued - SJLJ +// For tests (in particular test_gen), roc_panic is implemented in terms of +// setjmp/longjmp. LLVM is unable to generate code for longjmp on AArch64 (https://github.com/rtfeldman/roc/issues/2965), +// so instead we ask Zig to please provide implementations for us, which is does +// (seemingly via musl). +pub usingnamespace @import("std").c.builtins; +pub extern fn setjmp([*c]c_int) c_int; +pub extern fn longjmp([*c]c_int, c_int) noreturn; +pub extern fn _setjmp([*c]c_int) c_int; +pub extern fn _longjmp([*c]c_int, c_int) noreturn; +pub extern fn sigsetjmp([*c]c_int, c_int) c_int; +pub extern fn siglongjmp([*c]c_int, c_int) noreturn; +pub extern fn longjmperror() void; +// Zig won't expose the externs (and hence link correctly) unless we force them to be used. +fn __roc_force_setjmp(it: [*c]c_int) callconv(.C) c_int { + return setjmp(it); +} +fn __roc_force_longjmp(a0: [*c]c_int, a1: c_int) callconv(.C) noreturn { + longjmp(a0, a1); } // Export helpers - Must be run inside a comptime @@ -193,7 +220,6 @@ fn exportExpectFn(comptime func: anytype, comptime func_name: []const u8) void { // Custom panic function, as builtin Zig version errors during LLVM verification pub fn panic(message: []const u8, stacktrace: ?*std.builtin.StackTrace) noreturn { - const builtin = @import("builtin"); if (builtin.is_test) { std.debug.print("{s}: {?}", .{ message, stacktrace }); } else { diff --git a/compiler/builtins/docs/Bool.roc b/compiler/builtins/docs/Bool.roc index 29d8dbbc90..5ac3f4e394 100644 --- a/compiler/builtins/docs/Bool.roc +++ b/compiler/builtins/docs/Bool.roc @@ -69,11 +69,10 @@ xor : Bool, Bool -> Bool ## ## Structural equality works as follows: ## -## 1. Global tags are equal if they are the same tag, and also their contents (if any) are equal. -## 2. Private tags are equal if they are the same tag, in the same module, and also their contents (if any) are equal. -## 3. Records are equal if all their fields are equal. -## 4. Collections ([Str], [List], [Dict], and [Set]) are equal if they are the same length, and also all their corresponding elements are equal. -## 5. [Num] values are equal if their numbers are equal, with one exception: if both arguments to `isEq` are *NaN*, then `isEq` returns `False`. See `Num.isNaN` for more about *NaN*. +## 1. Tags are equal if they have the same tag name, and also their contents (if any) are equal. +## 2. Records are equal if all their fields are equal. +## 3. Collections ([Str], [List], [Dict], and [Set]) are equal if they are the same length, and also all their corresponding elements are equal. +## 4. [Num] values are equal if their numbers are equal, with one exception: if both arguments to `isEq` are *NaN*, then `isEq` returns `False`. See `Num.isNaN` for more about *NaN*. ## ## Note that `isEq` takes `'val` instead of `val`, which means `isEq` does not ## accept arguments whose types contain functions. diff --git a/compiler/builtins/docs/Dict.roc b/compiler/builtins/docs/Dict.roc index 0de2d5ad5d..8e0bba0a41 100644 --- a/compiler/builtins/docs/Dict.roc +++ b/compiler/builtins/docs/Dict.roc @@ -93,7 +93,7 @@ interface Dict ## ## 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 +Dict k v := [ Dict k v ] # TODO k should require a hashing and equating constraint ## An empty dictionary. empty : Dict * * diff --git a/compiler/builtins/docs/List.roc b/compiler/builtins/docs/List.roc index 25ddbc6344..3a57d27235 100644 --- a/compiler/builtins/docs/List.roc +++ b/compiler/builtins/docs/List.roc @@ -187,7 +187,7 @@ interface List ## * Even when copying is faster, other list operations may still be slightly slower with persistent data structures. For example, even if it were a persistent data structure, [List.map], [List.walk], and [List.keepIf] would all need to traverse every element in the list and build up the result from scratch. These operations are all ## * Roc's compiler optimizes many list operations into in-place mutations behind the scenes, depending on how the list is being used. For example, [List.map], [List.keepIf], and [List.set] can all be optimized to perform in-place mutations. ## * If possible, it is usually best for performance to use large lists in a way where the optimizer can turn them into in-place mutations. If this is not possible, a persistent data structure might be faster - but this is a rare enough scenario that it would not be good for the average Roc program's performance if this were the way [List] worked by default. Instead, you can look outside Roc's standard modules for an implementation of a persistent data structure - likely built using [List] under the hood! -List elem : [ @List elem ] +List elem := [ List elem ] ## Initialize diff --git a/compiler/builtins/docs/Num.roc b/compiler/builtins/docs/Num.roc index 2f45e22600..f9174c1bbc 100644 --- a/compiler/builtins/docs/Num.roc +++ b/compiler/builtins/docs/Num.roc @@ -47,7 +47,7 @@ interface Num compare, cos, div, - divFloor, + divTrunc, floor, intCast, isEven, @@ -61,6 +61,7 @@ interface Num isPositive, isZero, log, + logChecked, maxFloat, maxI8, maxU8, @@ -81,8 +82,6 @@ interface Num minI64, minU64, minI128, - modInt, - modFloat, mul, mulChecked, mulWrap, @@ -90,6 +89,7 @@ interface Num pow, powInt, rem, + remChecked, round, shiftLeftBy, shiftRightBy, @@ -99,6 +99,7 @@ interface Num subChecked, subWrap, sqrt, + sqrtChecked, tan, toI8, toI8Checked, @@ -188,7 +189,7 @@ interface Num ## ## In practice, these are rarely needed. It's most common to write ## number literals without any suffix. -Num a : [ @Num a ] +Num a := a ## A decimal number. ## @@ -222,7 +223,7 @@ Num a : [ @Num a ] ## [Dec] typically takes slightly less time than [F64] to perform addition and ## subtraction, but 10-20 times longer to perform multiplication and division. ## [sqrt] and trigonometry are massively slower with [Dec] than with [F64]. -Dec : Float [ @Decimal128 ] +Dec : Num (FloatingPoint Decimal) ## A fixed-size number with a fractional component. ## @@ -291,7 +292,7 @@ Dec : Float [ @Decimal128 ] ## loops and conditionals. If you need to do performance-critical trigonometry ## or square roots, either [F64] or [F32] is probably a better choice than the ## usual default choice of [Dec], despite the precision problems they bring. -Float a : Num [ @Fraction a ] +Float range : Num (FloatingPoint range) ## A fixed-size integer - that is, a number with no fractional component. ## @@ -342,19 +343,19 @@ Float a : Num [ @Fraction a ] ## * Start by deciding if this integer should allow negative numbers, and choose signed or unsigned accordingly. ## * Next, think about the range of numbers you expect this number to hold. Choose the smallest size you will never expect to overflow, no matter the inputs your program receives. (Validating inputs for size, and presenting the user with an error if they are too big, can help guard against overflow.) ## * Finally, if a particular numeric calculation is running too slowly, you can try experimenting with other number sizes. This rarely makes a meaningful difference, but some processors can operate on different number sizes at different speeds. -Int size : Num [ @Integer size ] +Int range : Num (Integer range) ## A signed 8-bit integer, ranging from -128 to 127 -I8 : Int [ @Signed8 ] -U8 : Int [ @Unsigned8 ] -I16 : Int [ @Signed16 ] -U16 : Int [ @Unsigned16 ] -I32 : Int [ @Signed32 ] -U32 : Int [ @Unsigned32 ] -I64 : Int [ @Signed64 ] -U64 : Int [ @Unsigned64 ] -I128 : Int [ @Signed128 ] -U128 : Int [ @Unsigned128 ] +I8 : Int Signed8 +U8 : Int Unsigned8 +I16 : Int Signed16 +U16 : Int Unsigned16 +I32 : Int Signed32 +U32 : Int Unsigned32 +I64 : Int Signed64 +U64 : Int Unsigned64 +I128 : Int Signed128 +U128 : Int Unsigned128 ## A [natural number](https://en.wikipedia.org/wiki/Natural_number) represented ## as a 64-bit unsigned integer on 64-bit systems, a 32-bit unsigned integer @@ -366,7 +367,7 @@ U128 : Int [ @Unsigned128 ] ## a [List] can hold on a 64-bit system fits in a 64-bit unsigned integer, and ## on a 32-bit system it fits in 32-bit unsigned integer. This makes [Nat] a ## good fit for [List.len] regardless of system. -Nat : Int [ @Natural ] +Nat : Num (Integer Natural) ## A 64-bit signed integer. All number literals without decimal points are compatible with #Int values. ## @@ -442,7 +443,7 @@ Nat : Int [ @Natural ] ## ## As such, it's very important to design your code not to exceed these bounds! ## If you need to do math outside these bounds, consider using a larger numeric size. -Int size : Num [ @Int size ] +Int range : Num (Integer range) ## Convert @@ -781,7 +782,7 @@ toU128 : Int * -> U128 ## there will be a loss of precision. toDec : Num * -> Dec -## Divide two integers and #Num.round the resulut. +## Divide two integers, truncating the result towards zero. ## ## Division by zero is undefined in mathematics. As such, you should make ## sure never to pass zero as the denomaintor to this function! @@ -791,40 +792,31 @@ toDec : Num * -> Dec ## * In a development build, you'll get an assertion failure. ## * In an optimized build, the function will return 0. ## -## `a // b` is shorthand for `Num.divRound a b`. +## `a // b` is shorthand for `Num.divTrunc a b`. ## ## >>> 5 // 7 ## -## >>> Num.divRound 5 7 +## >>> Num.divTrunc 5 7 ## ## >>> 8 // -3 ## -## >>> Num.divRound 8 -3 +## >>> Num.divTrunc 8 -3 ## ## This is the same as the #// operator. -divRound : Int a, Int a -> Int a +divTrunc : Int a, Int a -> Int a -## Perform flooring modulo on two integers. +## Obtain the remainder (truncating modulo) from the division of two integers. ## -## Modulo is the same as remainder when working with positive numbers, -## but if either number is negative, then modulo works differently. +## `a % b` is shorthand for `Num.rem a b`. ## -## Additionally, flooring modulo uses [Float].floor on the result. +## >>> 5 % 7 ## -## (Use [Float].mod for non-flooring modulo.) +## >>> Num.rem 5 7 ## -## Return `Err DivByZero` if the second integer is zero, because division by zero is undefined in mathematics. +## >>> -8 % -3 ## -## `a %% b` is shorthand for `Int.modFloor a b`. -## -## >>> 5 %% 7 -## -## >>> Int.modFloor 5 7 -## -## >>> -8 %% -3 -## -## >>> Int.modFloor -8 -3 -#modFloor : Int a, Int a -> Result (Int a) [ DivByZero ]* +## >>> Num.rem -8 -3 +rem : Int a, Int a -> Int a ## Bitwise @@ -1096,31 +1088,6 @@ atan : Float a -> Float a ## >>> |> Num.div 2.0 div : Float a, Float a -> Float a -## Perform modulo on two [Float]s. -## -## Modulo is the same as remainder when working with positive numbers, -## but if either number is negative, then modulo works differently. -## -## `a % b` is shorthand for `Num.mod a b`. -## -## [Division by zero is undefined in mathematics](https://en.wikipedia.org/wiki/Division_by_zero), -## and as such, so is modulo by zero. Because of this, you should make sure never -## to pass zero for the second argument to this function! -## -## Passing [mod] a [Dec] value of zero for its second argument will cause a panic. -## Passing [mod] a [F32] and [F64] value for its second argument will cause it -## to return [*NaN*](Num.isNaN). -## -## >>> 5.0 % 7.0 -## -## >>> Num.mod 5 7 -## -## `Num.mod` can be convenient in pipelines. -## -## >>> Num.pi -## >>> |> Num.mod 2.0 -mod : Float a, Float a -> Float a - ## Raises a [Float] to the power of another [Float]. ## ## ` @@ -1316,7 +1283,7 @@ isInfinite : Float * -> Bool ## ## >>> Num.isNaN 12.3 ## -## >>> Num.isNaN (Num.sqrt -2) +## >>> Num.isNaN (Num.pow -1 0.5) ## ## *NaN* is unusual from other numberic values in that: ## * *NaN* is not equal to any other number, even itself. [Bool.isEq] always returns `False` if either argument is *NaN*. diff --git a/compiler/builtins/docs/Result.roc b/compiler/builtins/docs/Result.roc index 0139029572..230c5f9fd1 100644 --- a/compiler/builtins/docs/Result.roc +++ b/compiler/builtins/docs/Result.roc @@ -13,7 +13,7 @@ interface Result ## The result of an operation that could fail: either the operation went ## okay, or else there was an error of some sort. -Result ok err : [ @Result ok err ] +Result ok err : [ Ok ok, Err err ] ## Return True if the result indicates a success, else return False ## diff --git a/compiler/builtins/docs/Set.roc b/compiler/builtins/docs/Set.roc index 98b5b3e286..6f7f87e9c4 100644 --- a/compiler/builtins/docs/Set.roc +++ b/compiler/builtins/docs/Set.roc @@ -18,7 +18,7 @@ interface Set imports [] ## A Set is an unordered collection of unique elements. -Set elem : [ @Set elem ] +Set elem := [ Set elem ] ## An empty set. empty : Set * diff --git a/compiler/builtins/docs/Str.roc b/compiler/builtins/docs/Str.roc index e67e4f234a..0bfa3ca2af 100644 --- a/compiler/builtins/docs/Str.roc +++ b/compiler/builtins/docs/Str.roc @@ -116,7 +116,7 @@ interface Str ## It has many more tools than this module does! ## A [Unicode](https://unicode.org) text value. -Str : [ @Str ] +Str := [ Str ] ## Convert diff --git a/compiler/builtins/roc/Dict.roc b/compiler/builtins/roc/Dict.roc index b2d6d9f5ef..aa457cee0e 100644 --- a/compiler/builtins/roc/Dict.roc +++ b/compiler/builtins/roc/Dict.roc @@ -1,6 +1,6 @@ interface Dict - exposes - [ + exposes + [ empty, single, get, @@ -15,7 +15,10 @@ interface Dict intersection, difference, ] - imports [ ] + imports + [ + Bool.{ Bool } + ] empty : Dict k v single : k, v -> Dict k v diff --git a/compiler/builtins/roc/List.roc b/compiler/builtins/roc/List.roc index be58ddd9d2..42de7f03ef 100644 --- a/compiler/builtins/roc/List.roc +++ b/compiler/builtins/roc/List.roc @@ -1,60 +1,72 @@ -isEmpty, -get, -set, -replace, -append, -map, -len, -walkBackwards, -concat, -first, -single, -repeat, -reverse, -prepend, -join, -keepIf, -contains, -sum, -walk, -last, -keepOks, -keepErrs, -mapWithIndex, -map2, -map3, -product, -walkUntil, -range, -sortWith, -drop, -swap, -dropAt, -dropLast, -min, -max, -map4, -dropFirst, -joinMap, -any, -takeFirst, -takeLast, -find, -sublist, -intersperse, -split, -all, -dropIf, -sortAsc, -sortDesc, +interface List + exposes + [ + isEmpty, + get, + set, + replace, + append, + map, + len, + walkBackwards, + concat, + first, + single, + repeat, + reverse, + prepend, + join, + keepIf, + contains, + sum, + walk, + last, + keepOks, + keepErrs, + mapWithIndex, + map2, + map3, + product, + walkUntil, + range, + sortWith, + drop, + swap, + dropAt, + dropLast, + min, + max, + map4, + dropFirst, + joinMap, + any, + takeFirst, + takeLast, + find, + sublist, + intersperse, + split, + all, + dropIf, + sortAsc, + sortDesc, + ] + imports + [ + Bool.{ Bool } + ] isEmpty : List a -> Bool isEmpty = \list -> List.len list == 0 get : List a, Nat -> Result a [ OutOfBounds ]* -set : List a, Nat, a -> List a replace : List a, Nat, a -> { list : List a, value : a } + +set : List a, Nat, a -> List a +set = \list, index, value -> + (List.replace list index value).list + append : List a, a -> List a prepend : List a, a -> List a len : List a -> Nat @@ -70,11 +82,11 @@ walkBackwards : List elem, state, (state, elem -> state) -> state walkUntil : List elem, state, (state, elem -> [ Continue state, Stop state ]) -> state sum : List (Num a) -> Num a -sum = \list -> +sum = \list -> List.walk list 0 Num.add product : List (Num a) -> Num a -product = \list -> +product = \list -> List.walk list 1 Num.mul any : List a, (a -> Bool) -> Bool @@ -82,6 +94,8 @@ all : List a, (a -> Bool) -> Bool keepIf : List a, (a -> Bool) -> List a dropIf : List a, (a -> Bool) -> List a +dropIf = \list, predicate -> + List.keepIf list (\e -> Bool.not (predicate e)) keepOks : List before, (before -> Result after *) -> List after keepErrs: List before, (before -> Result * after) -> List after @@ -89,7 +103,7 @@ map : List a, (a -> b) -> List b map2 : List a, List b, (a, b -> c) -> List c map3 : List a, List b, List c, (a, b, c -> d) -> List d map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e -mapWithIndex : List a, (a -> b) -> List b +mapWithIndex : List a, (a, Nat -> b) -> List b range : Int a, Int a -> List (Int a) sortWith : List a, (a, a -> [ LT, EQ, GT ] ) -> List a sortAsc : List (Num a) -> List (Num a) @@ -112,9 +126,47 @@ drop : List elem, Nat -> List elem dropAt : List elem, Nat -> List elem min : List (Num a) -> Result (Num a) [ ListWasEmpty ]* +min = \list -> + when List.first list is + Ok initial -> + Ok (minHelp list initial) + + Err ListWasEmpty -> + Err ListWasEmpty + + +minHelp : List (Num a), Num a -> Num a +minHelp = \list, initial -> + List.walk list initial \bestSoFar, current -> + if current < bestSoFar then + current + + else + bestSoFar + max : List (Num a) -> Result (Num a) [ ListWasEmpty ]* +max = \list -> + when List.first list is + Ok initial -> + Ok (maxHelp list initial) + + Err ListWasEmpty -> + Err ListWasEmpty + + +maxHelp : List (Num a), Num a -> Num a +maxHelp = \list, initial -> + List.walk list initial \bestSoFar, current -> + if current > bestSoFar then + current + + else + bestSoFar joinMap : List a, (a -> List b) -> List b +joinMap = \list, mapper -> + List.walk list [] (\state, elem -> List.concat state (mapper elem)) + find : List elem, (elem -> Bool) -> Result elem [ NotFound ]* sublist : List elem, { start : Nat, len : Nat } -> List elem intersperse : List elem, elem -> List elem diff --git a/compiler/builtins/roc/Num.roc b/compiler/builtins/roc/Num.roc index 3038842caa..dc0c1a7bd7 100644 --- a/compiler/builtins/roc/Num.roc +++ b/compiler/builtins/roc/Num.roc @@ -44,8 +44,6 @@ interface Num Binary32, Binary64, - maxFloat, - minFloat, abs, neg, add, @@ -68,12 +66,13 @@ interface Num isPositive, isNegative, rem, + remChecked, div, divChecked, - modInt, - modFloat, sqrt, + sqrtChecked, log, + logChecked, round, ceiling, floor, @@ -99,8 +98,8 @@ interface Num bytesToU32, divCeil, divCeilChecked, - divFloor, - divFloorChecked, + divTrunc, + divTruncChecked, toStr, isMultipleOf, minI8, @@ -123,6 +122,10 @@ interface Num maxI128, minU128, maxU128, + minF32, + maxF32, + minF64, + maxF64, toI8, toI8Checked, toI16, @@ -143,28 +146,37 @@ interface Num toU64Checked, toU128, toU128Checked, + toNat, + toNatChecked, + toF32, + toF32Checked, + toF64, + toF64Checked, + ] + imports + [ + Bool.{ Bool } ] - imports [ ] -Num range : [ @Num range ] +Num range := range Int range : Num (Integer range) Float range : Num (FloatingPoint range) -Signed128 : [ @Signed128 ] -Signed64 : [ @Signed64 ] -Signed32 : [ @Signed32 ] -Signed16 : [ @Signed16 ] -Signed8 : [ @Signed8 ] +Signed128 := [] +Signed64 := [] +Signed32 := [] +Signed16 := [] +Signed8 := [] -Unsigned128 : [ @Unsigned128 ] -Unsigned64 : [ @Unsigned64 ] -Unsigned32 : [ @Unsigned32 ] -Unsigned16 : [ @Unsigned16 ] -Unsigned8 : [ @Unsigned8 ] +Unsigned128 := [] +Unsigned64 := [] +Unsigned32 := [] +Unsigned16 := [] +Unsigned8 := [] -Natural : [ @Natural ] +Natural := [] -Integer range : [ @Integer range ] +Integer range := range I128 : Num (Integer Signed128) I64 : Num (Integer Signed64) @@ -180,11 +192,11 @@ U8 : Num (Integer Unsigned8) Nat : Num (Integer Natural) -Decimal : [ @Decimal ] -Binary64 : [ @Binary64 ] -Binary32 : [ @Binary32 ] +Decimal := [] +Binary64 := [] +Binary32 := [] -FloatingPoint range : [ @FloatingPoint range ] +FloatingPoint range := range F64 : Num (FloatingPoint Binary64) F32 : Num (FloatingPoint Binary32) @@ -230,19 +242,22 @@ asin : Float a -> Float a acos : Float a -> Float a atan : Float a -> Float a -sqrt : Float a -> Result (Float a) [ SqrtOfNegative ]* -log : Float a -> Result (Float a) [ LogNeedsPositive ]* +sqrt : Float a -> Float a +sqrtChecked : Float a -> Result (Float a) [ SqrtOfNegative ]* +log : Float a -> Float a +logChecked : Float a -> Result (Float a) [ LogNeedsPositive ]* + div : Float a, Float a -> Float a divChecked : Float a, Float a -> Result (Float a) [ DivByZero ]* - divCeil : Int a, Int a -> Int a divCeilChecked : Int a, Int a -> Result (Int a) [ DivByZero ]* -divFloor : Int a, Int a -> Int a -divFloorChecked : Int a, Int a -> Result (Int a) [ DivByZero ]* -# mod : Float a, Float a -> Result (Float a) [ DivByZero ]* -rem : Int a, Int a -> Result (Int a) [ DivByZero ]* -# mod : Int a, Int a -> Result (Int a) [ DivByZero ]* +divTrunc : Int a, Int a -> Int a +divTruncChecked : Int a, Int a -> Result (Int a) [ DivByZero ]* + +rem : Int a, Int a -> Int a +remChecked : Int a, Int a -> Result (Int a) [ DivByZero ]* + isMultipleOf : Int a, Int a -> Bool bitwiseAnd : Int a, Int a -> Int a @@ -331,6 +346,18 @@ minU128 = 0 maxU128 : U128 maxU128 = 0340282366920938463463374607431768211455 +minF32 : F32 +minF32 = -3.40282347e38 + +maxF32 : F32 +maxF32 = 3.40282347e38 + +minF64 : F64 +minF64 = -1.7976931348623157e308 + +maxF64 : F64 +maxF64 = 1.7976931348623157e308 + toI8 : Int * -> I8 toI16 : Int * -> I16 toI32 : Int * -> I32 @@ -341,6 +368,7 @@ toU16 : Int * -> U16 toU32 : Int * -> U32 toU64 : Int * -> U64 toU128 : Int * -> U128 +toNat : Int * -> Nat toF32 : Num * -> F32 toF64 : Num * -> F64 @@ -355,5 +383,6 @@ toU16Checked : Int * -> Result U16 [ OutOfBounds ]* toU32Checked : Int * -> Result U32 [ OutOfBounds ]* toU64Checked : Int * -> Result U64 [ OutOfBounds ]* toU128Checked : Int * -> Result U128 [ OutOfBounds ]* +toNatChecked : Int * -> Result Nat [ OutOfBounds ]* toF32Checked : Num * -> Result F32 [ OutOfBounds ]* toF64Checked : Num * -> Result F64 [ OutOfBounds ]* diff --git a/compiler/builtins/roc/Result.roc b/compiler/builtins/roc/Result.roc index a32f12d937..a857a6c456 100644 --- a/compiler/builtins/roc/Result.roc +++ b/compiler/builtins/roc/Result.roc @@ -1,6 +1,6 @@ interface Result exposes [ Result, isOk, isErr, map, mapErr, after, withDefault ] - imports [ ] + imports [ Bool.{ Bool } ] Result ok err : [ Ok ok, Err err ] diff --git a/compiler/builtins/roc/Set.roc b/compiler/builtins/roc/Set.roc index 93e0097c40..f19d80eb2d 100644 --- a/compiler/builtins/roc/Set.roc +++ b/compiler/builtins/roc/Set.roc @@ -1,6 +1,6 @@ -interface Dict - exposes - [ +interface Set + exposes + [ empty, single, walk, @@ -14,7 +14,7 @@ interface Dict intersection, difference, ] - imports [ ] + imports [ List, Bool.{ Bool }, Dict.{ values } ] empty : Set k single : k -> Set k @@ -35,4 +35,4 @@ toDict : Set k -> Dict k {} walk : Set k, state, (state, k -> state) -> state walk = \set, state, step -> - Dict.walk (toDict set) state (\s, k, _ -> step s k) + Dict.walk (Set.toDict set) state (\s, k, _ -> step s k) diff --git a/compiler/builtins/roc/Str.roc b/compiler/builtins/roc/Str.roc index 02f1263924..fae59869c1 100644 --- a/compiler/builtins/roc/Str.roc +++ b/compiler/builtins/roc/Str.roc @@ -34,7 +34,7 @@ interface Str toU8, toI8, ] - imports [ ] + imports [ Bool.{ Bool }, Result.{ Result } ] diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index 70cb7e86d6..73de5d2452 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -68,13 +68,9 @@ impl FloatWidth { pub const fn try_from_symbol(symbol: Symbol) -> Option { match symbol { - Symbol::NUM_F64 | Symbol::NUM_BINARY64 | Symbol::NUM_AT_BINARY64 => { - Some(FloatWidth::F64) - } + Symbol::NUM_F64 | Symbol::NUM_BINARY64 => Some(FloatWidth::F64), - Symbol::NUM_F32 | Symbol::NUM_BINARY32 | Symbol::NUM_AT_BINARY32 => { - Some(FloatWidth::F32) - } + Symbol::NUM_F32 | Symbol::NUM_BINARY32 => Some(FloatWidth::F32), _ => None, } @@ -136,26 +132,16 @@ impl IntWidth { pub const fn try_from_symbol(symbol: Symbol) -> Option { match symbol { - Symbol::NUM_I128 | Symbol::NUM_SIGNED128 | Symbol::NUM_AT_SIGNED128 => { - Some(IntWidth::I128) - } - Symbol::NUM_I64 | Symbol::NUM_SIGNED64 | Symbol::NUM_AT_SIGNED64 => Some(IntWidth::I64), - Symbol::NUM_I32 | Symbol::NUM_SIGNED32 | Symbol::NUM_AT_SIGNED32 => Some(IntWidth::I32), - Symbol::NUM_I16 | Symbol::NUM_SIGNED16 | Symbol::NUM_AT_SIGNED16 => Some(IntWidth::I16), - Symbol::NUM_I8 | Symbol::NUM_SIGNED8 | Symbol::NUM_AT_SIGNED8 => Some(IntWidth::I8), - Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128 | Symbol::NUM_AT_UNSIGNED128 => { - Some(IntWidth::U128) - } - Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64 | Symbol::NUM_AT_UNSIGNED64 => { - Some(IntWidth::U64) - } - Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32 | Symbol::NUM_AT_UNSIGNED32 => { - Some(IntWidth::U32) - } - Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16 | Symbol::NUM_AT_UNSIGNED16 => { - Some(IntWidth::U16) - } - Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 | Symbol::NUM_AT_UNSIGNED8 => Some(IntWidth::U8), + Symbol::NUM_I128 | Symbol::NUM_SIGNED128 => Some(IntWidth::I128), + Symbol::NUM_I64 | Symbol::NUM_SIGNED64 => Some(IntWidth::I64), + Symbol::NUM_I32 | Symbol::NUM_SIGNED32 => Some(IntWidth::I32), + Symbol::NUM_I16 | Symbol::NUM_SIGNED16 => Some(IntWidth::I16), + Symbol::NUM_I8 | Symbol::NUM_SIGNED8 => Some(IntWidth::I8), + Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128 => Some(IntWidth::U128), + Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64 => Some(IntWidth::U64), + Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32 => Some(IntWidth::U32), + Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16 => Some(IntWidth::U16), + Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 => Some(IntWidth::U8), _ => None, } } @@ -379,6 +365,9 @@ pub const UTILS_EXPECT_FAILED: &str = "roc_builtins.expect.expect_failed"; pub const UTILS_GET_EXPECT_FAILURES: &str = "roc_builtins.expect.get_expect_failures"; pub const UTILS_DEINIT_FAILURES: &str = "roc_builtins.expect.deinit_failures"; +pub const UTILS_LONGJMP: &str = "longjmp"; +pub const UTILS_SETJMP: &str = "setjmp"; + #[derive(Debug, Default)] pub struct IntToIntrinsicName { pub options: [IntrinsicName; 10], diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index cc0fd16ef0..8f5299aba6 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -131,7 +131,7 @@ pub fn types() -> MutMap { fn overflow() -> SolvedType { SolvedType::TagUnion( - vec![(TagName::Global("Overflow".into()), vec![])], + vec![(TagName::Tag("Overflow".into()), vec![])], Box::new(SolvedType::Wildcard), ) } @@ -312,20 +312,20 @@ pub fn types() -> MutMap { ); let div_by_zero = SolvedType::TagUnion( - vec![(TagName::Global("DivByZero".into()), vec![])], + vec![(TagName::Tag("DivByZero".into()), vec![])], Box::new(SolvedType::Wildcard), ); - // divFloor : Int a, Int a -> Int a + // divTrunc : Int a, Int a -> Int a add_top_level_function_type!( - Symbol::NUM_DIV_FLOOR, + Symbol::NUM_DIV_TRUNC, vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))], Box::new(int_type(flex(TVAR1))) ); - // divFloorChecked : Int a, Int a -> Result (Int a) [ DivByZero ]* + // divTruncChecked : Int a, Int a -> Result (Int a) [ DivByZero ]* add_top_level_function_type!( - Symbol::NUM_DIV_FLOOR_CHECKED, + Symbol::NUM_DIV_TRUNC_CHECKED, vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))], Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())), ); @@ -393,16 +393,16 @@ pub fn types() -> MutMap { Box::new(int_type(flex(TVAR2))) ); - // rem : Int a, Int a -> Result (Int a) [ DivByZero ]* + // rem : Int a, Int a -> Int a add_top_level_function_type!( Symbol::NUM_REM, vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))], - Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())), + Box::new(int_type(flex(TVAR1))), ); - // mod : Int a, Int a -> Result (Int a) [ DivByZero ]* + // remChecked : Int a, Int a -> Result (Int a) [ DivByZero ]* add_top_level_function_type!( - Symbol::NUM_MOD_INT, + Symbol::NUM_REM_CHECKED, vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))], Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())), ); @@ -476,7 +476,7 @@ pub fn types() -> MutMap { ); let out_of_bounds = SolvedType::TagUnion( - vec![(TagName::Global("OutOfBounds".into()), vec![])], + vec![(TagName::Tag("OutOfBounds".into()), vec![])], Box::new(SolvedType::Wildcard), ); @@ -551,7 +551,7 @@ pub fn types() -> MutMap { ); let out_of_bounds = SolvedType::TagUnion( - vec![(TagName::Global("OutOfBounds".into()), vec![])], + vec![(TagName::Tag("OutOfBounds".into()), vec![])], Box::new(SolvedType::Wildcard), ); @@ -680,36 +680,43 @@ pub fn types() -> MutMap { add_top_level_function_type!( Symbol::NUM_DIV_FLOAT_CHECKED, vec![float_type(flex(TVAR1)), float_type(flex(TVAR1))], - Box::new(result_type(float_type(flex(TVAR1)), div_by_zero.clone())), - ); - - // mod : Float a, Float a -> Result (Float a) [ DivByZero ]* - add_top_level_function_type!( - Symbol::NUM_MOD_FLOAT, - vec![float_type(flex(TVAR1)), float_type(flex(TVAR1))], Box::new(result_type(float_type(flex(TVAR1)), div_by_zero)), ); // sqrt : Float a -> Float a + add_top_level_function_type!( + Symbol::NUM_SQRT, + vec![float_type(flex(TVAR1))], + Box::new(float_type(flex(TVAR1))), + ); + + // sqrtChecked : Float a -> Result (Float a) [ SqrtOfNegative ]* let sqrt_of_negative = SolvedType::TagUnion( - vec![(TagName::Global("SqrtOfNegative".into()), vec![])], + vec![(TagName::Tag("SqrtOfNegative".into()), vec![])], Box::new(SolvedType::Wildcard), ); add_top_level_function_type!( - Symbol::NUM_SQRT, + Symbol::NUM_SQRT_CHECKED, vec![float_type(flex(TVAR1))], Box::new(result_type(float_type(flex(TVAR1)), sqrt_of_negative)), ); // log : Float a -> Float a + add_top_level_function_type!( + Symbol::NUM_LOG, + vec![float_type(flex(TVAR1))], + Box::new(float_type(flex(TVAR1))), + ); + + // logChecked : Float a -> Result (Float a) [ LogNeedsPositive ]* let log_needs_positive = SolvedType::TagUnion( - vec![(TagName::Global("LogNeedsPositive".into()), vec![])], + vec![(TagName::Tag("LogNeedsPositive".into()), vec![])], Box::new(SolvedType::Wildcard), ); add_top_level_function_type!( - Symbol::NUM_LOG, + Symbol::NUM_LOG_CHECKED, vec![float_type(flex(TVAR1))], Box::new(result_type(float_type(flex(TVAR1)), log_needs_positive)), ); @@ -800,7 +807,7 @@ pub fn types() -> MutMap { // bytesToU16 : List U8, Nat -> Result U16 [ OutOfBounds ] { let position_out_of_bounds = SolvedType::TagUnion( - vec![(TagName::Global("OutOfBounds".into()), vec![])], + vec![(TagName::Tag("OutOfBounds".into()), vec![])], Box::new(SolvedType::Wildcard), ); add_top_level_function_type!( @@ -813,7 +820,7 @@ pub fn types() -> MutMap { // bytesToU32 : List U8, Nat -> Result U32 [ OutOfBounds ] { let position_out_of_bounds = SolvedType::TagUnion( - vec![(TagName::Global("OutOfBounds".into()), vec![])], + vec![(TagName::Tag("OutOfBounds".into()), vec![])], Box::new(SolvedType::Wildcard), ); add_top_level_function_type!( @@ -935,7 +942,7 @@ pub fn types() -> MutMap { { let bad_utf8 = SolvedType::TagUnion( vec![( - TagName::Global("BadUtf8".into()), + TagName::Tag("BadUtf8".into()), vec![str_utf8_byte_problem_type(), nat_type()], )], Box::new(SolvedType::Wildcard), @@ -948,15 +955,15 @@ pub fn types() -> MutMap { ); } - // fromUtf8Range : List U8 -> Result Str [ BadUtf8 Utf8Problem, OutOfBounds ]* + // fromUtf8Range : List U8, { start : Nat, count : Nat } -> Result Str [ BadUtf8 Utf8Problem, OutOfBounds ]* { let bad_utf8 = SolvedType::TagUnion( vec![ ( - TagName::Global("BadUtf8".into()), + TagName::Tag("BadUtf8".into()), vec![str_utf8_byte_problem_type(), nat_type()], ), - (TagName::Global("OutOfBounds".into()), vec![]), + (TagName::Tag("OutOfBounds".into()), vec![]), ], Box::new(SolvedType::Wildcard), ); @@ -992,7 +999,7 @@ pub fn types() -> MutMap { // `str_to_num` in can `builtins.rs` let invalid_str = || { SolvedType::TagUnion( - vec![(TagName::Global("InvalidNumStr".into()), vec![])], + vec![(TagName::Tag("InvalidNumStr".into()), vec![])], Box::new(SolvedType::Wildcard), ) }; @@ -1099,7 +1106,7 @@ pub fn types() -> MutMap { // get : List elem, Nat -> Result elem [ OutOfBounds ]* let index_out_of_bounds = SolvedType::TagUnion( - vec![(TagName::Global("OutOfBounds".into()), vec![])], + vec![(TagName::Tag("OutOfBounds".into()), vec![])], Box::new(SolvedType::Wildcard), ); @@ -1111,7 +1118,7 @@ pub fn types() -> MutMap { // first : List elem -> Result elem [ ListWasEmpty ]* let list_was_empty = SolvedType::TagUnion( - vec![(TagName::Global("ListWasEmpty".into()), vec![])], + vec![(TagName::Tag("ListWasEmpty".into()), vec![])], Box::new(SolvedType::Wildcard), ); @@ -1216,8 +1223,8 @@ pub fn types() -> MutMap { // [ LT, EQ, GT ] SolvedType::TagUnion( vec![ - (TagName::Global("Continue".into()), vec![content.clone()]), - (TagName::Global("Stop".into()), vec![content]), + (TagName::Tag("Continue".into()), vec![content.clone()]), + (TagName::Tag("Stop".into()), vec![content]), ], Box::new(SolvedType::EmptyTagUnion), ) @@ -1578,7 +1585,7 @@ pub fn types() -> MutMap { // find : List elem, (elem -> Bool) -> Result elem [ NotFound ]* { let not_found = SolvedType::TagUnion( - vec![(TagName::Global("NotFound".into()), vec![])], + vec![(TagName::Tag("NotFound".into()), vec![])], Box::new(SolvedType::Wildcard), ); let (elem, cvar) = (TVAR1, TVAR2); @@ -1620,7 +1627,7 @@ pub fn types() -> MutMap { // get : Dict k v, k -> Result v [ KeyNotFound ]* let key_not_found = SolvedType::TagUnion( - vec![(TagName::Global("KeyNotFound".into()), vec![])], + vec![(TagName::Tag("KeyNotFound".into()), vec![])], Box::new(SolvedType::Wildcard), ); diff --git a/compiler/can/Cargo.toml b/compiler/can/Cargo.toml index 159b8429a3..b7b1851465 100644 --- a/compiler/can/Cargo.toml +++ b/compiler/can/Cargo.toml @@ -8,6 +8,7 @@ edition = "2018" [dependencies] roc_collections = { path = "../collections" } roc_error_macros = { path = "../../error_macros" } +roc_exhaustive = { path = "../exhaustive" } roc_region = { path = "../region" } roc_module = { path = "../module" } roc_parse = { path = "../parse" } @@ -17,6 +18,7 @@ roc_builtins = { path = "../builtins" } ven_graph = { path = "../../vendor/pathfinding" } bumpalo = { version = "3.8.0", features = ["collections"] } static_assertions = "1.1.0" +bitvec = "1" [dev-dependencies] pretty_assertions = "1.0.0" diff --git a/compiler/can/src/abilities.rs b/compiler/can/src/abilities.rs index 2879de8e0c..43f958eece 100644 --- a/compiler/can/src/abilities.rs +++ b/compiler/can/src/abilities.rs @@ -16,6 +16,7 @@ pub struct MemberVariables { #[derive(Debug, Clone, PartialEq, Eq)] pub struct AbilityMemberData { pub parent_ability: Symbol, + pub signature_var: Variable, pub signature: Type, pub variables: MemberVariables, pub region: Region, @@ -60,15 +61,16 @@ impl AbilitiesStore { pub fn register_ability( &mut self, ability: Symbol, - members: Vec<(Symbol, Region, Type, MemberVariables)>, + members: Vec<(Symbol, Region, Variable, Type, MemberVariables)>, ) { let mut members_vec = Vec::with_capacity(members.len()); - for (member, region, signature, variables) in members.into_iter() { + for (member, region, signature_var, signature, variables) in members.into_iter() { members_vec.push(member); let old_member = self.ability_members.insert( member, AbilityMemberData { parent_ability: ability, + signature_var, signature, region, variables, @@ -83,6 +85,10 @@ impl AbilitiesStore { ); } + pub fn is_ability(&self, ability: Symbol) -> bool { + self.members_of_ability.contains_key(&ability) + } + /// Records a specialization of `ability_member` with specialized type `implementing_type`. /// Entries via this function are considered a source of truth. It must be ensured that a /// specialization is validated before being registered here. diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index 087cc9d9b8..45f42692e4 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -1,6 +1,6 @@ use crate::env::Env; use crate::scope::Scope; -use roc_collections::all::{ImMap, MutMap, MutSet, SendMap}; +use roc_collections::{ImMap, MutSet, SendMap, VecMap, VecSet}; use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_parse::ast::{AssignedField, ExtractSpaces, Pattern, Tag, TypeAnnotation, TypeHeader}; @@ -8,14 +8,15 @@ use roc_problem::can::ShadowKind; use roc_region::all::{Loc, Region}; use roc_types::subs::{VarStore, Variable}; use roc_types::types::{ - Alias, AliasCommon, AliasKind, LambdaSet, Problem, RecordField, Type, TypeExtension, + name_type_var, Alias, AliasCommon, AliasKind, LambdaSet, Problem, RecordField, Type, + TypeExtension, }; -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct Annotation { pub typ: Type, pub introduced_variables: IntroducedVariables, - pub references: MutSet, + pub references: VecSet, pub aliases: SendMap, } @@ -32,6 +33,20 @@ impl<'a> NamedOrAbleVariable<'a> { NamedOrAbleVariable::Able(av) => av.first_seen, } } + + pub fn name(&self) -> &Lowercase { + match self { + NamedOrAbleVariable::Named(nv) => &nv.name, + NamedOrAbleVariable::Able(av) => &av.name, + } + } + + pub fn variable(&self) -> Variable { + match self { + NamedOrAbleVariable::Named(nv) => nv.variable, + NamedOrAbleVariable::Able(av) => av.variable, + } + } } /// A named type variable, not bound to an ability. @@ -53,14 +68,14 @@ pub struct AbleVariable { pub first_seen: Region, } -#[derive(Clone, Debug, PartialEq, Default)] +#[derive(Clone, Debug, Default)] pub struct IntroducedVariables { pub wildcards: Vec>, pub lambda_sets: Vec, pub inferred: Vec>, - pub named: Vec, - pub able: Vec, - pub host_exposed_aliases: MutMap, + pub named: VecSet, + pub able: VecSet, + pub host_exposed_aliases: VecMap, } impl IntroducedVariables { @@ -84,7 +99,7 @@ impl IntroducedVariables { first_seen: var.region, }; - self.named.push(named_variable); + self.named.insert(named_variable); } pub fn insert_able(&mut self, name: Lowercase, var: Loc, ability: Symbol) { @@ -97,7 +112,7 @@ impl IntroducedVariables { first_seen: var.region, }; - self.able.push(able_variable); + self.able.insert(able_variable); } pub fn insert_wildcard(&mut self, var: Loc) { @@ -125,15 +140,10 @@ impl IntroducedVariables { self.lambda_sets.extend(other.lambda_sets.iter().copied()); self.inferred.extend(other.inferred.iter().copied()); self.host_exposed_aliases - .extend(other.host_exposed_aliases.clone()); + .extend(other.host_exposed_aliases.iter().map(|(k, v)| (*k, *v))); self.named.extend(other.named.iter().cloned()); - self.named.sort(); - self.named.dedup(); - self.able.extend(other.able.iter().cloned()); - self.able.sort(); - self.able.dedup(); } pub fn union_owned(&mut self, other: Self) { @@ -143,8 +153,7 @@ impl IntroducedVariables { self.host_exposed_aliases.extend(other.host_exposed_aliases); self.named.extend(other.named); - self.named.sort(); - self.named.dedup(); + self.able.extend(other.able.iter().cloned()); } pub fn var_by_name(&self, name: &Lowercase) -> Option { @@ -154,19 +163,13 @@ impl IntroducedVariables { .map(|(_, var)| var) } + pub fn iter_named(&self) -> impl Iterator { + (self.named.iter().map(NamedOrAbleVariable::Named)) + .chain(self.able.iter().map(NamedOrAbleVariable::Able)) + } + pub fn named_var_by_name(&self, name: &Lowercase) -> Option { - if let Some(nav) = self - .named - .iter() - .find(|nv| &nv.name == name) - .map(NamedOrAbleVariable::Named) - { - return Some(nav); - } - self.able - .iter() - .find(|av| &av.name == name) - .map(NamedOrAbleVariable::Able) + self.iter_named().find(|v| v.name() == name) } pub fn collect_able(&self) -> Vec { @@ -193,37 +196,8 @@ fn malformed(env: &mut Env, region: Region, name: &str) { env.problem(roc_problem::can::Problem::RuntimeError(problem)); } +/// Canonicalizes a top-level type annotation. pub fn canonicalize_annotation( - env: &mut Env, - scope: &mut Scope, - annotation: &roc_parse::ast::TypeAnnotation, - region: Region, - var_store: &mut VarStore, -) -> Annotation { - let mut introduced_variables = IntroducedVariables::default(); - let mut references = MutSet::default(); - let mut aliases = SendMap::default(); - - let typ = can_annotation_help( - env, - annotation, - region, - scope, - var_store, - &mut introduced_variables, - &mut aliases, - &mut references, - ); - - Annotation { - typ, - introduced_variables, - references, - aliases, - } -} - -pub fn canonicalize_annotation_with_possible_clauses( env: &mut Env, scope: &mut Scope, annotation: &TypeAnnotation, @@ -232,7 +206,7 @@ pub fn canonicalize_annotation_with_possible_clauses( abilities_in_scope: &[Symbol], ) -> Annotation { let mut introduced_variables = IntroducedVariables::default(); - let mut references = MutSet::default(); + let mut references = VecSet::default(); let mut aliases = SendMap::default(); let (annotation, region) = match annotation { @@ -303,7 +277,7 @@ fn make_apply_symbol( } } } else { - match env.qualified_lookup(module_name, ident, region) { + match env.qualified_lookup(scope, module_name, ident, region) { Ok(symbol) => Ok(symbol), Err(problem) => { // Either the module wasn't imported, or @@ -391,7 +365,7 @@ pub fn find_type_def_symbols( while let Some(tag) = inner_stack.pop() { match tag { - Tag::Global { args, .. } | Tag::Private { args, .. } => { + Tag::Apply { args, .. } => { for t in args.iter() { stack.push(&t.value); } @@ -424,6 +398,13 @@ pub fn find_type_def_symbols( result } +fn find_fresh_var_name(introduced_variables: &IntroducedVariables) -> Lowercase { + name_type_var(0, &mut introduced_variables.iter_named(), |var, str| { + var.name().as_str() == str + }) + .0 +} + #[allow(clippy::too_many_arguments)] fn can_annotation_help( env: &mut Env, @@ -433,7 +414,7 @@ fn can_annotation_help( var_store: &mut VarStore, introduced_variables: &mut IntroducedVariables, local_aliases: &mut SendMap, - references: &mut MutSet, + references: &mut VecSet, ) -> Type { use roc_parse::ast::TypeAnnotation::*; @@ -445,7 +426,7 @@ fn can_annotation_help( let arg_ann = can_annotation_help( env, &arg.value, - region, + arg.region, scope, var_store, introduced_variables, @@ -483,6 +464,21 @@ fn can_annotation_help( references.insert(symbol); + if scope.abilities_store.is_ability(symbol) { + let fresh_ty_var = find_fresh_var_name(introduced_variables); + + env.problem(roc_problem::can::Problem::AbilityUsedAsType( + fresh_ty_var.clone(), + symbol, + region, + )); + + // Generate an variable bound to the ability so we can keep compiling. + let var = var_store.fresh(); + introduced_variables.insert_able(fresh_ty_var, Loc::at(region, var), symbol); + return Type::Variable(var); + } + for arg in *type_arguments { let arg_ann = can_annotation_help( env, @@ -583,12 +579,7 @@ fn can_annotation_help( vars: loc_vars, }, ) => { - let symbol = match scope.introduce( - name.value.into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - region, - ) { + let symbol = match scope.introduce(name.value.into(), region) { Ok(symbol) => symbol, Err((original_region, shadow, _new_symbol)) => { @@ -834,8 +825,7 @@ fn can_annotation_help( Where(_annotation, clauses) => { debug_assert!(!clauses.is_empty()); - // Has clauses are allowed only on the top level of an ability member signature (for - // now), which we handle elsewhere. + // Has clauses are allowed only on the top level of a signature, which we handle elsewhere. env.problem(roc_problem::can::Problem::IllegalHasClause { region: Region::across_all(clauses.iter().map(|clause| &clause.region)), }); @@ -861,7 +851,7 @@ fn canonicalize_has_clause( introduced_variables: &mut IntroducedVariables, clause: &Loc>, abilities_in_scope: &[Symbol], - references: &mut MutSet, + references: &mut VecSet, ) -> Result<(), Type> { let Loc { region, @@ -923,7 +913,7 @@ fn can_extension_type<'a>( var_store: &mut VarStore, introduced_variables: &mut IntroducedVariables, local_aliases: &mut SendMap, - references: &mut MutSet, + references: &mut VecSet, opt_ext: &Option<&Loc>>, ext_problem_kind: roc_problem::can::ExtensionTypeKind, ) -> Type { @@ -1105,7 +1095,7 @@ fn can_assigned_fields<'a>( var_store: &mut VarStore, introduced_variables: &mut IntroducedVariables, local_aliases: &mut SendMap, - references: &mut MutSet, + references: &mut VecSet, ) -> SendMap> { use roc_parse::ast::AssignedField::*; use roc_types::types::RecordField::*; @@ -1218,7 +1208,7 @@ fn can_tags<'a>( var_store: &mut VarStore, introduced_variables: &mut IntroducedVariables, local_aliases: &mut SendMap, - references: &mut MutSet, + references: &mut VecSet, ) -> Vec<(TagName, Vec)> { let mut tag_types = Vec::with_capacity(tags.len()); @@ -1234,7 +1224,7 @@ fn can_tags<'a>( // a duplicate let new_name = 'inner: loop { match tag { - Tag::Global { name, args } => { + Tag::Apply { name, args } => { let name = name.value.into(); let mut arg_types = Vec::with_capacity(args.len()); @@ -1253,32 +1243,7 @@ fn can_tags<'a>( arg_types.push(ann); } - let tag_name = TagName::Global(name); - tag_types.push((tag_name.clone(), arg_types)); - - break 'inner tag_name; - } - Tag::Private { name, args } => { - let ident_id = env.ident_ids.get_or_insert(&name.value.into()); - let symbol = Symbol::new(env.home, ident_id); - let mut arg_types = Vec::with_capacity(args.len()); - - for arg in args.iter() { - let ann = can_annotation_help( - env, - &arg.value, - arg.region, - scope, - var_store, - introduced_variables, - local_aliases, - references, - ); - - arg_types.push(ann); - } - - let tag_name = TagName::Private(symbol); + let tag_name = TagName::Tag(name); tag_types.push((tag_name.clone(), arg_types)); break 'inner tag_name; diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index d194966b8b..d84646f4eb 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -1,5 +1,5 @@ use crate::def::Def; -use crate::expr::{self, ClosureData, Expr::*, IntValue}; +use crate::expr::{self, AnnotatedMark, ClosureData, Expr::*, IntValue}; use crate::expr::{Expr, Field, Recursive}; use crate::num::{FloatBound, IntBound, IntWidth, NumericBound}; use crate::pattern::Pattern; @@ -9,7 +9,7 @@ use roc_module::ident::{Lowercase, TagName}; use roc_module::low_level::LowLevel; use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; -use roc_types::subs::{VarStore, Variable}; +use roc_types::subs::{ExhaustiveMark, RedundantMark, VarStore, Variable}; macro_rules! macro_magic { (@single $($x:tt)*) => (()); @@ -171,6 +171,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option SET_DIFFERENCE => set_difference, SET_TO_LIST => set_to_list, SET_FROM_LIST => set_from_list, + SET_TO_DICT=> set_to_dict, SET_INSERT => set_insert, SET_REMOVE => set_remove, SET_CONTAINS => set_contains, @@ -196,16 +197,19 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option NUM_TAN => num_tan, NUM_DIV_FLOAT => num_div_float, NUM_DIV_FLOAT_CHECKED => num_div_float_checked, - NUM_DIV_FLOOR => num_div_floor, - NUM_DIV_FLOOR_CHECKED => num_div_floor_checked, + NUM_DIV_TRUNC => num_div_trunc, + NUM_DIV_TRUNC_CHECKED => num_div_trunc_checked, NUM_DIV_CEIL => num_div_ceil, NUM_DIV_CEIL_CHECKED => num_div_ceil_checked, NUM_ABS => num_abs, NUM_NEG => num_neg, NUM_REM => num_rem, + NUM_REM_CHECKED => num_rem_checked, NUM_IS_MULTIPLE_OF => num_is_multiple_of, NUM_SQRT => num_sqrt, + NUM_SQRT_CHECKED => num_sqrt_checked, NUM_LOG => num_log, + NUM_LOG_CHECKED => num_log_checked, NUM_ROUND => num_round, NUM_IS_ODD => num_is_odd, NUM_IS_EVEN => num_is_even, @@ -229,24 +233,6 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option NUM_SHIFT_RIGHT => num_shift_right_by, NUM_SHIFT_RIGHT_ZERO_FILL => num_shift_right_zf_by, NUM_INT_CAST=> num_int_cast, - NUM_MIN_I8=> num_min_i8, - NUM_MAX_I8=> num_max_i8, - NUM_MIN_U8=> num_min_u8, - NUM_MAX_U8=> num_max_u8, - NUM_MIN_I16=> num_min_i16, - NUM_MAX_I16=> num_max_i16, - NUM_MIN_U16=> num_min_u16, - NUM_MAX_U16=> num_max_u16, - NUM_MIN_I32=> num_min_i32, - NUM_MAX_I32=> num_max_i32, - NUM_MIN_U32=> num_min_u32, - NUM_MAX_U32=> num_max_u32, - NUM_MIN_I64=> num_min_i64, - NUM_MAX_I64=> num_max_i64, - NUM_MIN_U64=> num_min_u64, - NUM_MAX_U64=> num_max_u64, - NUM_MIN_I128=> num_min_i128, - NUM_MAX_I128=> num_max_i128, NUM_TO_I8 => num_to_i8, NUM_TO_I8_CHECKED => num_to_i8_checked, NUM_TO_I16 => num_to_i16, @@ -730,6 +716,23 @@ fn bool_and(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } +fn num_unaryop(symbol: Symbol, var_store: &mut VarStore, op: LowLevel) -> Def { + let num_var = var_store.fresh(); + let body = RunLowLevel { + op, + args: vec![(num_var, Var(Symbol::ARG_1))], + ret_var: num_var, + }; + + defn( + symbol, + vec![(num_var, Symbol::ARG_1)], + var_store, + body, + num_var, + ) +} + /// Num a, Num a -> Num a fn num_binop(symbol: Symbol, var_store: &mut VarStore, op: LowLevel) -> Def { let num_var = var_store.fresh(); @@ -1169,8 +1172,13 @@ fn num_to_float(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } -/// Num.sqrt : Float -> Result Float [ SqrtOfNegative ]* +/// Num.sqrt : Float a -> Float a fn num_sqrt(symbol: Symbol, var_store: &mut VarStore) -> Def { + num_unaryop(symbol, var_store, LowLevel::NumSqrtUnchecked) +} + +/// Num.sqrtChecked : Float a -> Result (Float a) [ SqrtOfNegative ]* +fn num_sqrt_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { let bool_var = var_store.fresh(); let float_var = var_store.fresh(); let unbound_zero_var = var_store.fresh(); @@ -1218,8 +1226,13 @@ fn num_sqrt(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } -/// Num.log : Float -> Result Float [ LogNeedsPositive ]* +/// Num.log : Float a -> Float a fn num_log(symbol: Symbol, var_store: &mut VarStore) -> Def { + num_unaryop(symbol, var_store, LowLevel::NumLogUnchecked) +} + +/// Num.logChecked : Float a -> Result (Float a) [ LogNeedsPositive ]* +fn num_log_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { let bool_var = var_store.fresh(); let float_var = var_store.fresh(); let unbound_zero_var = var_store.fresh(); @@ -1473,106 +1486,6 @@ fn num_int_cast(symbol: Symbol, var_store: &mut VarStore) -> Def { lowlevel_1(symbol, LowLevel::NumIntCast, var_store) } -/// Num.minI8: I8 -fn num_min_i8(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, i8::MIN, IntBound::Exact(IntWidth::I8)) -} - -/// Num.maxI8: I8 -fn num_max_i8(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, i8::MAX, IntBound::Exact(IntWidth::I8)) -} - -/// Num.minU8: U8 -fn num_min_u8(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, u8::MIN, IntBound::Exact(IntWidth::U8)) -} - -/// Num.maxU8: U8 -fn num_max_u8(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, u8::MAX, IntBound::Exact(IntWidth::U8)) -} - -/// Num.minI16: I16 -fn num_min_i16(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, i16::MIN, IntBound::Exact(IntWidth::I16)) -} - -/// Num.maxI16: I16 -fn num_max_i16(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, i16::MAX, IntBound::Exact(IntWidth::I16)) -} - -/// Num.minU16: U16 -fn num_min_u16(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, u16::MIN, IntBound::Exact(IntWidth::U16)) -} - -/// Num.maxU16: U16 -fn num_max_u16(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, u16::MAX, IntBound::Exact(IntWidth::U16)) -} - -/// Num.minI32: I32 -fn num_min_i32(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, i32::MIN, IntBound::Exact(IntWidth::I32)) -} - -/// Num.maxI32: I32 -fn num_max_i32(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, i32::MAX, IntBound::Exact(IntWidth::I32)) -} - -/// Num.minU32: U32 -fn num_min_u32(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, u32::MIN, IntBound::Exact(IntWidth::U32)) -} - -/// Num.maxU32: U32 -fn num_max_u32(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, u32::MAX, IntBound::Exact(IntWidth::U32)) -} - -/// Num.minI64: I64 -fn num_min_i64(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, i64::MIN, IntBound::Exact(IntWidth::I64)) -} - -/// Num.maxI64: I64 -fn num_max_i64(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, i64::MAX, IntBound::Exact(IntWidth::I64)) -} - -/// Num.minU64: U64 -fn num_min_u64(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, u64::MIN, IntBound::Exact(IntWidth::U64)) -} - -/// Num.maxU64: U64 -fn num_max_u64(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::(symbol, var_store, u64::MAX, IntBound::Exact(IntWidth::U64)) -} - -/// Num.minI128: I128 -fn num_min_i128(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - i128::MIN, - IntBound::Exact(IntWidth::I128), - ) -} - -/// Num.maxI128: I128 -fn num_max_i128(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - i128::MAX, - IntBound::Exact(IntWidth::I128), - ) -} - /// List.isEmpty : List * -> Bool fn list_is_empty(symbol: Symbol, var_store: &mut VarStore) -> Def { let list_var = var_store.fresh(); @@ -2709,8 +2622,16 @@ fn list_intersperse(symbol: Symbol, var_store: &mut VarStore) -> Def { recursive: Recursive::NotRecursive, captured_symbols: vec![(sep_sym, sep_var)], arguments: vec![ - (clos_acc_var, no_region(Pattern::Identifier(clos_acc_sym))), - (sep_var, no_region(Pattern::Identifier(clos_elem_sym))), + ( + clos_acc_var, + AnnotatedMark::new(var_store), + no_region(Pattern::Identifier(clos_acc_sym)), + ), + ( + sep_var, + AnnotatedMark::new(var_store), + no_region(Pattern::Identifier(clos_elem_sym)), + ), ], loc_body: { let append_sep = RunLowLevel { @@ -2795,9 +2716,14 @@ fn list_split(symbol: Symbol, var_store: &mut VarStore) -> Def { arguments: vec![ ( clos_start_var, + AnnotatedMark::new(var_store), no_region(Pattern::Identifier(clos_start_sym)), ), - (clos_len_var, no_region(Pattern::Identifier(clos_len_sym))), + ( + clos_len_var, + AnnotatedMark::new(var_store), + no_region(Pattern::Identifier(clos_len_sym)), + ), ], loc_body: { Box::new(no_region(RunLowLevel { @@ -2981,7 +2907,11 @@ fn list_drop_if(symbol: Symbol, var_store: &mut VarStore) -> Def { name: Symbol::LIST_DROP_IF_PREDICATE, recursive: Recursive::NotRecursive, captured_symbols: vec![(sym_predicate, t_predicate)], - arguments: vec![(t_elem, no_region(Pattern::Identifier(Symbol::ARG_3)))], + arguments: vec![( + t_elem, + AnnotatedMark::new(var_store), + no_region(Pattern::Identifier(Symbol::ARG_3)), + )], loc_body: { let should_drop = Call( Box::new(( @@ -3165,8 +3095,16 @@ fn list_join_map(symbol: Symbol, var_store: &mut VarStore) -> Def { recursive: Recursive::NotRecursive, captured_symbols: vec![(Symbol::ARG_2, before2list_after)], arguments: vec![ - (list_after, no_region(Pattern::Identifier(Symbol::ARG_3))), - (before, no_region(Pattern::Identifier(Symbol::ARG_4))), + ( + list_after, + AnnotatedMark::new(var_store), + no_region(Pattern::Identifier(Symbol::ARG_3)), + ), + ( + before, + AnnotatedMark::new(var_store), + no_region(Pattern::Identifier(Symbol::ARG_4)), + ), ], loc_body: { let mapper = Box::new(( @@ -3696,8 +3634,16 @@ fn list_sort_desc(symbol: Symbol, var_store: &mut VarStore) -> Def { recursive: Recursive::NotRecursive, captured_symbols: vec![], arguments: vec![ - (num_var, no_region(Pattern::Identifier(Symbol::ARG_2))), - (num_var, no_region(Pattern::Identifier(Symbol::ARG_3))), + ( + num_var, + AnnotatedMark::new(var_store), + no_region(Pattern::Identifier(Symbol::ARG_2)), + ), + ( + num_var, + AnnotatedMark::new(var_store), + no_region(Pattern::Identifier(Symbol::ARG_3)), + ), ], loc_body: { Box::new(no_region(RunLowLevel { @@ -4098,6 +4044,11 @@ fn set_from_list(symbol: Symbol, var_store: &mut VarStore) -> Def { lowlevel_1(symbol, LowLevel::SetFromList, var_store) } +/// Set.toDict : Set k -> Dict k {} +fn set_to_dict(symbol: Symbol, var_store: &mut VarStore) -> Def { + lowlevel_1(symbol, LowLevel::SetToDict, var_store) +} + /// Set.insert : Set k, k -> Set k fn set_insert(symbol: Symbol, var_store: &mut VarStore) -> Def { let dict_var = var_store.fresh(); @@ -4166,9 +4117,21 @@ fn set_walk(symbol: Symbol, var_store: &mut VarStore) -> Def { recursive: Recursive::NotRecursive, captured_symbols: vec![(Symbol::ARG_3, func_var)], arguments: vec![ - (accum_var, no_region(Pattern::Identifier(Symbol::ARG_5))), - (key_var, no_region(Pattern::Identifier(Symbol::ARG_6))), - (Variable::EMPTY_RECORD, no_region(Pattern::Underscore)), + ( + accum_var, + AnnotatedMark::new(var_store), + no_region(Pattern::Identifier(Symbol::ARG_5)), + ), + ( + key_var, + AnnotatedMark::new(var_store), + no_region(Pattern::Identifier(Symbol::ARG_6)), + ), + ( + Variable::EMPTY_RECORD, + AnnotatedMark::new(var_store), + no_region(Pattern::Underscore), + ), ], loc_body: Box::new(no_region(call_func)), }); @@ -4196,8 +4159,13 @@ fn set_walk(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } -/// Num.rem : Int a, Int a -> Result (Int a) [ DivByZero ]* +/// Num.rem : Int a, Int a -> Int a fn num_rem(symbol: Symbol, var_store: &mut VarStore) -> Def { + num_binop(symbol, var_store, LowLevel::NumRemUnchecked) +} + +/// Num.remChecked : Int a, Int a -> Result (Int a) [ DivByZero ]* +fn num_rem_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { let num_var = var_store.fresh(); let unbound_zero_var = var_store.fresh(); let bool_var = var_store.fresh(); @@ -4369,13 +4337,13 @@ fn num_div_float_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } -/// Num.divFloor : Int a, Int a -> Int a -fn num_div_floor(symbol: Symbol, var_store: &mut VarStore) -> Def { +/// Num.divTrunc : Int a, Int a -> Int a +fn num_div_trunc(symbol: Symbol, var_store: &mut VarStore) -> Def { num_binop(symbol, var_store, LowLevel::NumDivUnchecked) } -/// Num.divFloorChecked : Int a , Int a -> Result (Int a) [ DivByZero ]* -fn num_div_floor_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { +/// Num.divTruncChecked : Int a , Int a -> Result (Int a) [ DivByZero ]* +fn num_div_trunc_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { let bool_var = var_store.fresh(); let num_var = var_store.fresh(); let unbound_zero_var = var_store.fresh(); @@ -4739,7 +4707,7 @@ fn result_map(symbol: Symbol, var_store: &mut VarStore) -> Def { CalledVia::Space, ); - let tag_name = TagName::Global("Ok".into()); + let tag_name = TagName::Tag("Ok".into()); // ok branch let ok = Tag { @@ -4763,6 +4731,7 @@ fn result_map(symbol: Symbol, var_store: &mut VarStore) -> Def { patterns: vec![no_region(pattern)], value: no_region(ok), guard: None, + redundant: RedundantMark::new(var_store), }; branches.push(branch); @@ -4770,7 +4739,7 @@ fn result_map(symbol: Symbol, var_store: &mut VarStore) -> Def { { // err branch - let tag_name = TagName::Global("Err".into()); + let tag_name = TagName::Tag("Err".into()); let err = Tag { variant_var: var_store.fresh(), @@ -4793,6 +4762,7 @@ fn result_map(symbol: Symbol, var_store: &mut VarStore) -> Def { patterns: vec![no_region(pattern)], value: no_region(err), guard: None, + redundant: RedundantMark::new(var_store), }; branches.push(branch); @@ -4804,6 +4774,8 @@ fn result_map(symbol: Symbol, var_store: &mut VarStore) -> Def { region: Region::zero(), loc_cond: Box::new(no_region(Var(Symbol::ARG_1))), branches, + branches_cond_var: var_store.fresh(), + exhaustive: ExhaustiveMark::new(var_store), }; defn( @@ -4836,7 +4808,7 @@ fn result_map_err(symbol: Symbol, var_store: &mut VarStore) -> Def { CalledVia::Space, ); - let tag_name = TagName::Global("Err".into()); + let tag_name = TagName::Tag("Err".into()); // ok branch let ok = Tag { @@ -4860,6 +4832,7 @@ fn result_map_err(symbol: Symbol, var_store: &mut VarStore) -> Def { patterns: vec![no_region(pattern)], value: no_region(ok), guard: None, + redundant: RedundantMark::new(var_store), }; branches.push(branch); @@ -4867,7 +4840,7 @@ fn result_map_err(symbol: Symbol, var_store: &mut VarStore) -> Def { { // err branch - let tag_name = TagName::Global("Ok".into()); + let tag_name = TagName::Tag("Ok".into()); let err = Tag { variant_var: var_store.fresh(), @@ -4890,6 +4863,7 @@ fn result_map_err(symbol: Symbol, var_store: &mut VarStore) -> Def { patterns: vec![no_region(pattern)], value: no_region(err), guard: None, + redundant: RedundantMark::new(var_store), }; branches.push(branch); @@ -4901,6 +4875,8 @@ fn result_map_err(symbol: Symbol, var_store: &mut VarStore) -> Def { region: Region::zero(), loc_cond: Box::new(no_region(Var(Symbol::ARG_1))), branches, + branches_cond_var: var_store.fresh(), + exhaustive: ExhaustiveMark::new(var_store), }; defn( @@ -4920,7 +4896,7 @@ fn result_with_default(symbol: Symbol, var_store: &mut VarStore) -> Def { { // ok branch - let tag_name = TagName::Global("Ok".into()); + let tag_name = TagName::Tag("Ok".into()); let pattern = Pattern::AppliedTag { whole_var: result_var, @@ -4933,6 +4909,7 @@ fn result_with_default(symbol: Symbol, var_store: &mut VarStore) -> Def { patterns: vec![no_region(pattern)], value: no_region(Var(Symbol::ARG_3)), guard: None, + redundant: RedundantMark::new(var_store), }; branches.push(branch); @@ -4940,7 +4917,7 @@ fn result_with_default(symbol: Symbol, var_store: &mut VarStore) -> Def { { // err branch - let tag_name = TagName::Global("Err".into()); + let tag_name = TagName::Tag("Err".into()); let pattern = Pattern::AppliedTag { whole_var: result_var, @@ -4953,6 +4930,7 @@ fn result_with_default(symbol: Symbol, var_store: &mut VarStore) -> Def { patterns: vec![no_region(pattern)], value: no_region(Var(Symbol::ARG_2)), guard: None, + redundant: RedundantMark::new(var_store), }; branches.push(branch); @@ -4964,6 +4942,8 @@ fn result_with_default(symbol: Symbol, var_store: &mut VarStore) -> Def { region: Region::zero(), loc_cond: Box::new(no_region(Var(Symbol::ARG_1))), branches, + branches_cond_var: var_store.fresh(), + exhaustive: ExhaustiveMark::new(var_store), }; defn( @@ -4983,7 +4963,7 @@ fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def { { // ok branch - let tag_name = TagName::Global("Ok".into()); + let tag_name = TagName::Tag("Ok".into()); let pattern = Pattern::AppliedTag { whole_var: result_var, @@ -4995,7 +4975,7 @@ fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def { let false_expr = Tag { variant_var: var_store.fresh(), ext_var: var_store.fresh(), - name: TagName::Global("False".into()), + name: TagName::Tag("False".into()), arguments: vec![], }; @@ -5003,6 +4983,7 @@ fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def { patterns: vec![no_region(pattern)], value: no_region(false_expr), guard: None, + redundant: RedundantMark::new(var_store), }; branches.push(branch); @@ -5010,7 +4991,7 @@ fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def { { // err branch - let tag_name = TagName::Global("Err".into()); + let tag_name = TagName::Tag("Err".into()); let pattern = Pattern::AppliedTag { whole_var: result_var, @@ -5022,7 +5003,7 @@ fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def { let true_expr = Tag { variant_var: var_store.fresh(), ext_var: var_store.fresh(), - name: TagName::Global("True".into()), + name: TagName::Tag("True".into()), arguments: vec![], }; @@ -5030,6 +5011,7 @@ fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def { patterns: vec![no_region(pattern)], value: no_region(true_expr), guard: None, + redundant: RedundantMark::new(var_store), }; branches.push(branch); @@ -5041,6 +5023,8 @@ fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def { region: Region::zero(), loc_cond: Box::new(no_region(Var(Symbol::ARG_1))), branches, + branches_cond_var: var_store.fresh(), + exhaustive: ExhaustiveMark::new(var_store), }; defn( @@ -5060,7 +5044,7 @@ fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def { { // ok branch - let tag_name = TagName::Global("Ok".into()); + let tag_name = TagName::Tag("Ok".into()); let pattern = Pattern::AppliedTag { whole_var: result_var, @@ -5072,7 +5056,7 @@ fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def { let true_expr = Tag { variant_var: var_store.fresh(), ext_var: var_store.fresh(), - name: TagName::Global("True".into()), + name: TagName::Tag("True".into()), arguments: vec![], }; @@ -5080,6 +5064,7 @@ fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def { patterns: vec![no_region(pattern)], value: no_region(true_expr), guard: None, + redundant: RedundantMark::new(var_store), }; branches.push(branch); @@ -5087,7 +5072,7 @@ fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def { { // err branch - let tag_name = TagName::Global("Err".into()); + let tag_name = TagName::Tag("Err".into()); let pattern = Pattern::AppliedTag { whole_var: result_var, @@ -5099,7 +5084,7 @@ fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def { let false_expr = Tag { variant_var: var_store.fresh(), ext_var: var_store.fresh(), - name: TagName::Global("False".into()), + name: TagName::Tag("False".into()), arguments: vec![], }; @@ -5107,6 +5092,7 @@ fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def { patterns: vec![no_region(pattern)], value: no_region(false_expr), guard: None, + redundant: RedundantMark::new(var_store), }; branches.push(branch); @@ -5118,6 +5104,8 @@ fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def { region: Region::zero(), loc_cond: Box::new(no_region(Var(Symbol::ARG_1))), branches, + branches_cond_var: var_store.fresh(), + exhaustive: ExhaustiveMark::new(var_store), }; defn( @@ -5150,7 +5138,7 @@ fn result_after(symbol: Symbol, var_store: &mut VarStore) -> Def { CalledVia::Space, ); - let tag_name = TagName::Global("Ok".into()); + let tag_name = TagName::Tag("Ok".into()); // ok branch let ok = call_func; @@ -5169,6 +5157,7 @@ fn result_after(symbol: Symbol, var_store: &mut VarStore) -> Def { patterns: vec![no_region(pattern)], value: no_region(ok), guard: None, + redundant: RedundantMark::new(var_store), }; branches.push(branch); @@ -5176,7 +5165,7 @@ fn result_after(symbol: Symbol, var_store: &mut VarStore) -> Def { { // err branch - let tag_name = TagName::Global("Err".into()); + let tag_name = TagName::Tag("Err".into()); let err = Tag { variant_var: var_store.fresh(), @@ -5199,6 +5188,7 @@ fn result_after(symbol: Symbol, var_store: &mut VarStore) -> Def { patterns: vec![no_region(pattern)], value: no_region(err), guard: None, + redundant: RedundantMark::new(var_store), }; branches.push(branch); @@ -5210,6 +5200,8 @@ fn result_after(symbol: Symbol, var_store: &mut VarStore) -> Def { region: Region::zero(), loc_cond: Box::new(no_region(Var(Symbol::ARG_1))), branches, + branches_cond_var: var_store.fresh(), + exhaustive: ExhaustiveMark::new(var_store), }; defn( @@ -5234,7 +5226,7 @@ fn tag(name: &'static str, args: Vec, var_store: &mut VarStore) -> Expr { Expr::Tag { variant_var: var_store.fresh(), ext_var: var_store.fresh(), - name: TagName::Global(name.into()), + name: TagName::Tag(name.into()), arguments: args .into_iter() .map(|expr| (var_store.fresh(), no_region(expr))) @@ -5395,7 +5387,13 @@ fn defn_help( let closure_args = args .into_iter() - .map(|(var, symbol)| (var, no_region(Identifier(symbol)))) + .map(|(var, symbol)| { + ( + var, + AnnotatedMark::new(var_store), + no_region(Identifier(symbol)), + ) + }) .collect(); Closure(ClosureData { @@ -5411,36 +5409,6 @@ fn defn_help( }) } -#[inline(always)] -fn int_min_or_max(symbol: Symbol, var_store: &mut VarStore, i: I128, bound: IntBound) -> Def -where - I128: Into, -{ - let int_var = var_store.fresh(); - let int_precision_var = var_store.fresh(); - let body = int::(int_var, int_precision_var, i, bound); - - let std = roc_builtins::std::types(); - let solved = std.get(&symbol).unwrap(); - let mut free_vars = roc_types::solved_types::FreeVars::default(); - let signature = roc_types::solved_types::to_type(&solved.0, &mut free_vars, var_store); - - let annotation = crate::def::Annotation { - signature, - introduced_variables: Default::default(), - region: Region::zero(), - aliases: Default::default(), - }; - - Def { - annotation: Some(annotation), - expr_var: int_var, - loc_expr: Loc::at_zero(body), - loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)), - pattern_vars: SendMap::default(), - } -} - fn num_no_bound() -> NumericBound { NumericBound::None } diff --git a/compiler/can/src/constraint.rs b/compiler/can/src/constraint.rs index 58d7b1f065..42e416b014 100644 --- a/compiler/can/src/constraint.rs +++ b/compiler/can/src/constraint.rs @@ -1,9 +1,10 @@ +use crate::exhaustive::{ExhaustiveContext, SketchedRows}; use crate::expected::{Expected, PExpected}; use roc_collections::soa::{EitherIndex, Index, Slice}; use roc_module::ident::TagName; use roc_module::symbol::{ModuleId, Symbol}; use roc_region::all::{Loc, Region}; -use roc_types::subs::Variable; +use roc_types::subs::{ExhaustiveMark, Variable}; use roc_types::types::{Category, PatternCategory, Type}; #[derive(Debug)] @@ -19,6 +20,9 @@ pub struct Constraints { pub pattern_expectations: Vec>, pub includes_tags: Vec, pub strings: Vec<&'static str>, + pub sketched_rows: Vec, + pub eq: Vec, + pub pattern_eq: Vec, } impl Default for Constraints { @@ -40,6 +44,9 @@ impl Constraints { let pattern_expectations = Vec::new(); let includes_tags = Vec::new(); let strings = Vec::new(); + let sketched_rows = Vec::new(); + let eq = Vec::new(); + let pattern_eq = Vec::new(); types.extend([ Type::EmptyRec, @@ -90,6 +97,9 @@ impl Constraints { pattern_expectations, includes_tags, strings, + sketched_rows, + eq, + pattern_eq, } } @@ -225,7 +235,7 @@ impl Constraints { 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) + Constraint::Eq(Eq(type_index, expected_index, category_index, region)) } #[inline(always)] @@ -240,7 +250,7 @@ impl Constraints { 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) + Constraint::Eq(Eq(type_index, expected_index, category_index, region)) } #[inline(always)] @@ -256,17 +266,17 @@ impl Constraints { 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 equal = Constraint::Eq(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( + let storage = Constraint::Eq(Eq( storage_type_index, expected_index, storage_category_index, region, - ); + )); self.and_constraint([equal, storage]) } @@ -544,11 +554,6 @@ impl Constraints { pub fn contains_save_the_environment(&self, constraint: &Constraint) -> bool { match constraint { - Constraint::Eq(..) => false, - Constraint::Store(..) => false, - Constraint::Lookup(..) => false, - Constraint::Pattern(..) => false, - Constraint::True => false, Constraint::SaveTheEnvironment => true, Constraint::Let(index, _) => { let let_constraint = &self.let_constraints[index.index()]; @@ -567,9 +572,15 @@ impl Constraints { .iter() .any(|c| self.contains_save_the_environment(c)) } - Constraint::IsOpenType(_) => false, - Constraint::IncludesTag(_) => false, - Constraint::PatternPresence(_, _, _, _) => false, + Constraint::Eq(..) + | Constraint::Store(..) + | Constraint::Lookup(..) + | Constraint::Pattern(..) + | Constraint::True + | Constraint::IsOpenType(_) + | Constraint::IncludesTag(_) + | Constraint::PatternPresence(_, _, _, _) + | Constraint::Exhaustive { .. } => false, } } @@ -597,18 +608,65 @@ impl Constraints { Constraint::Store(type_index, variable, string_index, line_number) } + + pub fn exhaustive( + &mut self, + real_var: Variable, + real_region: Region, + category_and_expectation: Result< + (Category, Expected), + (PatternCategory, PExpected), + >, + sketched_rows: SketchedRows, + context: ExhaustiveContext, + exhaustive: ExhaustiveMark, + ) -> Constraint { + let real_var = Self::push_type_variable(real_var); + let sketched_rows = Index::push_new(&mut self.sketched_rows, sketched_rows); + + let equality = match category_and_expectation { + Ok((category, expected)) => { + let category = Index::push_new(&mut self.categories, category); + let expected = Index::push_new(&mut self.expectations, expected); + let equality = Eq(real_var, expected, category, real_region); + let equality = Index::push_new(&mut self.eq, equality); + Ok(equality) + } + Err((category, expected)) => { + let category = Index::push_new(&mut self.pattern_categories, category); + let expected = Index::push_new(&mut self.pattern_expectations, expected); + let equality = PatternEq(real_var, expected, category, real_region); + let equality = Index::push_new(&mut self.pattern_eq, equality); + Err(equality) + } + }; + + Constraint::Exhaustive(equality, sketched_rows, context, exhaustive) + } } roc_error_macros::assert_sizeof_default!(Constraint, 3 * 8); +roc_error_macros::assert_sizeof_aarch64!(Constraint, 3 * 8); -#[derive(Clone, PartialEq)] +#[derive(Clone, Copy, Debug)] +pub struct Eq( + pub EitherIndex, + pub Index>, + pub Index, + pub Region, +); + +#[derive(Clone, Copy, Debug)] +pub struct PatternEq( + pub EitherIndex, + pub Index>, + pub Index, + pub Region, +); + +#[derive(Clone, Copy)] pub enum Constraint { - Eq( - EitherIndex, - Index>, - Index, - Region, - ), + Eq(Eq), Store( EitherIndex, Variable, @@ -641,15 +699,21 @@ pub enum Constraint { Index, Region, ), + Exhaustive( + Result, Index>, + Index, + ExhaustiveContext, + ExhaustiveMark, + ), } -#[derive(Debug, Clone, Copy, PartialEq, Default)] +#[derive(Debug, Clone, Copy, Default)] pub struct DefTypes { pub types: Slice, pub loc_symbols: Slice<(Symbol, Region)>, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct LetConstraint { pub rigid_vars: Slice, pub flex_vars: Slice, @@ -657,7 +721,7 @@ pub struct LetConstraint { pub defs_and_ret_constraint: Index<(Constraint, Constraint)>, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct IncludesTag { pub type_index: Index, pub tag_name: TagName, @@ -670,7 +734,7 @@ pub struct IncludesTag { 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) => { + Self::Eq(Eq(arg0, arg1, arg2, arg3)) => { write!(f, "Eq({:?}, {:?}, {:?}, {:?})", arg0, arg1, arg2, arg3) } Self::Store(arg0, arg1, arg2, arg3) => { @@ -695,6 +759,13 @@ impl std::fmt::Debug for Constraint { arg0, arg1, arg2, arg3 ) } + Self::Exhaustive(arg0, arg1, arg2, arg3) => { + write!( + f, + "Exhaustive({:?}, {:?}, {:?}, {:?})", + arg0, arg1, arg2, arg3 + ) + } } } } diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 571568592a..f93b8732e2 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -1,18 +1,22 @@ use crate::abilities::MemberVariables; use crate::annotation::canonicalize_annotation; -use crate::annotation::canonicalize_annotation_with_possible_clauses; use crate::annotation::IntroducedVariables; use crate::env::Env; +use crate::expr::AnnotatedMark; use crate::expr::ClosureData; use crate::expr::Expr::{self, *}; -use crate::expr::{canonicalize_expr, local_successors_with_duplicates, Output, Recursive}; -use crate::pattern::{bindings_from_patterns, canonicalize_def_header_pattern, Pattern}; +use crate::expr::{canonicalize_expr, Output, Recursive}; +use crate::pattern::{canonicalize_def_header_pattern, BindingsFromPattern, Pattern}; use crate::procedure::References; +use crate::reference_matrix::ReferenceMatrix; use crate::scope::create_alias; use crate::scope::Scope; -use roc_collections::all::ImSet; -use roc_collections::all::{default_hasher, ImEntry, ImMap, MutMap, MutSet, SendMap}; +use roc_collections::VecMap; +use roc_collections::{ImSet, MutMap, SendMap}; +use roc_module::ident::Ident; use roc_module::ident::Lowercase; +use roc_module::symbol::IdentId; +use roc_module::symbol::ModuleId; use roc_module::symbol::Symbol; use roc_parse::ast; use roc_parse::ast::AbilityMember; @@ -26,11 +30,9 @@ use roc_types::subs::{VarStore, Variable}; use roc_types::types::AliasKind; use roc_types::types::LambdaSet; use roc_types::types::{Alias, Type}; -use std::collections::HashMap; use std::fmt::Debug; -use ven_graph::{strongly_connected_components, topological_sort}; -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct Def { pub loc_pattern: Loc, pub loc_expr: Loc, @@ -39,7 +41,7 @@ pub struct Def { pub annotation: Option, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct Annotation { pub signature: Type, pub introduced_variables: IntroducedVariables, @@ -48,16 +50,17 @@ pub struct Annotation { } #[derive(Debug)] -pub struct CanDefs { - pub refs_by_symbol: MutMap, - pub can_defs_by_symbol: MutMap, - pub aliases: SendMap, +pub(crate) struct CanDefs { + defs: Vec>, + def_ordering: DefOrdering, + + aliases: VecMap, } /// A Def that has had patterns and type annnotations canonicalized, /// but no Expr canonicalization has happened yet. Also, it has had spaces /// and nesting resolved, and knows whether annotations are standalone or not. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] enum PendingValueDef<'a> { /// A standalone annotation with no body AnnotationOnly( @@ -80,7 +83,17 @@ enum PendingValueDef<'a> { ), } -#[derive(Debug, Clone, PartialEq)] +impl PendingValueDef<'_> { + fn loc_pattern(&self) -> &Loc { + match self { + PendingValueDef::AnnotationOnly(_, loc_pattern, _) => loc_pattern, + PendingValueDef::Body(_, loc_pattern, _) => loc_pattern, + PendingValueDef::TypedBody(_, loc_pattern, _, _) => loc_pattern, + } + } +} + +#[derive(Debug, Clone)] enum PendingTypeDef<'a> { /// A structural or opaque type alias, e.g. `Ints : List Int` or `Age := U32` respectively. Alias { @@ -96,17 +109,49 @@ enum PendingTypeDef<'a> { }, /// An invalid alias, that is ignored in the rest of the pipeline - /// e.g. a shadowed alias, or a definition like `MyAlias 1 : Int` + /// e.g. a definition like `MyAlias 1 : Int` /// with an incorrect pattern - InvalidAlias { kind: AliasKind }, + InvalidAlias { + #[allow(dead_code)] + kind: AliasKind, + symbol: Symbol, + region: Region, + }, + + /// An alias with a name that shadows another symbol + ShadowedAlias, /// An invalid ability, that is ignored in the rest of the pipeline. /// E.g. a shadowed ability, or with a bad definition. - InvalidAbility, + InvalidAbility { + symbol: Symbol, + region: Region, + }, + + AbilityNotOnToplevel, + AbilityShadows, +} + +impl PendingTypeDef<'_> { + fn introduction(&self) -> Option<(Symbol, Region)> { + match self { + PendingTypeDef::Alias { name, ann, .. } => { + let region = Region::span_across(&name.region, &ann.region); + + Some((name.value, region)) + } + PendingTypeDef::Ability { name, .. } => Some((name.value, name.region)), + PendingTypeDef::InvalidAlias { symbol, region, .. } => Some((*symbol, *region)), + PendingTypeDef::ShadowedAlias { .. } => None, + PendingTypeDef::InvalidAbility { symbol, region } => Some((*symbol, *region)), + PendingTypeDef::AbilityNotOnToplevel => None, + PendingTypeDef::AbilityShadows => None, + } + } } // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] #[allow(clippy::large_enum_variant)] pub enum Declaration { Declare(Def), @@ -129,77 +174,45 @@ impl Declaration { /// Returns a topologically sorted sequence of alias/opaque names fn sort_type_defs_before_introduction( - mut referenced_symbols: MutMap>, + referenced_symbols: VecMap>, ) -> Vec { - let defined_symbols: Vec = referenced_symbols.keys().copied().collect(); + let capacity = referenced_symbols.len(); + let mut matrix = ReferenceMatrix::new(capacity); - // find the strongly connected components and their relations - let sccs = { - // only retain symbols from the current set of defined symbols; the rest come from other modules - for v in referenced_symbols.iter_mut() { - v.1.retain(|x| defined_symbols.iter().any(|s| s == x)); - } + let (symbols, referenced) = referenced_symbols.unzip(); - let all_successors_with_self = |symbol: &Symbol| referenced_symbols[symbol].iter().copied(); - - strongly_connected_components(&defined_symbols, all_successors_with_self) - }; - - // then sort the strongly connected components - let groups: Vec<_> = (0..sccs.len()).collect(); - let mut group_symbols: Vec> = vec![Vec::new(); groups.len()]; - - let mut symbol_to_group_index = MutMap::default(); - let mut group_to_groups = vec![Vec::new(); groups.len()]; - - for (index, group) in sccs.iter().enumerate() { - for s in group { - symbol_to_group_index.insert(*s, index); - } - } - - for (index, group) in sccs.iter().enumerate() { - for s in group { - let reachable = &referenced_symbols[s]; - for r in reachable { - let new_index = symbol_to_group_index[r]; - - if new_index != index { - group_to_groups[index].push(new_index); - } + for (index, references) in referenced.iter().enumerate() { + for referenced in references { + match symbols.iter().position(|k| k == referenced) { + None => { /* not defined in this scope */ } + Some(ref_index) => matrix.set_row_col(index, ref_index, true), } } } - for v in group_symbols.iter_mut() { - v.sort(); - v.dedup(); + // find the strongly connected components and their relations + let nodes: Vec<_> = (0..capacity as u32).collect(); + + let mut output = Vec::with_capacity(capacity); + + for group in matrix.strongly_connected_components(&nodes).groups() { + for index in group.iter_ones() { + output.push(symbols[index]) + } } - let all_successors_with_self = |group: &usize| group_to_groups[*group].iter().copied(); - - // split into self-recursive and mutually recursive - match topological_sort(&groups, all_successors_with_self) { - Ok(result) => result - .iter() - .rev() - .flat_map(|group_index| sccs[*group_index].iter()) - .copied() - .collect(), - - Err(_loop_detected) => unreachable!("the groups cannot recurse"), - } + output } #[inline(always)] -pub fn canonicalize_defs<'a>( +pub(crate) fn canonicalize_defs<'a>( env: &mut Env<'a>, mut output: Output, var_store: &mut VarStore, - original_scope: &Scope, + scope: &mut Scope, loc_defs: &'a [&'a Loc>], pattern_type: PatternType, -) -> (CanDefs, Scope, Output, MutMap) { +) -> (CanDefs, Output, MutMap) { // Canonicalizing defs while detecting shadowing involves a multi-step process: // // 1. Go through each of the patterns. @@ -217,39 +230,21 @@ pub fn canonicalize_defs<'a>( // This naturally handles recursion too, because a given expr which refers // to itself won't be processed until after its def has been added to scope. - // Record both the original and final idents from the scope, - // so we can diff them while detecting unused defs. - let mut scope = original_scope.clone(); let num_defs = loc_defs.len(); - let mut refs_by_symbol = MutMap::default(); - let mut can_defs_by_symbol = HashMap::with_capacity_and_hasher(num_defs, default_hasher()); - - let mut type_defs = Vec::with_capacity(num_defs); + let mut pending_type_defs = Vec::with_capacity(num_defs); let mut value_defs = Vec::with_capacity(num_defs); for loc_def in loc_defs { match loc_def.value.unroll_def() { - Ok(type_def) => type_defs.push(Loc::at(loc_def.region, type_def)), + Ok(type_def) => { + pending_type_defs.push(to_pending_type_def(env, type_def, scope, pattern_type)) + } Err(value_def) => value_defs.push(Loc::at(loc_def.region, value_def)), } } - // We need to canonicalize all the type defs first. - // Clippy is wrong - we do need the collect, otherwise "env" and "scope" are captured for - // longer than we'd like. - #[allow(clippy::needless_collect)] - let pending_type_defs = type_defs - .into_iter() - .filter_map(|loc_def| { - to_pending_type_def(env, loc_def.value, &mut scope).map(|(new_output, pending_def)| { - output.union(new_output); - pending_def - }) - }) - .collect::>(); - if cfg!(debug_assertions) { - env.home.register_debug_idents(&env.ident_ids); + scope.register_debug_idents(); } enum TypeDef<'a> { @@ -265,9 +260,16 @@ pub fn canonicalize_defs<'a>( let mut type_defs = MutMap::default(); let mut abilities_in_scope = Vec::new(); - let mut referenced_type_symbols = MutMap::default(); + let mut referenced_type_symbols = VecMap::default(); + + // Determine which idents we introduced in the course of this process. + let mut symbols_introduced = MutMap::default(); for pending_def in pending_type_defs.into_iter() { + if let Some((symbol, region)) = pending_def.introduction() { + symbols_introduced.insert(symbol, region); + } + match pending_def { PendingTypeDef::Alias { name, @@ -277,7 +279,8 @@ pub fn canonicalize_defs<'a>( } => { let referenced_symbols = crate::annotation::find_type_def_symbols( env.home, - &mut env.ident_ids, + // TODO IDENT_IDS + &mut scope.ident_ids, &ann.value, ); @@ -294,7 +297,8 @@ pub fn canonicalize_defs<'a>( // definition. referenced_symbols.extend(crate::annotation::find_type_def_symbols( env.home, - &mut env.ident_ids, + // TODO IDENT_IDS + &mut scope.ident_ids, &member.typ.value, )); } @@ -303,21 +307,30 @@ pub fn canonicalize_defs<'a>( type_defs.insert(name.value, TypeDef::Ability(name, members)); abilities_in_scope.push(name.value); } - PendingTypeDef::InvalidAlias { .. } | PendingTypeDef::InvalidAbility { .. } => { /* ignore */ - } + PendingTypeDef::InvalidAlias { .. } + | PendingTypeDef::InvalidAbility { .. } + | PendingTypeDef::AbilityShadows + | PendingTypeDef::ShadowedAlias { .. } + | PendingTypeDef::AbilityNotOnToplevel => { /* ignore */ } } } let sorted = sort_type_defs_before_introduction(referenced_type_symbols); - let mut aliases = SendMap::default(); + let mut aliases = VecMap::default(); let mut abilities = MutMap::default(); for type_name in sorted { match type_defs.remove(&type_name).unwrap() { TypeDef::AliasLike(name, vars, ann, kind) => { let symbol = name.value; - let can_ann = - canonicalize_annotation(env, &mut scope, &ann.value, ann.region, var_store); + let can_ann = canonicalize_annotation( + env, + scope, + &ann.value, + ann.region, + var_store, + &abilities_in_scope, + ); // Does this alias reference any abilities? For now, we don't permit that. let ability_references = can_ann @@ -335,8 +348,7 @@ pub fn canonicalize_defs<'a>( // Record all the annotation's references in output.references.lookups for symbol in can_ann.references { - output.references.type_lookups.insert(symbol); - output.references.referenced_type_defs.insert(symbol); + output.references.insert_type_lookup(symbol); } let mut can_vars: Vec> = Vec::with_capacity(vars.len()); @@ -344,7 +356,8 @@ pub fn canonicalize_defs<'a>( let mut named = can_ann.introduced_variables.named; for loc_lowercase in vars.iter() { - match named.iter().position(|nv| nv.name == loc_lowercase.value) { + let opt_index = named.iter().position(|nv| nv.name == loc_lowercase.value); + match opt_index { Some(index) => { // This is a valid lowercase rigid var for the type def. let named_variable = named.swap_remove(index); @@ -405,7 +418,8 @@ pub fn canonicalize_defs<'a>( can_ann.typ.clone(), kind, ); - aliases.insert(symbol, alias.clone()); + + aliases.insert(symbol, alias); } TypeDef::Ability(name, members) => { @@ -419,6 +433,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); + for (symbol, alias) in aliases.iter() { scope.add_alias( *symbol, @@ -429,35 +444,135 @@ pub fn canonicalize_defs<'a>( ); } - // Now we can go through and resolve all pending abilities, to add them to scope. + // Resolve all pending abilities, to add them to scope. + resolve_abilities( + env, + &mut output, + var_store, + scope, + abilities, + &abilities_in_scope, + pattern_type, + ); + + // Now that we have the scope completely assembled, and shadowing resolved, + // we're ready to canonicalize any body exprs. + + // Canonicalize all the patterns, record shadowing problems, and store + // the ast::Expr values in pending_exprs for further canonicalization + // once we've finished assembling the entire scope. + let mut pending_value_defs = Vec::with_capacity(value_defs.len()); + for loc_def in value_defs.into_iter() { + let mut new_output = Output::default(); + match to_pending_value_def( + env, + var_store, + loc_def.value, + scope, + &mut new_output, + pattern_type, + ) { + None => { /* skip */ } + Some(pending_def) => { + // Record the ast::Expr for later. We'll do another pass through these + // once we have the entire scope assembled. If we were to canonicalize + // the exprs right now, they wouldn't have symbols in scope from defs + // that get would have gotten added later in the defs list! + pending_value_defs.push(pending_def); + output.union(new_output); + } + } + } + + let mut symbol_to_index: Vec<(IdentId, u32)> = Vec::with_capacity(pending_value_defs.len()); + + for (def_index, pending_def) in pending_value_defs.iter().enumerate() { + for (s, r) in BindingsFromPattern::new(pending_def.loc_pattern()) { + // store the top-level defs, used to ensure that closures won't capture them + if let PatternType::TopLevelDef = pattern_type { + env.top_level_symbols.insert(s); + } + + symbols_introduced.insert(s, r); + + debug_assert_eq!(env.home, s.module_id()); + debug_assert!( + !symbol_to_index.iter().any(|(id, _)| *id == s.ident_id()), + "{:?}", + s + ); + + symbol_to_index.push((s.ident_id(), def_index as u32)); + } + } + + let capacity = pending_value_defs.len(); + let mut defs = Vec::with_capacity(capacity); + let mut def_ordering = DefOrdering::from_symbol_to_id(env.home, symbol_to_index, capacity); + + for (def_id, pending_def) in pending_value_defs.into_iter().enumerate() { + let temp_output = canonicalize_pending_value_def( + env, + pending_def, + output, + scope, + var_store, + &mut aliases, + &abilities_in_scope, + ); + + output = temp_output.output; + + defs.push(Some(temp_output.def)); + + def_ordering.insert_symbol_references(def_id as u32, &temp_output.references) + } + + ( + CanDefs { + defs, + def_ordering, + // The result needs a thread-safe `SendMap` + aliases, + }, + output, + symbols_introduced, + ) +} + +/// Resolve all pending abilities, to add them to scope. +#[allow(clippy::too_many_arguments)] +fn resolve_abilities<'a>( + env: &mut Env<'a>, + output: &mut Output, + var_store: &mut VarStore, + scope: &mut Scope, + abilities: MutMap, &[AbilityMember])>, + abilities_in_scope: &[Symbol], + pattern_type: PatternType, +) { for (loc_ability_name, members) in abilities.into_values() { let mut can_members = Vec::with_capacity(members.len()); for member in members { - let member_annot = canonicalize_annotation_with_possible_clauses( + let member_annot = canonicalize_annotation( env, - &mut scope, + scope, &member.typ.value, member.typ.region, var_store, - &abilities_in_scope, + abilities_in_scope, ); // Record all the annotation's references in output.references.lookups for symbol in member_annot.references { - output.references.type_lookups.insert(symbol); - output.references.referenced_type_defs.insert(symbol); + output.references.insert_type_lookup(symbol); } let name_region = member.name.region; let member_name = member.name.extract_spaces().item; - let member_sym = match scope.introduce( - member_name.into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - name_region, - ) { + let member_sym = match scope.introduce(member_name.into(), name_region) { Ok(sym) => sym, Err((original_region, shadow, _new_symbol)) => { env.problem(roc_problem::can::Problem::Shadowing { @@ -470,6 +585,10 @@ pub fn canonicalize_defs<'a>( } }; + if pattern_type == PatternType::TopLevelDef { + env.top_level_symbols.insert(member_sym); + } + // What variables in the annotation are bound to the parent ability, and what variables // are bound to some other ability? let (variables_bound_to_ability, variables_bound_to_other_abilities): (Vec<_>, Vec<_>) = @@ -542,7 +661,13 @@ pub fn canonicalize_defs<'a>( flex_vars: iv.collect_flex(), }; - can_members.push((member_sym, name_region, member_annot.typ, variables)); + can_members.push(( + member_sym, + name_region, + var_store.fresh(), + member_annot.typ, + variables, + )); } // Store what symbols a type must define implementations for to have this ability. @@ -550,99 +675,105 @@ pub fn canonicalize_defs<'a>( .abilities_store .register_ability(loc_ability_name.value, can_members); } +} - // Now that we have the scope completely assembled, and shadowing resolved, - // we're ready to canonicalize any body exprs. +#[derive(Debug)] +struct DefOrdering { + home: ModuleId, + symbol_to_id: Vec<(IdentId, u32)>, - // Canonicalize all the patterns, record shadowing problems, and store - // the ast::Expr values in pending_exprs for further canonicalization - // once we've finished assembling the entire scope. - let mut pending_value_defs = Vec::with_capacity(value_defs.len()); - for loc_def in value_defs.into_iter() { - match to_pending_value_def(env, var_store, loc_def.value, &mut scope, pattern_type) { - None => { /* skip */ } - Some((new_output, pending_def)) => { - // store the top-level defs, used to ensure that closures won't capture them - if let PatternType::TopLevelDef = pattern_type { - match &pending_def { - PendingValueDef::AnnotationOnly(_, loc_can_pattern, _) - | PendingValueDef::Body(_, loc_can_pattern, _) - | PendingValueDef::TypedBody(_, loc_can_pattern, _, _) => { - env.top_level_symbols.extend( - bindings_from_patterns(std::iter::once(loc_can_pattern)) - .iter() - .map(|t| t.0), - ) - } + // an length x length matrix indicating who references who + references: ReferenceMatrix, + + // references without looking into closure bodies. + // Used to spot definitely-wrong recursion + direct_references: ReferenceMatrix, +} + +impl DefOrdering { + fn from_symbol_to_id( + home: ModuleId, + symbol_to_id: Vec<(IdentId, u32)>, + capacity: usize, + ) -> Self { + // NOTE: because of `Pair a b = someDef` patterns, we can have more symbols than defs + // but because `_ = someDef` we can also have more defs than symbols + + Self { + home, + symbol_to_id, + references: ReferenceMatrix::new(capacity), + direct_references: ReferenceMatrix::new(capacity), + } + } + + fn insert_symbol_references(&mut self, def_id: u32, def_references: &DefReferences) { + match def_references { + DefReferences::Value(references) => { + let it = references.value_lookups().chain(references.calls()); + + for referenced in it { + if let Some(ref_id) = self.get_id(*referenced) { + self.references + .set_row_col(def_id as usize, ref_id as usize, true); + + self.direct_references + .set_row_col(def_id as usize, ref_id as usize, true); } } - // Record the ast::Expr for later. We'll do another pass through these - // once we have the entire scope assembled. If we were to canonicalize - // the exprs right now, they wouldn't have symbols in scope from defs - // that get would have gotten added later in the defs list! - pending_value_defs.push(pending_def); - output.union(new_output); + } + DefReferences::Function(references) => { + let it = references.value_lookups().chain(references.calls()); + + for referenced in it { + if let Some(ref_id) = self.get_id(*referenced) { + self.references + .set_row_col(def_id as usize, ref_id as usize, true); + } + } + } + DefReferences::AnnotationWithoutBody => { + // annotatations without bodies don't reference any other definitions } } } - for pending_def in pending_value_defs.into_iter() { - output = canonicalize_pending_value_def( - env, - pending_def, - output, - &mut scope, - &mut can_defs_by_symbol, - var_store, - &mut refs_by_symbol, - &mut aliases, - ); - - // TODO we should do something with these references; they include - // things like type annotations. - } - - // Determine which idents we introduced in the course of this process. - let mut symbols_introduced = MutMap::default(); - - for (symbol, region) in scope.symbols() { - if !original_scope.contains_symbol(*symbol) { - symbols_introduced.insert(*symbol, *region); + fn get_id(&self, symbol: Symbol) -> Option { + if symbol.module_id() != self.home { + return None; } + + let target = symbol.ident_id(); + + for (ident_id, def_id) in self.symbol_to_id.iter() { + if target == *ident_id { + return Some(*def_id); + } + } + + None } - // This returns both the defs info as well as the new scope. - // - // We have to return the new scope because we added defs to it - // (and those lookups shouldn't fail later, e.g. when canonicalizing - // the return expr), but we didn't want to mutate the original scope - // directly because we wanted to keep a clone of it around to diff - // when looking for unused idents. - // - // We have to return the scope separately from the defs, because the - // defs need to get moved later. - ( - CanDefs { - refs_by_symbol, - can_defs_by_symbol, - // The result needs a thread-safe `SendMap` - aliases: aliases.into_iter().collect(), - }, - scope, - output, - symbols_introduced, - ) + fn get_symbol(&self, id: usize) -> Option { + for (ident_id, def_id) in self.symbol_to_id.iter() { + if id as u32 == *def_id { + return Some(Symbol::new(self.home, *ident_id)); + } + } + + None + } } #[inline(always)] -pub fn sort_can_defs( +pub(crate) fn sort_can_defs( env: &mut Env<'_>, defs: CanDefs, mut output: Output, ) -> (Result, RuntimeError>, Output) { let CanDefs { - refs_by_symbol, - mut can_defs_by_symbol, + mut defs, + def_ordering, aliases, } = defs; @@ -650,388 +781,121 @@ pub fn sort_can_defs( output.aliases.insert(symbol, alias); } - let mut defined_symbols: Vec = Vec::new(); + macro_rules! take_def { + ($index:expr) => { + match defs[$index].take() { + Some(def) => def, + None => { + // NOTE: a `_ = someDef` can mean we don't have a symbol here + let symbol = def_ordering.get_symbol($index); - for symbol in can_defs_by_symbol.keys() { - defined_symbols.push(*symbol); + roc_error_macros::internal_error!("def not available {:?}", symbol) + } + } + }; } - // Use topological sort to reorder the defs based on their dependencies to one another. - // This way, during code gen, no def will refer to a value that hasn't been initialized yet. - // As a bonus, the topological sort also reveals any cycles between the defs, allowing - // us to give a CircularAssignment error for invalid (mutual) recursion, and a `DeclareRec` for mutually - // recursive definitions. + let nodes: Vec<_> = (0..defs.len() as u32).collect(); - // All successors that occur in the body of a symbol. - let all_successors_without_self = |symbol: &Symbol| -> Vec { - // This may not be in refs_by_symbol. For example, the `f` in `f x` here: - // - // f = \z -> z - // - // (\x -> - // a = f x - // x - // ) - // - // It's not part of the current defs (the one with `a = f x`); rather, - // it's in the enclosing scope. It's still referenced though, so successors - // will receive it as an argument! - match refs_by_symbol.get(symbol) { - Some((_, references)) => { - // We can only sort the symbols at the current level. That is safe because - // symbols defined at higher levels cannot refer to symbols at lower levels. - // Therefore they can never form a cycle! - // - // In the above example, `f` cannot reference `a`, and in the closure - // a call to `f` cannot cycle back to `a`. - let mut loc_succ = local_successors_with_duplicates(references, &env.closures); + // We first perform SCC based on any reference, both variable usage and calls + // considering both value definitions and function bodies. This will spot any + // recursive relations between any 2 definitions. + let sccs = def_ordering + .references + .strongly_connected_components(&nodes); - // if the current symbol is a closure, peek into its body - if let Some(References { value_lookups, .. }) = env.closures.get(symbol) { - let home = env.home; + let mut declarations = Vec::new(); - for lookup in value_lookups { - if lookup != symbol && lookup.module_id() == home { - // DO NOT register a self-call behind a lambda! - // - // We allow `boom = \_ -> boom {}`, but not `x = x` - loc_succ.push(*lookup); - } - } - } + for group in sccs.groups() { + if group.count_ones() == 1 { + // a group with a single Def, nice and simple + let index = group.iter_ones().next().unwrap(); - // remove anything that is not defined in the current block - loc_succ.retain(|key| defined_symbols.contains(key)); + let def = take_def!(index); - loc_succ.sort(); - loc_succ.dedup(); + let declaration = if def_ordering.direct_references.get_row_col(index, index) { + // a definition like `x = x + 1`, which is invalid in roc + let symbol = def_ordering.get_symbol(index).unwrap(); - loc_succ - } - None => vec![], - } - }; + let entries = vec![make_cycle_entry(symbol, &def)]; - // All successors that occur in the body of a symbol, including the symbol itself - // This is required to determine whether a symbol is recursive. Recursive symbols - // (that are not faulty) always need a DeclareRec, even if there is just one symbol in the - // group - let mut all_successors_with_self = |symbol: &Symbol| -> Vec { - // This may not be in refs_by_symbol. For example, the `f` in `f x` here: - // - // f = \z -> z - // - // (\x -> - // a = f x - // x - // ) - // - // It's not part of the current defs (the one with `a = f x`); rather, - // it's in the enclosing scope. It's still referenced though, so successors - // will receive it as an argument! - match refs_by_symbol.get(symbol) { - Some((_, references)) => { - // We can only sort the symbols at the current level. That is safe because - // symbols defined at higher levels cannot refer to symbols at lower levels. - // Therefore they can never form a cycle! - // - // In the above example, `f` cannot reference `a`, and in the closure - // a call to `f` cannot cycle back to `a`. - let mut loc_succ = local_successors_with_duplicates(references, &env.closures); + let problem = Problem::RuntimeError(RuntimeError::CircularDef(entries.clone())); + env.problem(problem); - // if the current symbol is a closure, peek into its body - if let Some(References { value_lookups, .. }) = env.closures.get(symbol) { - for lookup in value_lookups { - loc_succ.push(*lookup); - } - } - - // remove anything that is not defined in the current block - loc_succ.retain(|key| defined_symbols.contains(key)); - - loc_succ.sort(); - loc_succ.dedup(); - - loc_succ - } - None => vec![], - } - }; - - // If a symbol is a direct successor of itself, there is an invalid cycle. - // The difference with the function above is that this one does not look behind lambdas, - // but does consider direct self-recursion. - let direct_successors = |symbol: &Symbol| -> Vec { - match refs_by_symbol.get(symbol) { - Some((_, references)) => { - let mut loc_succ = local_successors_with_duplicates(references, &env.closures); - - // NOTE: if the symbol is a closure we DONT look into its body - - // remove anything that is not defined in the current block - loc_succ.retain(|key| defined_symbols.contains(key)); - - // NOTE: direct recursion does matter here: `x = x` is invalid recursion! - - loc_succ.sort(); - loc_succ.dedup(); - - loc_succ - } - None => vec![], - } - }; - - // TODO also do the same `addDirects` check elm/compiler does, so we can - // report an error if a recursive definition can't possibly terminate! - match ven_graph::topological_sort_into_groups( - defined_symbols.as_slice(), - all_successors_without_self, - ) { - Ok(groups) => { - let mut declarations = Vec::new(); - - // groups are in reversed order - for group in groups.into_iter().rev() { - group_to_declaration( - &group, - &env.closures, - &mut all_successors_with_self, - &mut can_defs_by_symbol, - &mut declarations, - ); - } - - (Ok(declarations), output) - } - Err((mut groups, nodes_in_cycle)) => { - let mut declarations = Vec::new(); - let mut problems = Vec::new(); - - // nodes_in_cycle are symbols that form a syntactic cycle. That isn't always a problem, - // and in general it's impossible to decide whether it is. So we use a crude heuristic: - // - // Definitions where the cycle occurs behind a lambda are OK - // - // boom = \_ -> boom {} - // - // But otherwise we report an error, e.g. - // - // foo = if b then foo else bar - - for cycle in strongly_connected_components(&nodes_in_cycle, all_successors_without_self) - { - // check whether the cycle is faulty, which is when it has - // a direct successor in the current cycle. This catches things like: - // - // x = x - // - // or - // - // p = q - // q = p - let is_invalid_cycle = match cycle.get(0) { - Some(symbol) => { - let mut succs = direct_successors(symbol); - - succs.retain(|key| cycle.contains(key)); - - !succs.is_empty() - } - None => false, - }; - - if is_invalid_cycle { - // We want to show the entire cycle in the error message, so expand it out. - let mut entries = Vec::new(); - - for symbol in &cycle { - match refs_by_symbol.get(symbol) { - None => unreachable!( - r#"Symbol `{:?}` not found in refs_by_symbol! refs_by_symbol was: {:?}"#, - symbol, refs_by_symbol - ), - Some((region, _)) => { - let expr_region = - can_defs_by_symbol.get(symbol).unwrap().loc_expr.region; - - let entry = CycleEntry { - symbol: *symbol, - symbol_region: *region, - expr_region, - }; - - entries.push(entry); - } - } - } - - // Sort them by line number to make the report more helpful. - entries.sort_by_key(|entry| entry.symbol_region); - - problems.push(Problem::RuntimeError(RuntimeError::CircularDef( - entries.clone(), - ))); - - declarations.push(Declaration::InvalidCycle(entries)); - } - - // if it's an invalid cycle, other groups may depend on the - // symbols defined here, so also push this cycle onto the groups - // - // if it's not an invalid cycle, this is slightly inefficient, - // because we know this becomes exactly one DeclareRec already - groups.push(cycle); - } - - // now we have a collection of groups whose dependencies are not cyclic. - // They are however not yet topologically sorted. Here we have to get a bit - // creative to get all the definitions in the correct sorted order. - - let mut group_ids = Vec::with_capacity(groups.len()); - let mut symbol_to_group_index = MutMap::default(); - for (i, group) in groups.iter().enumerate() { - for symbol in group { - symbol_to_group_index.insert(*symbol, i); - } - - group_ids.push(i); - } - - let successors_of_group = |group_id: &usize| { - let mut result = MutSet::default(); - - // for each symbol in this group - for symbol in &groups[*group_id] { - // find its successors - for succ in all_successors_without_self(symbol) { - // and add its group to the result - match symbol_to_group_index.get(&succ) { - Some(index) => { - result.insert(*index); - } - None => unreachable!("no index for symbol {:?}", succ), - } - } - } - - // don't introduce any cycles to self - result.remove(group_id); - - result + Declaration::InvalidCycle(entries) + } else if def_ordering.references.get_row_col(index, index) { + // this function calls itself, and must be typechecked as a recursive def + Declaration::DeclareRec(vec![mark_def_recursive(def)]) + } else { + Declaration::Declare(def) }; - match ven_graph::topological_sort_into_groups(&group_ids, successors_of_group) { - Ok(sorted_group_ids) => { - for sorted_group in sorted_group_ids.iter().rev() { - for group_id in sorted_group.iter().rev() { - let group = &groups[*group_id]; + declarations.push(declaration); + } else { + // There is something recursive going on between the Defs of this group. + // Now we use the direct_references to see if it is clearly invalid recursion, e.g. + // + // x = y + // y = x + // + // We allow indirect recursion (behind a lambda), e.g. + // + // boom = \{} -> boom {} + // + // In general we cannot spot faulty recursion (halting problem) so this is our best attempt + let nodes: Vec<_> = group.iter_ones().map(|v| v as u32).collect(); + let direct_sccs = def_ordering + .direct_references + .strongly_connected_components(&nodes); - group_to_declaration( - group, - &env.closures, - &mut all_successors_with_self, - &mut can_defs_by_symbol, - &mut declarations, - ); - } - } + let declaration = if direct_sccs.groups().count() == 1 { + // all defs are part of the same direct cycle, that is invalid! + let mut entries = Vec::with_capacity(group.count_ones()); + + for index in group.iter_ones() { + let def = take_def!(index); + let symbol = def_ordering.get_symbol(index).unwrap(); + + entries.push(make_cycle_entry(symbol, &def)) } - Err(_) => unreachable!("there should be no cycles now!"), - } - for problem in problems { + let problem = Problem::RuntimeError(RuntimeError::CircularDef(entries.clone())); env.problem(problem); - } - (Ok(declarations), output) + Declaration::InvalidCycle(entries) + } else { + let rec_defs = group + .iter_ones() + .map(|index| mark_def_recursive(take_def!(index))) + .collect(); + + Declaration::DeclareRec(rec_defs) + }; + + declarations.push(declaration); } } + + (Ok(declarations), output) } -fn group_to_declaration( - group: &[Symbol], - closures: &MutMap, - successors: &mut dyn FnMut(&Symbol) -> Vec, - can_defs_by_symbol: &mut MutMap, - declarations: &mut Vec, -) { - use Declaration::*; +fn mark_def_recursive(mut def: Def) -> Def { + if let Closure(ClosureData { + recursive: recursive @ Recursive::NotRecursive, + .. + }) = &mut def.loc_expr.value + { + *recursive = Recursive::Recursive + } - // We want only successors in the current group, otherwise definitions get duplicated - let filtered_successors = |symbol: &Symbol| -> Vec { - let mut result = successors(symbol); + def +} - result.retain(|key| group.contains(key)); - result - }; - - // Patterns like - // - // { x, y } = someDef - // - // Can bind multiple symbols. When not incorrectly recursive (which is guaranteed in this function), - // normally `someDef` would be inserted twice. We use the region of the pattern as a unique key - // for a definition, so every definition is only inserted (thus typechecked and emitted) once - let mut seen_pattern_regions: Vec = Vec::with_capacity(2); - - for cycle in strongly_connected_components(group, filtered_successors) { - if cycle.len() == 1 { - let symbol = &cycle[0]; - - match can_defs_by_symbol.remove(symbol) { - Some(mut new_def) => { - // Determine recursivity of closures that are not tail-recursive - if let Closure(ClosureData { - recursive: recursive @ Recursive::NotRecursive, - .. - }) = &mut new_def.loc_expr.value - { - *recursive = closure_recursivity(*symbol, closures); - } - - let is_recursive = successors(symbol).contains(symbol); - - if !seen_pattern_regions.contains(&new_def.loc_pattern.region) { - seen_pattern_regions.push(new_def.loc_pattern.region); - - if is_recursive { - declarations.push(DeclareRec(vec![new_def])); - } else { - declarations.push(Declare(new_def)); - } - } - } - None => roc_error_macros::internal_error!("def not available {:?}", symbol), - } - } else { - let mut can_defs = Vec::new(); - - // Topological sort gives us the reverse of the sorting we want! - for symbol in cycle.into_iter().rev() { - match can_defs_by_symbol.remove(&symbol) { - Some(mut new_def) => { - // Determine recursivity of closures that are not tail-recursive - if let Closure(ClosureData { - recursive: recursive @ Recursive::NotRecursive, - .. - }) = &mut new_def.loc_expr.value - { - *recursive = closure_recursivity(symbol, closures); - } - - if !seen_pattern_regions.contains(&new_def.loc_pattern.region) { - seen_pattern_regions.push(new_def.loc_pattern.region); - - can_defs.push(new_def); - } - } - None => roc_error_macros::internal_error!("def not available {:?}", symbol), - } - } - - declarations.push(DeclareRec(can_defs)); - } +fn make_cycle_entry(symbol: Symbol, def: &Def) -> CycleEntry { + CycleEntry { + symbol, + symbol_region: def.loc_pattern.region, + expr_region: def.loc_expr.region, } } @@ -1113,20 +977,35 @@ fn single_can_def( fn add_annotation_aliases( type_annotation: &crate::annotation::Annotation, - aliases: &mut ImMap, + aliases: &mut VecMap, ) { for (name, alias) in type_annotation.aliases.iter() { - match aliases.entry(*name) { - ImEntry::Occupied(_) => { - // do nothing - } - ImEntry::Vacant(vacant) => { - vacant.insert(alias.clone()); - } + if !aliases.contains_key(name) { + aliases.insert(*name, alias.clone()); } } } +// Functions' references don't count in defs. +// See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its +// parent commit for the bug this fixed! +enum DefReferences { + /// A value may not reference itself + Value(References), + + /// If the def is a function, different rules apply (it can call itself) + Function(References), + + /// An annotation without a body references no other defs + AnnotationWithoutBody, +} + +struct DefOutput { + output: Output, + def: Def, + references: DefReferences, +} + // TODO trim down these arguments! #[allow(clippy::too_many_arguments)] #[allow(clippy::cognitive_complexity)] @@ -1135,29 +1014,33 @@ fn canonicalize_pending_value_def<'a>( pending_def: PendingValueDef<'a>, mut output: Output, scope: &mut Scope, - can_defs_by_symbol: &mut MutMap, var_store: &mut VarStore, - refs_by_symbol: &mut MutMap, - aliases: &mut ImMap, -) -> Output { + aliases: &mut VecMap, + abilities_in_scope: &[Symbol], +) -> DefOutput { use PendingValueDef::*; - // Make types for the body expr, even if we won't end up having a body. - let expr_var = var_store.fresh(); - let mut vars_by_symbol = SendMap::default(); - match pending_def { AnnotationOnly(_, loc_can_pattern, loc_ann) => { + // Make types for the body expr, even if we won't end up having a body. + let expr_var = var_store.fresh(); + let mut vars_by_symbol = SendMap::default(); + // annotation sans body cannot introduce new rigids that are visible in other annotations // but the rigids can show up in type error messages, so still register them - let type_annotation = - canonicalize_annotation(env, scope, &loc_ann.value, loc_ann.region, var_store); + let type_annotation = canonicalize_annotation( + env, + scope, + &loc_ann.value, + loc_ann.region, + var_store, + abilities_in_scope, + ); // Record all the annotation's references in output.references.lookups for symbol in type_annotation.references.iter() { - output.references.type_lookups.insert(*symbol); - output.references.referenced_type_defs.insert(*symbol); + output.references.insert_type_lookup(*symbol); } add_annotation_aliases(&type_annotation, aliases); @@ -1191,7 +1074,7 @@ fn canonicalize_pending_value_def<'a>( region: loc_ann.region, } } else { - let symbol = env.gen_unique_symbol(); + let symbol = scope.gen_unique_symbol(); // generate a fake pattern for each argument. this makes signatures // that are functions only crash when they are applied. @@ -1203,7 +1086,11 @@ fn canonicalize_pending_value_def<'a>( region: Region::zero(), }; - underscores.push((var_store.fresh(), underscore)); + underscores.push(( + var_store.fresh(), + AnnotatedMark::known_exhaustive(), + underscore, + )); } let body_expr = Loc { @@ -1227,57 +1114,34 @@ fn canonicalize_pending_value_def<'a>( } }; - if let Pattern::Identifier(symbol) - | Pattern::AbilityMemberSpecialization { ident: symbol, .. } = loc_can_pattern.value - { - let def = single_can_def( - loc_can_pattern, - loc_can_expr, - expr_var, - Some(Loc::at(loc_ann.region, type_annotation)), - vars_by_symbol.clone(), - ); - can_defs_by_symbol.insert(symbol, def); - } else { - for (_, (symbol, _)) in scope.idents() { - if !vars_by_symbol.contains_key(symbol) { - continue; - } + let def = single_can_def( + loc_can_pattern, + loc_can_expr, + expr_var, + Some(Loc::at(loc_ann.region, type_annotation)), + vars_by_symbol.clone(), + ); - // We could potentially avoid some clones here by using Rc strategically, - // but the total amount of cloning going on here should typically be minimal. - can_defs_by_symbol.insert( - *symbol, - Def { - expr_var, - // TODO try to remove this .clone()! - loc_pattern: loc_can_pattern.clone(), - loc_expr: Loc { - region: loc_can_expr.region, - // TODO try to remove this .clone()! - value: loc_can_expr.value.clone(), - }, - pattern_vars: vars_by_symbol.clone(), - annotation: Some(Annotation { - signature: type_annotation.typ.clone(), - introduced_variables: output.introduced_variables.clone(), - aliases: type_annotation.aliases.clone(), - region: loc_ann.region, - }), - }, - ); - } + DefOutput { + output, + references: DefReferences::AnnotationWithoutBody, + def, } } TypedBody(_loc_pattern, loc_can_pattern, loc_ann, loc_expr) => { - let type_annotation = - canonicalize_annotation(env, scope, &loc_ann.value, loc_ann.region, var_store); + let type_annotation = canonicalize_annotation( + env, + scope, + &loc_ann.value, + loc_ann.region, + var_store, + abilities_in_scope, + ); // Record all the annotation's references in output.references.lookups for symbol in type_annotation.references.iter() { - output.references.type_lookups.insert(*symbol); - output.references.referenced_type_defs.insert(*symbol); + output.references.insert_type_lookup(*symbol); } add_annotation_aliases(&type_annotation, aliases); @@ -1286,294 +1150,137 @@ fn canonicalize_pending_value_def<'a>( .introduced_variables .union(&type_annotation.introduced_variables); - // bookkeeping for tail-call detection. If we're assigning to an - // identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called. - let outer_identifier = env.tailcallable_symbol; - - if let Pattern::Identifier(ref defined_symbol) = &loc_can_pattern.value { - env.tailcallable_symbol = Some(*defined_symbol); - }; - - // register the name of this closure, to make sure the closure won't capture it's own name - if let (Pattern::Identifier(ref defined_symbol), &ast::Expr::Closure(_, _)) = - (&loc_can_pattern.value, &loc_expr.value) - { - env.closure_name_symbol = Some(*defined_symbol); - }; - - pattern_to_vars_by_symbol(&mut vars_by_symbol, &loc_can_pattern.value, expr_var); - - let (mut loc_can_expr, can_output) = - canonicalize_expr(env, var_store, scope, loc_expr.region, &loc_expr.value); - - output.references = output.references.union(can_output.references.clone()); - - // reset the tailcallable_symbol - env.tailcallable_symbol = outer_identifier; - - // First, make sure we are actually assigning an identifier instead of (for example) a tag. - // - // If we're assigning (UserId userId) = ... then this is certainly not a closure declaration, - // which also implies it's not a self tail call! - // - // Only defs of the form (foo = ...) can be closure declarations or self tail calls. - if let Pattern::Identifier(symbol) - | Pattern::AbilityMemberSpecialization { ident: symbol, .. } = loc_can_pattern.value - { - if let Closure(ClosureData { - function_type, - closure_type, - closure_ext_var, - return_type, - name: ref closure_name, - ref arguments, - loc_body: ref body, - ref captured_symbols, - .. - }) = loc_can_expr.value - { - // Since everywhere in the code it'll be referred to by its defined name, - // remove its generated name from the closure map. (We'll re-insert it later.) - let references = env.closures.remove(closure_name).unwrap_or_else(|| { - panic!( - "Tried to remove symbol {:?} from procedures, but it was not found: {:?}", - closure_name, env.closures - ) - }); - - // Re-insert the closure into the map, under its defined name. - // closures don't have a name, and therefore pick a fresh symbol. But in this - // case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...` - // and we want to reference it by that name. - env.closures.insert(symbol, references); - - // The closure is self tail recursive iff it tail calls itself (by defined name). - let is_recursive = match can_output.tail_call { - Some(tail_symbol) if tail_symbol == symbol => Recursive::TailRecursive, - _ => Recursive::NotRecursive, - }; - - // 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.value_lookups = refs.value_lookups.without(&symbol); - }); - - // renamed_closure_def = Some(&symbol); - loc_can_expr.value = Closure(ClosureData { - function_type, - closure_type, - closure_ext_var, - return_type, - name: symbol, - captured_symbols: captured_symbols.clone(), - recursive: is_recursive, - arguments: arguments.clone(), - loc_body: body.clone(), - }); - - // Functions' references don't count in defs. - // See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its - // parent commit for the bug this fixed! - let refs = References::new(); - - refs_by_symbol.insert(symbol, (loc_can_pattern.region, refs)); - } else { - let refs = can_output.references; - refs_by_symbol.insert(symbol, (loc_ann.region, refs)); - } - - let def = single_can_def( - loc_can_pattern, - loc_can_expr, - expr_var, - Some(Loc::at(loc_ann.region, type_annotation)), - vars_by_symbol.clone(), - ); - can_defs_by_symbol.insert(symbol, def); - } else { - for (_, (symbol, region)) in scope.idents() { - if !vars_by_symbol.contains_key(symbol) { - continue; - } - - let refs = can_output.references.clone(); - - refs_by_symbol.insert(*symbol, (*region, refs)); - - can_defs_by_symbol.insert( - *symbol, - Def { - expr_var, - // TODO try to remove this .clone()! - loc_pattern: loc_can_pattern.clone(), - loc_expr: Loc { - region: loc_can_expr.region, - // TODO try to remove this .clone()! - value: loc_can_expr.value.clone(), - }, - pattern_vars: vars_by_symbol.clone(), - annotation: Some(Annotation { - signature: type_annotation.typ.clone(), - introduced_variables: type_annotation.introduced_variables.clone(), - aliases: type_annotation.aliases.clone(), - region: loc_ann.region, - }), - }, - ); - } - } + canonicalize_pending_body( + env, + output, + scope, + var_store, + loc_can_pattern, + loc_expr, + Some(Loc::at(loc_ann.region, type_annotation)), + ) } - // If we have a pattern, then the def has a body (that is, it's not a - // standalone annotation), so we need to canonicalize the pattern and expr. - Body(loc_pattern, loc_can_pattern, loc_expr) => { - // bookkeeping for tail-call detection. If we're assigning to an - // identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called. - let outer_identifier = env.tailcallable_symbol; + Body(_loc_pattern, loc_can_pattern, loc_expr) => { + // + canonicalize_pending_body( + env, + output, + scope, + var_store, + loc_can_pattern, + loc_expr, + None, + ) + } + } +} - if let (&ast::Pattern::Identifier(_name), &Pattern::Identifier(ref defined_symbol)) = - (&loc_pattern.value, &loc_can_pattern.value) - { +// TODO trim down these arguments! +#[allow(clippy::too_many_arguments)] +#[allow(clippy::cognitive_complexity)] +fn canonicalize_pending_body<'a>( + env: &mut Env<'a>, + mut output: Output, + scope: &mut Scope, + var_store: &mut VarStore, + + loc_can_pattern: Loc, + loc_expr: &'a Loc, + + opt_loc_annotation: Option>, +) -> DefOutput { + // We treat closure definitions `foo = \a, b -> ...` differently from other body expressions, + // because they need more bookkeeping (for tail calls, closure captures, etc.) + // + // Only defs of the form `foo = ...` can be closure declarations or self tail calls. + let (loc_can_expr, def_references) = { + match (&loc_can_pattern.value, &loc_expr.value) { + ( + Pattern::Identifier(defined_symbol) + | Pattern::AbilityMemberSpecialization { + ident: defined_symbol, + .. + }, + ast::Expr::Closure(arguments, body), + ) => { + // bookkeeping for tail-call detection. + let outer_tailcallable = env.tailcallable_symbol; env.tailcallable_symbol = Some(*defined_symbol); - // TODO isn't types_by_symbol enough? Do we need vars_by_symbol too? - vars_by_symbol.insert(*defined_symbol, expr_var); - }; - - // register the name of this closure, to make sure the closure won't capture it's own name - if let (Pattern::Identifier(ref defined_symbol), &ast::Expr::Closure(_, _)) = - (&loc_can_pattern.value, &loc_expr.value) - { - env.closure_name_symbol = Some(*defined_symbol); - }; - - let (mut loc_can_expr, can_output) = - canonicalize_expr(env, var_store, scope, loc_expr.region, &loc_expr.value); - - // reset the tailcallable_symbol - env.tailcallable_symbol = outer_identifier; - - // First, make sure we are actually assigning an identifier instead of (for example) a tag. - // - // If we're assigning (UserId userId) = ... then this is certainly not a closure declaration, - // which also implies it's not a self tail call! - // - // Only defs of the form (foo = ...) can be closure declarations or self tail calls. - if let Pattern::Identifier(symbol) = loc_can_pattern.value { - if let Closure(ClosureData { - function_type, - closure_type, - closure_ext_var, - return_type, - name: ref closure_name, - ref arguments, - loc_body: ref body, - ref captured_symbols, - .. - }) = loc_can_expr.value - { - // Since everywhere in the code it'll be referred to by its defined name, - // remove its generated name from the closure map. (We'll re-insert it later.) - let references = env.closures.remove(closure_name).unwrap_or_else(|| { - panic!( - "Tried to remove symbol {:?} from procedures, but it was not found: {:?}", - closure_name, env.closures - ) - }); - - // Re-insert the closure into the map, under its defined name. - // closures don't have a name, and therefore pick a fresh symbol. But in this - // case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...` - // and we want to reference it by that name. - env.closures.insert(symbol, references); - - // The closure is self tail recursive iff it tail calls itself (by defined name). - let is_recursive = match can_output.tail_call { - Some(tail_symbol) if tail_symbol == symbol => Recursive::TailRecursive, - _ => Recursive::NotRecursive, - }; - - // 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.value_lookups = refs.value_lookups.without(&symbol); - }); - - loc_can_expr.value = Closure(ClosureData { - function_type, - closure_type, - closure_ext_var, - return_type, - name: symbol, - captured_symbols: captured_symbols.clone(), - recursive: is_recursive, - arguments: arguments.clone(), - loc_body: body.clone(), - }); - - // Functions' references don't count in defs. - // See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its - // parent commit for the bug this fixed! - let refs = References::new(); - refs_by_symbol.insert(symbol, (loc_pattern.region, refs)); - } else { - let refs = can_output.references.clone(); - refs_by_symbol.insert(symbol, (loc_pattern.region, refs)); - } - - let def = single_can_def( - loc_can_pattern, - loc_can_expr, - expr_var, - None, - vars_by_symbol.clone(), + let (mut closure_data, can_output) = crate::expr::canonicalize_closure( + env, + var_store, + scope, + arguments, + body, + Some(*defined_symbol), ); - can_defs_by_symbol.insert(symbol, def); - } else { - // Store the referenced locals in the refs_by_symbol map, so we can later figure out - // which defined names reference each other. - for (symbol, region) in bindings_from_patterns(std::iter::once(&loc_can_pattern)) { - let refs = can_output.references.clone(); - refs_by_symbol.insert(symbol, (region, refs)); - can_defs_by_symbol.insert( - symbol, - Def { - expr_var, - // TODO try to remove this .clone()! - loc_pattern: loc_can_pattern.clone(), - loc_expr: Loc { - // TODO try to remove this .clone()! - region: loc_can_expr.region, - value: loc_can_expr.value.clone(), - }, - pattern_vars: vars_by_symbol.clone(), - annotation: None, - }, - ); - } + // reset the tailcallable_symbol + env.tailcallable_symbol = outer_tailcallable; + + // The closure is self tail recursive iff it tail calls itself (by defined name). + let is_recursive = match can_output.tail_call { + Some(tail_symbol) if tail_symbol == *defined_symbol => Recursive::TailRecursive, + _ => Recursive::NotRecursive, + }; + + closure_data.recursive = is_recursive; + closure_data.name = *defined_symbol; + + let loc_can_expr = Loc::at(loc_expr.region, Expr::Closure(closure_data)); + + let def_references = DefReferences::Function(can_output.references.clone()); + output.union(can_output); + + (loc_can_expr, def_references) } - output.union(can_output); + _ => { + let (loc_can_expr, can_output) = + canonicalize_expr(env, var_store, scope, loc_expr.region, &loc_expr.value); + + let def_references = DefReferences::Value(can_output.references.clone()); + output.union(can_output); + + (loc_can_expr, def_references) + } } }; - output + let expr_var = var_store.fresh(); + let mut vars_by_symbol = SendMap::default(); + + pattern_to_vars_by_symbol(&mut vars_by_symbol, &loc_can_pattern.value, expr_var); + + let def = single_can_def( + loc_can_pattern, + loc_can_expr, + expr_var, + opt_loc_annotation, + vars_by_symbol, + ); + + DefOutput { + output, + references: def_references, + def, + } } #[inline(always)] pub fn can_defs_with_return<'a>( env: &mut Env<'a>, var_store: &mut VarStore, - scope: Scope, + scope: &mut Scope, loc_defs: &'a [&'a Loc>], loc_ret: &'a Loc>, ) -> (Expr, Output) { - let (unsorted, mut scope, defs_output, symbols_introduced) = canonicalize_defs( + let (unsorted, defs_output, symbols_introduced) = canonicalize_defs( env, Output::default(), var_store, - &scope, + scope, loc_defs, PatternType::DefExpr, ); @@ -1581,18 +1288,17 @@ pub fn can_defs_with_return<'a>( // The def as a whole is a tail call iff its return expression is a tail call. // Use its output as a starting point because its tail_call already has the right answer! let (ret_expr, mut output) = - canonicalize_expr(env, var_store, &mut scope, loc_ret.region, &loc_ret.value); + canonicalize_expr(env, var_store, scope, loc_ret.region, &loc_ret.value); output .introduced_variables .union(&defs_output.introduced_variables); - output.references = output.references.union(defs_output.references); + output.references.union_mut(&defs_output.references); // 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_value_lookup(symbol) - && !output.references.has_type_lookup(symbol) + if !output.references.has_type_or_value_lookup(symbol) && !scope.abilities_store.is_specialization_name(symbol) { env.problem(Problem::UnusedDef(symbol, region)); @@ -1634,45 +1340,12 @@ fn decl_to_let(var_store: &mut VarStore, decl: Declaration, loc_ret: Loc) } } -fn closure_recursivity(symbol: Symbol, closures: &MutMap) -> Recursive { - let mut visited = MutSet::default(); - - let mut stack = Vec::new(); - - if let Some(references) = closures.get(&symbol) { - for v in &references.calls { - stack.push(*v); - } - - // while there are symbols left to visit - while let Some(nested_symbol) = stack.pop() { - if nested_symbol == symbol { - return Recursive::Recursive; - } - - // if the called symbol not yet in the graph - if !visited.contains(&nested_symbol) { - // add it to the visited set - // if it calls any functions - if let Some(nested_references) = closures.get(&nested_symbol) { - // add its called to the stack - for v in &nested_references.calls { - stack.push(*v); - } - } - visited.insert(nested_symbol); - } - } - } - - Recursive::NotRecursive -} - fn to_pending_type_def<'a>( env: &mut Env<'a>, def: &'a ast::TypeDef<'a>, scope: &mut Scope, -) -> Option<(Output, PendingTypeDef<'a>)> { + pattern_type: PatternType, +) -> PendingTypeDef<'a> { use ast::TypeDef::*; match def { @@ -1692,12 +1365,7 @@ fn to_pending_type_def<'a>( let region = Region::span_across(&name.region, &ann.region); - match scope.introduce( - name.value.into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - region, - ) { + match scope.introduce_without_shadow_symbol(&Ident::from(name.value), region) { Ok(symbol) => { let mut can_rigids: Vec> = Vec::with_capacity(vars.len()); @@ -1720,10 +1388,11 @@ fn to_pending_type_def<'a>( }; env.problems.push(problem); - return Some(( - Output::default(), - PendingTypeDef::InvalidAlias { kind }, - )); + return PendingTypeDef::InvalidAlias { + kind, + symbol, + region, + }; } } } @@ -1733,39 +1402,47 @@ fn to_pending_type_def<'a>( value: symbol, }; - let pending_def = PendingTypeDef::Alias { + PendingTypeDef::Alias { name, vars: can_rigids, ann, kind, - }; - - Some((Output::default(), pending_def)) + } } - Err((original_region, loc_shadowed_symbol, _new_symbol)) => { + Err((original_region, loc_shadowed_symbol)) => { env.problem(Problem::Shadowing { original_region, shadow: loc_shadowed_symbol, kind: shadow_kind, }); - Some((Output::default(), PendingTypeDef::InvalidAlias { kind })) + PendingTypeDef::ShadowedAlias } } } + Ability { + header, members, .. + } if pattern_type != PatternType::TopLevelDef => { + let header_region = header.region(); + let region = Region::span_across( + &header_region, + &members.last().map(|m| m.region()).unwrap_or(header_region), + ); + env.problem(Problem::AbilityNotOnToplevel { region }); + + PendingTypeDef::AbilityNotOnToplevel + } + Ability { header: TypeHeader { name, vars }, members, loc_has: _, } => { - let name = match scope.introduce_without_shadow_symbol( - name.value.into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - name.region, - ) { + let name = match scope + .introduce_without_shadow_symbol(&Ident::from(name.value), name.region) + { Ok(symbol) => Loc::at(name.region, symbol), Err((original_region, shadowed_symbol)) => { env.problem(Problem::Shadowing { @@ -1773,7 +1450,7 @@ fn to_pending_type_def<'a>( shadow: shadowed_symbol, kind: ShadowKind::Ability, }); - return Some((Output::default(), PendingTypeDef::InvalidAbility)); + return PendingTypeDef::AbilityShadows; } }; @@ -1785,16 +1462,17 @@ fn to_pending_type_def<'a>( name: name.value, variables_region, }); - return Some((Output::default(), PendingTypeDef::InvalidAbility)); + return PendingTypeDef::InvalidAbility { + symbol: name.value, + region: name.region, + }; } - let pending_ability = PendingTypeDef::Ability { + PendingTypeDef::Ability { name, // We'll handle adding the member symbols later on when we do all value defs. members, - }; - - Some((Output::default(), pending_ability)) + } } } } @@ -1804,41 +1482,46 @@ fn to_pending_value_def<'a>( var_store: &mut VarStore, def: &'a ast::ValueDef<'a>, scope: &mut Scope, + output: &mut Output, pattern_type: PatternType, -) -> Option<(Output, PendingValueDef<'a>)> { +) -> Option> { use ast::ValueDef::*; match def { Annotation(loc_pattern, loc_ann) => { // This takes care of checking for shadowing and adding idents to scope. - let (output, loc_can_pattern) = canonicalize_def_header_pattern( + let loc_can_pattern = canonicalize_def_header_pattern( env, var_store, scope, + output, pattern_type, &loc_pattern.value, loc_pattern.region, ); - Some(( - output, - PendingValueDef::AnnotationOnly(loc_pattern, loc_can_pattern, loc_ann), + Some(PendingValueDef::AnnotationOnly( + loc_pattern, + loc_can_pattern, + loc_ann, )) } Body(loc_pattern, loc_expr) => { // This takes care of checking for shadowing and adding idents to scope. - let (output, loc_can_pattern) = canonicalize_def_header_pattern( + let loc_can_pattern = canonicalize_def_header_pattern( env, var_store, scope, + output, pattern_type, &loc_pattern.value, loc_pattern.region, ); - Some(( - output, - PendingValueDef::Body(loc_pattern, loc_can_pattern, loc_expr), + Some(PendingValueDef::Body( + loc_pattern, + loc_can_pattern, + loc_expr, )) } @@ -1857,18 +1540,21 @@ fn to_pending_value_def<'a>( // { x, y ? False } = rec // // This takes care of checking for shadowing and adding idents to scope. - let (output, loc_can_pattern) = canonicalize_def_header_pattern( + let loc_can_pattern = canonicalize_def_header_pattern( env, var_store, scope, + output, pattern_type, &body_pattern.value, body_pattern.region, ); - Some(( - output, - PendingValueDef::TypedBody(body_pattern, loc_can_pattern, ann_type, body_expr), + Some(PendingValueDef::TypedBody( + body_pattern, + loc_can_pattern, + ann_type, + body_expr, )) } else { // the pattern of the annotation does not match the pattern of the body direc @@ -1891,74 +1577,147 @@ fn to_pending_value_def<'a>( /// Make aliases recursive fn correct_mutual_recursive_type_alias<'a>( env: &mut Env<'a>, - mut original_aliases: SendMap, + original_aliases: VecMap, var_store: &mut VarStore, -) -> ImMap { - let symbols_introduced: Vec = original_aliases.keys().copied().collect(); +) -> VecMap { + let capacity = original_aliases.len(); + let mut matrix = ReferenceMatrix::new(capacity); - let all_successors_with_self = |symbol: &Symbol| -> Vec { - match original_aliases.get(symbol) { - Some(alias) => { - let mut loc_succ = alias.typ.symbols(); - // remove anything that is not defined in the current block - loc_succ.retain(|key| symbols_introduced.contains(key)); + let (symbols_introduced, mut aliases) = original_aliases.unzip(); - loc_succ + for (index, alias) in aliases.iter().enumerate() { + for referenced in alias.typ.symbols() { + match symbols_introduced.iter().position(|k| referenced == *k) { + None => { /* ignore */ } + Some(ref_id) => matrix.set_row_col(index, ref_id, true), } - None => vec![], } - }; + } - // TODO investigate should this be in a loop? - let defined_symbols: Vec = original_aliases.keys().copied().collect(); + let mut solved_aliases = bitvec::vec::BitVec::::repeat(false, capacity); - let cycles = strongly_connected_components(&defined_symbols, all_successors_with_self); - let mut solved_aliases = ImMap::default(); + let group: Vec<_> = (0u32..capacity as u32).collect(); + let sccs = matrix.strongly_connected_components(&group); - for cycle in cycles { - debug_assert!(!cycle.is_empty()); + // scratchpad to store aliases that are modified in the current iteration. + // Only used when there is are more than one alias in a group. See below why + // this is needed. + let scratchpad_capacity = sccs + .groups() + .map(|r| r.count_ones()) + .max() + .unwrap_or_default(); + let mut scratchpad = Vec::with_capacity(scratchpad_capacity); - let mut pending_aliases: ImMap<_, _> = cycle - .iter() - .map(|&sym| (sym, original_aliases.remove(&sym).unwrap())) - .collect(); + for cycle in sccs.groups() { + debug_assert!(cycle.count_ones() > 0); + + // We need to instantiate the alias with any symbols in the currrent module it + // depends on. + // + // the `strongly_connected_components` returns SCCs in a topologically sorted order: + // SCC_0 has those aliases that don't rely on any other, SCC_1 has only those that rely on SCC_1, etc. + // + // Hence, we only need to worry about symbols in the current SCC or any prior one. + // It cannot be using any of the others, and we've already instantiated aliases coming from other modules. + let mut to_instantiate = solved_aliases | cycle; // 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; - // 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 index in cycle.iter_ones() { + // Don't try to instantiate the alias itself in its own definition. + to_instantiate.set(index, false); - 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(); + // Within a recursive group, we must instantiate all aliases like how they came to the + // loop. e.g. given + // + // A : [ ConsA B, NilA ] + // B : [ ConsB A, NilB ] + // + // Our goal is + // + // A : [ ConsA [ ConsB A, NilB ], NilA ] + // B : [ ConsB [ ConsA B, NilA ], NilB ] + // + // But if we would first instantiate B into A, then use the updated A to instantiate B, + // we get + // + // A : [ ConsA [ ConsB A, NilB ], NilA ] + // B : [ ConsB [ ConsA [ ConsB A, NilB ], NilA ], NilB ] + // + // Which is incorrect. We do need the instantiated version however. + // e.g. if in a next group we have: + // + // C : A + // + // Then we must use the instantiated version + // + // C : [ ConsA [ ConsB A, NilB ], NilA ] + // + // So, we cannot replace the original version of A with its instantiated version + // while we process A's group. We have to store the instantiated version until the + // current group is done, then move it to the `aliases` array. That is what the scratchpad is for. + let alias = if cycle.count_ones() == 1 { + // an optimization: we can modify the alias in the `aliases` list directly + // because it is the only alias in the group. + &mut aliases[index] + } else { + scratchpad.push((index, aliases[index].clone())); + + &mut scratchpad.last_mut().unwrap().1 + }; + + // Now, `alias` is possibly a mutable borrow from the `aliases` vector. But we also want + // to immutably borrow other elements from that vector to instantiate them into `alias`. + // The borrow checker disallows that. + // + // So we get creative: we swap out the element we want to modify with a dummy. We can + // then freely modify the type we moved out, and the `to_instantiate` mask + // makes sure that our dummy is not used. + + let alias_region = alias.region; + let mut alias_type = Type::EmptyRec; + + std::mem::swap(&mut alias_type, &mut alias.typ); + + let can_instantiate_symbol = |s| match symbols_introduced.iter().position(|i| *i == s) { + Some(s_index) if to_instantiate[s_index] => aliases.get(s_index), + _ => None, + }; let mut new_lambda_sets = ImSet::default(); - alias.typ.instantiate_aliases( - alias.region, - &to_instantiate, + alias_type.instantiate_aliases( + alias_region, + &can_instantiate_symbol, var_store, &mut new_lambda_sets, ); - for lambda_set_var in new_lambda_sets { - alias - .lambda_set_variables - .push(LambdaSet(Type::Variable(lambda_set_var))); - } + let alias = if cycle.count_ones() > 1 { + &mut scratchpad.last_mut().unwrap().1 + } else { + &mut aliases[index] + }; - to_instantiate.insert(rec, original_alias_def); + // swap the type back + std::mem::swap(&mut alias_type, &mut alias.typ); + + // We can instantiate this alias in future iterations + to_instantiate.set(index, true); + + // add any lambda sets that the instantiation created to the current alias + alias.lambda_set_variables.extend( + new_lambda_sets + .iter() + .map(|var| LambdaSet(Type::Variable(*var))), + ); // Now mark the alias recursive, if it needs to be. - let is_self_recursive = alias.typ.contains_symbol(rec); - let is_mutually_recursive = cycle.len() > 1; + let rec = symbols_introduced[index]; + let is_self_recursive = cycle.count_ones() == 1 && matrix.get_row_col(index, index); + let is_mutually_recursive = cycle.count_ones() > 1; if is_self_recursive || is_mutually_recursive { let _made_recursive = make_tag_union_of_alias_recursive( @@ -1972,20 +1731,28 @@ fn correct_mutual_recursive_type_alias<'a>( } } + // the current group has instantiated. Now we can move the updated aliases to the `aliases` vector + for (index, alias) in scratchpad.drain(..) { + aliases[index] = alias; + } + // The cycle we just instantiated and marked recursive may still be an illegal cycle, if // all the types in the cycle are narrow newtypes. We can't figure this out until now, // because we need all the types to be deeply instantiated. - let all_are_narrow = cycle.iter().all(|sym| { - let typ = &pending_aliases.get(sym).unwrap().typ; + let all_are_narrow = cycle.iter_ones().all(|index| { + let typ = &aliases[index].typ; matches!(typ, Type::RecursiveTagUnion(..)) && typ.is_narrow() }); if all_are_narrow { // This cycle is illegal! - let mut rest = cycle; - let alias_name = rest.pop().unwrap(); + let mut indices = cycle.iter_ones(); + let first_index = indices.next().unwrap(); - let alias = pending_aliases.get_mut(&alias_name).unwrap(); + let rest: Vec = indices.map(|i| symbols_introduced[i]).collect(); + + let alias_name = symbols_introduced[first_index]; + let alias = aliases.get_mut(first_index).unwrap(); mark_cyclic_alias( env, @@ -1997,11 +1764,12 @@ fn correct_mutual_recursive_type_alias<'a>( ) } - // Now, promote all resolved aliases in this cycle as solved. - solved_aliases.extend(pending_aliases); + // We've instantiated all we could, so all instantiatable aliases are solved now + solved_aliases = to_instantiate; } - solved_aliases + // Safety: both vectors are equal length and there are no duplicates + unsafe { VecMap::zip(symbols_introduced, aliases) } } fn make_tag_union_of_alias_recursive<'a>( @@ -2010,7 +1778,7 @@ fn make_tag_union_of_alias_recursive<'a>( alias: &mut Alias, others: Vec, var_store: &mut VarStore, - can_report_error: &mut bool, + can_report_cyclic_error: &mut bool, ) -> Result<(), ()> { let alias_args = alias .type_variables @@ -2025,7 +1793,7 @@ fn make_tag_union_of_alias_recursive<'a>( others, &mut alias.typ, var_store, - can_report_error, + can_report_cyclic_error, ); match made_recursive { @@ -2074,22 +1842,25 @@ fn make_tag_union_recursive_help<'a>( others: Vec, typ: &mut Type, var_store: &mut VarStore, - can_report_error: &mut bool, + can_report_cyclic_error: &mut bool, ) -> MakeTagUnionRecursive { use MakeTagUnionRecursive::*; - let Loc { - value: (symbol, args), - region: alias_region, - } = recursive_alias; - let vars = args.iter().map(|(_, t)| t.clone()).collect::>(); + let (symbol, args) = recursive_alias.value; + let alias_region = recursive_alias.region; + match typ { Type::TagUnion(tags, ext) => { let recursion_variable = var_store.fresh(); + let type_arguments = args.iter().map(|(_, t)| t.clone()).collect::>(); + let mut pending_typ = Type::RecursiveTagUnion(recursion_variable, tags.to_vec(), ext.clone()); - let substitution_result = - pending_typ.substitute_alias(symbol, &vars, &Type::Variable(recursion_variable)); + let substitution_result = pending_typ.substitute_alias( + symbol, + &type_arguments, + &Type::Variable(recursion_variable), + ); match substitution_result { Ok(()) => { // We can substitute the alias presence for the variable exactly. @@ -2115,18 +1886,22 @@ fn make_tag_union_recursive_help<'a>( actual, type_arguments, .. - } => make_tag_union_recursive_help( - env, - Loc::at_zero((symbol, type_arguments)), - region, - others, - actual, - var_store, - can_report_error, - ), + } => { + // try to make `actual` recursive + make_tag_union_recursive_help( + env, + Loc::at_zero((symbol, type_arguments)), + region, + others, + actual, + var_store, + can_report_cyclic_error, + ) + } _ => { - mark_cyclic_alias(env, typ, symbol, region, others, *can_report_error); - *can_report_error = false; + // take care to report a cyclic alias only once (not once for each alias in the cycle) + mark_cyclic_alias(env, typ, symbol, region, others, *can_report_cyclic_error); + *can_report_cyclic_error = false; Cyclic } diff --git a/compiler/can/src/effect_module.rs b/compiler/can/src/effect_module.rs index 1c2f76d951..b302b64e71 100644 --- a/compiler/can/src/effect_module.rs +++ b/compiler/can/src/effect_module.rs @@ -1,18 +1,17 @@ use crate::annotation::IntroducedVariables; use crate::def::{Declaration, Def}; -use crate::env::Env; -use crate::expr::{ClosureData, Expr, Recursive}; +use crate::expr::{AnnotatedMark, ClosureData, Expr, Recursive}; use crate::pattern::Pattern; use crate::scope::Scope; -use roc_collections::all::{MutSet, SendMap}; +use roc_collections::{SendMap, VecSet}; use roc_module::called_via::CalledVia; -use roc_module::ident::TagName; +use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; -use roc_types::subs::{VarStore, Variable}; -use roc_types::types::{AliasKind, Type, TypeExtension}; +use roc_types::subs::{ExhaustiveMark, RedundantMark, VarStore, Variable}; +use roc_types::types::{AliasKind, LambdaSet, Type, TypeExtension}; -#[derive(Default, Clone, Copy)] +#[derive(Debug, Default, Clone, Copy)] pub(crate) struct HostedGeneratedFunctions { pub(crate) after: bool, pub(crate) map: bool, @@ -30,28 +29,21 @@ pub(crate) struct HostedGeneratedFunctions { /// /// The effect alias is implemented as /// -/// Effect a : [ @Effect ({} -> a) ] +/// Effect a := {} -> a /// /// For this alias we implement the functions specified in HostedGeneratedFunctions with the /// standard implementation. pub(crate) fn build_effect_builtins( - env: &mut Env, scope: &mut Scope, effect_symbol: Symbol, var_store: &mut VarStore, - exposed_symbols: &mut MutSet, + exposed_symbols: &mut VecSet, declarations: &mut Vec, generated_functions: HostedGeneratedFunctions, ) { macro_rules! helper { ($f:expr) => {{ - let (symbol, def) = $f( - env, - scope, - effect_symbol, - TagName::Private(effect_symbol), - var_store, - ); + let (symbol, def) = $f(scope, effect_symbol, var_store); // make the outside world know this symbol exists exposed_symbols.insert(symbol); @@ -93,69 +85,42 @@ pub(crate) fn build_effect_builtins( // show up with their name. We have to register them like below to make the names show up in // debug prints if false { - env.home.register_debug_idents(&env.ident_ids); + scope.register_debug_idents(); } } macro_rules! new_symbol { - ($scope:expr, $env:expr, $name:expr) => {{ - $scope - .introduce( - $name.into(), - &$env.exposed_ident_ids, - &mut $env.ident_ids, - Region::zero(), - ) - .unwrap() + ($scope:expr, $name:expr) => {{ + $scope.introduce($name.into(), Region::zero()).unwrap() }}; } fn build_effect_always( - env: &mut Env, scope: &mut Scope, effect_symbol: Symbol, - effect_tag_name: TagName, var_store: &mut VarStore, ) -> (Symbol, Def) { // Effect.always = \value -> @Effect \{} -> value let value_symbol = { scope - .introduce( - "effect_always_value".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) + .introduce("effect_always_value".into(), Region::zero()) .unwrap() }; let inner_closure_symbol = { scope - .introduce( - "effect_always_inner".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) + .introduce("effect_always_inner".into(), Region::zero()) .unwrap() }; - let always_symbol = { - scope - .introduce( - "always".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() - }; + let always_symbol = { scope.introduce("always".into(), Region::zero()).unwrap() }; // \{} -> value let const_closure = { let arguments = vec![( var_store.fresh(), + AnnotatedMark::new(var_store), Loc::at_zero(empty_record_pattern(var_store)), )]; @@ -177,15 +142,20 @@ fn build_effect_always( // \value -> @Effect \{} -> value let (function_var, always_closure) = { // `@Effect \{} -> value` - let body = Expr::Tag { - variant_var: var_store.fresh(), - ext_var: var_store.fresh(), - name: effect_tag_name.clone(), - arguments: vec![(var_store.fresh(), Loc::at_zero(const_closure))], + let (specialized_def_type, type_arguments, lambda_set_variables) = + build_fresh_opaque_variables(var_store); + let body = Expr::OpaqueRef { + opaque_var: var_store.fresh(), + name: effect_symbol, + argument: Box::new((var_store.fresh(), Loc::at_zero(const_closure))), + specialized_def_type, + type_arguments, + lambda_set_variables, }; let arguments = vec![( var_store.fresh(), + AnnotatedMark::new(var_store), Loc::at_zero(Pattern::Identifier(value_symbol)), )]; @@ -212,9 +182,8 @@ fn build_effect_always( let var_a = var_store.fresh(); introduced_variables.insert_named("a".into(), Loc::at_zero(var_a)); - let effect_a = build_effect_alias( + let effect_a = build_effect_opaque( effect_symbol, - effect_tag_name, "a", var_a, Type::Variable(var_a), @@ -254,46 +223,25 @@ fn build_effect_always( } fn build_effect_map( - env: &mut Env, scope: &mut Scope, effect_symbol: Symbol, - effect_tag_name: TagName, var_store: &mut VarStore, ) -> (Symbol, Def) { // Effect.map = \@Effect thunk, mapper -> @Effect \{} -> mapper (thunk {}) let thunk_symbol = { scope - .introduce( - "effect_map_thunk".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) + .introduce("effect_map_thunk".into(), Region::zero()) .unwrap() }; let mapper_symbol = { scope - .introduce( - "effect_map_mapper".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) + .introduce("effect_map_mapper".into(), Region::zero()) .unwrap() }; - let map_symbol = { - scope - .introduce( - "map".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() - }; + let map_symbol = { scope.introduce("map".into(), Region::zero()).unwrap() }; // `thunk {}` let force_thunk_call = { @@ -323,12 +271,7 @@ fn build_effect_map( let inner_closure_symbol = { scope - .introduce( - "effect_map_inner".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) + .introduce("effect_map_inner".into(), Region::zero()) .unwrap() }; @@ -336,6 +279,7 @@ fn build_effect_map( let inner_closure = { let arguments = vec![( var_store.fresh(), + AnnotatedMark::new(var_store), Loc::at_zero(empty_record_pattern(var_store)), )]; @@ -355,31 +299,42 @@ fn build_effect_map( }) }; + // \@Effect thunk, mapper + let (specialized_def_type, type_arguments, lambda_set_variables) = + build_fresh_opaque_variables(var_store); let arguments = vec![ ( var_store.fresh(), - Loc::at_zero(Pattern::AppliedTag { + AnnotatedMark::new(var_store), + Loc::at_zero(Pattern::UnwrappedOpaque { + opaque: effect_symbol, whole_var: var_store.fresh(), - ext_var: var_store.fresh(), - tag_name: effect_tag_name.clone(), - arguments: vec![( + argument: Box::new(( var_store.fresh(), Loc::at_zero(Pattern::Identifier(thunk_symbol)), - )], + )), + specialized_def_type, + type_arguments, + lambda_set_variables, }), ), ( var_store.fresh(), + AnnotatedMark::new(var_store), Loc::at_zero(Pattern::Identifier(mapper_symbol)), ), ]; // `@Effect \{} -> (mapper (thunk {}))` - let body = Expr::Tag { - variant_var: var_store.fresh(), - ext_var: var_store.fresh(), - name: effect_tag_name.clone(), - arguments: vec![(var_store.fresh(), Loc::at_zero(inner_closure))], + let (specialized_def_type, type_arguments, lambda_set_variables) = + build_fresh_opaque_variables(var_store); + let body = Expr::OpaqueRef { + opaque_var: var_store.fresh(), + name: effect_symbol, + argument: Box::new((var_store.fresh(), Loc::at_zero(inner_closure))), + specialized_def_type, + type_arguments, + lambda_set_variables, }; let function_var = var_store.fresh(); @@ -405,9 +360,8 @@ fn build_effect_map( 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( + let effect_a = build_effect_opaque( effect_symbol, - effect_tag_name.clone(), "a", var_a, Type::Variable(var_a), @@ -415,9 +369,8 @@ fn build_effect_map( &mut introduced_variables, ); - let effect_b = build_effect_alias( + let effect_b = build_effect_opaque( effect_symbol, - effect_tag_name, "b", var_b, Type::Variable(var_b), @@ -466,46 +419,25 @@ fn build_effect_map( } fn build_effect_after( - env: &mut Env, scope: &mut Scope, effect_symbol: Symbol, - effect_tag_name: TagName, var_store: &mut VarStore, ) -> (Symbol, Def) { // Effect.after = \@Effect effect, toEffect -> toEffect (effect {}) let thunk_symbol = { scope - .introduce( - "effect_after_thunk".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) + .introduce("effect_after_thunk".into(), Region::zero()) .unwrap() }; let to_effect_symbol = { scope - .introduce( - "effect_after_toEffect".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) + .introduce("effect_after_toEffect".into(), Region::zero()) .unwrap() }; - let after_symbol = { - scope - .introduce( - "after".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() - }; + let after_symbol = { scope.introduce("after".into(), Region::zero()).unwrap() }; // `thunk {}` let force_thunk_call = { @@ -533,21 +465,28 @@ fn build_effect_after( Expr::Call(Box::new(boxed), arguments, CalledVia::Space) }; + let (specialized_def_type, type_arguments, lambda_set_variables) = + build_fresh_opaque_variables(var_store); + let arguments = vec![ ( var_store.fresh(), - Loc::at_zero(Pattern::AppliedTag { + AnnotatedMark::new(var_store), + Loc::at_zero(Pattern::UnwrappedOpaque { + opaque: effect_symbol, whole_var: var_store.fresh(), - ext_var: var_store.fresh(), - tag_name: effect_tag_name.clone(), - arguments: vec![( + argument: Box::new(( var_store.fresh(), Loc::at_zero(Pattern::Identifier(thunk_symbol)), - )], + )), + specialized_def_type, + type_arguments, + lambda_set_variables, }), ), ( var_store.fresh(), + AnnotatedMark::new(var_store), Loc::at_zero(Pattern::Identifier(to_effect_symbol)), ), ]; @@ -574,9 +513,8 @@ fn build_effect_after( 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( + let effect_a = build_effect_opaque( effect_symbol, - effect_tag_name.clone(), "a", var_a, Type::Variable(var_a), @@ -584,9 +522,8 @@ fn build_effect_after( &mut introduced_variables, ); - let effect_b = build_effect_alias( + let effect_b = build_effect_opaque( effect_symbol, - effect_tag_name, "b", var_b, Type::Variable(var_b), @@ -635,7 +572,7 @@ fn build_effect_after( /// turn `value` into `@Effect \{} -> value` fn wrap_in_effect_thunk( body: Expr, - effect_tag_name: TagName, + effect_symbol: Symbol, closure_name: Symbol, captured_symbols: Vec, var_store: &mut VarStore, @@ -649,6 +586,7 @@ fn wrap_in_effect_thunk( let const_closure = { let arguments = vec![( var_store.fresh(), + AnnotatedMark::new(var_store), Loc::at_zero(empty_record_pattern(var_store)), )]; @@ -667,31 +605,38 @@ fn wrap_in_effect_thunk( }; // `@Effect \{} -> value` - Expr::Tag { - variant_var: var_store.fresh(), - ext_var: var_store.fresh(), - name: effect_tag_name, - arguments: vec![(var_store.fresh(), Loc::at_zero(const_closure))], + let (specialized_def_type, type_arguments, lambda_set_variables) = + build_fresh_opaque_variables(var_store); + Expr::OpaqueRef { + opaque_var: var_store.fresh(), + name: effect_symbol, + argument: Box::new((var_store.fresh(), Loc::at_zero(const_closure))), + specialized_def_type, + type_arguments, + lambda_set_variables, } } /// given `effect : Effect a`, unwrap the thunk and force it, giving a value of type `a` fn force_effect( effect: Expr, - effect_tag_name: TagName, + effect_symbol: Symbol, thunk_symbol: Symbol, var_store: &mut VarStore, ) -> Expr { let whole_var = var_store.fresh(); - let ext_var = var_store.fresh(); let thunk_var = var_store.fresh(); - let pattern = Pattern::AppliedTag { - ext_var, + let (specialized_def_type, type_arguments, lambda_set_variables) = + build_fresh_opaque_variables(var_store); + let pattern = Pattern::UnwrappedOpaque { whole_var, - tag_name: effect_tag_name, - arguments: vec![(thunk_var, Loc::at_zero(Pattern::Identifier(thunk_symbol)))], + opaque: effect_symbol, + argument: Box::new((thunk_var, Loc::at_zero(Pattern::Identifier(thunk_symbol)))), + specialized_def_type, + type_arguments, + lambda_set_variables, }; let pattern_vars = SendMap::default(); @@ -725,10 +670,8 @@ fn force_effect( } fn build_effect_forever( - env: &mut Env, scope: &mut Scope, effect_symbol: Symbol, - effect_tag_name: TagName, var_store: &mut VarStore, ) -> (Symbol, Def) { // morally @@ -779,38 +722,17 @@ fn build_effect_forever( // // Making `foreverInner` perfectly tail-call optimizable - let forever_symbol = { - scope - .introduce( - "forever".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() - }; + let forever_symbol = { scope.introduce("forever".into(), Region::zero()).unwrap() }; - let effect = { - scope - .introduce( - "effect".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() - }; + let effect = { scope.introduce("effect".into(), Region::zero()).unwrap() }; - let body = build_effect_forever_body( - env, - scope, - effect_tag_name.clone(), - forever_symbol, - effect, - var_store, - ); + let body = build_effect_forever_body(scope, effect_symbol, forever_symbol, effect, var_store); - let arguments = vec![(var_store.fresh(), Loc::at_zero(Pattern::Identifier(effect)))]; + let arguments = vec![( + var_store.fresh(), + AnnotatedMark::new(var_store), + Loc::at_zero(Pattern::Identifier(effect)), + )]; let function_var = var_store.fresh(); let after_closure = Expr::Closure(ClosureData { @@ -834,9 +756,8 @@ fn build_effect_forever( 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( + let effect_a = build_effect_opaque( effect_symbol, - effect_tag_name.clone(), "a", var_a, Type::Variable(var_a), @@ -844,9 +765,8 @@ fn build_effect_forever( &mut introduced_variables, ); - let effect_b = build_effect_alias( + let effect_b = build_effect_opaque( effect_symbol, - effect_tag_name, "b", var_b, Type::Variable(var_b), @@ -886,37 +806,25 @@ fn build_effect_forever( } fn build_effect_forever_body( - env: &mut Env, scope: &mut Scope, - effect_tag_name: TagName, + effect_symbol: Symbol, forever_symbol: Symbol, effect: Symbol, var_store: &mut VarStore, ) -> Expr { let closure_name = { scope - .introduce( - "forever_inner".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) + .introduce("forever_inner".into(), Region::zero()) .unwrap() }; - let inner_body = build_effect_forever_inner_body( - env, - scope, - effect_tag_name.clone(), - forever_symbol, - effect, - var_store, - ); + let inner_body = + build_effect_forever_inner_body(scope, effect_symbol, forever_symbol, effect, var_store); let captured_symbols = vec![effect]; wrap_in_effect_thunk( inner_body, - effect_tag_name, + effect_symbol, closure_name, captured_symbols, var_store, @@ -924,47 +832,31 @@ fn build_effect_forever_body( } fn build_effect_forever_inner_body( - env: &mut Env, scope: &mut Scope, - effect_tag_name: TagName, + effect_symbol: Symbol, forever_symbol: Symbol, effect: Symbol, var_store: &mut VarStore, ) -> Expr { - let thunk1_symbol = { - scope - .introduce( - "thunk1".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() - }; + let thunk1_symbol = { scope.introduce("thunk1".into(), Region::zero()).unwrap() }; - let thunk2_symbol = { - scope - .introduce( - "thunk2".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() - }; + let thunk2_symbol = { scope.introduce("thunk2".into(), Region::zero()).unwrap() }; - // Effect thunk1 = effect + // @Effect thunk1 = effect let thunk_from_effect = { let whole_var = var_store.fresh(); - let ext_var = var_store.fresh(); let thunk_var = var_store.fresh(); - let pattern = Pattern::AppliedTag { - ext_var, + let (specialized_def_type, type_arguments, lambda_set_variables) = + build_fresh_opaque_variables(var_store); + let pattern = Pattern::UnwrappedOpaque { whole_var, - tag_name: effect_tag_name.clone(), - arguments: vec![(thunk_var, Loc::at_zero(Pattern::Identifier(thunk1_symbol)))], + opaque: effect_symbol, + argument: Box::new((thunk_var, Loc::at_zero(Pattern::Identifier(thunk1_symbol)))), + specialized_def_type, + type_arguments, + lambda_set_variables, }; let pattern_vars = SendMap::default(); @@ -1017,12 +909,12 @@ fn build_effect_forever_inner_body( }; // ``` - // Effect thunk2 = forever effect + // @Effect thunk2 = forever effect // thunk2 {} // ``` let force_thunk2 = Loc::at_zero(force_effect( forever_effect, - effect_tag_name, + effect_symbol, thunk2_symbol, var_store, )); @@ -1039,20 +931,17 @@ fn build_effect_forever_inner_body( } fn build_effect_loop( - env: &mut Env, scope: &mut Scope, effect_symbol: Symbol, - effect_tag_name: TagName, var_store: &mut VarStore, ) -> (Symbol, Def) { - let loop_symbol = new_symbol!(scope, env, "loop"); - let state_symbol = new_symbol!(scope, env, "state"); - let step_symbol = new_symbol!(scope, env, "step"); + let loop_symbol = new_symbol!(scope, "loop"); + let state_symbol = new_symbol!(scope, "state"); + let step_symbol = new_symbol!(scope, "step"); let body = build_effect_loop_body( - env, scope, - effect_tag_name.clone(), + effect_symbol, loop_symbol, state_symbol, step_symbol, @@ -1062,10 +951,12 @@ fn build_effect_loop( let arguments = vec![ ( var_store.fresh(), + AnnotatedMark::new(var_store), Loc::at_zero(Pattern::Identifier(state_symbol)), ), ( var_store.fresh(), + AnnotatedMark::new(var_store), Loc::at_zero(Pattern::Identifier(step_symbol)), ), ]; @@ -1092,9 +983,8 @@ fn build_effect_loop( 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( + let effect_b = build_effect_opaque( effect_symbol, - effect_tag_name.clone(), "b", var_b, Type::Variable(var_b), @@ -1103,8 +993,8 @@ fn build_effect_loop( ); let state_type = { - let step_tag_name = TagName::Global("Step".into()); - let done_tag_name = TagName::Global("Done".into()); + let step_tag_name = TagName::Tag("Step".into()); + let done_tag_name = TagName::Tag("Done".into()); Type::TagUnion( vec![ @@ -1119,19 +1009,11 @@ fn build_effect_loop( let closure_var = var_store.fresh(); introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); - let actual = { - Type::TagUnion( - vec![( - effect_tag_name, - vec![Type::Function( - vec![Type::EmptyRec], - Box::new(Type::Variable(closure_var)), - Box::new(state_type.clone()), - )], - )], - TypeExtension::Closed, - ) - }; + let actual = Type::Function( + vec![Type::EmptyRec], + Box::new(Type::Variable(closure_var)), + Box::new(state_type.clone()), + ); Type::Alias { symbol: effect_symbol, @@ -1140,7 +1022,7 @@ fn build_effect_loop( closure_var, ))], actual: Box::new(actual), - kind: AliasKind::Structural, + kind: AliasKind::Opaque, } }; @@ -1185,9 +1067,8 @@ fn build_effect_loop( } fn build_effect_loop_body( - env: &mut Env, scope: &mut Scope, - effect_tag_name: TagName, + effect_symbol: Symbol, loop_symbol: Symbol, state_symbol: Symbol, step_symbol: Symbol, @@ -1195,19 +1076,13 @@ fn build_effect_loop_body( ) -> Expr { let closure_name = { scope - .introduce( - "loop_inner".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) + .introduce("loop_inner".into(), Region::zero()) .unwrap() }; let inner_body = build_effect_loop_inner_body( - env, scope, - effect_tag_name.clone(), + effect_symbol, loop_symbol, state_symbol, step_symbol, @@ -1217,7 +1092,7 @@ fn build_effect_loop_body( let captured_symbols = vec![state_symbol, step_symbol]; wrap_in_effect_thunk( inner_body, - effect_tag_name, + effect_symbol, closure_name, captured_symbols, var_store, @@ -1247,32 +1122,34 @@ fn applied_tag_pattern( } fn build_effect_loop_inner_body( - env: &mut Env, scope: &mut Scope, - effect_tag_name: TagName, + effect_symbol: Symbol, loop_symbol: Symbol, state_symbol: Symbol, step_symbol: Symbol, var_store: &mut VarStore, ) -> Expr { - let thunk1_symbol = new_symbol!(scope, env, "thunk3"); - let thunk2_symbol = new_symbol!(scope, env, "thunk4"); + let thunk1_symbol = new_symbol!(scope, "thunk3"); + let thunk2_symbol = new_symbol!(scope, "thunk4"); - let new_state_symbol = new_symbol!(scope, env, "newState"); - let done_symbol = new_symbol!(scope, env, "done"); + let new_state_symbol = new_symbol!(scope, "newState"); + let done_symbol = new_symbol!(scope, "done"); // Effect thunk1 = step state let thunk_from_effect = { let whole_var = var_store.fresh(); - let ext_var = var_store.fresh(); let thunk_var = var_store.fresh(); - let pattern = Pattern::AppliedTag { - ext_var, + let (specialized_def_type, type_arguments, lambda_set_variables) = + build_fresh_opaque_variables(var_store); + let pattern = Pattern::UnwrappedOpaque { whole_var, - tag_name: effect_tag_name.clone(), - arguments: vec![(thunk_var, Loc::at_zero(Pattern::Identifier(thunk1_symbol)))], + opaque: effect_symbol, + argument: Box::new((thunk_var, Loc::at_zero(Pattern::Identifier(thunk1_symbol)))), + specialized_def_type, + type_arguments, + lambda_set_variables, }; let pattern_vars = SendMap::default(); @@ -1332,18 +1209,13 @@ fn build_effect_loop_inner_body( }; // ``` - // Effect thunk2 = loop effect + // @Effect thunk2 = loop effect // thunk2 {} // ``` - let force_thunk2 = force_effect( - loop_new_state_step, - effect_tag_name, - thunk2_symbol, - var_store, - ); + let force_thunk2 = force_effect(loop_new_state_step, effect_symbol, thunk2_symbol, var_store); let step_branch = { - let step_tag_name = TagName::Global("Step".into()); + let step_tag_name = TagName::Tag("Step".into()); let step_pattern = applied_tag_pattern(step_tag_name, &[new_state_symbol], var_store); @@ -1351,17 +1223,19 @@ fn build_effect_loop_inner_body( patterns: vec![Loc::at_zero(step_pattern)], value: Loc::at_zero(force_thunk2), guard: None, + redundant: RedundantMark::new(var_store), } }; let done_branch = { - let done_tag_name = TagName::Global("Done".into()); + let done_tag_name = TagName::Tag("Done".into()); let done_pattern = applied_tag_pattern(done_tag_name, &[done_symbol], var_store); crate::expr::WhenBranch { patterns: vec![Loc::at_zero(done_pattern)], value: Loc::at_zero(Expr::Var(done_symbol)), guard: None, + redundant: RedundantMark::new(var_store), } }; @@ -1373,6 +1247,8 @@ fn build_effect_loop_inner_body( region: Region::zero(), loc_cond: Box::new(force_thunk_call), branches, + branches_cond_var: var_store.fresh(), + exhaustive: ExhaustiveMark::new(var_store), }; Expr::LetNonRec( @@ -1383,11 +1259,10 @@ fn build_effect_loop_inner_body( } pub fn build_host_exposed_def( - env: &mut Env, scope: &mut Scope, symbol: Symbol, ident: &str, - effect_tag_name: TagName, + effect_symbol: Symbol, var_store: &mut VarStore, annotation: crate::annotation::Annotation, ) -> Def { @@ -1396,31 +1271,35 @@ pub fn build_host_exposed_def( let mut pattern_vars = SendMap::default(); pattern_vars.insert(symbol, expr_var); - let mut arguments: Vec<(Variable, Loc)> = Vec::new(); + let mut arguments: Vec<(Variable, AnnotatedMark, Loc)> = Vec::new(); let mut linked_symbol_arguments: Vec<(Variable, Expr)> = Vec::new(); let mut captured_symbols: Vec<(Symbol, Variable)> = Vec::new(); + let crate::annotation::Annotation { + introduced_variables, + typ, + aliases, + .. + } = annotation; + let def_body = { - match annotation.typ.shallow_dealias() { + match typ.shallow_structural_dealias() { Type::Function(args, _, _) => { for i in 0..args.len() { let name = format!("closure_arg_{}_{}", ident, i); let arg_symbol = { let ident = name.clone().into(); - scope - .introduce( - ident, - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() + scope.introduce(ident, Region::zero()).unwrap() }; let arg_var = var_store.fresh(); - arguments.push((arg_var, Loc::at_zero(Pattern::Identifier(arg_symbol)))); + arguments.push(( + arg_var, + AnnotatedMark::new(var_store), + Loc::at_zero(Pattern::Identifier(arg_symbol)), + )); captured_symbols.push((arg_symbol, arg_var)); linked_symbol_arguments.push((arg_var, Expr::Var(arg_symbol))); @@ -1437,14 +1316,7 @@ pub fn build_host_exposed_def( let name = format!("effect_closure_{}", ident); let ident = name.into(); - scope - .introduce( - ident, - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() + scope.introduce(ident, Region::zero()).unwrap() }; let effect_closure = Expr::Closure(ClosureData { @@ -1457,16 +1329,21 @@ pub fn build_host_exposed_def( recursive: Recursive::NotRecursive, arguments: vec![( var_store.fresh(), + AnnotatedMark::new(var_store), Loc::at_zero(empty_record_pattern(var_store)), )], loc_body: Box::new(Loc::at_zero(low_level_call)), }); - let body = Expr::Tag { - variant_var: var_store.fresh(), - ext_var: var_store.fresh(), - name: effect_tag_name, - arguments: vec![(var_store.fresh(), Loc::at_zero(effect_closure))], + let (specialized_def_type, type_arguments, lambda_set_variables) = + build_fresh_opaque_variables(var_store); + let body = Expr::OpaqueRef { + opaque_var: var_store.fresh(), + name: effect_symbol, + argument: Box::new((var_store.fresh(), Loc::at_zero(effect_closure))), + specialized_def_type, + type_arguments, + lambda_set_variables, }; Expr::Closure(ClosureData { @@ -1495,14 +1372,7 @@ pub fn build_host_exposed_def( let name = format!("effect_closure_{}", ident); let ident = name.into(); - scope - .introduce( - ident, - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() + scope.introduce(ident, Region::zero()).unwrap() }; let empty_record_pattern = Pattern::RecordDestructure { @@ -1519,24 +1389,32 @@ pub fn build_host_exposed_def( name: effect_closure_symbol, captured_symbols, recursive: Recursive::NotRecursive, - arguments: vec![(var_store.fresh(), Loc::at_zero(empty_record_pattern))], + arguments: vec![( + var_store.fresh(), + AnnotatedMark::new(var_store), + Loc::at_zero(empty_record_pattern), + )], loc_body: Box::new(Loc::at_zero(low_level_call)), }); - Expr::Tag { - variant_var: var_store.fresh(), - ext_var: var_store.fresh(), - name: effect_tag_name, - arguments: vec![(var_store.fresh(), Loc::at_zero(effect_closure))], + let (specialized_def_type, type_arguments, lambda_set_variables) = + build_fresh_opaque_variables(var_store); + Expr::OpaqueRef { + opaque_var: var_store.fresh(), + name: effect_symbol, + argument: Box::new((var_store.fresh(), Loc::at_zero(effect_closure))), + specialized_def_type, + type_arguments, + lambda_set_variables, } } } }; let def_annotation = crate::def::Annotation { - signature: annotation.typ, - introduced_variables: annotation.introduced_variables, - aliases: annotation.aliases, + signature: typ, + introduced_variables, + aliases, region: Region::zero(), }; @@ -1549,9 +1427,19 @@ pub fn build_host_exposed_def( } } -fn build_effect_alias( +pub fn build_effect_actual(a_type: Type, var_store: &mut VarStore) -> Type { + let closure_var = var_store.fresh(); + + Type::Function( + vec![Type::EmptyRec], + Box::new(Type::Variable(closure_var)), + Box::new(a_type), + ) +} + +/// Effect a := {} -> a +fn build_effect_opaque( effect_symbol: Symbol, - effect_tag_name: TagName, a_name: &str, a_var: Variable, a_type: Type, @@ -1561,47 +1449,39 @@ fn build_effect_alias( let closure_var = var_store.fresh(); introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); - let actual = { - Type::TagUnion( - vec![( - effect_tag_name, - vec![Type::Function( - vec![Type::EmptyRec], - Box::new(Type::Variable(closure_var)), - Box::new(a_type), - )], - )], - TypeExtension::Closed, - ) - }; + let actual = Type::Function( + vec![Type::EmptyRec], + Box::new(Type::Variable(closure_var)), + Box::new(a_type), + ); Type::Alias { symbol: effect_symbol, type_arguments: vec![(a_name.into(), Type::Variable(a_var))], lambda_set_variables: vec![roc_types::types::LambdaSet(Type::Variable(closure_var))], actual: Box::new(actual), - kind: AliasKind::Structural, + kind: AliasKind::Opaque, } } -pub fn build_effect_actual( - effect_tag_name: TagName, - a_type: Type, +fn build_fresh_opaque_variables( var_store: &mut VarStore, -) -> Type { +) -> (Box, Vec<(Lowercase, Type)>, Vec) { let closure_var = var_store.fresh(); - Type::TagUnion( - vec![( - effect_tag_name, - vec![Type::Function( - vec![Type::EmptyRec], - Box::new(Type::Variable(closure_var)), - Box::new(a_type), - )], - )], - TypeExtension::Closed, - ) + // NB: if there are bugs, check whether not introducing variables is a problem! + // introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); + + let a_var = var_store.fresh(); + let actual = Type::Function( + vec![Type::EmptyRec], + Box::new(Type::Variable(closure_var)), + Box::new(Type::Variable(a_var)), + ); + let type_arguments = vec![("a".into(), Type::Variable(a_var))]; + let lambda_set_variables = vec![roc_types::types::LambdaSet(Type::Variable(closure_var))]; + + (Box::new(actual), type_arguments, lambda_set_variables) } #[inline(always)] diff --git a/compiler/can/src/env.rs b/compiler/can/src/env.rs index ebbb79b349..550ce5abec 100644 --- a/compiler/can/src/env.rs +++ b/compiler/can/src/env.rs @@ -1,17 +1,18 @@ use crate::procedure::References; -use roc_collections::all::{MutMap, MutSet}; +use crate::scope::Scope; +use roc_collections::{MutMap, VecSet}; use roc_module::ident::{Ident, Lowercase, ModuleName}; -use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; +use roc_module::symbol::{IdentIdsByModule, ModuleId, ModuleIds, Symbol}; use roc_problem::can::{Problem, RuntimeError}; use roc_region::all::{Loc, Region}; /// The canonicalization environment for a particular module. pub struct Env<'a> { - /// The module's path. Private tags and unqualified references to identifiers + /// The module's path. Opaques and unqualified references to identifiers /// are assumed to be relative to this path. pub home: ModuleId, - pub dep_idents: &'a MutMap, + pub dep_idents: &'a IdentIdsByModule, pub module_ids: &'a ModuleIds, @@ -24,47 +25,38 @@ pub struct Env<'a> { /// current tail-callable symbol pub tailcallable_symbol: Option, - /// current closure name (if any) - pub closure_name_symbol: Option, - /// Symbols of values/functions which were referenced by qualified lookups. - pub qualified_value_lookups: MutSet, + pub qualified_value_lookups: VecSet, /// Symbols of types which were referenced by qualified lookups. - pub qualified_type_lookups: MutSet, + pub qualified_type_lookups: VecSet, - pub top_level_symbols: MutSet, - - pub ident_ids: IdentIds, - pub exposed_ident_ids: IdentIds, + pub top_level_symbols: VecSet, } impl<'a> Env<'a> { pub fn new( home: ModuleId, - dep_idents: &'a MutMap, + dep_idents: &'a IdentIdsByModule, module_ids: &'a ModuleIds, - exposed_ident_ids: IdentIds, ) -> Env<'a> { Env { home, dep_idents, module_ids, - ident_ids: exposed_ident_ids.clone(), // we start with these, but will add more later - exposed_ident_ids, problems: Vec::new(), closures: MutMap::default(), - qualified_value_lookups: MutSet::default(), - qualified_type_lookups: MutSet::default(), + qualified_value_lookups: VecSet::default(), + qualified_type_lookups: VecSet::default(), tailcallable_symbol: None, - closure_name_symbol: None, - top_level_symbols: MutSet::default(), + top_level_symbols: VecSet::default(), } } /// Returns Err if the symbol resolved, but it was not exposed by the given module pub fn qualified_lookup( &mut self, + scope: &Scope, module_name_str: &str, ident: &str, region: Region, @@ -85,7 +77,7 @@ impl<'a> Env<'a> { // You can do qualified lookups on your own module, e.g. // if I'm in the Foo module, I can do a `Foo.bar` lookup. if module_id == self.home { - match self.ident_ids.get_id(&ident) { + match scope.ident_ids.get_id(&ident) { Some(ident_id) => { let symbol = Symbol::new(module_id, ident_id); @@ -97,16 +89,20 @@ impl<'a> Env<'a> { Ok(symbol) } - None => Err(RuntimeError::LookupNotInScope( - Loc { - value: ident, - region, - }, - self.ident_ids - .idents() - .map(|(_, string)| string.as_ref().into()) - .collect(), - )), + None => { + let error = RuntimeError::LookupNotInScope( + Loc { + value: ident, + region, + }, + scope + .ident_ids + .ident_strs() + .map(|(_, string)| string.into()) + .collect(), + ); + Err(error) + } } } else { match self.dep_idents.get(&module_id) { @@ -124,11 +120,11 @@ impl<'a> Env<'a> { } None => { let exposed_values = exposed_ids - .idents() + .ident_strs() .filter(|(_, ident)| { - ident.as_ref().starts_with(|c: char| c.is_lowercase()) + ident.starts_with(|c: char| c.is_lowercase()) }) - .map(|(_, ident)| Lowercase::from(ident.as_ref())) + .map(|(_, ident)| Lowercase::from(ident)) .collect(); Err(RuntimeError::ValueNotExposed { module_name, @@ -165,22 +161,7 @@ impl<'a> Env<'a> { } } - /// Generates a unique, new symbol like "$1" or "$5", - /// using the home module as the module_id. - /// - /// This is used, for example, during canonicalization of an Expr::Closure - /// to generate a unique symbol to refer to that closure. - pub fn gen_unique_symbol(&mut self) -> Symbol { - let ident_id = self.ident_ids.gen_unique(); - - Symbol::new(self.home, ident_id) - } - pub fn problem(&mut self, problem: Problem) { self.problems.push(problem) } - - pub fn register_closure(&mut self, symbol: Symbol, references: References) { - self.closures.insert(symbol, references); - } } diff --git a/compiler/can/src/exhaustive.rs b/compiler/can/src/exhaustive.rs new file mode 100644 index 0000000000..c97f9d0013 --- /dev/null +++ b/compiler/can/src/exhaustive.rs @@ -0,0 +1,423 @@ +use crate::expr::{IntValue, WhenBranch}; +use crate::pattern::DestructType; +use roc_collections::all::HumanIndex; +use roc_error_macros::internal_error; +use roc_exhaustive::{ + is_useful, Ctor, CtorName, Error, Guard, Literal, Pattern, RenderAs, TagId, Union, +}; +use roc_module::ident::{TagIdIntType, TagName}; +use roc_region::all::{Loc, Region}; +use roc_types::subs::{Content, FlatType, RedundantMark, Subs, SubsFmtContent, Variable}; +use roc_types::types::AliasKind; + +pub use roc_exhaustive::Context as ExhaustiveContext; + +pub const GUARD_CTOR: &str = "#Guard"; +pub const NONEXHAUSIVE_CTOR: &str = "#Open"; + +pub struct ExhaustiveSummary { + pub errors: Vec, + pub exhaustive: bool, + pub redundancies: Vec, +} + +pub fn check( + subs: &Subs, + sketched_rows: SketchedRows, + context: ExhaustiveContext, +) -> ExhaustiveSummary { + let overall_region = sketched_rows.overall_region; + let mut all_errors = Vec::with_capacity(1); + + let NonRedundantSummary { + non_redundant_rows, + errors, + redundancies, + } = sketched_rows.reify_to_non_redundant(subs); + all_errors.extend(errors); + + let exhaustive = match roc_exhaustive::check(overall_region, context, non_redundant_rows) { + Ok(()) => true, + Err(errors) => { + all_errors.extend(errors); + false + } + }; + + ExhaustiveSummary { + errors: all_errors, + exhaustive, + redundancies, + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +enum SketchedPattern { + Anything, + Literal(Literal), + Ctor(Variable, TagName, Vec), + KnownCtor(Union, TagId, Vec), +} + +impl SketchedPattern { + fn reify(self, subs: &Subs) -> Pattern { + match self { + Self::Anything => Pattern::Anything, + Self::Literal(lit) => Pattern::Literal(lit), + Self::KnownCtor(union, tag_id, patterns) => Pattern::Ctor( + union, + tag_id, + patterns.into_iter().map(|pat| pat.reify(subs)).collect(), + ), + Self::Ctor(var, tag_name, patterns) => { + let (union, tag_id) = convert_tag(subs, var, &tag_name); + Pattern::Ctor( + union, + tag_id, + patterns.into_iter().map(|pat| pat.reify(subs)).collect(), + ) + } + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct SketchedRow { + patterns: Vec, + region: Region, + guard: Guard, + redundant_mark: RedundantMark, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SketchedRows { + rows: Vec, + overall_region: Region, +} + +impl SketchedRows { + fn reify_to_non_redundant(self, subs: &Subs) -> NonRedundantSummary { + to_nonredundant_rows(subs, self) + } +} + +fn sketch_pattern(var: Variable, pattern: &crate::pattern::Pattern) -> SketchedPattern { + use crate::pattern::Pattern::*; + use SketchedPattern as SP; + + match pattern { + &NumLiteral(_, _, IntValue::I128(n), _) | &IntLiteral(_, _, _, IntValue::I128(n), _) => { + SP::Literal(Literal::Int(n)) + } + &NumLiteral(_, _, IntValue::U128(n), _) | &IntLiteral(_, _, _, IntValue::U128(n), _) => { + SP::Literal(Literal::U128(n)) + } + &FloatLiteral(_, _, _, f, _) => SP::Literal(Literal::Float(f64::to_bits(f))), + StrLiteral(v) => SP::Literal(Literal::Str(v.clone())), + &SingleQuote(c) => SP::Literal(Literal::Byte(c as u8)), + RecordDestructure { destructs, .. } => { + let tag_id = TagId(0); + let mut patterns = std::vec::Vec::with_capacity(destructs.len()); + let mut field_names = std::vec::Vec::with_capacity(destructs.len()); + + for Loc { + value: destruct, + region: _, + } in destructs + { + field_names.push(destruct.label.clone()); + + match &destruct.typ { + DestructType::Required | DestructType::Optional(..) => { + patterns.push(SP::Anything) + } + DestructType::Guard(_, guard) => { + patterns.push(sketch_pattern(destruct.var, &guard.value)) + } + } + } + + let union = Union { + render_as: RenderAs::Record(field_names), + alternatives: vec![Ctor { + name: CtorName::Tag(TagName::Tag("#Record".into())), + tag_id, + arity: destructs.len(), + }], + }; + + SP::KnownCtor(union, tag_id, patterns) + } + + AppliedTag { + tag_name, + arguments, + .. + } => { + let simplified_args: std::vec::Vec<_> = arguments + .iter() + .map(|(var, arg)| sketch_pattern(*var, &arg.value)) + .collect(); + + SP::Ctor(var, tag_name.clone(), simplified_args) + } + + UnwrappedOpaque { + opaque, argument, .. + } => { + let (arg_var, argument) = &(**argument); + + let tag_id = TagId(0); + + let union = Union { + render_as: RenderAs::Opaque, + alternatives: vec![Ctor { + name: CtorName::Opaque(*opaque), + tag_id, + arity: 1, + }], + }; + + SP::KnownCtor( + union, + tag_id, + vec![sketch_pattern(*arg_var, &argument.value)], + ) + } + + // Treat this like a literal so we mark it as non-exhaustive + MalformedPattern(..) => SP::Literal(Literal::Byte(1)), + + Underscore + | Identifier(_) + | AbilityMemberSpecialization { .. } + | Shadowed(..) + | OpaqueNotInScope(..) + | UnsupportedPattern(..) => SP::Anything, + } +} + +pub fn sketch_when_branches( + target_var: Variable, + region: Region, + patterns: &[WhenBranch], +) -> SketchedRows { + let mut rows: Vec = Vec::with_capacity(patterns.len()); + + // If any of the branches has a guard, e.g. + // + // when x is + // y if y < 10 -> "foo" + // _ -> "bar" + // + // then we treat it as a pattern match on the pattern and a boolean, wrapped in the #Guard + // constructor. We can use this special constructor name to generate better error messages. + // This transformation of the pattern match only works because we only report exhaustiveness + // errors: the Pattern created in this file is not used for code gen. + // + // when x is + // #Guard y True -> "foo" + // #Guard _ _ -> "bar" + let any_has_guard = patterns.iter().any(|branch| branch.guard.is_some()); + + use SketchedPattern as SP; + for WhenBranch { + patterns, + guard, + value: _, + redundant, + } in patterns + { + let guard = if guard.is_some() { + Guard::HasGuard + } else { + Guard::NoGuard + }; + + for loc_pat in patterns { + // Decompose each pattern in the branch into its own row. + + let patterns = if any_has_guard { + let guard_pattern = match guard { + Guard::HasGuard => SP::Literal(Literal::Bit(true)), + Guard::NoGuard => SP::Anything, + }; + + let tag_id = TagId(0); + + let union = Union { + render_as: RenderAs::Guard, + alternatives: vec![Ctor { + tag_id, + name: CtorName::Tag(TagName::Tag(GUARD_CTOR.into())), + arity: 2, + }], + }; + + vec![SP::KnownCtor( + union, + tag_id, + // NB: ordering the guard pattern first seems to be better at catching + // non-exhaustive constructors in the second argument; see the paper to see if + // there is a way to improve this in general. + vec![guard_pattern, sketch_pattern(target_var, &loc_pat.value)], + )] + } else { + // Simple case + vec![sketch_pattern(target_var, &loc_pat.value)] + }; + + let row = SketchedRow { + patterns, + region: loc_pat.region, + guard, + redundant_mark: *redundant, + }; + rows.push(row); + } + } + + SketchedRows { + rows, + overall_region: region, + } +} + +pub fn sketch_pattern_to_rows( + target_var: Variable, + region: Region, + pattern: &crate::pattern::Pattern, +) -> SketchedRows { + let row = SketchedRow { + patterns: vec![sketch_pattern(target_var, pattern)], + region, + // A single row cannot be redundant! + redundant_mark: RedundantMark::known_non_redundant(), + guard: Guard::NoGuard, + }; + SketchedRows { + rows: vec![row], + overall_region: region, + } +} + +/// REDUNDANT PATTERNS + +struct NonRedundantSummary { + non_redundant_rows: Vec>, + redundancies: Vec, + errors: Vec, +} + +/// INVARIANT: Produces a list of rows where (forall row. length row == 1) +fn to_nonredundant_rows(subs: &Subs, rows: SketchedRows) -> NonRedundantSummary { + let SketchedRows { + rows, + overall_region, + } = rows; + let mut checked_rows = Vec::with_capacity(rows.len()); + + let mut redundancies = vec![]; + let mut errors = vec![]; + + for SketchedRow { + patterns, + guard, + region, + redundant_mark, + } in rows.into_iter() + { + let next_row: Vec = patterns + .into_iter() + .map(|pattern| pattern.reify(subs)) + .collect(); + + if matches!(guard, Guard::HasGuard) || is_useful(checked_rows.clone(), next_row.clone()) { + checked_rows.push(next_row); + } else { + redundancies.push(redundant_mark); + errors.push(Error::Redundant { + overall_region, + branch_region: region, + index: HumanIndex::zero_based(checked_rows.len()), + }); + } + } + + NonRedundantSummary { + non_redundant_rows: checked_rows, + redundancies, + errors, + } +} + +fn convert_tag(subs: &Subs, whole_var: Variable, this_tag: &TagName) -> (Union, TagId) { + let content = subs.get_content_without_compacting(whole_var); + + use {Content::*, FlatType::*}; + + match dealias_tag(subs, content) { + Structure(TagUnion(tags, ext) | RecursiveTagUnion(_, tags, ext)) => { + let (sorted_tags, ext) = tags.sorted_iterator_and_ext(subs, *ext); + + let mut num_tags = sorted_tags.len(); + + // DEVIATION: model openness by attaching a #Open constructor, that can never + // be matched unless there's an `Anything` pattern. + let opt_openness_tag = match subs.get_content_without_compacting(ext) { + FlexVar(_) | RigidVar(_) => { + let openness_tag = TagName::Tag(NONEXHAUSIVE_CTOR.into()); + num_tags += 1; + Some((openness_tag, &[] as _)) + } + Structure(EmptyTagUnion) => None, + // Anything else is erroneous and we ignore + _ => None, + }; + + // High tag ID if we're out-of-bounds. + let mut my_tag_id = TagId(num_tags as TagIdIntType); + + let mut alternatives = Vec::with_capacity(num_tags); + let alternatives_iter = sorted_tags.into_iter().chain(opt_openness_tag.into_iter()); + + for (index, (tag, args)) in alternatives_iter.enumerate() { + let tag_id = TagId(index as TagIdIntType); + if this_tag == &tag { + my_tag_id = tag_id; + } + alternatives.push(Ctor { + name: CtorName::Tag(tag), + tag_id, + arity: args.len(), + }); + } + + let union = Union { + alternatives, + render_as: RenderAs::Tag, + }; + + (union, my_tag_id) + } + _ => internal_error!( + "Content is not a tag union: {:?}", + SubsFmtContent(content, subs) + ), + } +} + +pub fn dealias_tag<'a>(subs: &'a Subs, content: &'a Content) -> &'a Content { + use Content::*; + let mut result = content; + loop { + match result { + Alias(_, _, real_var, AliasKind::Structural) + | RecursionVar { + structure: real_var, + .. + } => result = subs.get_content_without_compacting(*real_var), + _ => return result, + } + } +} diff --git a/compiler/can/src/expected.rs b/compiler/can/src/expected.rs index aad8bd42b4..df156e00fc 100644 --- a/compiler/can/src/expected.rs +++ b/compiler/can/src/expected.rs @@ -2,7 +2,7 @@ use crate::pattern::Pattern; use roc_region::all::{Loc, Region}; use roc_types::types::{AnnotationSource, PReason, Reason}; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub enum Expected { NoExpectation(T), FromAnnotation(Loc, usize, AnnotationSource, T), @@ -10,7 +10,7 @@ pub enum Expected { } /// Like Expected, but for Patterns. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone)] pub enum PExpected { NoExpectation(T), ForReason(PReason, T, Region), diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index d9262ceab1..d9d844291c 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -6,10 +6,10 @@ use crate::num::{ finish_parsing_base, finish_parsing_float, finish_parsing_num, float_expr_from_result, int_expr_from_result, num_expr_from_result, FloatBound, IntBound, NumericBound, }; -use crate::pattern::{canonicalize_pattern, Pattern}; +use crate::pattern::{canonicalize_pattern, BindingsFromPattern, Pattern}; use crate::procedure::References; use crate::scope::Scope; -use roc_collections::all::{MutMap, MutSet, SendMap}; +use roc_collections::{SendMap, VecMap, VecSet}; use roc_module::called_via::CalledVia; use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::low_level::LowLevel; @@ -18,23 +18,23 @@ use roc_parse::ast::{self, EscapedChar, StrLiteral}; use roc_parse::pattern::PatternType::*; use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError}; use roc_region::all::{Loc, Region}; -use roc_types::subs::{VarStore, Variable}; -use roc_types::types::{Alias, LambdaSet, Type}; +use roc_types::subs::{ExhaustiveMark, RedundantMark, VarStore, Variable}; +use roc_types::types::{Alias, Category, LambdaSet, Type}; use std::fmt::{Debug, Display}; use std::{char, u32}; -#[derive(Clone, Default, Debug, PartialEq)] +#[derive(Clone, Default, Debug)] pub struct Output { pub references: References, pub tail_call: Option, pub introduced_variables: IntroducedVariables, - pub aliases: SendMap, - pub non_closures: MutSet, + pub aliases: VecMap, + pub non_closures: VecSet, } impl Output { pub fn union(&mut self, other: Self) { - self.references.union_mut(other.references); + self.references.union_mut(&other.references); if let (None, Some(later)) = (self.tail_call, other.tail_call) { self.tail_call = Some(later); @@ -62,7 +62,7 @@ impl Display for IntValue { } } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub enum Expr { // Literals @@ -84,11 +84,18 @@ pub enum Expr { Var(Symbol), // Branching When { + /// The actual condition of the when expression. + loc_cond: Box>, cond_var: Variable, + /// Result type produced by the branches. expr_var: Variable, region: Region, - loc_cond: Box>, + /// The branches of the when, and the type of the condition that they expect to be matched + /// against. branches: Vec, + branches_cond_var: Variable, + /// Whether the branches are exhaustive. + exhaustive: ExhaustiveMark, }, If { cond_var: Variable, @@ -163,8 +170,7 @@ pub enum Expr { name: TagName, }, - /// A wrapping of an opaque type, like `$Age 21` - // TODO(opaques): $->@ above when opaques land + /// A wrapping of an opaque type, like `@Age 21` OpaqueRef { opaque_var: Variable, name: Symbol, @@ -194,7 +200,74 @@ pub enum Expr { // Compiles, but will crash if reached RuntimeError(RuntimeError), } -#[derive(Clone, Debug, PartialEq)] + +impl Expr { + pub fn category(&self) -> Category { + match self { + Self::Num(..) => Category::Num, + Self::Int(..) => Category::Int, + Self::Float(..) => Category::Float, + Self::Str(..) => Category::Str, + Self::SingleQuote(..) => Category::Character, + Self::List { .. } => Category::List, + &Self::Var(sym) => Category::Lookup(sym), + Self::When { .. } => Category::When, + Self::If { .. } => Category::If, + Self::LetRec(_, expr, _) => expr.value.category(), + Self::LetNonRec(_, expr, _) => expr.value.category(), + &Self::Call(_, _, called_via) => Category::CallResult(None, called_via), + &Self::RunLowLevel { op, .. } => Category::LowLevelOpResult(op), + Self::ForeignCall { .. } => Category::ForeignCall, + Self::Closure(..) => Category::Lambda, + Self::Record { .. } => Category::Record, + Self::EmptyRecord => Category::Record, + Self::Access { field, .. } => Category::Access(field.clone()), + Self::Accessor(data) => Category::Accessor(data.field.clone()), + Self::Update { .. } => Category::Record, + Self::Tag { + name, arguments, .. + } => Category::TagApply { + tag_name: name.clone(), + args_count: arguments.len(), + }, + Self::ZeroArgumentTag { name, .. } => Category::TagApply { + tag_name: name.clone(), + args_count: 0, + }, + &Self::OpaqueRef { name, .. } => Category::OpaqueWrap(name), + Self::Expect(..) => Category::Expect, + Self::RuntimeError(..) => Category::Unknown, + } + } +} + +/// Stores exhaustiveness-checking metadata for a closure argument that may +/// have an annotated type. +#[derive(Clone, Copy, Debug)] +pub struct AnnotatedMark { + pub annotation_var: Variable, + pub exhaustive: ExhaustiveMark, +} + +impl AnnotatedMark { + pub fn new(var_store: &mut VarStore) -> Self { + Self { + annotation_var: var_store.fresh(), + exhaustive: ExhaustiveMark::new(var_store), + } + } + + // NOTE: only ever use this if you *know* a pattern match is surely exhaustive! + // Otherwise you will get unpleasant unification errors. + pub fn known_exhaustive() -> Self { + Self { + annotation_var: Variable::EMPTY_TAG_UNION, + exhaustive: ExhaustiveMark::known_exhaustive(), + } + } +} + +#[derive(Clone, Debug)] pub struct ClosureData { pub function_type: Variable, pub closure_type: Variable, @@ -203,7 +276,7 @@ pub struct ClosureData { pub name: Symbol, pub captured_symbols: Vec<(Symbol, Variable)>, pub recursive: Recursive, - pub arguments: Vec<(Variable, Loc)>, + pub arguments: Vec<(Variable, AnnotatedMark, Loc)>, pub loc_body: Box>, } @@ -255,7 +328,11 @@ impl AccessorData { let loc_body = Loc::at_zero(body); - let arguments = vec![(record_var, Loc::at_zero(Pattern::Identifier(record_symbol)))]; + let arguments = vec![( + record_var, + AnnotatedMark::known_exhaustive(), + Loc::at_zero(Pattern::Identifier(record_symbol)), + )]; ClosureData { function_type: function_var, @@ -271,7 +348,7 @@ impl AccessorData { } } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct Field { pub var: Variable, // The region of the full `foo: f bar`, rather than just `f bar` @@ -286,11 +363,30 @@ pub enum Recursive { TailRecursive = 2, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct WhenBranch { pub patterns: Vec>, pub value: Loc, pub guard: Option>, + /// Whether this branch is redundant in the `when` it appears in + pub redundant: RedundantMark, +} + +impl WhenBranch { + pub fn pattern_region(&self) -> Region { + Region::span_across( + &self + .patterns + .first() + .expect("when branch has no pattern?") + .region, + &self + .patterns + .last() + .expect("when branch has no pattern?") + .region, + ) + } } pub fn canonicalize_expr<'a>( @@ -354,7 +450,7 @@ pub fn canonicalize_expr<'a>( if let Var(symbol) = &can_update.value { match canonicalize_fields(env, var_store, scope, region, fields.items) { Ok((can_fields, mut output)) => { - output.references = output.references.union(update_out.references); + output.references.union_mut(&update_out.references); let answer = Update { record_var: var_store.fresh(), @@ -432,7 +528,7 @@ pub fn canonicalize_expr<'a>( let (can_expr, elem_out) = canonicalize_expr(env, var_store, scope, loc_elem.region, &loc_elem.value); - references = references.union(elem_out.references); + references.union_mut(&elem_out.references); can_elems.push(can_expr); } @@ -466,7 +562,7 @@ pub fn canonicalize_expr<'a>( canonicalize_expr(env, var_store, scope, loc_arg.region, &loc_arg.value); args.push((var_store.fresh(), arg_expr)); - output.references = output.references.union(arg_out.references); + output.references.union_mut(&arg_out.references); } if let ast::Expr::OpaqueRef(name) = loc_fn.value { @@ -487,8 +583,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.type_lookups.insert(name); + output.references.insert_type_lookup(name); let (type_arguments, lambda_set_variables, specialized_def_type) = freshen_opaque_def(var_store, opaque_def); @@ -518,7 +613,7 @@ pub fn canonicalize_expr<'a>( let expr = match fn_expr.value { Var(symbol) => { - output.references.calls.insert(symbol); + output.references.insert_call(symbol); // we're tail-calling a symbol by name, check if it's the tail-callable symbol output.tail_call = match &env.tailcallable_symbol { @@ -597,146 +692,19 @@ pub fn canonicalize_expr<'a>( (RuntimeError(problem), Output::default()) } ast::Expr::Defs(loc_defs, loc_ret) => { - can_defs_with_return( - env, - var_store, - // The body expression gets a new scope for canonicalization, - // so clone it. - scope.clone(), - loc_defs, - loc_ret, - ) + // The body expression gets a new scope for canonicalization, + scope.inner_scope(|inner_scope| { + can_defs_with_return(env, var_store, inner_scope, loc_defs, loc_ret) + }) } ast::Expr::Backpassing(_, _, _) => { unreachable!("Backpassing should have been desugared by now") } ast::Expr::Closure(loc_arg_patterns, loc_body_expr) => { - // The globally unique symbol that will refer to this closure once it gets converted - // into a top-level procedure for code gen. - // - // In the Foo module, this will look something like Foo.$1 or Foo.$2. - let symbol = env - .closure_name_symbol - .unwrap_or_else(|| env.gen_unique_symbol()); - env.closure_name_symbol = None; + let (closure_data, output) = + canonicalize_closure(env, var_store, scope, loc_arg_patterns, loc_body_expr, None); - // The body expression gets a new scope for canonicalization. - // Shadow `scope` to make sure we don't accidentally use the original one for the - // rest of this block, but keep the original around for later diffing. - let original_scope = scope; - let mut scope = original_scope.clone(); - let mut can_args = Vec::with_capacity(loc_arg_patterns.len()); - let mut output = Output::default(); - - let mut bound_by_argument_patterns = MutSet::default(); - - for loc_pattern in loc_arg_patterns.iter() { - let (new_output, can_arg) = canonicalize_pattern( - env, - var_store, - &mut scope, - FunctionArg, - &loc_pattern.value, - loc_pattern.region, - ); - - bound_by_argument_patterns - .extend(new_output.references.bound_symbols.iter().copied()); - - output.union(new_output); - - can_args.push((var_store.fresh(), can_arg)); - } - - let (loc_body_expr, new_output) = canonicalize_expr( - env, - var_store, - &mut scope, - loc_body_expr.region, - &loc_body_expr.value, - ); - - let mut captured_symbols: MutSet = new_output - .references - .value_lookups - .iter() - .copied() - .collect(); - - // filter out the closure's name itself - captured_symbols.remove(&symbol); - - // symbols bound either in this pattern or deeper down are not captured! - captured_symbols.retain(|s| !new_output.references.bound_symbols.contains(s)); - captured_symbols.retain(|s| !bound_by_argument_patterns.contains(s)); - - // filter out top-level symbols - // those will be globally available, and don't need to be captured - captured_symbols.retain(|s| !env.top_level_symbols.contains(s)); - - // filter out imported symbols - // those will be globally available, and don't need to be captured - captured_symbols.retain(|s| s.module_id() == env.home); - - // TODO any Closure that has an empty `captured_symbols` list could be excluded! - - output.union(new_output); - - // filter out aliases - 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)); - - // Now that we've collected all the references, check to see if any of the args we defined - // 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_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)); - } - - // 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.value_lookups.remove(sub_symbol); - } - } - - env.register_closure(symbol, output.references.clone()); - - let mut captured_symbols: Vec<_> = captured_symbols - .into_iter() - .map(|s| (s, var_store.fresh())) - .collect(); - - // sort symbols, so we know the order in which they're stored in the closure record - captured_symbols.sort(); - - // store that this function doesn't capture anything. It will be promoted to a - // top-level function, and does not need to be captured by other surrounding functions. - if captured_symbols.is_empty() { - output.non_closures.insert(symbol); - } - - ( - Closure(ClosureData { - function_type: var_store.fresh(), - closure_type: var_store.fresh(), - closure_ext_var: var_store.fresh(), - return_type: var_store.fresh(), - name: symbol, - captured_symbols, - recursive: Recursive::NotRecursive, - arguments: can_args, - loc_body: Box::new(loc_body_expr), - }), - output, - ) + (Closure(closure_data), output) } ast::Expr::When(loc_cond, branches) => { // Infer the condition expression's type. @@ -750,10 +718,18 @@ pub fn canonicalize_expr<'a>( let mut can_branches = Vec::with_capacity(branches.len()); for branch in branches.iter() { - let (can_when_branch, branch_references) = - canonicalize_when_branch(env, var_store, scope, region, *branch, &mut output); + let (can_when_branch, branch_references) = scope.inner_scope(|inner_scope| { + canonicalize_when_branch( + env, + var_store, + inner_scope, + region, + *branch, + &mut output, + ) + }); - output.references = output.references.union(branch_references); + output.references.union_mut(&branch_references); can_branches.push(can_when_branch); } @@ -772,6 +748,8 @@ pub fn canonicalize_expr<'a>( region, loc_cond: Box::new(can_cond), branches: can_branches, + branches_cond_var: var_store.fresh(), + exhaustive: ExhaustiveMark::new(var_store), }; (expr, output) @@ -792,7 +770,7 @@ pub fn canonicalize_expr<'a>( } ast::Expr::AccessorFunction(field) => ( Accessor(AccessorData { - name: env.gen_unique_symbol(), + name: scope.gen_unique_symbol(), function_var: var_store.fresh(), record_var: var_store.fresh(), ext_var: var_store.fresh(), @@ -803,15 +781,15 @@ pub fn canonicalize_expr<'a>( }), Output::default(), ), - ast::Expr::GlobalTag(tag) => { + ast::Expr::Tag(tag) => { let variant_var = var_store.fresh(); let ext_var = var_store.fresh(); - let symbol = env.gen_unique_symbol(); + let symbol = scope.gen_unique_symbol(); ( ZeroArgumentTag { - name: TagName::Global((*tag).into()), + name: TagName::Tag((*tag).into()), variant_var, closure_name: symbol, ext_var, @@ -819,23 +797,6 @@ pub fn canonicalize_expr<'a>( Output::default(), ) } - ast::Expr::PrivateTag(tag) => { - let variant_var = var_store.fresh(); - let ext_var = var_store.fresh(); - let tag_ident = env.ident_ids.get_or_insert(&(*tag).into()); - let symbol = Symbol::new(env.home, tag_ident); - let lambda_set_symbol = env.gen_unique_symbol(); - - ( - ZeroArgumentTag { - name: TagName::Private(symbol), - variant_var, - ext_var, - closure_name: lambda_set_symbol, - }, - Output::default(), - ) - } ast::Expr::OpaqueRef(opaque_ref) => { // If we're here, the opaque reference is definitely not wrapping an argument - wrapped // arguments are handled in the Apply branch. @@ -886,8 +847,8 @@ pub fn canonicalize_expr<'a>( branches.push((loc_cond, loc_then)); - output.references = output.references.union(cond_output.references); - output.references = output.references.union(then_output.references); + output.references.union_mut(&cond_output.references); + output.references.union_mut(&then_output.references); } let (loc_else, else_output) = canonicalize_expr( @@ -898,7 +859,7 @@ pub fn canonicalize_expr<'a>( &final_else_branch.value, ); - output.references = output.references.union(else_output.references); + output.references.union_mut(&else_output.references); ( If { @@ -1024,6 +985,133 @@ pub fn canonicalize_expr<'a>( ) } +pub fn canonicalize_closure<'a>( + env: &mut Env<'a>, + var_store: &mut VarStore, + scope: &mut Scope, + loc_arg_patterns: &'a [Loc>], + loc_body_expr: &'a Loc>, + opt_def_name: Option, +) -> (ClosureData, Output) { + scope.inner_scope(|inner_scope| { + canonicalize_closure_body( + env, + var_store, + inner_scope, + loc_arg_patterns, + loc_body_expr, + opt_def_name, + ) + }) +} + +fn canonicalize_closure_body<'a>( + env: &mut Env<'a>, + var_store: &mut VarStore, + scope: &mut Scope, + loc_arg_patterns: &'a [Loc>], + loc_body_expr: &'a Loc>, + opt_def_name: Option, +) -> (ClosureData, Output) { + // The globally unique symbol that will refer to this closure once it gets converted + // into a top-level procedure for code gen. + let symbol = opt_def_name.unwrap_or_else(|| scope.gen_unique_symbol()); + + let mut can_args = Vec::with_capacity(loc_arg_patterns.len()); + let mut output = Output::default(); + + for loc_pattern in loc_arg_patterns.iter() { + let can_argument_pattern = canonicalize_pattern( + env, + var_store, + scope, + &mut output, + FunctionArg, + &loc_pattern.value, + loc_pattern.region, + ); + + can_args.push(( + var_store.fresh(), + AnnotatedMark::new(var_store), + can_argument_pattern, + )); + } + + let bound_by_argument_patterns: Vec<_> = + BindingsFromPattern::new_many(can_args.iter().map(|x| &x.2)).collect(); + + let (loc_body_expr, new_output) = canonicalize_expr( + env, + var_store, + scope, + loc_body_expr.region, + &loc_body_expr.value, + ); + + let mut captured_symbols: Vec<_> = new_output + .references + .value_lookups() + .copied() + // filter out the closure's name itself + .filter(|s| *s != symbol) + // symbols bound either in this pattern or deeper down are not captured! + .filter(|s| !new_output.references.bound_symbols().any(|x| x == s)) + .filter(|s| bound_by_argument_patterns.iter().all(|(k, _)| s != k)) + // filter out top-level symbols those will be globally available, and don't need to be captured + .filter(|s| !env.top_level_symbols.contains(s)) + // filter out imported symbols those will be globally available, and don't need to be captured + .filter(|s| s.module_id() == env.home) + // filter out functions that don't close over anything + .filter(|s| !new_output.non_closures.contains(s)) + .filter(|s| !output.non_closures.contains(s)) + .map(|s| (s, var_store.fresh())) + .collect(); + + output.union(new_output); + + // Now that we've collected all the references, check to see if any of the args we defined + // went unreferenced. If any did, report them as unused arguments. + for (sub_symbol, region) in bound_by_argument_patterns { + 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)); + } else { + // 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.remove_value_lookup(&sub_symbol); + } + } + + // store the references of this function in the Env. This information is used + // when we canonicalize a surrounding def (if it exists) + env.closures.insert(symbol, output.references.clone()); + + // sort symbols, so we know the order in which they're stored in the closure record + captured_symbols.sort(); + + // store that this function doesn't capture anything. It will be promoted to a + // top-level function, and does not need to be captured by other surrounding functions. + if captured_symbols.is_empty() { + output.non_closures.insert(symbol); + } + + let closure_data = ClosureData { + function_type: var_store.fresh(), + closure_type: var_store.fresh(), + closure_ext_var: var_store.fresh(), + return_type: var_store.fresh(), + name: symbol, + captured_symbols, + recursive: Recursive::NotRecursive, + arguments: can_args, + loc_body: Box::new(loc_body_expr), + }; + + (closure_data, output) +} + #[inline(always)] fn canonicalize_when_branch<'a>( env: &mut Env<'a>, @@ -1035,29 +1123,25 @@ fn canonicalize_when_branch<'a>( ) -> (WhenBranch, References) { let mut patterns = Vec::with_capacity(branch.patterns.len()); - let original_scope = scope; - let mut scope = original_scope.clone(); - // TODO report symbols not bound in all patterns for loc_pattern in branch.patterns.iter() { - let (new_output, can_pattern) = canonicalize_pattern( + let can_pattern = canonicalize_pattern( env, var_store, - &mut scope, + scope, + output, WhenBranch, &loc_pattern.value, loc_pattern.region, ); - output.union(new_output); - patterns.push(can_pattern); } let (value, mut branch_output) = canonicalize_expr( env, var_store, - &mut scope, + scope, branch.value.region, &branch.value.value, ); @@ -1066,70 +1150,35 @@ fn canonicalize_when_branch<'a>( None => None, Some(loc_expr) => { let (can_guard, guard_branch_output) = - canonicalize_expr(env, var_store, &mut scope, loc_expr.region, &loc_expr.value); + canonicalize_expr(env, var_store, scope, loc_expr.region, &loc_expr.value); branch_output.union(guard_branch_output); Some(can_guard) } }; - // Now that we've collected all the references for this branch, check to see if - // any of the new idents it defined were unused. If any were, report it. - for (symbol, region) in scope.symbols() { - let symbol = *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) - && !scope.abilities_store.is_specialization_name(symbol) - { - env.problem(Problem::UnusedDef(symbol, *region)); - } - } - let references = branch_output.references.clone(); output.union(branch_output); + // Now that we've collected all the references for this branch, check to see if + // any of the new idents it defined were unused. If any were, report it. + for (symbol, region) in BindingsFromPattern::new_many(patterns.iter()) { + if !output.references.has_value_lookup(symbol) { + env.problem(Problem::UnusedDef(symbol, region)); + } + } + ( WhenBranch { patterns, value, guard, + redundant: RedundantMark::new(var_store), }, references, ) } -pub fn local_successors_with_duplicates<'a>( - references: &'a References, - closures: &'a MutMap, -) -> Vec { - let mut answer: Vec<_> = references.value_lookups.iter().copied().collect(); - - let mut stack: Vec<_> = references.calls.iter().copied().collect(); - let mut seen = Vec::new(); - - while let Some(symbol) = stack.pop() { - if seen.contains(&symbol) { - continue; - } - - if let Some(references) = closures.get(&symbol) { - answer.extend(references.value_lookups.iter().copied()); - stack.extend(references.calls.iter().copied()); - - seen.push(symbol); - } - } - - answer.sort(); - answer.dedup(); - - answer -} - enum CanonicalizeRecordProblem { InvalidOptionalValue { field_name: Lowercase, @@ -1167,7 +1216,7 @@ fn canonicalize_fields<'a>( }); } - output.references = output.references.union(field_out.references); + output.references.union_mut(&field_out.references); } Err(CanonicalizeFieldProblem::InvalidOptionalValue { field_name, @@ -1255,7 +1304,7 @@ fn canonicalize_var_lookup( // Look it up in scope! match scope.lookup(&(*ident).into(), region) { Ok(symbol) => { - output.references.value_lookups.insert(symbol); + output.references.insert_value_lookup(symbol); Var(symbol) } @@ -1268,9 +1317,9 @@ fn canonicalize_var_lookup( } else { // Since module_name was nonempty, this is a qualified var. // Look it up in the env! - match env.qualified_lookup(module_name, ident, region) { + match env.qualified_lookup(scope, module_name, ident, region) { Ok(symbol) => { - output.references.value_lookups.insert(symbol); + output.references.insert_value_lookup(symbol); Var(symbol) } @@ -1336,6 +1385,8 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) -> region, loc_cond, branches, + branches_cond_var, + exhaustive, } => { let loc_cond = Box::new(Loc { region: loc_cond.region, @@ -1360,6 +1411,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) -> patterns: branch.patterns, value, guard, + redundant: RedundantMark::new(var_store), }; new_branches.push(new_branch); @@ -1371,6 +1423,8 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) -> region, loc_cond, branches: new_branches, + branches_cond_var, + exhaustive, } } If { @@ -1600,7 +1654,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) -> // Wrap the body in one LetNonRec for each argument, // such that at the end we have all the arguments in // scope with the values the caller provided. - for ((_param_var, loc_pattern), (expr_var, loc_expr)) in + for ((_param_var, _exhaustive_mark, loc_pattern), (expr_var, loc_expr)) in params.iter().cloned().zip(args.into_iter()).rev() { // TODO get the correct vars into here. @@ -1726,7 +1780,7 @@ fn flatten_str_lines<'a>( Interpolated(loc_expr) => { if is_valid_interpolation(loc_expr.value) { // Interpolations desugar to Str.concat calls - output.references.calls.insert(Symbol::STR_CONCAT); + output.references.insert_call(Symbol::STR_CONCAT); if !buf.is_empty() { segments.push(StrSegment::Plaintext(buf.into())); diff --git a/compiler/can/src/lib.rs b/compiler/can/src/lib.rs index f22d1a1fe6..b2dc3f4aa1 100644 --- a/compiler/can/src/lib.rs +++ b/compiler/can/src/lib.rs @@ -8,6 +8,7 @@ pub mod constraint; pub mod def; pub mod effect_module; pub mod env; +pub mod exhaustive; pub mod expected; pub mod expr; pub mod module; @@ -15,5 +16,7 @@ pub mod num; pub mod operator; pub mod pattern; pub mod procedure; +mod reference_matrix; pub mod scope; pub mod string; +pub mod traverse; diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index 01e6664d14..7f79b60f4d 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -7,10 +7,10 @@ use crate::operator::desugar_def; use crate::pattern::Pattern; use crate::scope::Scope; use bumpalo::Bump; -use roc_collections::all::{MutMap, MutSet, SendMap}; +use roc_collections::{MutMap, SendMap, VecSet}; +use roc_module::ident::Ident; use roc_module::ident::Lowercase; -use roc_module::ident::{Ident, TagName}; -use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; +use roc_module::symbol::{IdentIds, IdentIdsByModule, ModuleId, ModuleIds, Symbol}; use roc_parse::ast; use roc_parse::header::HeaderFor; use roc_parse::pattern::PatternType; @@ -23,9 +23,9 @@ use roc_types::types::{Alias, AliasKind, Type}; pub struct Module { pub module_id: ModuleId, pub exposed_imports: MutMap, - pub exposed_symbols: MutSet, - pub referenced_values: MutSet, - pub referenced_types: MutSet, + pub exposed_symbols: VecSet, + pub referenced_values: VecSet, + pub referenced_types: VecSet, /// all aliases. `bool` indicates whether it is exposed pub aliases: MutMap, pub rigid_variables: RigidVariables, @@ -36,7 +36,7 @@ pub struct Module { pub struct RigidVariables { pub named: MutMap, pub able: MutMap, - pub wildcards: MutSet, + pub wildcards: VecSet, } #[derive(Debug)] @@ -47,9 +47,8 @@ pub struct ModuleOutput { pub exposed_imports: MutMap, pub lookups: Vec<(Symbol, Variable, Region)>, pub problems: Vec, - pub ident_ids: IdentIds, - pub referenced_values: MutSet, - pub referenced_types: MutSet, + pub referenced_values: VecSet, + pub referenced_types: VecSet, pub scope: Scope, } @@ -77,6 +76,83 @@ fn validate_generate_with<'a>( (functions, unknown) } +#[derive(Debug)] +enum GeneratedInfo { + Hosted { + effect_symbol: Symbol, + generated_functions: HostedGeneratedFunctions, + }, + Builtin, + NotSpecial, +} + +impl GeneratedInfo { + fn from_header_for<'a>( + env: &mut Env, + scope: &mut Scope, + var_store: &mut VarStore, + header_for: &HeaderFor<'a>, + ) -> Self { + match header_for { + HeaderFor::Hosted { + generates, + generates_with, + } => { + let name: &str = generates.into(); + let (generated_functions, unknown_generated) = + validate_generate_with(generates_with); + + for unknown in unknown_generated { + env.problem(Problem::UnknownGeneratesWith(unknown)); + } + + let effect_symbol = scope.introduce(name.into(), Region::zero()).unwrap(); + + { + let a_var = var_store.fresh(); + + let actual = + crate::effect_module::build_effect_actual(Type::Variable(a_var), var_store); + + scope.add_alias( + effect_symbol, + Region::zero(), + vec![Loc::at_zero(("a".into(), a_var))], + actual, + AliasKind::Opaque, + ); + } + + GeneratedInfo::Hosted { + effect_symbol, + generated_functions, + } + } + HeaderFor::Builtin { generates_with } => { + debug_assert!(generates_with.is_empty()); + GeneratedInfo::Builtin + } + _ => GeneratedInfo::NotSpecial, + } + } +} + +fn has_no_implementation(expr: &Expr) -> bool { + match expr { + Expr::RuntimeError(RuntimeError::NoImplementationNamed { .. }) => true, + Expr::Closure(closure_data) + if matches!( + closure_data.loc_body.value, + Expr::RuntimeError(RuntimeError::NoImplementationNamed { .. }) + ) => + { + true + } + + _ => false, + } +} + // TODO trim these down #[allow(clippy::too_many_arguments)] pub fn canonicalize_module_defs<'a>( @@ -86,15 +162,15 @@ pub fn canonicalize_module_defs<'a>( home: ModuleId, module_ids: &ModuleIds, exposed_ident_ids: IdentIds, - dep_idents: &'a MutMap, + dep_idents: &'a IdentIdsByModule, aliases: MutMap, exposed_imports: MutMap, - exposed_symbols: &MutSet, + exposed_symbols: &VecSet, var_store: &mut VarStore, ) -> Result { let mut can_exposed_imports = MutMap::default(); - let mut scope = Scope::new(home, var_store); - let mut env = Env::new(home, dep_idents, module_ids, exposed_ident_ids); + let mut scope = Scope::new(home, exposed_ident_ids); + let mut env = Env::new(home, dep_idents, module_ids); let num_deps = dep_idents.len(); for (name, alias) in aliases.into_iter() { @@ -107,59 +183,8 @@ pub fn canonicalize_module_defs<'a>( ); } - struct Hosted { - effect_symbol: Symbol, - generated_functions: HostedGeneratedFunctions, - } - - let hosted_info = if let HeaderFor::Hosted { - generates, - generates_with, - } = header_for - { - let name: &str = generates.into(); - let (generated_functions, unknown_generated) = validate_generate_with(generates_with); - - for unknown in unknown_generated { - env.problem(Problem::UnknownGeneratesWith(unknown)); - } - - let effect_symbol = scope - .introduce( - name.into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap(); - - let effect_tag_name = TagName::Private(effect_symbol); - - { - let a_var = var_store.fresh(); - - let actual = crate::effect_module::build_effect_actual( - effect_tag_name, - Type::Variable(a_var), - var_store, - ); - - scope.add_alias( - effect_symbol, - Region::zero(), - vec![Loc::at_zero(("a".into(), a_var))], - actual, - AliasKind::Structural, - ); - } - - Some(Hosted { - effect_symbol, - generated_functions, - }) - } else { - None - }; + let generated_info = + GeneratedInfo::from_header_for(&mut env, &mut scope, var_store, header_for); // Desugar operators (convert them to Apply calls, taking into account // operator precedence and associativity rules), before doing other canonicalization. @@ -215,14 +240,25 @@ pub fn canonicalize_module_defs<'a>( panic!("TODO gracefully handle shadowing in imports.") } } + } else if [ + Symbol::LIST_LIST, + Symbol::STR_STR, + Symbol::DICT_DICT, + Symbol::SET_SET, + Symbol::BOX_BOX_TYPE, + ] + .contains(&symbol) + { + // These are not aliases but Apply's and we make sure they are always in scope } else { // This is a type alias // the symbol should already be added to the scope when this module is canonicalized debug_assert!( scope.contains_alias(symbol), - "apparently, {:?} is not actually a type alias", - symbol + "The {:?} is not a type alias known in {:?}", + symbol, + home ); // but now we know this symbol by a different identifier, so we still need to add it to @@ -231,18 +267,21 @@ pub fn canonicalize_module_defs<'a>( Ok(()) => { // here we do nothing special } - Err((_shadowed_symbol, _region)) => { - panic!("TODO gracefully handle shadowing in imports.") + Err((shadowed_symbol, _region)) => { + panic!( + "TODO gracefully handle shadowing in imports, {:?} is shadowed.", + shadowed_symbol + ) } } } } - let (defs, mut scope, output, symbols_introduced) = canonicalize_defs( + let (defs, output, symbols_introduced) = canonicalize_defs( &mut env, Output::default(), var_store, - &scope, + &mut scope, &desugared, PatternType::TopLevelDef, ); @@ -250,8 +289,7 @@ 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_value_lookup(symbol) - && !output.references.has_type_lookup(symbol) + if !output.references.has_type_or_value_lookup(symbol) && !exposed_symbols.contains(&symbol) && !scope.abilities_store.is_specialization_name(symbol) { @@ -273,15 +311,15 @@ pub fn canonicalize_module_defs<'a>( rigid_variables.wildcards.insert(var.value); } - let mut referenced_values = MutSet::default(); - let mut referenced_types = MutSet::default(); + let mut referenced_values = VecSet::default(); + let mut referenced_types = VecSet::default(); // Gather up all the symbols that were referenced across all the defs' lookups. - referenced_values.extend(output.references.value_lookups); - referenced_types.extend(output.references.type_lookups); + referenced_values.extend(output.references.value_lookups().copied()); + referenced_types.extend(output.references.type_lookups().copied()); // Gather up all the symbols that were referenced across all the defs' calls. - referenced_values.extend(output.references.calls); + referenced_values.extend(output.references.calls().copied()); // Gather up all the symbols that were referenced from other modules. referenced_values.extend(env.qualified_value_lookups.iter().copied()); @@ -314,16 +352,15 @@ pub fn canonicalize_module_defs<'a>( (Ok(mut declarations), output) => { use crate::def::Declaration::*; - if let Some(Hosted { + if let GeneratedInfo::Hosted { effect_symbol, generated_functions, - }) = hosted_info + } = generated_info { - let mut exposed_symbols = MutSet::default(); + let mut exposed_symbols = VecSet::default(); // NOTE this currently builds all functions, not just the ones that the user requested crate::effect_module::build_effect_builtins( - &mut env, &mut scope, effect_symbol, var_store, @@ -350,13 +387,25 @@ pub fn canonicalize_module_defs<'a>( // Temporary hack: we don't know exactly what symbols are hosted symbols, // and which are meant to be normal definitions without a body. So for now // we just assume they are hosted functions (meant to be provided by the platform) - if let Some(Hosted { effect_symbol, .. }) = hosted_info { - macro_rules! make_hosted_def { - () => { + if has_no_implementation(&def.loc_expr.value) { + match generated_info { + GeneratedInfo::Builtin => { + let symbol = def.pattern_vars.iter().next().unwrap().0; + match crate::builtins::builtin_defs_map(*symbol, var_store) { + None => { + panic!("A builtin module contains a signature without implementation for {:?}", symbol) + } + Some(mut replacement_def) => { + replacement_def.annotation = def.annotation.take(); + *def = replacement_def; + } + } + } + GeneratedInfo::Hosted { effect_symbol, .. } => { let symbol = def.pattern_vars.iter().next().unwrap().0; let ident_id = symbol.ident_id(); let ident = - env.ident_ids.get_name(ident_id).unwrap().to_string(); + scope.ident_ids.get_name(ident_id).unwrap().to_string(); let def_annotation = def.annotation.clone().unwrap(); let annotation = crate::annotation::Annotation { typ: def_annotation.signature, @@ -366,37 +415,17 @@ pub fn canonicalize_module_defs<'a>( }; let hosted_def = crate::effect_module::build_host_exposed_def( - &mut env, &mut scope, *symbol, &ident, - TagName::Private(effect_symbol), + effect_symbol, var_store, annotation, ); *def = hosted_def; - }; - } - - match &def.loc_expr.value { - Expr::RuntimeError(RuntimeError::NoImplementationNamed { - .. - }) => { - make_hosted_def!(); } - Expr::Closure(closure_data) - if matches!( - closure_data.loc_body.value, - Expr::RuntimeError( - RuntimeError::NoImplementationNamed { .. } - ) - ) => - { - make_hosted_def!(); - } - - _ => {} + _ => (), } } } @@ -431,7 +460,7 @@ pub fn canonicalize_module_defs<'a>( let mut aliases = MutMap::default(); - if let Some(Hosted { effect_symbol, .. }) = hosted_info { + if let GeneratedInfo::Hosted { effect_symbol, .. } = generated_info { // Remove this from exposed_symbols, // so that at the end of the process, // we can see if there were any @@ -483,11 +512,11 @@ pub fn canonicalize_module_defs<'a>( } // Incorporate any remaining output.lookups entries into references. - referenced_values.extend(output.references.value_lookups); - referenced_types.extend(output.references.type_lookups); + referenced_values.extend(output.references.value_lookups().copied()); + referenced_types.extend(output.references.type_lookups().copied()); // Incorporate any remaining output.calls entries into references. - referenced_values.extend(output.references.calls); + referenced_values.extend(output.references.calls().copied()); // Gather up all the symbols that were referenced from other modules. referenced_values.extend(env.qualified_value_lookups.iter().copied()); @@ -495,25 +524,14 @@ pub fn canonicalize_module_defs<'a>( for declaration in declarations.iter_mut() { match declaration { - Declare(def) => fix_values_captured_in_closure_def(def, &mut MutSet::default()), + Declare(def) => fix_values_captured_in_closure_def(def, &mut VecSet::default()), DeclareRec(defs) => { - fix_values_captured_in_closure_defs(defs, &mut MutSet::default()) + fix_values_captured_in_closure_defs(defs, &mut VecSet::default()) } InvalidCycle(_) | Builtin(_) => {} } } - // 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 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) { - declarations.push(Declaration::Builtin(def)); - } - } - } - let output = ModuleOutput { scope, aliases, @@ -524,7 +542,6 @@ pub fn canonicalize_module_defs<'a>( exposed_imports: can_exposed_imports, problems: env.problems, lookups, - ident_ids: env.ident_ids, }; Ok(output) @@ -535,7 +552,7 @@ pub fn canonicalize_module_defs<'a>( fn fix_values_captured_in_closure_def( def: &mut crate::def::Def, - no_capture_symbols: &mut MutSet, + no_capture_symbols: &mut VecSet, ) { // patterns can contain default expressions, so much go over them too! fix_values_captured_in_closure_pattern(&mut def.loc_pattern.value, no_capture_symbols); @@ -545,7 +562,7 @@ fn fix_values_captured_in_closure_def( fn fix_values_captured_in_closure_defs( defs: &mut Vec, - no_capture_symbols: &mut MutSet, + no_capture_symbols: &mut VecSet, ) { // recursive defs cannot capture each other for def in defs.iter() { @@ -561,7 +578,7 @@ fn fix_values_captured_in_closure_defs( fn fix_values_captured_in_closure_pattern( pattern: &mut crate::pattern::Pattern, - no_capture_symbols: &mut MutSet, + no_capture_symbols: &mut VecSet, ) { use crate::pattern::Pattern::*; @@ -610,7 +627,7 @@ fn fix_values_captured_in_closure_pattern( fn fix_values_captured_in_closure_expr( expr: &mut crate::expr::Expr, - no_capture_symbols: &mut MutSet, + no_capture_symbols: &mut VecSet, ) { use crate::expr::Expr::*; @@ -646,7 +663,7 @@ fn fix_values_captured_in_closure_expr( } // patterns can contain default expressions, so much go over them too! - for (_, loc_pat) in arguments.iter_mut() { + for (_, _, loc_pat) in arguments.iter_mut() { fix_values_captured_in_closure_pattern(&mut loc_pat.value, no_capture_symbols); } diff --git a/compiler/can/src/operator.rs b/compiler/can/src/operator.rs index 6bbb3d23b9..271e33ff0a 100644 --- a/compiler/can/src/operator.rs +++ b/compiler/can/src/operator.rs @@ -150,8 +150,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc>) -> &'a Loc | MalformedIdent(_, _) | MalformedClosure | PrecedenceConflict { .. } - | GlobalTag(_) - | PrivateTag(_) + | Tag(_) | OpaqueRef(_) => loc_expr, Access(sub_expr, paths) => { @@ -423,9 +422,8 @@ fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) { Caret => (ModuleName::NUM, "pow"), Star => (ModuleName::NUM, "mul"), Slash => (ModuleName::NUM, "div"), - DoubleSlash => (ModuleName::NUM, "divFloor"), + DoubleSlash => (ModuleName::NUM, "divTrunc"), Percent => (ModuleName::NUM, "rem"), - DoublePercent => (ModuleName::NUM, "mod"), Plus => (ModuleName::NUM, "add"), Minus => (ModuleName::NUM, "sub"), Equals => (ModuleName::BOOL, "isEq"), diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index e620f41f00..f2ca57a239 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -13,11 +13,11 @@ use roc_parse::pattern::PatternType; use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError, ShadowKind}; use roc_region::all::{Loc, Region}; use roc_types::subs::{VarStore, Variable}; -use roc_types::types::{LambdaSet, Type}; +use roc_types::types::{LambdaSet, PatternCategory, Type}; /// A pattern, including possible problems (e.g. shadowing) so that /// codegen can generate a runtime error if this pattern is reached. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub enum Pattern { Identifier(Symbol), AppliedTag { @@ -82,7 +82,85 @@ pub enum Pattern { MalformedPattern(MalformedPatternProblem, Region), } -#[derive(Clone, Debug, PartialEq)] +impl Pattern { + pub fn opt_var(&self) -> Option { + use Pattern::*; + match self { + Identifier(_) => None, + + AppliedTag { whole_var, .. } => Some(*whole_var), + UnwrappedOpaque { whole_var, .. } => Some(*whole_var), + RecordDestructure { whole_var, .. } => Some(*whole_var), + NumLiteral(var, ..) => Some(*var), + IntLiteral(var, ..) => Some(*var), + FloatLiteral(var, ..) => Some(*var), + StrLiteral(_) => None, + SingleQuote(_) => None, + Underscore => None, + + AbilityMemberSpecialization { .. } => None, + + Shadowed(..) | OpaqueNotInScope(..) | UnsupportedPattern(..) | MalformedPattern(..) => { + None + } + } + } + + /// Is this pattern sure to cover all instances of a type T, assuming it typechecks against T? + pub fn surely_exhaustive(&self) -> bool { + use Pattern::*; + match self { + Identifier(..) + | Underscore + | Shadowed(..) + | OpaqueNotInScope(..) + | UnsupportedPattern(..) + | MalformedPattern(..) + | AbilityMemberSpecialization { .. } => true, + RecordDestructure { destructs, .. } => destructs.is_empty(), + AppliedTag { .. } + | NumLiteral(..) + | IntLiteral(..) + | FloatLiteral(..) + | StrLiteral(..) + | SingleQuote(..) => false, + UnwrappedOpaque { argument, .. } => { + // Opaques can only match against one constructor (the opaque symbol), so this is + // surely exhaustive against T if the inner pattern is surely exhaustive against + // its type U. + argument.1.value.surely_exhaustive() + } + } + } + + pub fn category(&self) -> PatternCategory { + use Pattern::*; + use PatternCategory as C; + + match self { + Identifier(_) => C::PatternDefault, + + AppliedTag { tag_name, .. } => C::Ctor(tag_name.clone()), + UnwrappedOpaque { opaque, .. } => C::Opaque(*opaque), + RecordDestructure { destructs, .. } if destructs.is_empty() => C::EmptyRecord, + RecordDestructure { .. } => C::Record, + NumLiteral(..) => C::Num, + IntLiteral(..) => C::Int, + FloatLiteral(..) => C::Float, + StrLiteral(_) => C::Str, + SingleQuote(_) => C::Character, + Underscore => C::PatternDefault, + + AbilityMemberSpecialization { .. } => C::PatternDefault, + + Shadowed(..) | OpaqueNotInScope(..) | UnsupportedPattern(..) | MalformedPattern(..) => { + C::PatternDefault + } + } + } +} + +#[derive(Clone, Debug)] pub struct RecordDestruct { pub var: Variable, pub label: Lowercase, @@ -90,7 +168,7 @@ pub struct RecordDestruct { pub typ: DestructType, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub enum DestructType { Required, Optional(Variable, Loc), @@ -156,47 +234,44 @@ pub fn canonicalize_def_header_pattern<'a>( env: &mut Env<'a>, var_store: &mut VarStore, scope: &mut Scope, + output: &mut Output, pattern_type: PatternType, pattern: &ast::Pattern<'a>, region: Region, -) -> (Output, Loc) { +) -> Loc { use roc_parse::ast::Pattern::*; - let mut output = Output::default(); match pattern { // Identifiers that shadow ability members may appear (and may only appear) at the header of a def. - Identifier(name) => match scope.introduce_or_shadow_ability_member( - (*name).into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - region, - ) { - Ok((symbol, shadowing_ability_member)) => { - output.references.bound_symbols.insert(symbol); - let can_pattern = match shadowing_ability_member { - // A fresh identifier. - None => Pattern::Identifier(symbol), - // Likely a specialization of an ability. - Some(ability_member_name) => Pattern::AbilityMemberSpecialization { - ident: symbol, - specializes: ability_member_name, - }, - }; - (output, Loc::at(region, can_pattern)) - } - Err((original_region, shadow, new_symbol)) => { - env.problem(Problem::RuntimeError(RuntimeError::Shadowing { - original_region, - shadow: shadow.clone(), - kind: ShadowKind::Variable, - })); - output.references.bound_symbols.insert(new_symbol); + Identifier(name) => { + match scope.introduce_or_shadow_ability_member((*name).into(), region) { + Ok((symbol, shadowing_ability_member)) => { + output.references.insert_bound(symbol); + let can_pattern = match shadowing_ability_member { + // A fresh identifier. + None => Pattern::Identifier(symbol), + // Likely a specialization of an ability. + Some(ability_member_name) => Pattern::AbilityMemberSpecialization { + ident: symbol, + specializes: ability_member_name, + }, + }; + Loc::at(region, can_pattern) + } + Err((original_region, shadow, new_symbol)) => { + env.problem(Problem::RuntimeError(RuntimeError::Shadowing { + original_region, + shadow: shadow.clone(), + kind: ShadowKind::Variable, + })); + output.references.insert_bound(new_symbol); - let can_pattern = Pattern::Shadowed(original_region, shadow, new_symbol); - (output, Loc::at(region, can_pattern)) + let can_pattern = Pattern::Shadowed(original_region, shadow, new_symbol); + Loc::at(region, can_pattern) + } } - }, - _ => canonicalize_pattern(env, var_store, scope, pattern_type, pattern, region), + } + _ => canonicalize_pattern(env, var_store, scope, output, pattern_type, pattern, region), } } @@ -204,23 +279,18 @@ pub fn canonicalize_pattern<'a>( env: &mut Env<'a>, var_store: &mut VarStore, scope: &mut Scope, + output: &mut Output, pattern_type: PatternType, pattern: &ast::Pattern<'a>, region: Region, -) -> (Output, Loc) { +) -> Loc { use roc_parse::ast::Pattern::*; use PatternType::*; - let mut output = Output::default(); let can_pattern = match pattern { - Identifier(name) => match scope.introduce( - (*name).into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - region, - ) { + Identifier(name) => match scope.introduce((*name).into(), region) { Ok(symbol) => { - output.references.bound_symbols.insert(symbol); + output.references.insert_bound(symbol); Pattern::Identifier(symbol) } @@ -230,28 +300,17 @@ pub fn canonicalize_pattern<'a>( shadow: shadow.clone(), kind: ShadowKind::Variable, })); - output.references.bound_symbols.insert(new_symbol); + output.references.insert_bound(new_symbol); Pattern::Shadowed(original_region, shadow, new_symbol) } }, - GlobalTag(name) => { + Tag(name) => { // Canonicalize the tag's name. Pattern::AppliedTag { whole_var: var_store.fresh(), ext_var: var_store.fresh(), - tag_name: TagName::Global((*name).into()), - arguments: vec![], - } - } - PrivateTag(name) => { - let ident_id = env.ident_ids.get_or_insert(&(*name).into()); - - // Canonicalize the tag's name. - Pattern::AppliedTag { - whole_var: var_store.fresh(), - ext_var: var_store.fresh(), - tag_name: TagName::Private(Symbol::new(env.home, ident_id)), + tag_name: TagName::Tag((*name).into()), arguments: vec![], } } @@ -266,34 +325,22 @@ pub fn canonicalize_pattern<'a>( Apply(tag, patterns) => { let mut can_patterns = Vec::with_capacity(patterns.len()); for loc_pattern in *patterns { - let (new_output, can_pattern) = canonicalize_pattern( + let can_pattern = canonicalize_pattern( env, var_store, scope, + output, pattern_type, &loc_pattern.value, loc_pattern.region, ); - output.union(new_output); - can_patterns.push((var_store.fresh(), can_pattern)); } match tag.value { - GlobalTag(name) => { - let tag_name = TagName::Global(name.into()); - Pattern::AppliedTag { - whole_var: var_store.fresh(), - ext_var: var_store.fresh(), - tag_name, - arguments: can_patterns, - } - } - PrivateTag(name) => { - let ident_id = env.ident_ids.get_or_insert(&name.into()); - let tag_name = TagName::Private(Symbol::new(env.home, ident_id)); - + Tag(name) => { + let tag_name = TagName::Tag(name.into()); Pattern::AppliedTag { whole_var: var_store.fresh(), ext_var: var_store.fresh(), @@ -318,8 +365,7 @@ pub fn canonicalize_pattern<'a>( let (type_arguments, lambda_set_variables, specialized_def_type) = freshen_opaque_def(var_store, opaque_def); - output.references.referenced_type_defs.insert(opaque); - output.references.type_lookups.insert(opaque); + output.references.insert_type_lookup(opaque); Pattern::UnwrappedOpaque { whole_var: var_store.fresh(), @@ -443,7 +489,15 @@ pub fn canonicalize_pattern<'a>( } SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) => { - return canonicalize_pattern(env, var_store, scope, pattern_type, sub_pattern, region) + return canonicalize_pattern( + env, + var_store, + scope, + output, + pattern_type, + sub_pattern, + region, + ) } RecordDestructure(patterns) => { let ext_var = var_store.fresh(); @@ -454,14 +508,9 @@ pub fn canonicalize_pattern<'a>( for loc_pattern in patterns.iter() { match loc_pattern.value { Identifier(label) => { - match scope.introduce( - label.into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - region, - ) { + match scope.introduce(label.into(), region) { Ok(symbol) => { - output.references.bound_symbols.insert(symbol); + output.references.insert_bound(symbol); destructs.push(Loc { region: loc_pattern.region, @@ -492,18 +541,17 @@ pub fn canonicalize_pattern<'a>( RequiredField(label, loc_guard) => { // a guard does not introduce the label into scope! - let symbol = scope.ignore(label.into(), &mut env.ident_ids); - let (new_output, can_guard) = canonicalize_pattern( + let symbol = scope.ignore(&Ident::from(label)); + let can_guard = canonicalize_pattern( env, var_store, scope, + output, pattern_type, &loc_guard.value, loc_guard.region, ); - output.union(new_output); - destructs.push(Loc { region: loc_pattern.region, value: RecordDestruct { @@ -516,12 +564,7 @@ pub fn canonicalize_pattern<'a>( } OptionalField(label, loc_default) => { // an optional DOES introduce the label into scope! - match scope.introduce( - label.into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - region, - ) { + match scope.introduce(label.into(), region) { Ok(symbol) => { let (can_default, expr_output) = canonicalize_expr( env, @@ -532,7 +575,7 @@ pub fn canonicalize_pattern<'a>( ); // an optional field binds the symbol! - output.references.bound_symbols.insert(symbol); + output.references.insert_bound(symbol); output.union(expr_output); @@ -598,13 +641,10 @@ pub fn canonicalize_pattern<'a>( } }; - ( - output, - Loc { - region, - value: can_pattern, - }, - ) + Loc { + region, + value: can_pattern, + } } /// When we detect an unsupported pattern type (e.g. 5 = 1 + 2 is unsupported because you can't @@ -639,69 +679,122 @@ fn malformed_pattern(env: &mut Env, problem: MalformedPatternProblem, region: Re Pattern::MalformedPattern(problem, region) } -pub fn bindings_from_patterns<'a, I>(loc_patterns: I) -> Vec<(Symbol, Region)> -where - I: Iterator>, -{ - let mut answer = Vec::new(); - - for loc_pattern in loc_patterns { - add_bindings_from_patterns(&loc_pattern.region, &loc_pattern.value, &mut answer); - } - - answer +/// An iterator over the bindings made by a pattern. +/// +/// We attempt to make no allocations when we can. +pub enum BindingsFromPattern<'a> { + Empty, + One(&'a Loc), + Many(Vec>), } -/// helper function for idents_from_patterns -fn add_bindings_from_patterns( - region: &Region, - pattern: &Pattern, - answer: &mut Vec<(Symbol, Region)>, -) { - use Pattern::*; +pub enum BindingsFromPatternWork<'a> { + Pattern(&'a Loc), + Destruct(&'a Loc), +} - match pattern { - Identifier(symbol) - | Shadowed(_, _, symbol) - | AbilityMemberSpecialization { - ident: symbol, - specializes: _, - } => { - answer.push((*symbol, *region)); +impl<'a> BindingsFromPattern<'a> { + pub fn new(initial: &'a Loc) -> Self { + Self::One(initial) + } + + pub fn new_many(mut it: I) -> Self + where + I: Iterator>, + { + if let (1, Some(1)) = it.size_hint() { + Self::new(it.next().unwrap()) + } else { + Self::Many(it.map(BindingsFromPatternWork::Pattern).collect()) } - AppliedTag { - arguments: loc_args, - .. - } => { - for (_, loc_arg) in loc_args { - add_bindings_from_patterns(&loc_arg.region, &loc_arg.value, answer); + } + + fn next_many(stack: &mut Vec>) -> Option<(Symbol, Region)> { + use Pattern::*; + + while let Some(work) = stack.pop() { + match work { + BindingsFromPatternWork::Pattern(loc_pattern) => { + use BindingsFromPatternWork::*; + + match &loc_pattern.value { + Identifier(symbol) + | AbilityMemberSpecialization { + ident: symbol, + specializes: _, + } => { + return Some((*symbol, loc_pattern.region)); + } + AppliedTag { + arguments: loc_args, + .. + } => { + let it = loc_args.iter().rev().map(|(_, p)| Pattern(p)); + stack.extend(it); + } + UnwrappedOpaque { argument, .. } => { + let (_, loc_arg) = &**argument; + stack.push(Pattern(loc_arg)); + } + RecordDestructure { destructs, .. } => { + let it = destructs.iter().rev().map(Destruct); + stack.extend(it); + } + NumLiteral(..) + | IntLiteral(..) + | FloatLiteral(..) + | StrLiteral(_) + | SingleQuote(_) + | Underscore + | Shadowed(_, _, _) + | MalformedPattern(_, _) + | UnsupportedPattern(_) + | OpaqueNotInScope(..) => (), + } + } + BindingsFromPatternWork::Destruct(loc_destruct) => { + match &loc_destruct.value.typ { + DestructType::Required | DestructType::Optional(_, _) => { + return Some((loc_destruct.value.symbol, loc_destruct.region)); + } + DestructType::Guard(_, inner) => { + // a guard does not introduce the symbol + stack.push(BindingsFromPatternWork::Pattern(inner)) + } + } + } } } - UnwrappedOpaque { - argument, opaque, .. - } => { - let (_, loc_arg) = &**argument; - add_bindings_from_patterns(&loc_arg.region, &loc_arg.value, answer); - answer.push((*opaque, *region)); + + None + } +} + +impl<'a> Iterator for BindingsFromPattern<'a> { + type Item = (Symbol, Region); + + fn next(&mut self) -> Option { + use Pattern::*; + + match self { + BindingsFromPattern::Empty => None, + BindingsFromPattern::One(loc_pattern) => match &loc_pattern.value { + Identifier(symbol) + | AbilityMemberSpecialization { + ident: symbol, + specializes: _, + } => { + let region = loc_pattern.region; + *self = Self::Empty; + Some((*symbol, region)) + } + _ => { + *self = Self::Many(vec![BindingsFromPatternWork::Pattern(loc_pattern)]); + self.next() + } + }, + BindingsFromPattern::Many(stack) => Self::next_many(stack), } - RecordDestructure { destructs, .. } => { - for Loc { - region, - value: RecordDestruct { symbol, .. }, - } in destructs - { - answer.push((*symbol, *region)); - } - } - NumLiteral(..) - | IntLiteral(..) - | FloatLiteral(..) - | StrLiteral(_) - | SingleQuote(_) - | Underscore - | MalformedPattern(_, _) - | UnsupportedPattern(_) - | OpaqueNotInScope(..) => (), } } diff --git a/compiler/can/src/procedure.rs b/compiler/can/src/procedure.rs index 6f616206e0..15ab82ff9f 100644 --- a/compiler/can/src/procedure.rs +++ b/compiler/can/src/procedure.rs @@ -1,11 +1,10 @@ use crate::expr::Expr; use crate::pattern::Pattern; -use roc_collections::all::ImSet; use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; use roc_types::subs::Variable; -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct Procedure { pub name: Option>, pub is_self_tail_recursive: bool, @@ -39,47 +38,147 @@ impl Procedure { } } -/// These are all ordered sets because they end up getting traversed in a graph search -/// to determine how defs should be ordered. We want builds to be reproducible, -/// so it's important that building the same code gives the same order every time! -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Debug, Default, Clone, Copy)] +struct ReferencesBitflags(u8); + +impl ReferencesBitflags { + const VALUE_LOOKUP: Self = ReferencesBitflags(1); + const TYPE_LOOKUP: Self = ReferencesBitflags(2); + const CALL: Self = ReferencesBitflags(4); + const BOUND: Self = ReferencesBitflags(8); +} + +#[derive(Clone, Debug, Default)] pub struct References { - pub bound_symbols: ImSet, - pub type_lookups: ImSet, - pub value_lookups: ImSet, - /// Aliases or opaque types referenced - pub referenced_type_defs: ImSet, - pub calls: ImSet, + symbols: Vec, + bitflags: Vec, } impl References { - pub fn new() -> References { + pub fn new() -> Self { Self::default() } - pub fn union(mut self, other: References) -> Self { - 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); - - self + pub fn union_mut(&mut self, other: &Self) { + for (k, v) in other.symbols.iter().zip(other.bitflags.iter()) { + self.insert(*k, *v); + } } - pub fn union_mut(&mut self, other: References) { - 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); + // iterators + + fn retain<'a, P: Fn(&'a ReferencesBitflags) -> bool>( + &'a self, + pred: P, + ) -> impl Iterator { + self.symbols + .iter() + .zip(self.bitflags.iter()) + .filter_map(move |(a, b)| if pred(b) { Some(a) } else { None }) } + pub fn value_lookups(&self) -> impl Iterator { + self.retain(|b| b.0 & ReferencesBitflags::VALUE_LOOKUP.0 > 0) + } + + pub fn type_lookups(&self) -> impl Iterator { + self.retain(|b| b.0 & ReferencesBitflags::TYPE_LOOKUP.0 > 0) + } + + pub fn bound_symbols(&self) -> impl Iterator { + self.retain(|b| b.0 & ReferencesBitflags::BOUND.0 > 0) + } + + pub fn calls(&self) -> impl Iterator { + self.retain(|b| b.0 & ReferencesBitflags::CALL.0 > 0) + } + + // insert + + fn insert(&mut self, symbol: Symbol, flags: ReferencesBitflags) { + match self.symbols.iter().position(|x| *x == symbol) { + None => { + self.symbols.push(symbol); + self.bitflags.push(flags); + } + Some(index) => { + // idea: put some debug_asserts in here? + self.bitflags[index].0 |= flags.0; + } + } + } + + pub fn insert_value_lookup(&mut self, symbol: Symbol) { + self.insert(symbol, ReferencesBitflags::VALUE_LOOKUP); + } + + pub fn insert_type_lookup(&mut self, symbol: Symbol) { + self.insert(symbol, ReferencesBitflags::TYPE_LOOKUP); + } + + pub fn insert_bound(&mut self, symbol: Symbol) { + self.insert(symbol, ReferencesBitflags::BOUND); + } + + pub fn insert_call(&mut self, symbol: Symbol) { + self.insert(symbol, ReferencesBitflags::CALL); + } + + // remove + + pub fn remove_value_lookup(&mut self, symbol: &Symbol) { + match self.symbols.iter().position(|x| x == symbol) { + None => { + // it's not in there; do nothing + } + Some(index) => { + // idea: put some debug_asserts in here? + self.bitflags[index].0 ^= ReferencesBitflags::VALUE_LOOKUP.0; + } + } + } + + // contains + pub fn has_value_lookup(&self, symbol: Symbol) -> bool { - self.value_lookups.contains(&symbol) + // println!("has a value lookup? {} {:?}", self.symbols.len(), symbol); + let it = self.symbols.iter().zip(self.bitflags.iter()); + + for (a, b) in it { + if *a == symbol && b.0 & ReferencesBitflags::VALUE_LOOKUP.0 > 0 { + return true; + } + } + + false } - pub fn has_type_lookup(&self, symbol: Symbol) -> bool { - self.type_lookups.contains(&symbol) + fn has_type_lookup(&self, symbol: Symbol) -> bool { + let it = self.symbols.iter().zip(self.bitflags.iter()); + + for (a, b) in it { + if *a == symbol && b.0 & ReferencesBitflags::TYPE_LOOKUP.0 > 0 { + return true; + } + } + + false + } + + pub fn has_type_or_value_lookup(&self, symbol: Symbol) -> bool { + let mask = ReferencesBitflags::VALUE_LOOKUP.0 | ReferencesBitflags::TYPE_LOOKUP.0; + let it = self.symbols.iter().zip(self.bitflags.iter()); + + for (a, b) in it { + if *a == symbol && b.0 & mask > 0 { + return true; + } + } + + false + } + + pub fn references_type_def(&self, symbol: Symbol) -> bool { + self.has_type_lookup(symbol) } } diff --git a/compiler/can/src/reference_matrix.rs b/compiler/can/src/reference_matrix.rs new file mode 100644 index 0000000000..13ba3caea2 --- /dev/null +++ b/compiler/can/src/reference_matrix.rs @@ -0,0 +1,282 @@ +// see if we get better performance with different integer types +type Order = bitvec::order::Lsb0; +type Element = usize; +type BitVec = bitvec::vec::BitVec; +type BitSlice = bitvec::prelude::BitSlice; + +/// A square boolean matrix used to store relations +/// +/// We use this for sorting definitions so every definition is defined before it is used. +/// This functionality is also used to spot and report invalid recursion. +#[derive(Debug)] +pub(crate) struct ReferenceMatrix { + bitvec: BitVec, + length: usize, +} + +impl ReferenceMatrix { + pub fn new(length: usize) -> Self { + Self { + bitvec: BitVec::repeat(false, length * length), + length, + } + } + + pub fn references_for(&self, row: usize) -> impl Iterator + '_ { + self.row_slice(row).iter_ones() + } + + #[inline(always)] + fn row_slice(&self, row: usize) -> &BitSlice { + &self.bitvec[row * self.length..][..self.length] + } + + #[inline(always)] + pub fn set_row_col(&mut self, row: usize, col: usize, value: bool) { + self.bitvec.set(row * self.length + col, value) + } + + #[inline(always)] + pub fn get_row_col(&self, row: usize, col: usize) -> bool { + self.bitvec[row * self.length + col] + } +} + +// Topological sort and strongly-connected components +// +// Adapted from the Pathfinding crate v2.0.3 by Samuel Tardieu , +// licensed under the Apache License, version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 +// +// The original source code can be found at: https://github.com/samueltardieu/pathfinding +// +// Thank you, Samuel! +impl ReferenceMatrix { + #[allow(dead_code)] + pub fn topological_sort_into_groups(&self) -> TopologicalSort { + if self.length == 0 { + return TopologicalSort::Groups { groups: Vec::new() }; + } + + let mut preds_map: Vec = vec![0; self.length]; + + // this is basically summing the columns, I don't see a better way to do it + for row in self.bitvec.chunks(self.length) { + for succ in row.iter_ones() { + preds_map[succ] += 1; + } + } + + let mut groups = Vec::>::new(); + + // the initial group contains all symbols with no predecessors + let mut prev_group: Vec = preds_map + .iter() + .enumerate() + .filter_map(|(node, &num_preds)| { + if num_preds == 0 { + Some(node as u32) + } else { + None + } + }) + .collect(); + + if prev_group.is_empty() { + let remaining: Vec = (0u32..self.length as u32).collect(); + + return TopologicalSort::HasCycles { + groups: Vec::new(), + nodes_in_cycle: remaining, + }; + } + + while preds_map.iter().any(|x| *x > 0) { + let mut next_group = Vec::::new(); + for node in &prev_group { + for succ in self.references_for(*node as usize) { + { + let num_preds = preds_map.get_mut(succ).unwrap(); + *num_preds = num_preds.saturating_sub(1); + if *num_preds > 0 { + continue; + } + } + + // NOTE: we use -1 to mark nodes that have no predecessors, but are already + // part of an earlier group. That ensures nodes are added to just 1 group + let count = preds_map[succ]; + preds_map[succ] = -1; + + if count > -1 { + next_group.push(succ as u32); + } + } + } + groups.push(std::mem::replace(&mut prev_group, next_group)); + if prev_group.is_empty() { + let remaining: Vec = (0u32..self.length as u32) + .filter(|i| preds_map[*i as usize] > 0) + .collect(); + + return TopologicalSort::HasCycles { + groups, + nodes_in_cycle: remaining, + }; + } + } + groups.push(prev_group); + + TopologicalSort::Groups { groups } + } + + /// Get the strongly-connected components of the set of input nodes. + pub fn strongly_connected_components(&self, nodes: &[u32]) -> Sccs { + let mut params = Params::new(self.length, nodes); + + 'outer: loop { + for (node, value) in params.preorders.iter().enumerate() { + if let Preorder::Removed = value { + continue; + } + + recurse_onto(self.length, &self.bitvec, node, &mut params); + + continue 'outer; + } + + break params.scc; + } + } +} + +#[allow(dead_code)] +pub(crate) enum TopologicalSort { + /// There were no cycles, all nodes have been partitioned into groups + Groups { groups: Vec> }, + /// Cycles were found. All nodes that are not part of a cycle have been partitioned + /// into groups. The other elements are in the `cyclic` vector. However, there may be + /// many cycles, or just one big one. Use strongly-connected components to find out + /// exactly what the cycles are and how they fit into the groups. + HasCycles { + groups: Vec>, + nodes_in_cycle: Vec, + }, +} + +#[derive(Clone, Copy)] +enum Preorder { + Empty, + Filled(usize), + Removed, +} + +struct Params { + preorders: Vec, + c: usize, + p: Vec, + s: Vec, + scc: Sccs, + scca: Vec, +} + +impl Params { + fn new(length: usize, group: &[u32]) -> Self { + let mut preorders = vec![Preorder::Removed; length]; + + for value in group { + preorders[*value as usize] = Preorder::Empty; + } + + Self { + preorders, + c: 0, + s: Vec::new(), + p: Vec::new(), + scc: Sccs { + matrix: ReferenceMatrix::new(length), + components: 0, + }, + scca: Vec::new(), + } + } +} + +fn recurse_onto(length: usize, bitvec: &BitVec, v: usize, params: &mut Params) { + params.preorders[v] = Preorder::Filled(params.c); + + params.c += 1; + + params.s.push(v as u32); + params.p.push(v as u32); + + for w in bitvec[v * length..][..length].iter_ones() { + if !params.scca.contains(&(w as u32)) { + match params.preorders[w] { + Preorder::Filled(pw) => loop { + let index = *params.p.last().unwrap(); + + match params.preorders[index as usize] { + Preorder::Empty => unreachable!(), + Preorder::Filled(current) => { + if current > pw { + params.p.pop(); + } else { + break; + } + } + Preorder::Removed => {} + } + }, + Preorder::Empty => recurse_onto(length, bitvec, w, params), + Preorder::Removed => {} + } + } + } + + if params.p.last() == Some(&(v as u32)) { + params.p.pop(); + + while let Some(node) = params.s.pop() { + params + .scc + .matrix + .set_row_col(params.scc.components, node as usize, true); + params.scca.push(node); + params.preorders[node as usize] = Preorder::Removed; + if node as usize == v { + break; + } + } + + params.scc.components += 1; + } +} + +#[derive(Debug)] +pub(crate) struct Sccs { + components: usize, + matrix: ReferenceMatrix, +} + +impl Sccs { + /// Iterate over the individual components. Each component is represented as a bit vector where + /// a one indicates that the node is part of the group and a zero that it is not. + /// + /// A good way to get the actual nodes is the `.iter_ones()` method. + /// + /// It is guaranteed that a group is non-empty, and that flattening the groups gives a valid + /// topological ordering. + pub fn groups(&self) -> std::iter::Take> { + // work around a panic when requesting a chunk size of 0 + let length = if self.matrix.length == 0 { + // the `.take(self.components)` ensures the resulting iterator will be empty + assert!(self.components == 0); + + 1 + } else { + self.matrix.length + }; + + self.matrix.bitvec.chunks(length).take(self.components) + } +} diff --git a/compiler/can/src/scope.rs b/compiler/can/src/scope.rs index f4cb75e1be..a99c158cc7 100644 --- a/compiler/can/src/scope.rs +++ b/compiler/can/src/scope.rs @@ -1,4 +1,4 @@ -use roc_collections::all::{MutSet, SendMap}; +use roc_collections::{MutSet, SmallStringInterner, VecMap}; use roc_module::ident::{Ident, Lowercase}; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_problem::can::RuntimeError; @@ -8,18 +8,12 @@ use roc_types::types::{Alias, AliasKind, Type}; use crate::abilities::AbilitiesStore; -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct Scope { - /// All the identifiers in scope, mapped to were they were defined and - /// the Symbol they resolve to. - idents: SendMap, - - /// A cache of all the symbols in scope. This makes lookups much - /// faster when checking for unused defs and unused arguments. - symbols: SendMap, + idents: IdentStore, /// The type aliases currently in scope - pub aliases: SendMap, + pub aliases: VecMap, /// The abilities currently in scope, and their implementors. pub abilities_store: AbilitiesStore, @@ -27,101 +21,123 @@ pub struct Scope { /// The current module being processed. This will be used to turn /// unqualified idents into Symbols. home: ModuleId, + + pub ident_ids: IdentIds, + + /// The first `exposed_ident_count` identifiers are exposed + exposed_ident_count: usize, +} + +fn add_aliases(var_store: &mut VarStore) -> VecMap { + use roc_types::solved_types::{BuiltinAlias, FreeVars}; + + let solved_aliases = roc_types::builtin_aliases::aliases(); + let mut aliases = VecMap::default(); + + for (symbol, builtin_alias) in solved_aliases { + let BuiltinAlias { + region, + vars, + typ, + kind, + } = builtin_alias; + + let mut free_vars = FreeVars::default(); + let typ = roc_types::solved_types::to_type(&typ, &mut free_vars, var_store); + + let mut variables = Vec::new(); + // make sure to sort these variables to make them line up with the type arguments + let mut type_variables: Vec<_> = free_vars.unnamed_vars.into_iter().collect(); + type_variables.sort(); + for (loc_name, (_, var)) in vars.iter().zip(type_variables) { + variables.push(Loc::at(loc_name.region, (loc_name.value.clone(), var))); + } + + let alias = Alias { + region, + typ, + lambda_set_variables: Vec::new(), + recursion_variables: MutSet::default(), + type_variables: variables, + kind, + }; + + aliases.insert(symbol, alias); + } + + aliases } impl Scope { - pub fn new(home: ModuleId, var_store: &mut VarStore) -> Scope { - use roc_types::solved_types::{BuiltinAlias, FreeVars}; - let solved_aliases = roc_types::builtin_aliases::aliases(); - let mut aliases = SendMap::default(); - - for (symbol, builtin_alias) in solved_aliases { - let BuiltinAlias { region, vars, typ } = builtin_alias; - - let mut free_vars = FreeVars::default(); - let typ = roc_types::solved_types::to_type(&typ, &mut free_vars, var_store); - - let mut variables = Vec::new(); - // make sure to sort these variables to make them line up with the type arguments - let mut type_variables: Vec<_> = free_vars.unnamed_vars.into_iter().collect(); - type_variables.sort(); - for (loc_name, (_, var)) in vars.iter().zip(type_variables) { - variables.push(Loc::at(loc_name.region, (loc_name.value.clone(), var))); - } - - let alias = Alias { - region, - typ, - lambda_set_variables: Vec::new(), - recursion_variables: MutSet::default(), - type_variables: variables, - // TODO(opaques): replace when opaques are included in the stdlib - kind: AliasKind::Structural, - }; - - aliases.insert(symbol, alias); - } - + pub fn new(home: ModuleId, initial_ident_ids: IdentIds) -> Scope { Scope { home, - idents: Symbol::default_in_scope(), - symbols: SendMap::default(), - aliases, + exposed_ident_count: initial_ident_ids.len(), + ident_ids: initial_ident_ids, + idents: IdentStore::new(), + aliases: VecMap::default(), // TODO(abilities): default abilities in scope abilities_store: AbilitiesStore::default(), } } - pub fn idents(&self) -> impl Iterator { - self.idents.iter() - } - - pub fn symbols(&self) -> impl Iterator { - self.symbols.iter() - } - - pub fn contains_ident(&self, ident: &Ident) -> bool { - self.idents.contains_key(ident) - } - - pub fn contains_symbol(&self, symbol: Symbol) -> bool { - self.symbols.contains_key(&symbol) - } - - pub fn num_idents(&self) -> usize { - self.idents.len() + pub fn new_with_aliases( + home: ModuleId, + var_store: &mut VarStore, + initial_ident_ids: IdentIds, + ) -> Scope { + Scope { + home, + exposed_ident_count: initial_ident_ids.len(), + ident_ids: initial_ident_ids, + idents: IdentStore::new(), + aliases: add_aliases(var_store), + // TODO(abilities): default abilities in scope + abilities_store: AbilitiesStore::default(), + } } pub fn lookup(&self, ident: &Ident, region: Region) -> Result { - match self.idents.get(ident) { - Some((symbol, _)) => Ok(*symbol), - None => Err(RuntimeError::LookupNotInScope( - Loc { - region, - value: ident.clone(), - }, - self.idents.keys().map(|v| v.as_ref().into()).collect(), - )), + match self.idents.get_symbol(ident) { + Some(symbol) => Ok(symbol), + None => { + let error = RuntimeError::LookupNotInScope( + Loc { + region, + value: ident.clone(), + }, + self.idents + .iter_idents() + .map(|v| v.as_ref().into()) + .collect(), + ); + + Err(error) + } } } + #[cfg(test)] + fn idents_in_scope(&self) -> impl Iterator + '_ { + self.idents.iter_idents() + } + pub fn lookup_alias(&self, symbol: Symbol) -> Option<&Alias> { self.aliases.get(&symbol) } /// Check if there is an opaque type alias referenced by `opaque_ref` referenced in the - /// current scope. E.g. `$Age` must reference an opaque `Age` declared in this module, not any + /// current scope. E.g. `@Age` must reference an opaque `Age` declared in this module, not any /// other! - // TODO(opaques): $->@ in the above comment pub fn lookup_opaque_ref( &self, opaque_ref: &str, lookup_region: Region, ) -> Result<(Symbol, &Alias), RuntimeError> { - debug_assert!(opaque_ref.starts_with('$')); + debug_assert!(opaque_ref.starts_with('@')); let opaque = opaque_ref[1..].into(); - match self.idents.get(&opaque) { + match self.idents.get_symbol_and_region(&opaque) { // TODO: is it worth caching any of these results? Some((symbol, decl_region)) => { if symbol.module_id() != self.home { @@ -131,11 +147,11 @@ impl Scope { return Err(RuntimeError::OpaqueOutsideScope { opaque, referenced_region: lookup_region, - imported_region: *decl_region, + imported_region: decl_region, }); } - match self.aliases.get(symbol) { + match self.aliases.get(&symbol) { None => Err(self.opaque_not_defined_error(opaque, lookup_region, None)), Some(alias) => match alias.kind { @@ -146,7 +162,7 @@ impl Scope { Some(alias.header_region()), )), // All is good - AliasKind::Opaque => Ok((*symbol, alias)), + AliasKind::Opaque => Ok((symbol, alias)), }, } } @@ -161,8 +177,9 @@ impl Scope { opt_defined_alias: Option, ) -> RuntimeError { let opaques_in_scope = self - .idents() - .filter(|(_, (sym, _))| { + .idents + .iter_idents_symbols() + .filter(|(_, sym)| { self.aliases .get(sym) .map(|alias| alias.kind) @@ -191,46 +208,34 @@ impl Scope { pub fn introduce( &mut self, ident: Ident, - exposed_ident_ids: &IdentIds, - all_ident_ids: &mut IdentIds, region: Region, ) -> Result, Symbol)> { - match self.idents.get(&ident) { - Some(&(_, original_region)) => { - let shadow = Loc { - value: ident.clone(), - region, - }; - - let ident_id = all_ident_ids.add(ident.clone()); + match self.introduce_without_shadow_symbol(&ident, region) { + Ok(symbol) => Ok(symbol), + Err((original_region, shadow)) => { + let ident_id = self.ident_ids.add_ident(&ident); let symbol = Symbol::new(self.home, ident_id); - self.symbols.insert(symbol, region); - self.idents.insert(ident, (symbol, region)); - Err((original_region, shadow, symbol)) } - None => Ok(self.commit_introduction(ident, exposed_ident_ids, all_ident_ids, region)), } } /// Like [Self::introduce], but does not introduce a new symbol for the shadowing symbol. pub fn introduce_without_shadow_symbol( &mut self, - ident: Ident, - exposed_ident_ids: &IdentIds, - all_ident_ids: &mut IdentIds, + ident: &Ident, region: Region, ) -> Result)> { - match self.idents.get(&ident) { - Some(&(_, original_region)) => { + match self.idents.get_symbol_and_region(ident) { + Some((_, original_region)) => { let shadow = Loc { value: ident.clone(), region, }; Err((original_region, shadow)) } - None => Ok(self.commit_introduction(ident, exposed_ident_ids, all_ident_ids, region)), + None => Ok(self.commit_introduction(ident, region)), } } @@ -244,21 +249,24 @@ impl Scope { pub fn introduce_or_shadow_ability_member( &mut self, ident: Ident, - exposed_ident_ids: &IdentIds, - all_ident_ids: &mut IdentIds, region: Region, ) -> Result<(Symbol, Option), (Region, Loc, Symbol)> { - match self.idents.get(&ident) { - Some(&(original_symbol, original_region)) => { - let shadow_ident_id = all_ident_ids.add(ident.clone()); - let shadow_symbol = Symbol::new(self.home, shadow_ident_id); + match self.idents.get_index(&ident) { + Some(index) => { + let original_symbol = self.idents.symbols[index]; + let original_region = self.idents.regions[index]; - self.symbols.insert(shadow_symbol, region); + let shadow_ident_id = self.ident_ids.add_ident(&ident); + let shadow_symbol = Symbol::new(self.home, shadow_ident_id); if self.abilities_store.is_ability_member_name(original_symbol) { self.abilities_store .register_specializing_symbol(shadow_symbol, original_symbol); + // Add a symbol for the shadow, but don't re-associate the member name. + let dummy = Ident::default(); + self.idents.insert_unchecked(&dummy, shadow_symbol, region); + Ok((shadow_symbol, Some(original_symbol))) } else { // This is an illegal shadow. @@ -267,38 +275,26 @@ impl Scope { region, }; - self.idents.insert(ident, (shadow_symbol, region)); - Err((original_region, shadow, shadow_symbol)) } } None => { - let new_symbol = - self.commit_introduction(ident, exposed_ident_ids, all_ident_ids, region); + let new_symbol = self.commit_introduction(&ident, region); Ok((new_symbol, None)) } } } - fn commit_introduction( - &mut self, - ident: Ident, - exposed_ident_ids: &IdentIds, - all_ident_ids: &mut IdentIds, - region: Region, - ) -> Symbol { - // If this IdentId was already added previously - // when the value was exposed in the module header, - // use that existing IdentId. Otherwise, create a fresh one. - let ident_id = match exposed_ident_ids.get_id(&ident) { - Some(ident_id) => ident_id, - None => all_ident_ids.add(ident.clone()), + fn commit_introduction(&mut self, ident: &Ident, region: Region) -> Symbol { + // if the identifier is exposed, use the IdentId we already have for it + let ident_id = match self.ident_ids.get_id(ident) { + Some(ident_id) if ident_id.index() < self.exposed_ident_count => ident_id, + _ => self.ident_ids.add_ident(ident), }; let symbol = Symbol::new(self.home, ident_id); - self.symbols.insert(symbol, region); - self.idents.insert(ident, (symbol, region)); + self.idents.insert_unchecked(ident, symbol, region); symbol } @@ -306,8 +302,8 @@ impl Scope { /// Ignore an identifier. /// /// Used for record guards like { x: Just _ } - pub fn ignore(&mut self, ident: Ident, all_ident_ids: &mut IdentIds) -> Symbol { - let ident_id = all_ident_ids.add(ident); + pub fn ignore(&mut self, ident: &Ident) -> Symbol { + let ident_id = self.ident_ids.add_ident(ident); Symbol::new(self.home, ident_id) } @@ -321,11 +317,10 @@ impl Scope { symbol: Symbol, region: Region, ) -> Result<(), (Symbol, Region)> { - match self.idents.get(&ident) { - Some(shadowed) => Err(*shadowed), + match self.idents.get_symbol_and_region(&ident) { + Some(shadowed) => Err(shadowed), None => { - self.symbols.insert(symbol, region); - self.idents.insert(ident, (symbol, region)); + self.idents.insert_unchecked(&ident, symbol, region); Ok(()) } @@ -347,6 +342,43 @@ impl Scope { pub fn contains_alias(&mut self, name: Symbol) -> bool { self.aliases.contains_key(&name) } + + pub fn inner_scope(&mut self, f: F) -> T + where + F: FnOnce(&mut Scope) -> T, + { + // store enough information to roll back to the original outer scope + // + // - abilities_store: ability definitions not allowed in inner scopes + // - ident_ids: identifiers in inner scopes should still be available in the ident_ids + // - idents: we have to clone for now + // - aliases: stored in a VecMap, we just discard anything added in an inner scope + // - exposed_ident_count: unchanged + let idents = self.idents.clone(); + let aliases_count = self.aliases.len(); + + let result = f(self); + + self.idents = idents; + self.aliases.truncate(aliases_count); + + result + } + + pub fn register_debug_idents(&self) { + self.home.register_debug_idents(&self.ident_ids) + } + + /// Generates a unique, new symbol like "$1" or "$5", + /// using the home module as the module_id. + /// + /// This is used, for example, during canonicalization of an Expr::Closure + /// to generate a unique symbol to refer to that closure. + pub fn gen_unique_symbol(&mut self) -> Symbol { + let ident_id = self.ident_ids.gen_unique(); + + Symbol::new(self.home, ident_id) + } } pub fn create_alias( @@ -393,3 +425,220 @@ pub fn create_alias( kind, } } + +#[derive(Clone, Debug)] +struct IdentStore { + interner: SmallStringInterner, + + /// A Symbol for each Ident + symbols: Vec, + + /// A Region for each Ident + regions: Vec, +} + +impl IdentStore { + fn new() -> Self { + let defaults = Symbol::default_in_scope(); + let capacity = defaults.len(); + + let mut this = Self { + interner: SmallStringInterner::with_capacity(capacity), + symbols: Vec::with_capacity(capacity), + regions: Vec::with_capacity(capacity), + }; + + for (ident, (symbol, region)) in defaults { + this.insert_unchecked(&ident, symbol, region); + } + + this + } + + fn iter_idents(&self) -> impl Iterator + '_ { + self.interner.iter().filter_map(move |string| { + // empty string is used when ability members are shadowed + if string.is_empty() { + None + } else { + Some(Ident::from(string)) + } + }) + } + + fn iter_idents_symbols(&self) -> impl Iterator + '_ { + self.interner + .iter() + .zip(self.symbols.iter()) + .filter_map(move |(string, symbol)| { + // empty slice is used when ability members are shadowed + if string.is_empty() { + None + } else { + Some((Ident::from(string), *symbol)) + } + }) + } + + fn get_index(&self, ident: &Ident) -> Option { + let ident_str = ident.as_inline_str().as_str(); + + self.interner.find_index(ident_str) + } + + fn get_symbol(&self, ident: &Ident) -> Option { + Some(self.symbols[self.get_index(ident)?]) + } + + fn get_symbol_and_region(&self, ident: &Ident) -> Option<(Symbol, Region)> { + let index = self.get_index(ident)?; + + Some((self.symbols[index], self.regions[index])) + } + + /// Does not check that the ident is unique + fn insert_unchecked(&mut self, ident: &Ident, symbol: Symbol, region: Region) { + let ident_str = ident.as_inline_str().as_str(); + + let index = self.interner.insert(ident_str); + + debug_assert_eq!(index, self.symbols.len()); + debug_assert_eq!(index, self.regions.len()); + + self.symbols.push(symbol); + self.regions.push(region); + } +} + +#[cfg(test)] +mod test { + use super::*; + use roc_module::symbol::ModuleIds; + use roc_region::all::Position; + + use pretty_assertions::{assert_eq, assert_ne}; + + #[test] + fn scope_contains_introduced() { + let _register_module_debug_names = ModuleIds::default(); + let mut scope = Scope::new(ModuleId::ATTR, IdentIds::default()); + + let region = Region::zero(); + let ident = Ident::from("mezolit"); + + assert!(scope.lookup(&ident, region).is_err()); + + assert!(scope.introduce(ident.clone(), region).is_ok()); + + assert!(scope.lookup(&ident, region).is_ok()); + } + + #[test] + fn second_introduce_shadows() { + let _register_module_debug_names = ModuleIds::default(); + let mut scope = Scope::new(ModuleId::ATTR, IdentIds::default()); + + let region1 = Region::from_pos(Position { offset: 10 }); + let region2 = Region::from_pos(Position { offset: 20 }); + let ident = Ident::from("mezolit"); + + assert!(scope.lookup(&ident, Region::zero()).is_err()); + + let first = scope.introduce(ident.clone(), region1).unwrap(); + let (original_region, _ident, shadow_symbol) = + scope.introduce(ident.clone(), region2).unwrap_err(); + + scope.register_debug_idents(); + + assert_ne!(first, shadow_symbol); + assert_eq!(original_region, region1); + + let lookup = scope.lookup(&ident, Region::zero()).unwrap(); + + assert_eq!(first, lookup); + } + + #[test] + fn inner_scope_does_not_influence_outer() { + let _register_module_debug_names = ModuleIds::default(); + let mut scope = Scope::new(ModuleId::ATTR, IdentIds::default()); + + let region = Region::zero(); + let ident = Ident::from("urΓ‘nia"); + + assert!(scope.lookup(&ident, region).is_err()); + + scope.inner_scope(|inner| { + assert!(inner.introduce(ident.clone(), region).is_ok()); + }); + + assert!(scope.lookup(&ident, region).is_err()); + } + + #[test] + fn idents_with_inner_scope() { + let _register_module_debug_names = ModuleIds::default(); + let mut scope = Scope::new(ModuleId::ATTR, IdentIds::default()); + + let idents: Vec<_> = scope.idents_in_scope().collect(); + + assert_eq!( + &idents, + &[ + Ident::from("Box"), + Ident::from("Set"), + Ident::from("Dict"), + Ident::from("Str"), + Ident::from("Ok"), + Ident::from("False"), + Ident::from("List"), + Ident::from("True"), + Ident::from("Err"), + ] + ); + + let builtin_count = idents.len(); + + let region = Region::zero(); + + let ident1 = Ident::from("urΓ‘nia"); + let ident2 = Ident::from("malmok"); + let ident3 = Ident::from("JΓ‘rnak"); + + scope.introduce(ident1.clone(), region).unwrap(); + scope.introduce(ident2.clone(), region).unwrap(); + scope.introduce(ident3.clone(), region).unwrap(); + + let idents: Vec<_> = scope.idents_in_scope().collect(); + + assert_eq!( + &idents[builtin_count..], + &[ident1.clone(), ident2.clone(), ident3.clone(),] + ); + + scope.inner_scope(|inner| { + let ident4 = Ident::from("Γ…ngstrΓΆm"); + let ident5 = Ident::from("SirΓ‘ly"); + + inner.introduce(ident4.clone(), region).unwrap(); + inner.introduce(ident5.clone(), region).unwrap(); + + let idents: Vec<_> = inner.idents_in_scope().collect(); + + assert_eq!( + &idents[builtin_count..], + &[ + ident1.clone(), + ident2.clone(), + ident3.clone(), + ident4, + ident5 + ] + ); + }); + + let idents: Vec<_> = scope.idents_in_scope().collect(); + + assert_eq!(&idents[builtin_count..], &[ident1, ident2, ident3,]); + } +} diff --git a/compiler/can/src/traverse.rs b/compiler/can/src/traverse.rs new file mode 100644 index 0000000000..55ecdf14b9 --- /dev/null +++ b/compiler/can/src/traverse.rs @@ -0,0 +1,187 @@ +//! Traversals over the can ast. + +use roc_region::all::{Loc, Region}; +use roc_types::subs::Variable; + +use crate::{ + def::{Annotation, Declaration, Def}, + expr::{ClosureData, Expr, WhenBranch}, + pattern::Pattern, +}; + +macro_rules! visit_list { + ($visitor:ident, $walk:ident, $list:expr) => { + for elem in $list { + $visitor.$walk(elem) + } + }; +} + +fn walk_decls(visitor: &mut V, decls: &[Declaration]) { + visit_list!(visitor, visit_decl, decls) +} + +fn walk_decl(visitor: &mut V, decl: &Declaration) { + match decl { + Declaration::Declare(def) => { + visitor.visit_def(def); + } + Declaration::DeclareRec(defs) => { + visit_list!(visitor, visit_def, defs) + } + Declaration::Builtin(def) => visitor.visit_def(def), + Declaration::InvalidCycle(_cycles) => { + todo!() + } + } +} + +fn walk_def(visitor: &mut V, def: &Def) { + let Def { + loc_pattern, + loc_expr, + annotation, + expr_var, + .. + } = def; + + visitor.visit_pattern( + &loc_pattern.value, + loc_pattern.region, + loc_pattern.value.opt_var(), + ); + visitor.visit_expr(&loc_expr.value, loc_expr.region, *expr_var); + if let Some(annot) = &annotation { + visitor.visit_annotation(annot); + } +} + +fn walk_expr(visitor: &mut V, expr: &Expr) { + match expr { + Expr::Closure(closure_data) => walk_closure(visitor, closure_data), + Expr::When { + cond_var, + expr_var, + loc_cond, + branches, + region: _, + branches_cond_var: _, + exhaustive: _, + } => { + walk_when(visitor, *cond_var, *expr_var, loc_cond, branches); + } + e => todo!("{:?}", e), + } +} + +fn walk_closure(visitor: &mut V, clos: &ClosureData) { + let ClosureData { + arguments, + loc_body, + return_type, + .. + } = clos; + + arguments.iter().for_each(|(var, _exhaustive_mark, arg)| { + visitor.visit_pattern(&arg.value, arg.region, Some(*var)) + }); + + visitor.visit_expr(&loc_body.value, loc_body.region, *return_type); +} + +fn walk_when( + visitor: &mut V, + cond_var: Variable, + expr_var: Variable, + loc_cond: &Loc, + branches: &[WhenBranch], +) { + visitor.visit_expr(&loc_cond.value, loc_cond.region, cond_var); + + branches + .iter() + .for_each(|branch| walk_when_branch(visitor, branch, expr_var)); +} + +fn walk_when_branch(visitor: &mut V, branch: &WhenBranch, expr_var: Variable) { + let WhenBranch { + patterns, + value, + guard, + redundant: _, + } = branch; + + patterns + .iter() + .for_each(|pat| visitor.visit_pattern(&pat.value, pat.region, pat.value.opt_var())); + visitor.visit_expr(&value.value, value.region, expr_var); + if let Some(guard) = guard { + visitor.visit_expr(&guard.value, guard.region, Variable::BOOL); + } +} + +fn walk_pattern(_visitor: &mut V, _pat: &Pattern) { + todo!() +} + +trait Visitor: Sized { + fn visit_decls(&mut self, decls: &[Declaration]) { + walk_decls(self, decls); + } + + fn visit_decl(&mut self, decl: &Declaration) { + walk_decl(self, decl); + } + + fn visit_def(&mut self, def: &Def) { + walk_def(self, def); + } + + fn visit_pattern(&mut self, pat: &Pattern, _region: Region, _opt_var: Option) { + walk_pattern(self, pat) + } + + fn visit_annotation(&mut self, _pat: &Annotation) { + // TODO + } + + fn visit_expr(&mut self, expr: &Expr, _region: Region, _var: Variable) { + walk_expr(self, expr); + } +} + +struct TypeAtVisitor { + region: Region, + typ: Option, +} + +impl Visitor for TypeAtVisitor { + fn visit_expr(&mut self, expr: &Expr, region: Region, var: Variable) { + if region == self.region { + debug_assert!(self.typ.is_none()); + self.typ = Some(var); + return; + } + if region.contains(&self.region) { + walk_expr(self, expr); + } + } + + fn visit_pattern(&mut self, pat: &Pattern, region: Region, opt_var: Option) { + if region == self.region { + debug_assert!(self.typ.is_none()); + self.typ = opt_var; + return; + } + if region.contains(&self.region) { + walk_pattern(self, pat) + } + } +} + +/// Attempts to find the type of an expression at `region`, if it exists. +pub fn find_type_at(region: Region, decls: &[Declaration]) -> Option { + let mut visitor = TypeAtVisitor { region, typ: None }; + visitor.visit_decls(decls); + visitor.typ +} diff --git a/compiler/can/tests/can_inline.rs b/compiler/can/tests/can_inline.rs deleted file mode 100644 index ac26cb90a3..0000000000 --- a/compiler/can/tests/can_inline.rs +++ /dev/null @@ -1,110 +0,0 @@ -#[macro_use] -extern crate pretty_assertions; -#[macro_use] -extern crate indoc; - -extern crate bumpalo; -extern crate roc_can; -extern crate roc_parse; -extern crate roc_region; - -mod helpers; - -#[cfg(test)] -mod can_inline { - use crate::helpers::{can_expr_with, test_home}; - use bumpalo::Bump; - use roc_can::expr::inline_calls; - use roc_can::expr::Expr::{self, *}; - use roc_can::scope::Scope; - use roc_types::subs::VarStore; - - fn assert_inlines_to(input: &str, expected: Expr, var_store: &mut VarStore) { - let arena = Bump::new(); - let scope = &mut Scope::new(test_home(), var_store); - let actual_out = can_expr_with(&arena, test_home(), input); - let actual = inline_calls(var_store, scope, actual_out.loc_expr.value); - - assert_eq!(actual, expected); - } - - #[test] - fn inline_empty_record() { - // fn inline_list_len() { - let var_store = &mut VarStore::default(); - - assert_inlines_to( - indoc!( - r#" - {} - "# - ), - EmptyRecord, - var_store, - ); - - // TODO testing with hardcoded variables is very brittle. - // Should find a better way to test this! - // (One idea would be to traverse both Exprs and zero out all the Variables, - // so they always pass equality.) - // let aliases = SendMap::default(); - // assert_inlines_to( - // indoc!( - // r#" - // Int.isZero 5 - // "# - // ), - // LetNonRec( - // Box::new(Def { - // loc_pattern: Located { - // region: Region::zero(), - // value: Pattern::Identifier(Symbol::ARG_1), - // }, - // pattern_vars: SendMap::default(), - // loc_expr: Located { - // region: Region::new(0, 0, 11, 12), - // value: Num(unsafe { Variable::unsafe_test_debug_variable(7) }, 5), - // }, - // expr_var: unsafe { Variable::unsafe_test_debug_variable(8) }, - // annotation: None, - // }), - // Box::new(Located { - // region: Region::zero(), - // value: Expr::Call( - // Box::new(( - // unsafe { Variable::unsafe_test_debug_variable(138) }, - // Located { - // region: Region::zero(), - // value: Expr::Var(Symbol::BOOL_EQ), - // }, - // unsafe { Variable::unsafe_test_debug_variable(139) }, - // )), - // vec![ - // ( - // unsafe { Variable::unsafe_test_debug_variable(140) }, - // Located { - // region: Region::zero(), - // value: Var(Symbol::ARG_1), - // }, - // ), - // ( - // unsafe { Variable::unsafe_test_debug_variable(141) }, - // Located { - // region: Region::zero(), - // value: Int( - // unsafe { Variable::unsafe_test_debug_variable(137) }, - // 0, - // ), - // }, - // ), - // ], - // CalledVia::Space, - // ), - // }), - // unsafe { Variable::unsafe_test_debug_variable(198) }, - // aliases, - // ), - // var_store, - // ) - } -} diff --git a/compiler/can/tests/helpers/mod.rs b/compiler/can/tests/helpers/mod.rs index 922ffb6436..23c1b4191b 100644 --- a/compiler/can/tests/helpers/mod.rs +++ b/compiler/can/tests/helpers/mod.rs @@ -7,10 +7,11 @@ use roc_can::expr::{canonicalize_expr, Expr}; use roc_can::operator; use roc_can::scope::Scope; use roc_collections::all::MutMap; -use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds}; +use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol}; use roc_problem::can::Problem; use roc_region::all::{Loc, Region}; use roc_types::subs::{VarStore, Variable}; +use roc_types::types::Type; use std::hash::Hash; pub fn test_home() -> ModuleId { @@ -54,9 +55,17 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut // rules multiple times unnecessarily. let loc_expr = operator::desugar_expr(arena, &loc_expr); - let mut scope = Scope::new(home, &mut var_store); + let mut scope = Scope::new(home, IdentIds::default()); + scope.add_alias( + Symbol::NUM_INT, + Region::zero(), + vec![Loc::at_zero(("a".into(), Variable::EMPTY_RECORD))], + Type::EmptyRec, + roc_types::types::AliasKind::Structural, + ); + let dep_idents = IdentIds::exposed_builtins(0); - let mut env = Env::new(home, &dep_idents, &module_ids, IdentIds::default()); + let mut env = Env::new(home, &dep_idents, &module_ids); let (loc_expr, output) = canonicalize_expr( &mut env, &mut var_store, @@ -65,15 +74,8 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut &loc_expr.value, ); - let mut all_ident_ids = MutMap::default(); - - // When pretty printing types, we may need the exposed builtins, - // so include them in the Interns we'll ultimately return. - for (module_id, ident_ids) in IdentIds::exposed_builtins(0) { - all_ident_ids.insert(module_id, ident_ids); - } - - all_ident_ids.insert(home, env.ident_ids); + let mut all_ident_ids = IdentIds::exposed_builtins(1); + all_ident_ids.insert(home, scope.ident_ids); let interns = Interns { module_ids: env.module_ids.clone(), diff --git a/compiler/can/tests/test_can.rs b/compiler/can/tests/test_can.rs index a3ee85d6db..a4117f165b 100644 --- a/compiler/can/tests/test_can.rs +++ b/compiler/can/tests/test_can.rs @@ -20,11 +20,32 @@ mod test_can { use roc_region::all::{Position, Region}; use std::{f64, i64}; - fn assert_can(input: &str, expected: Expr) { + fn assert_can_runtime_error(input: &str, expected: RuntimeError) { let arena = Bump::new(); let actual_out = can_expr_with(&arena, test_home(), input); - assert_eq!(actual_out.loc_expr.value, expected); + match actual_out.loc_expr.value { + Expr::RuntimeError(actual) => { + assert_eq!(expected, actual); + } + actual => { + panic!("Expected a Float, but got: {:?}", actual); + } + } + } + + fn assert_can_string(input: &str, expected: &str) { + let arena = Bump::new(); + let actual_out = can_expr_with(&arena, test_home(), input); + + match actual_out.loc_expr.value { + Expr::Str(actual) => { + assert_eq!(expected, &*actual); + } + actual => { + panic!("Expected a Float, but got: {:?}", actual); + } + } } fn assert_can_float(input: &str, expected: f64) { @@ -50,7 +71,7 @@ mod test_can { assert_eq!(IntValue::I128(expected), actual); } actual => { - panic!("Expected an Int *, but got: {:?}", actual); + panic!("Expected an Num.Int *, but got: {:?}", actual); } } } @@ -69,10 +90,6 @@ mod test_can { } } - fn expr_str(contents: &str) -> Expr { - Expr::Str(contents.into()) - } - // NUMBER LITERALS #[test] @@ -81,14 +98,14 @@ mod test_can { let string = "340_282_366_920_938_463_463_374_607_431_768_211_456".to_string(); - assert_can( + assert_can_runtime_error( &string.clone(), - RuntimeError(RuntimeError::InvalidInt( + RuntimeError::InvalidInt( IntErrorKind::Overflow, Base::Decimal, Region::zero(), string.into_boxed_str(), - )), + ), ); } @@ -98,14 +115,14 @@ mod test_can { let string = "-170_141_183_460_469_231_731_687_303_715_884_105_729".to_string(); - assert_can( + assert_can_runtime_error( &string.clone(), - RuntimeError(RuntimeError::InvalidInt( + RuntimeError::InvalidInt( IntErrorKind::Underflow, Base::Decimal, Region::zero(), string.into(), - )), + ), ); } @@ -114,13 +131,9 @@ mod test_can { let string = format!("{}1.0", f64::MAX); let region = Region::zero(); - assert_can( + assert_can_runtime_error( &string.clone(), - RuntimeError(RuntimeError::InvalidFloat( - FloatErrorKind::PositiveInfinity, - region, - string.into(), - )), + RuntimeError::InvalidFloat(FloatErrorKind::PositiveInfinity, region, string.into()), ); } @@ -129,13 +142,9 @@ mod test_can { let string = format!("{}1.0", f64::MIN); let region = Region::zero(); - assert_can( + assert_can_runtime_error( &string.clone(), - RuntimeError(RuntimeError::InvalidFloat( - FloatErrorKind::NegativeInfinity, - region, - string.into(), - )), + RuntimeError::InvalidFloat(FloatErrorKind::NegativeInfinity, region, string.into()), ); } @@ -144,13 +153,9 @@ mod test_can { let string = "1.1.1"; let region = Region::zero(); - assert_can( + assert_can_runtime_error( string.clone(), - RuntimeError(RuntimeError::InvalidFloat( - FloatErrorKind::Error, - region, - string.into(), - )), + RuntimeError::InvalidFloat(FloatErrorKind::Error, region, string.into()), ); } @@ -274,7 +279,7 @@ mod test_can { fn correct_annotated_body() { let src = indoc!( r#" - f : Int * -> Int * + f : Num.Int * -> Num.Int * f = \ a -> a f @@ -290,7 +295,7 @@ mod test_can { fn correct_annotated_body_with_comments() { let src = indoc!( r#" - f : Int * -> Int * # comment + f : Num.Int * -> Num.Int * # comment f = \ a -> a f @@ -306,7 +311,7 @@ mod test_can { fn name_mismatch_annotated_body() { let src = indoc!( r#" - f : Int * -> Int * + f : Num.Int * -> Num.Int * g = \ a -> a g @@ -332,7 +337,7 @@ mod test_can { fn name_mismatch_annotated_body_with_comment() { let src = indoc!( r#" - f : Int * -> Int * # comment + f : Num.Int * -> Num.Int * # comment g = \ a -> a g @@ -358,7 +363,7 @@ mod test_can { fn separated_annotated_body() { let src = indoc!( r#" - f : Int * -> Int * + f : Num.Int * -> Num.Int * f = \ a -> a @@ -368,11 +373,9 @@ mod test_can { let arena = Bump::new(); let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src); - assert_eq!(problems.len(), 2); + assert_eq!(problems.len(), 1); assert!(problems.iter().all(|problem| match problem { Problem::RuntimeError(RuntimeError::Shadowing { .. }) => true, - // Due to one of the shadows - Problem::UnusedDef(..) => true, _ => false, })); } @@ -381,7 +384,7 @@ mod test_can { fn separated_annotated_body_with_comment() { let src = indoc!( r#" - f : Int * -> Int * + f : Num.Int * -> Num.Int * # comment f = \ a -> a @@ -391,11 +394,9 @@ mod test_can { let arena = Bump::new(); let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src); - assert_eq!(problems.len(), 2); + assert_eq!(problems.len(), 1); assert!(problems.iter().all(|problem| match problem { Problem::RuntimeError(RuntimeError::Shadowing { .. }) => true, - // Due to one of the shadows - Problem::UnusedDef(..) => true, _ => false, })); } @@ -404,9 +405,9 @@ mod test_can { fn shadowed_annotation() { let src = indoc!( r#" - f : Int * -> Int * + f : Num.Int * -> Num.Int * - f : Int * -> Int * + f : Num.Int * -> Num.Int * f "# @@ -414,12 +415,10 @@ mod test_can { let arena = Bump::new(); let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src); - assert_eq!(problems.len(), 2); + assert_eq!(problems.len(), 1); println!("{:#?}", problems); assert!(problems.iter().all(|problem| match problem { Problem::RuntimeError(RuntimeError::Shadowing { .. }) => true, - // Due to one of the shadows - Problem::UnusedDef(..) => true, _ => false, })); } @@ -428,7 +427,7 @@ mod test_can { fn correct_nested_unannotated_body() { let src = indoc!( r#" - f : Int * + f : Num.Int * f = g = 42 @@ -447,9 +446,9 @@ mod test_can { fn correct_nested_annotated_body() { let src = indoc!( r#" - f : Int * + f : Num.Int * f = - g : Int * + g : Num.Int * g = 42 g + 1 @@ -467,11 +466,11 @@ mod test_can { fn correct_nested_body_annotated_multiple_lines() { let src = indoc!( r#" - f : Int * + f : Num.Int * f = - g : Int * + g : Num.Int * g = 42 - h : Int * + h : Num.Int * h = 5 z = 4 g + h + z @@ -489,10 +488,10 @@ mod test_can { fn correct_nested_body_unannotated_multiple_lines() { let src = indoc!( r#" - f : Int * + f : Num.Int * f = g = 42 - h : Int * + h : Num.Int * h = 5 z = 4 g + h + z @@ -509,7 +508,7 @@ mod test_can { fn correct_double_nested_body() { let src = indoc!( r#" - f : Int * + f : Num.Int * f = g = h = 42 @@ -1582,27 +1581,27 @@ mod test_can { #[test] fn string_with_valid_unicode_escapes() { - assert_can(r#""x\u(00A0)x""#, expr_str("x\u{00A0}x")); - assert_can(r#""x\u(101010)x""#, expr_str("x\u{101010}x")); + assert_can_string(r#""x\u(00A0)x""#, "x\u{00A0}x"); + assert_can_string(r#""x\u(101010)x""#, "x\u{101010}x"); } #[test] fn block_string() { - assert_can( + assert_can_string( r#" """foobar""" "#, - expr_str("foobar"), + "foobar", ); - assert_can( + assert_can_string( indoc!( r#" """foo bar""" "# ), - expr_str("foo\nbar"), + "foo\nbar", ); } diff --git a/compiler/collections/src/lib.rs b/compiler/collections/src/lib.rs index 16f8d165dc..bb83cbddf2 100644 --- a/compiler/collections/src/lib.rs +++ b/compiler/collections/src/lib.rs @@ -3,4 +3,12 @@ #![allow(clippy::large_enum_variant)] pub mod all; +mod small_string_interner; pub mod soa; +mod vec_map; +mod vec_set; + +pub use all::{default_hasher, BumpMap, ImEntry, ImMap, ImSet, MutMap, MutSet, SendMap}; +pub use small_string_interner::SmallStringInterner; +pub use vec_map::VecMap; +pub use vec_set::VecSet; diff --git a/compiler/collections/src/small_string_interner.rs b/compiler/collections/src/small_string_interner.rs new file mode 100644 index 0000000000..387e6fcd2c --- /dev/null +++ b/compiler/collections/src/small_string_interner.rs @@ -0,0 +1,170 @@ +/// Collection of small (length < u16::MAX) strings, stored compactly. +#[derive(Clone, Default, PartialEq, Eq)] +pub struct SmallStringInterner { + buffer: Vec, + + // lengths could be Vec, but the mono refcount generation + // stringifies Layout's and that creates > 256 character strings + lengths: Vec, + offsets: Vec, +} + +impl std::fmt::Debug for SmallStringInterner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let strings: Vec<_> = self.iter().collect(); + + f.debug_struct("SmallStringInterner") + .field("buffer", &self.buffer) + .field("lengths", &self.lengths) + .field("offsets", &self.offsets) + .field("strings", &strings) + .finish() + } +} + +impl SmallStringInterner { + pub fn with_capacity(capacity: usize) -> Self { + Self { + // guess: the average symbol length is 5 + buffer: Vec::with_capacity(5 * capacity), + + lengths: Vec::with_capacity(capacity), + offsets: Vec::with_capacity(capacity), + } + } + + pub const fn from_parts(buffer: Vec, lengths: Vec, offsets: Vec) -> Self { + Self { + buffer, + lengths, + offsets, + } + } + + pub fn iter(&self) -> impl Iterator { + (0..self.offsets.len()).map(move |index| self.get(index)) + } + + pub fn insert(&mut self, string: &str) -> usize { + let bytes = string.as_bytes(); + + assert!(bytes.len() < u16::MAX as usize); + + let offset = self.buffer.len() as u32; + let length = bytes.len() as u16; + + let index = self.lengths.len(); + + self.lengths.push(length); + self.offsets.push(offset); + + self.buffer.extend(bytes); + + index + } + + /// Insert a string equal to the current length into the interner. + /// + /// Assuming that normally you don't insert strings consisting of just digits, + /// this is an easy way to create a unique string name. We use this to create + /// unique variable names: variable names cannot start with a digit in the source, + /// so if we insert the current length of `length` as its digits, that is always unique + pub fn insert_index_str(&mut self) -> usize { + use std::io::Write; + + let index = self.lengths.len(); + + let offset = self.buffer.len(); + write!(self.buffer, "{}", index).unwrap(); + let length = self.buffer.len() - offset; + + self.lengths.push(length as u16); + self.offsets.push(offset as u32); + + index + } + + #[inline(always)] + pub fn find_index(&self, string: &str) -> Option { + let target_length = string.len() as u16; + + // there can be gaps in the parts of the string that we use (because of updates) + // hence we can't just sum the lengths we've seen so far to get the next offset + for (index, length) in self.lengths.iter().enumerate() { + if *length == target_length { + let offset = self.offsets[index]; + let slice = &self.buffer[offset as usize..][..*length as usize]; + + if string.as_bytes() == slice { + return Some(index); + } + } + } + + None + } + + fn get(&self, index: usize) -> &str { + let length = self.lengths[index] as usize; + let offset = self.offsets[index] as usize; + + let bytes = &self.buffer[offset..][..length]; + + unsafe { std::str::from_utf8_unchecked(bytes) } + } + + pub fn try_get(&self, index: usize) -> Option<&str> { + if index < self.lengths.len() { + Some(self.get(index)) + } else { + None + } + } + + pub fn update(&mut self, index: usize, new_string: &str) { + let length = new_string.len(); + let offset = self.buffer.len(); + + // future optimization idea: if the current name bytes are at the end of + // `buffer`, we can update them in-place + self.buffer.extend(new_string.bytes()); + + self.lengths[index] = length as u16; + self.offsets[index] = offset as u32; + } + + pub fn find_and_update(&mut self, old_string: &str, new_string: &str) -> Option { + match self.find_index(old_string) { + Some(index) => { + self.update(index, new_string); + + Some(index) + } + None => None, + } + } + + pub fn len(&self) -> usize { + self.lengths.len() + } + + pub fn is_empty(&self) -> bool { + self.lengths.is_empty() + } +} + +#[cfg(test)] +mod test { + use super::SmallStringInterner; + + #[test] + fn update_key() { + let mut interner = SmallStringInterner::default(); + + interner.insert("main"); + interner.insert("a"); + assert!(interner.find_and_update("a", "ab").is_some()); + interner.insert("c"); + assert!(interner.find_and_update("c", "cd").is_some()); + } +} diff --git a/compiler/collections/src/vec_map.rs b/compiler/collections/src/vec_map.rs new file mode 100644 index 0000000000..e4cb42011d --- /dev/null +++ b/compiler/collections/src/vec_map.rs @@ -0,0 +1,182 @@ +#[derive(Debug, Clone)] +pub struct VecMap { + keys: Vec, + values: Vec, +} + +impl Default for VecMap { + fn default() -> Self { + Self { + keys: Vec::new(), + values: Vec::new(), + } + } +} + +impl VecMap { + pub fn with_capacity(capacity: usize) -> Self { + Self { + keys: Vec::with_capacity(capacity), + values: Vec::with_capacity(capacity), + } + } + + pub fn len(&self) -> usize { + debug_assert_eq!(self.keys.len(), self.values.len()); + self.keys.len() + } + + pub fn is_empty(&self) -> bool { + debug_assert_eq!(self.keys.len(), self.values.len()); + self.keys.is_empty() + } + + pub fn swap_remove(&mut self, index: usize) -> (K, V) { + let k = self.keys.swap_remove(index); + let v = self.values.swap_remove(index); + + (k, v) + } + + pub fn insert(&mut self, key: K, mut value: V) -> Option { + match self.keys.iter().position(|x| x == &key) { + Some(index) => { + std::mem::swap(&mut value, &mut self.values[index]); + + Some(value) + } + None => { + self.keys.push(key); + self.values.push(value); + + None + } + } + } + + pub fn contains_key(&self, key: &K) -> bool { + self.keys.contains(key) + } + + pub fn remove(&mut self, key: &K) { + match self.keys.iter().position(|x| x == key) { + None => { + // just do nothing + } + Some(index) => { + self.swap_remove(index); + } + } + } + + pub fn get(&self, key: &K) -> Option<&V> { + match self.keys.iter().position(|x| x == key) { + None => None, + Some(index) => Some(&self.values[index]), + } + } + + pub fn get_mut(&mut self, key: &K) -> Option<&mut V> { + match self.keys.iter().position(|x| x == key) { + None => None, + Some(index) => Some(&mut self.values[index]), + } + } + + pub fn get_or_insert(&mut self, key: K, default_value: impl Fn() -> V) -> &mut V { + match self.keys.iter().position(|x| x == &key) { + Some(index) => &mut self.values[index], + None => { + let value = default_value(); + + self.keys.push(key); + self.values.push(value); + + self.values.last_mut().unwrap() + } + } + } + + pub fn iter(&self) -> impl Iterator { + self.keys.iter().zip(self.values.iter()) + } + + pub fn keys(&self) -> impl Iterator { + self.keys.iter() + } + + pub fn values(&self) -> impl Iterator { + self.values.iter() + } + + pub fn truncate(&mut self, len: usize) { + self.keys.truncate(len); + self.values.truncate(len); + } + + pub fn unzip(self) -> (Vec, Vec) { + (self.keys, self.values) + } + + /// # Safety + /// + /// keys and values must have the same length, and there must not + /// be any duplicates in the keys vector + pub unsafe fn zip(keys: Vec, values: Vec) -> Self { + Self { keys, values } + } +} + +impl Extend<(K, V)> for VecMap { + #[inline(always)] + fn extend>(&mut self, iter: T) { + let it = iter.into_iter(); + let hint = it.size_hint(); + + match hint { + (0, Some(0)) => { + // done, do nothing + } + (1, Some(1)) | (2, Some(2)) => { + for (k, v) in it { + self.insert(k, v); + } + } + (_min, _opt_max) => { + // TODO do this with sorting and dedup? + for (k, v) in it { + self.insert(k, v); + } + } + } + } +} + +impl IntoIterator for VecMap { + type Item = (K, V); + + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + IntoIter { + keys: self.keys.into_iter(), + values: self.values.into_iter(), + } + } +} + +pub struct IntoIter { + keys: std::vec::IntoIter, + values: std::vec::IntoIter, +} + +impl Iterator for IntoIter { + type Item = (K, V); + + fn next(&mut self) -> Option { + match (self.keys.next(), self.values.next()) { + (Some(k), Some(v)) => Some((k, v)), + _ => None, + } + } +} diff --git a/compiler/collections/src/vec_set.rs b/compiler/collections/src/vec_set.rs new file mode 100644 index 0000000000..4869084677 --- /dev/null +++ b/compiler/collections/src/vec_set.rs @@ -0,0 +1,95 @@ +#[derive(Clone, Debug, PartialEq)] +pub struct VecSet { + elements: Vec, +} + +impl Default for VecSet { + fn default() -> Self { + Self { + elements: Vec::new(), + } + } +} + +impl VecSet { + pub fn with_capacity(capacity: usize) -> Self { + Self { + elements: Vec::with_capacity(capacity), + } + } + + pub fn len(&self) -> usize { + self.elements.len() + } + + pub fn is_empty(&self) -> bool { + self.elements.is_empty() + } + + pub fn swap_remove(&mut self, index: usize) -> T { + self.elements.swap_remove(index) + } + + pub fn insert(&mut self, value: T) -> bool { + if self.elements.contains(&value) { + true + } else { + self.elements.push(value); + + false + } + } + + pub fn contains(&self, value: &T) -> bool { + self.elements.contains(value) + } + + pub fn remove(&mut self, value: &T) { + match self.elements.iter().position(|x| x == value) { + None => { + // just do nothing + } + Some(index) => { + self.elements.swap_remove(index); + } + } + } + + pub fn iter(&self) -> impl Iterator { + self.elements.iter() + } +} + +impl Extend for VecSet { + fn extend>(&mut self, iter: T) { + let it = iter.into_iter(); + let hint = it.size_hint(); + + match hint { + (0, Some(0)) => { + // done, do nothing + } + (1, Some(1)) | (2, Some(2)) => { + for value in it { + self.insert(value); + } + } + _ => { + self.elements.extend(it); + + self.elements.sort(); + self.elements.dedup(); + } + } + } +} + +impl IntoIterator for VecSet { + type Item = T; + + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.elements.into_iter() + } +} diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index 86a812aa64..dcabe649ab 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -2,13 +2,13 @@ use arrayvec::ArrayVec; use roc_can::constraint::{Constraint, Constraints}; use roc_can::expected::Expected::{self, *}; use roc_can::num::{FloatBound, FloatWidth, IntBound, IntWidth, NumericBound, SignDemand}; -use roc_module::ident::{Lowercase, TagName}; +use roc_module::ident::Lowercase; 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)] @@ -163,14 +163,14 @@ fn builtin_alias( symbol: Symbol, type_arguments: Vec<(Lowercase, Type)>, actual: Box, + kind: AliasKind, ) -> Type { Type::Alias { symbol, type_arguments, actual, lambda_set_variables: vec![], - // TODO(opaques): revisit later - kind: AliasKind::Structural, + kind, } } @@ -180,49 +180,48 @@ pub fn num_float(range: Type) -> Type { Symbol::NUM_FLOAT, vec![("range".into(), range.clone())], Box::new(num_num(num_floatingpoint(range))), + AliasKind::Structural, ) } #[inline(always)] pub fn num_floatingpoint(range: Type) -> Type { - let alias_content = Type::TagUnion( - vec![( - TagName::Private(Symbol::NUM_AT_FLOATINGPOINT), - vec![range.clone()], - )], - TypeExtension::Closed, - ); - builtin_alias( Symbol::NUM_FLOATINGPOINT, - vec![("range".into(), range)], - Box::new(alias_content), + vec![("range".into(), range.clone())], + Box::new(range), + AliasKind::Opaque, ) } #[inline(always)] pub fn num_u32() -> Type { - builtin_alias(Symbol::NUM_U32, vec![], Box::new(num_int(num_unsigned32()))) + builtin_alias( + Symbol::NUM_U32, + vec![], + Box::new(num_int(num_unsigned32())), + AliasKind::Structural, + ) } #[inline(always)] fn num_unsigned32() -> Type { - let alias_content = Type::TagUnion( - vec![(TagName::Private(Symbol::NUM_AT_UNSIGNED32), vec![])], - TypeExtension::Closed, - ); - - builtin_alias(Symbol::NUM_UNSIGNED32, vec![], Box::new(alias_content)) + builtin_alias( + Symbol::NUM_UNSIGNED32, + vec![], + Box::new(Type::EmptyTagUnion), + AliasKind::Opaque, + ) } #[inline(always)] pub fn num_binary64() -> Type { - let alias_content = Type::TagUnion( - vec![(TagName::Private(Symbol::NUM_AT_BINARY64), vec![])], - TypeExtension::Closed, - ); - - builtin_alias(Symbol::NUM_BINARY64, vec![], Box::new(alias_content)) + builtin_alias( + Symbol::NUM_BINARY64, + vec![], + Box::new(Type::EmptyTagUnion), + AliasKind::Opaque, + ) } #[inline(always)] @@ -231,47 +230,37 @@ pub fn num_int(range: Type) -> Type { Symbol::NUM_INT, vec![("range".into(), range.clone())], Box::new(num_num(num_integer(range))), + AliasKind::Structural, ) } #[inline(always)] pub fn num_signed64() -> Type { - let alias_content = Type::TagUnion( - vec![(TagName::Private(Symbol::NUM_AT_SIGNED64), vec![])], - TypeExtension::Closed, - ); - - builtin_alias(Symbol::NUM_SIGNED64, vec![], Box::new(alias_content)) + builtin_alias( + Symbol::NUM_SIGNED64, + vec![], + Box::new(Type::EmptyTagUnion), + AliasKind::Opaque, + ) } #[inline(always)] pub fn num_integer(range: Type) -> Type { - let alias_content = Type::TagUnion( - vec![( - TagName::Private(Symbol::NUM_AT_INTEGER), - vec![range.clone()], - )], - TypeExtension::Closed, - ); - builtin_alias( Symbol::NUM_INTEGER, - vec![("range".into(), range)], - Box::new(alias_content), + vec![("range".into(), range.clone())], + Box::new(range), + AliasKind::Opaque, ) } #[inline(always)] pub fn num_num(typ: Type) -> Type { - let alias_content = Type::TagUnion( - vec![(TagName::Private(Symbol::NUM_AT_NUM), vec![typ.clone()])], - TypeExtension::Closed, - ); - builtin_alias( Symbol::NUM_NUM, - vec![("range".into(), typ)], - Box::new(alias_content), + vec![("range".into(), typ.clone())], + Box::new(typ), + AliasKind::Opaque, ) } diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 9fc45351dc..32d449f7b0 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -5,10 +5,11 @@ use crate::pattern::{constrain_pattern, PatternState}; use roc_can::annotation::IntroducedVariables; use roc_can::constraint::{Constraint, Constraints}; use roc_can::def::{Declaration, Def}; +use roc_can::exhaustive::{sketch_pattern_to_rows, sketch_when_branches, ExhaustiveContext}; use roc_can::expected::Expected::{self, *}; use roc_can::expected::PExpected; use roc_can::expr::Expr::{self, *}; -use roc_can::expr::{AccessorData, ClosureData, Field, WhenBranch}; +use roc_can::expr::{AccessorData, AnnotatedMark, ClosureData, Field, WhenBranch}; use roc_can::pattern::Pattern; use roc_collections::all::{HumanIndex, MutMap, SendMap}; use roc_module::ident::{Lowercase, TagName}; @@ -49,7 +50,7 @@ pub struct Env { fn constrain_untyped_args( constraints: &mut Constraints, env: &Env, - arguments: &[(Variable, Loc)], + arguments: &[(Variable, AnnotatedMark, Loc)], closure_type: Type, return_type: Type, ) -> (Vec, PatternState, Type) { @@ -58,7 +59,10 @@ fn constrain_untyped_args( let mut pattern_state = PatternState::default(); - for (pattern_var, loc_pattern) in arguments { + for (pattern_var, annotated_mark, loc_pattern) in arguments { + // Untyped args don't need exhaustiveness checking because they are the source of truth! + let _ = annotated_mark; + let pattern_type = Type::Variable(*pattern_var); let pattern_expected = PExpected::NoExpectation(pattern_type.clone()); @@ -579,126 +583,187 @@ pub fn constrain_expr( } } When { - cond_var, + cond_var: real_cond_var, expr_var, loc_cond, branches, + branches_cond_var, + exhaustive, .. } => { - // Infer the condition expression's type. - let cond_var = *cond_var; - let cond_type = Variable(cond_var); - let expr_con = constrain_expr( - constraints, - env, - region, - &loc_cond.value, - NoExpectation(cond_type.clone()), - ); + let branches_cond_var = *branches_cond_var; + let branches_cond_type = Variable(branches_cond_var); - let mut branch_constraints = Vec::with_capacity(branches.len() + 1); - branch_constraints.push(expr_con); + let body_var = *expr_var; + let body_type = Variable(body_var); - match &expected { - FromAnnotation(name, arity, ann_source, _typ) => { - // NOTE deviation from elm. - // - // in elm, `_typ` is used, but because we have this `expr_var` too - // and need to constrain it, this is what works and gives better error messages - let typ = Type::Variable(*expr_var); + let branches_region = { + debug_assert!(!branches.is_empty()); + Region::span_across( + &loc_cond.region, + // &branches.first().unwrap().region(), + &branches.last().unwrap().pattern_region(), + ) + }; - for (index, when_branch) in branches.iter().enumerate() { - let pattern_region = - Region::across_all(when_branch.patterns.iter().map(|v| &v.region)); + let branch_expr_reason = + |expected: &Expected, index, branch_region| match expected { + FromAnnotation(name, arity, ann_source, _typ) => { + // NOTE deviation from elm. + // + // in elm, `_typ` is used, but because we have this `expr_var` too + // and need to constrain it, this is what works and gives better error messages + FromAnnotation( + name.clone(), + *arity, + AnnotationSource::TypedWhenBranch { + index, + region: ann_source.region(), + }, + body_type.clone(), + ) + } - let branch_con = constrain_when_branch( - constraints, - env, + _ => ForReason( + Reason::WhenBranch { index }, + body_type.clone(), + branch_region, + ), + }; + + // Our goal is to constrain and introduce variables in all pattern when branch patterns before + // looking at their bodies. + // + // pat1 -> body1 + // *^^^ +~~~~ + // pat2 -> body2 + // *^^^ +~~~~ + // + // * solve first + // + solve second + // + // For a single pattern/body pair, we must introduce variables and symbols defined in the + // pattern before solving the body, since those definitions are effectively let-bound. + // + // But also, we'd like to solve all branch pattern constraints in one swoop before looking at + // the bodies, because the patterns may have presence constraints that expect to be built up + // together. + // + // For this reason, we distinguish the two - and introduce variables in the branch patterns + // as part of the pattern constraint, solving all of those at once, and then solving the body + // constraints. + let mut pattern_vars = Vec::with_capacity(branches.len()); + let mut pattern_headers = SendMap::default(); + let mut pattern_cons = Vec::with_capacity(branches.len() + 2); + let mut branch_cons = Vec::with_capacity(branches.len()); + + for (index, when_branch) in branches.iter().enumerate() { + let expected_pattern = |sub_pattern, sub_region| { + PExpected::ForReason( + PReason::WhenMatch { + index: HumanIndex::zero_based(index), + sub_pattern, + }, + branches_cond_type.clone(), + sub_region, + ) + }; + + let (new_pattern_vars, new_pattern_headers, pattern_con, branch_con) = + constrain_when_branch_help( + constraints, + env, + region, + when_branch, + expected_pattern, + branch_expr_reason( + &expected, + HumanIndex::zero_based(index), when_branch.value.region, - when_branch, - PExpected::ForReason( - PReason::WhenMatch { - index: HumanIndex::zero_based(index), - }, - cond_type.clone(), - pattern_region, - ), - FromAnnotation( - name.clone(), - *arity, - AnnotationSource::TypedWhenBranch { - index: HumanIndex::zero_based(index), - region: ann_source.region(), - }, - typ.clone(), - ), - ); + ), + ); - branch_constraints.push(branch_con); - } + pattern_vars.extend(new_pattern_vars); + debug_assert!( + pattern_headers + .clone() + .intersection(new_pattern_headers.clone()) + .is_empty(), + "Two patterns introduce the same symbols - that's a bug!\n{:?}", + pattern_headers.clone().intersection(new_pattern_headers) + ); + pattern_headers.extend(new_pattern_headers); + pattern_cons.push(pattern_con); - branch_constraints.push(constraints.equal_types_var( - *expr_var, - expected, - Category::When, - region, - )); - - return constraints.exists_many([cond_var, *expr_var], branch_constraints); - } - - _ => { - 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() { - let pattern_region = - Region::across_all(when_branch.patterns.iter().map(|v| &v.region)); - let branch_con = constrain_when_branch( - constraints, - env, - region, - when_branch, - PExpected::ForReason( - PReason::WhenMatch { - index: HumanIndex::zero_based(index), - }, - cond_type.clone(), - pattern_region, - ), - ForReason( - Reason::WhenBranch { - index: HumanIndex::zero_based(index), - }, - branch_type.clone(), - when_branch.value.region, - ), - ); - - branch_cons.push(branch_con); - } - - // Deviation: elm adds another layer of And nesting - // - // Record the original conditional expression's constraint. - // Each branch's pattern must have the same type - // as the condition expression did. - // - // The return type of each branch must equal the return type of - // the entire when-expression. - branch_cons.push(constraints.equal_types_var( - branch_var, - expected, - Category::When, - region, - )); - branch_constraints.push(constraints.and_constraint(branch_cons)); - } + branch_cons.push(branch_con); } - // exhautiveness checking happens when converting to mono::Expr - constraints.exists_many([cond_var, *expr_var], branch_constraints) + // Deviation: elm adds another layer of And nesting + // + // Record the original conditional expression's constraint. + // Each branch's pattern must have the same type + // as the condition expression did. + // + // The return type of each branch must equal the return type of + // the entire when-expression. + + // After solving the condition variable with what's expected from the branch patterns, + // check it against the condition expression. + // + // First, solve the condition type. + let real_cond_var = *real_cond_var; + let real_cond_type = Type::Variable(real_cond_var); + let cond_constraint = constrain_expr( + constraints, + env, + loc_cond.region, + &loc_cond.value, + Expected::NoExpectation(real_cond_type), + ); + pattern_cons.push(cond_constraint); + + // Now check the condition against the type expected by the branches. + let sketched_rows = sketch_when_branches(real_cond_var, branches_region, branches); + let cond_matches_branches_constraint = constraints.exhaustive( + real_cond_var, + loc_cond.region, + Ok(( + loc_cond.value.category(), + Expected::ForReason(Reason::WhenBranches, branches_cond_type, branches_region), + )), + sketched_rows, + ExhaustiveContext::BadCase, + *exhaustive, + ); + pattern_cons.push(cond_matches_branches_constraint); + + // Solve all the pattern constraints together, introducing variables in the pattern as + // need be before solving the bodies. + let pattern_constraints = constraints.and_constraint(pattern_cons); + let body_constraints = constraints.and_constraint(branch_cons); + let when_body_con = constraints.let_constraint( + [], + pattern_vars, + pattern_headers, + pattern_constraints, + body_constraints, + ); + + let result_con = + constraints.equal_types_var(body_var, expected, Category::When, region); + + let total_cons = [when_body_con, result_con]; + let branch_constraints = constraints.and_constraint(total_cons); + + constraints.exists( + [ + exhaustive.variable_for_introduction(), + branches_cond_var, + real_cond_var, + *expr_var, + ], + branch_constraints, + ) } Access { record_var, @@ -1087,15 +1152,22 @@ pub fn constrain_expr( } } +/// Constrain a when branch, returning (variables in pattern, symbols introduced in pattern, pattern constraint, body constraint). +/// We want to constraint all pattern constraints in a "when" before body constraints. #[inline(always)] -fn constrain_when_branch( +fn constrain_when_branch_help( constraints: &mut Constraints, env: &Env, region: Region, when_branch: &WhenBranch, - pattern_expected: PExpected, + pattern_expected: impl Fn(HumanIndex, Region) -> PExpected, expr_expected: Expected, -) -> Constraint { +) -> ( + Vec, + SendMap>, + Constraint, + Constraint, +) { let ret_constraint = constrain_expr( constraints, env, @@ -1106,24 +1178,27 @@ fn constrain_when_branch( let mut state = PatternState { headers: SendMap::default(), - vars: Vec::with_capacity(1), - constraints: Vec::with_capacity(1), + vars: Vec::with_capacity(2), + constraints: Vec::with_capacity(2), + delayed_is_open_constraints: Vec::new(), }; // TODO investigate for error messages, is it better to unify all branches with a variable, // then unify that variable with the expectation? - for loc_pattern in &when_branch.patterns { + for (i, loc_pattern) in when_branch.patterns.iter().enumerate() { + let pattern_expected = pattern_expected(HumanIndex::zero_based(i), loc_pattern.region); + constrain_pattern( constraints, env, &loc_pattern.value, loc_pattern.region, - pattern_expected.clone(), + pattern_expected, &mut state, ); } - if let Some(loc_guard) = &when_branch.guard { + let (pattern_constraints, body_constraints) = if let Some(loc_guard) = &when_branch.guard { let guard_constraint = constrain_expr( constraints, env, @@ -1137,20 +1212,27 @@ fn constrain_when_branch( ); // must introduce the headers from the pattern before constraining the guard + state + .constraints + .append(&mut state.delayed_is_open_constraints); let state_constraints = constraints.and_constraint(state.constraints); let inner = constraints.let_constraint([], [], [], guard_constraint, ret_constraint); - constraints.let_constraint([], state.vars, state.headers, state_constraints, inner) + (state_constraints, inner) } else { + state + .constraints + .append(&mut state.delayed_is_open_constraints); let state_constraints = constraints.and_constraint(state.constraints); - constraints.let_constraint( - [], - state.vars, - state.headers, - state_constraints, - ret_constraint, - ) - } + (state_constraints, ret_constraint) + }; + + ( + state.vars, + state.headers, + pattern_constraints, + body_constraints, + ) } fn constrain_field( @@ -1232,6 +1314,7 @@ fn constrain_def_pattern( headers: SendMap::default(), vars: Vec::with_capacity(1), constraints: Vec::with_capacity(1), + delayed_is_open_constraints: vec![], }; constrain_pattern( @@ -1329,6 +1412,7 @@ fn constrain_typed_def( headers: SendMap::default(), vars: Vec::with_capacity(arguments.len()), constraints: Vec::with_capacity(1), + delayed_is_open_constraints: vec![], }; let mut vars = Vec::with_capacity(argument_pattern_state.vars.capacity() + 1); let ret_var = *ret_var; @@ -1464,7 +1548,7 @@ fn constrain_typed_function_arguments( def: &Def, def_pattern_state: &mut PatternState, argument_pattern_state: &mut PatternState, - arguments: &[(Variable, Loc)], + arguments: &[(Variable, AnnotatedMark, Loc)], arg_types: &[Type], ) { // ensure type matches the one in the annotation @@ -1475,38 +1559,113 @@ fn constrain_typed_function_arguments( }; let it = arguments.iter().zip(arg_types.iter()).enumerate(); - for (index, ((pattern_var, loc_pattern), loc_ann)) in it { - let pattern_expected = PExpected::ForReason( - PReason::TypedArg { - index: HumanIndex::zero_based(index), - opt_name: opt_label, - }, - loc_ann.clone(), - loc_pattern.region, - ); - - constrain_pattern( - constraints, - env, - &loc_pattern.value, - loc_pattern.region, - pattern_expected, - argument_pattern_state, - ); - - { - // NOTE: because we perform an equality with part of the signature - // this constraint must be to the def_pattern_state's constraints - def_pattern_state.vars.push(*pattern_var); - - let pattern_con = constraints.equal_types_var( - *pattern_var, - Expected::NoExpectation(loc_ann.clone()), - Category::Storage(std::file!(), std::line!()), + for (index, ((pattern_var, annotated_mark, loc_pattern), ann)) in it { + if loc_pattern.value.surely_exhaustive() { + // OPT: we don't need to perform any type-level exhaustiveness checking. + // Check instead only that the pattern unifies with the annotation type. + let pattern_expected = PExpected::ForReason( + PReason::TypedArg { + index: HumanIndex::zero_based(index), + opt_name: opt_label, + }, + ann.clone(), loc_pattern.region, ); - def_pattern_state.constraints.push(pattern_con); + constrain_pattern( + constraints, + env, + &loc_pattern.value, + loc_pattern.region, + pattern_expected, + argument_pattern_state, + ); + + { + // NOTE: because we perform an equality with part of the signature + // this constraint must be to the def_pattern_state's constraints + def_pattern_state.vars.push(*pattern_var); + + let pattern_con = constraints.equal_types_var( + *pattern_var, + Expected::NoExpectation(ann.clone()), + Category::Storage(std::file!(), std::line!()), + loc_pattern.region, + ); + + def_pattern_state.constraints.push(pattern_con); + } + } else { + // We need to check the types, and run exhaustiveness checking. + let &AnnotatedMark { + annotation_var, + exhaustive, + } = annotated_mark; + + def_pattern_state.vars.push(*pattern_var); + def_pattern_state.vars.push(annotation_var); + + { + // First, solve the type that the pattern is expecting to match in this + // position. + let pattern_expected = PExpected::NoExpectation(Type::Variable(*pattern_var)); + constrain_pattern( + constraints, + env, + &loc_pattern.value, + loc_pattern.region, + pattern_expected, + argument_pattern_state, + ); + } + + { + // Store the actual type in a variable. + argument_pattern_state + .constraints + .push(constraints.equal_types_var( + annotation_var, + Expected::NoExpectation(ann.clone()), + Category::Storage(file!(), line!()), + Region::zero(), + )); + } + + { + // let pattern_expected = PExpected::ForReason( + // PReason::TypedArg { + // index: HumanIndex::zero_based(index), + // opt_name: opt_label, + // }, + // ann.clone(), + // loc_pattern.region, + // ); + + // Exhaustiveness-check the type in the pattern against what the + // annotation wants. + let sketched_rows = + sketch_pattern_to_rows(annotation_var, loc_pattern.region, &loc_pattern.value); + let category = loc_pattern.value.category(); + let expected = PExpected::ForReason( + PReason::TypedArg { + index: HumanIndex::zero_based(index), + opt_name: opt_label, + }, + Type::Variable(*pattern_var), + loc_pattern.region, + ); + let exhaustive_constraint = constraints.exhaustive( + annotation_var, + loc_pattern.region, + Err((category, expected)), + sketched_rows, + ExhaustiveContext::BadArg, + exhaustive, + ); + argument_pattern_state + .constraints + .push(exhaustive_constraint) + } } } } @@ -1649,18 +1808,18 @@ fn instantiate_rigids( let mut new_rigid_variables: Vec = Vec::new(); let mut rigid_substitution: MutMap = MutMap::default(); - for named in introduced_vars.named.iter() { + for named in introduced_vars.iter_named() { use std::collections::hash_map::Entry::*; - match ftv.entry(named.name.clone()) { + match ftv.entry(named.name().clone()) { Occupied(occupied) => { let existing_rigid = occupied.get(); - rigid_substitution.insert(named.variable, *existing_rigid); + rigid_substitution.insert(named.variable(), *existing_rigid); } Vacant(vacant) => { // It's possible to use this rigid in nested defs - vacant.insert(named.variable); - new_rigid_variables.push(named.variable); + vacant.insert(named.variable()); + new_rigid_variables.push(named.variable()); } } } @@ -1808,9 +1967,9 @@ pub fn rec_defs_help( headers: SendMap::default(), vars: Vec::with_capacity(arguments.len()), constraints: Vec::with_capacity(1), + delayed_is_open_constraints: vec![], }; let mut vars = Vec::with_capacity(state.vars.capacity() + 1); - let mut pattern_types = Vec::with_capacity(state.vars.capacity()); let ret_var = *ret_var; let closure_var = *closure_var; let closure_ext_var = *closure_ext_var; @@ -1820,53 +1979,16 @@ pub fn rec_defs_help( vars.push(closure_var); vars.push(closure_ext_var); - let it = arguments.iter().zip(arg_types.iter()).enumerate(); - for (index, ((pattern_var, loc_pattern), loc_ann)) in it { - { - // ensure type matches the one in the annotation - let opt_label = - if let Pattern::Identifier(label) = def.loc_pattern.value { - Some(label) - } else { - None - }; - let pattern_type: &Type = loc_ann; - - let pattern_expected = PExpected::ForReason( - PReason::TypedArg { - index: HumanIndex::zero_based(index), - opt_name: opt_label, - }, - pattern_type.clone(), - loc_pattern.region, - ); - - constrain_pattern( - constraints, - env, - &loc_pattern.value, - loc_pattern.region, - pattern_expected, - &mut state, - ); - } - - { - // NOTE: because we perform an equality with part of the signature - // this constraint must be to the def_pattern_state's constraints - def_pattern_state.vars.push(*pattern_var); - pattern_types.push(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, - ); - - def_pattern_state.constraints.push(pattern_con); - } - } + constrain_typed_function_arguments( + constraints, + env, + def, + &mut def_pattern_state, + &mut state, + arguments, + arg_types, + ); + let pattern_types = arguments.iter().map(|a| Type::Variable(a.0)).collect(); let closure_constraint = constrain_closure_size( constraints, diff --git a/compiler/constrain/src/module.rs b/compiler/constrain/src/module.rs index e47fe1130a..f0761ea965 100644 --- a/compiler/constrain/src/module.rs +++ b/compiler/constrain/src/module.rs @@ -2,12 +2,14 @@ use roc_builtins::std::StdLib; use roc_can::abilities::AbilitiesStore; use roc_can::constraint::{Constraint, Constraints}; use roc_can::def::Declaration; +use roc_can::expected::Expected; use roc_collections::all::MutMap; use roc_error_macros::internal_error; use roc_module::symbol::{ModuleId, Symbol}; -use roc_region::all::Loc; +use roc_region::all::{Loc, Region}; use roc_types::solved_types::{FreeVars, SolvedType}; use roc_types::subs::{VarStore, Variable}; +use roc_types::types::{Category, Type}; /// The types of all exposed values/functions of a collection of modules #[derive(Clone, Debug, Default)] @@ -65,17 +67,8 @@ impl ExposedForModule { 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()) - { + let module = exposed_by_module.exposed.get(&symbol.module_id()); + if let Some(ExposedModuleTypes::Valid { .. }) = module { imported_values.push(*symbol); } else { continue; @@ -105,27 +98,53 @@ pub fn constrain_module( declarations: &[Declaration], home: ModuleId, ) -> Constraint { - let mut constraint = crate::expr::constrain_decls(constraints, home, declarations); + let constraint = crate::expr::constrain_decls(constraints, home, declarations); + let constraint = frontload_ability_constraints(constraints, abilities_store, constraint); + + // The module constraint should always save the environment at the end. + debug_assert!(constraints.contains_save_the_environment(&constraint)); + + constraint +} + +pub fn frontload_ability_constraints( + constraints: &mut Constraints, + abilities_store: &AbilitiesStore, + mut constraint: Constraint, +) -> Constraint { for (member_name, member_data) in abilities_store.root_ability_members().iter() { + // 1. Attach the type of member signature to the reserved signature_var. This is + // infallible. + let unify_with_signature_var = constraints.equal_types_var( + member_data.signature_var, + Expected::NoExpectation(member_data.signature.clone()), + Category::Storage(std::file!(), std::column!()), + Region::zero(), + ); + + // 2. Store the member signature on the member symbol. This makes sure we generalize it on + // the toplevel, as appropriate. let vars = &member_data.variables; let rigids = (vars.rigid_vars.iter()) // For our purposes, in the let constraint, able vars are treated like rigids. .chain(vars.able_vars.iter()) .copied(); let flex = vars.flex_vars.iter().copied(); - constraint = constraints.let_constraint( + + let let_constr = constraints.let_constraint( rigids, flex, - [(*member_name, Loc::at_zero(member_data.signature.clone()))], + [( + *member_name, + Loc::at_zero(Type::Variable(member_data.signature_var)), + )], Constraint::True, constraint, ); + + constraint = constraints.and_constraint([unify_with_signature_var, let_constr]); } - - // The module constraint should always save the environment at the end. - debug_assert!(constraints.contains_save_the_environment(&constraint)); - constraint } @@ -170,17 +189,6 @@ pub fn constrain_builtin_imports( } } 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; } }; diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index 2672602b7b..9f6cc6c468 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -18,6 +18,7 @@ pub struct PatternState { pub headers: SendMap>, pub vars: Vec, pub constraints: Vec, + pub delayed_is_open_constraints: Vec, } /// If there is a type annotation, the pattern state headers can be optimized by putting the @@ -180,7 +181,7 @@ pub fn constrain_pattern( // so, we know that "x" (in this case, a tag union) must be open. if could_be_a_tag_union(expected.get_type_ref()) { state - .constraints + .delayed_is_open_constraints .push(constraints.is_open_type(expected.get_type())); } } @@ -191,7 +192,7 @@ pub fn constrain_pattern( Identifier(symbol) | Shadowed(_, _, symbol) => { if could_be_a_tag_union(expected.get_type_ref()) { state - .constraints + .delayed_is_open_constraints .push(constraints.is_open_type(expected.get_type_ref().clone())); } @@ -494,6 +495,9 @@ pub fn constrain_pattern( state.vars.push(*ext_var); state.constraints.push(whole_con); state.constraints.push(tag_con); + state + .constraints + .append(&mut state.delayed_is_open_constraints); } UnwrappedOpaque { diff --git a/compiler/exhaustive/src/lib.rs b/compiler/exhaustive/src/lib.rs index 644af3add0..a01e0a7033 100644 --- a/compiler/exhaustive/src/lib.rs +++ b/compiler/exhaustive/src/lib.rs @@ -2,7 +2,10 @@ //! http://moscova.inria.fr/~maranget/papers/warn/warn.pdf use roc_collections::all::{HumanIndex, MutMap}; -use roc_module::ident::{Lowercase, TagIdIntType, TagName}; +use roc_module::{ + ident::{Lowercase, TagIdIntType, TagName}, + symbol::Symbol, +}; use roc_region::all::Region; use roc_std::RocDec; @@ -15,9 +18,9 @@ pub struct Union { } impl Union { - pub fn newtype_wrapper(tag_name: TagName, arity: usize) -> Self { + pub fn newtype_wrapper(name: CtorName, arity: usize) -> Self { let alternatives = vec![Ctor { - name: tag_name, + name, tag_id: TagId(0), arity, }]; @@ -40,9 +43,24 @@ pub enum RenderAs { #[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)] pub struct TagId(pub TagIdIntType); +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum CtorName { + Tag(TagName), + Opaque(Symbol), +} + +impl CtorName { + pub fn is_tag(&self, tag_name: &TagName) -> bool { + match self { + Self::Tag(test) => test == tag_name, + _ => false, + } + } +} + #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Ctor { - pub name: TagName, + pub name: CtorName, pub tag_id: TagId, pub arity: usize, } @@ -54,12 +72,13 @@ pub enum Pattern { Ctor(Union, TagId, std::vec::Vec), } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum Literal { Int(i128), U128(u128), Bit(bool), Byte(u8), + /// Stores the float bits Float(u64), Decimal(RocDec), Str(Box), @@ -77,14 +96,14 @@ pub enum Error { }, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Context { BadArg, BadDestruct, BadCase, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Guard { HasGuard, NoGuard, diff --git a/compiler/fmt/src/annotation.rs b/compiler/fmt/src/annotation.rs index 8a6184f944..99220a4043 100644 --- a/compiler/fmt/src/annotation.rs +++ b/compiler/fmt/src/annotation.rs @@ -468,9 +468,7 @@ impl<'a> Formattable for Tag<'a> { use self::Tag::*; match self { - Global { args, .. } | Private { args, .. } => { - args.iter().any(|arg| (&arg.value).is_multiline()) - } + Apply { args, .. } => args.iter().any(|arg| (&arg.value).is_multiline()), Tag::SpaceBefore(_, _) | Tag::SpaceAfter(_, _) => true, Malformed(text) => text.chars().any(|c| c == '\n'), } @@ -486,25 +484,7 @@ impl<'a> Formattable for Tag<'a> { let is_multiline = self.is_multiline(); match self { - Tag::Global { name, args } => { - buf.indent(indent); - buf.push_str(name.value); - if is_multiline { - let arg_indent = indent + INDENT; - - for arg in *args { - buf.newline(); - arg.format_with_options(buf, Parens::InApply, Newlines::No, arg_indent); - } - } else { - for arg in *args { - buf.spaces(1); - arg.format_with_options(buf, Parens::InApply, Newlines::No, indent); - } - } - } - Tag::Private { name, args } => { - debug_assert!(name.value.starts_with('@')); + Tag::Apply { name, args } => { buf.indent(indent); buf.push_str(name.value); if is_multiline { diff --git a/compiler/fmt/src/def.rs b/compiler/fmt/src/def.rs index ca99d19110..72f95b0dab 100644 --- a/compiler/fmt/src/def.rs +++ b/compiler/fmt/src/def.rs @@ -191,12 +191,26 @@ pub fn fmt_body<'a, 'buf>( buf.push_str(" ="); if body.is_multiline() { match body { - Expr::SpaceBefore(_, _) => { - body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT); - } - Expr::Record { .. } | Expr::List { .. } => { - buf.newline(); - body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT); + Expr::SpaceBefore(sub_def, spaces) => { + let should_outdent = match sub_def { + Expr::Record { .. } | Expr::List { .. } => { + let is_only_newlines = spaces.iter().all(|s| s.is_newline()); + is_only_newlines && sub_def.is_multiline() + } + _ => false, + }; + + if should_outdent { + buf.spaces(1); + sub_def.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); + } else { + body.format_with_options( + buf, + Parens::NotNeeded, + Newlines::Yes, + indent + INDENT, + ); + } } _ => { buf.spaces(1); diff --git a/compiler/fmt/src/expr.rs b/compiler/fmt/src/expr.rs index b54ff826b2..fecb7fee37 100644 --- a/compiler/fmt/src/expr.rs +++ b/compiler/fmt/src/expr.rs @@ -37,8 +37,7 @@ impl<'a> Formattable for Expr<'a> { | Underscore { .. } | MalformedIdent(_, _) | MalformedClosure - | GlobalTag(_) - | PrivateTag(_) + | Tag(_) | OpaqueRef(_) => false, // These expressions always have newlines @@ -118,25 +117,16 @@ impl<'a> Formattable for Expr<'a> { use self::Expr::*; //dbg!(self); - let format_newlines = newlines == Newlines::Yes; let apply_needs_parens = parens == Parens::InApply; match self { SpaceBefore(sub_expr, spaces) => { - if format_newlines { - fmt_spaces(buf, spaces.iter(), indent); - } else { - fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent); - } + format_spaces(buf, spaces, newlines, indent); sub_expr.format_with_options(buf, parens, newlines, indent); } SpaceAfter(sub_expr, spaces) => { sub_expr.format_with_options(buf, parens, newlines, indent); - if format_newlines { - fmt_spaces(buf, spaces.iter(), indent); - } else { - fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent); - } + format_spaces(buf, spaces, newlines, indent); } ParensAround(sub_expr) => { if parens == Parens::NotNeeded && !sub_expr_requests_parens(sub_expr) { @@ -187,13 +177,73 @@ impl<'a> Formattable for Expr<'a> { let multiline_args = loc_args.iter().any(|loc_arg| loc_arg.is_multiline()); - if multiline_args { + let mut found_multiline_expr = false; + let mut iter = loc_args.iter().peekable(); + + while let Some(loc_arg) = iter.next() { + if iter.peek().is_none() { + found_multiline_expr = match loc_arg.value { + SpaceBefore(sub_expr, spaces) => match sub_expr { + Record { .. } | List { .. } => { + let is_only_newlines = spaces.iter().all(|s| s.is_newline()); + is_only_newlines + && !found_multiline_expr + && sub_expr.is_multiline() + } + _ => false, + }, + Record { .. } | List { .. } | Closure { .. } => { + !found_multiline_expr && loc_arg.is_multiline() + } + _ => false, + } + } else { + found_multiline_expr = loc_arg.is_multiline(); + } + } + + let should_outdent_last_arg = found_multiline_expr; + + if multiline_args && !should_outdent_last_arg { let arg_indent = indent + INDENT; for loc_arg in loc_args.iter() { buf.newline(); loc_arg.format_with_options(buf, Parens::InApply, Newlines::No, arg_indent); } + } else if multiline_args && should_outdent_last_arg { + let mut iter = loc_args.iter().peekable(); + while let Some(loc_arg) = iter.next() { + buf.spaces(1); + + if iter.peek().is_none() { + match loc_arg.value { + SpaceBefore(sub_expr, _) => { + sub_expr.format_with_options( + buf, + Parens::InApply, + Newlines::Yes, + indent, + ); + } + _ => { + loc_arg.format_with_options( + buf, + Parens::InApply, + Newlines::Yes, + indent, + ); + } + } + } else { + loc_arg.format_with_options( + buf, + Parens::InApply, + Newlines::Yes, + indent, + ); + } + } } else { for loc_arg in loc_args.iter() { buf.spaces(1); @@ -213,7 +263,7 @@ impl<'a> Formattable for Expr<'a> { buf.indent(indent); buf.push_str(string); } - GlobalTag(string) | PrivateTag(string) | OpaqueRef(string) => { + Tag(string) | OpaqueRef(string) => { buf.indent(indent); buf.push_str(string) } @@ -262,15 +312,39 @@ impl<'a> Formattable for Expr<'a> { fmt_def(buf, &loc_def.value, indent); } - let empty_line_before_return = empty_line_before_expr(&ret.value); + match &ret.value { + SpaceBefore(sub_expr, spaces) => { + let empty_line_before_return = empty_line_before_expr(&ret.value); + let has_inline_comment = with_inline_comment(&ret.value); - if !empty_line_before_return { - buf.newline(); + if has_inline_comment { + buf.spaces(1); + format_spaces(buf, spaces, newlines, indent); + + if !empty_line_before_return { + buf.newline(); + } + + sub_expr.format_with_options( + buf, + Parens::NotNeeded, + Newlines::Yes, + indent, + ); + } else { + if !empty_line_before_return { + buf.newline(); + } + + ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); + } + } + _ => { + // Even if there were no defs, which theoretically should never happen, + // still print the return value. + ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); + } } - - // Even if there were no defs, which theoretically should never happen, - // still print the return value. - ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); } Expect(condition, continuation) => { fmt_expect(buf, condition, continuation, self.is_multiline(), indent); @@ -372,7 +446,6 @@ fn push_op(buf: &mut Buf, op: BinOp) { called_via::BinOp::Slash => buf.push('/'), called_via::BinOp::DoubleSlash => buf.push_str("//"), called_via::BinOp::Percent => buf.push('%'), - called_via::BinOp::DoublePercent => buf.push_str("%%"), called_via::BinOp::Plus => buf.push('+'), called_via::BinOp::Minus => buf.push('-'), called_via::BinOp::Equals => buf.push_str("=="), @@ -483,6 +556,34 @@ fn fmt_bin_ops<'a, 'buf>( loc_right_side.format_with_options(buf, apply_needs_parens, Newlines::Yes, next_indent); } +fn format_spaces<'a, 'buf>( + buf: &mut Buf<'buf>, + spaces: &[CommentOrNewline<'a>], + newlines: Newlines, + indent: u16, +) { + let format_newlines = newlines == Newlines::Yes; + + if format_newlines { + fmt_spaces(buf, spaces.iter(), indent); + } else { + fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent); + } +} + +fn with_inline_comment<'a>(expr: &'a Expr<'a>) -> bool { + use roc_parse::ast::Expr::*; + + match expr { + SpaceBefore(_, spaces) => match spaces.iter().next() { + Some(CommentOrNewline::LineComment(_)) => true, + Some(_) => false, + None => false, + }, + _ => false, + } +} + fn empty_line_before_expr<'a>(expr: &'a Expr<'a>) -> bool { use roc_parse::ast::Expr::*; @@ -847,7 +948,34 @@ fn fmt_closure<'a, 'buf>( } }; - loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, body_indent); + if is_multiline { + match &loc_ret.value { + SpaceBefore(sub_expr, spaces) => { + let should_outdent = match sub_expr { + Record { .. } | List { .. } => { + let is_only_newlines = spaces.iter().all(|s| s.is_newline()); + is_only_newlines && sub_expr.is_multiline() + } + _ => false, + }; + + if should_outdent { + buf.spaces(1); + sub_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); + } else { + loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, body_indent); + } + } + Record { .. } | List { .. } => { + loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); + } + _ => { + loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, body_indent); + } + } + } else { + loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, body_indent); + } } fn fmt_backpassing<'a, 'buf>( @@ -1104,7 +1232,6 @@ fn sub_expr_requests_parens(expr: &Expr<'_>) -> bool { | BinOp::Slash | BinOp::DoubleSlash | BinOp::Percent - | BinOp::DoublePercent | BinOp::Plus | BinOp::Minus | BinOp::Equals diff --git a/compiler/fmt/src/pattern.rs b/compiler/fmt/src/pattern.rs index c903ad9596..e4f53fc47c 100644 --- a/compiler/fmt/src/pattern.rs +++ b/compiler/fmt/src/pattern.rs @@ -28,8 +28,7 @@ impl<'a> Formattable for Pattern<'a> { Pattern::OptionalField(_, expr) => expr.is_multiline(), Pattern::Identifier(_) - | Pattern::GlobalTag(_) - | Pattern::PrivateTag(_) + | Pattern::Tag(_) | Pattern::OpaqueRef(_) | Pattern::Apply(_, _) | Pattern::NumLiteral(..) @@ -58,7 +57,7 @@ impl<'a> Formattable for Pattern<'a> { buf.indent(indent); buf.push_str(string) } - GlobalTag(name) | PrivateTag(name) | OpaqueRef(name) => { + Tag(name) | OpaqueRef(name) => { buf.indent(indent); buf.push_str(name); } diff --git a/compiler/fmt/tests/test_fmt.rs b/compiler/fmt/tests/test_fmt.rs index c5bde106a7..51a87c5797 100644 --- a/compiler/fmt/tests/test_fmt.rs +++ b/compiler/fmt/tests/test_fmt.rs @@ -106,26 +106,61 @@ mod test_fmt { } #[test] - #[ignore] - fn def_with_comment_on_same_line() { - // TODO(joshuawarner32): make trailing comments format stabily - // This test currently fails because the comment ends up as SpaceBefore for the following `a` - // This works fine when formatted _once_ - but if you format again, the formatter wants to - // insert a newline between `a = "Hello"` and the comment, further muddying the waters. - // Clearly the formatter shouldn't be allowed to migrate a comment around like that. + fn def_with_inline_comment() { + expr_formats_same(indoc!( + r#" + x = 0 # comment + + x + "# + )); + expr_formats_to( indoc!( r#" - a = "Hello" # This variable is for greeting + x = 0# comment - a + x "# ), indoc!( r#" - a = "Hello" - # This variable is for greeting - a + x = 0 # comment + + x + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + x = 0# comment + x + "# + ), + indoc!( + r#" + x = 0 # comment + + x + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + x = 0 # comment + + x + "# + ), + indoc!( + r#" + x = 0 # comment + + x "# ), ); @@ -626,6 +661,392 @@ mod test_fmt { )); } + #[test] + fn lambda_returns_record() { + expr_formats_same(indoc!( + r#" + toRecord = \_ -> { + x: 1, + y: 2, + z: 3, + } + + toRecord + "# + )); + + expr_formats_same(indoc!( + r#" + func = \_ -> + { x: 1, y: 2, z: 3 } + + func + "# + )); + + expr_formats_same(indoc!( + r#" + toRecord = \_ -> + val = 0 + + { + x: 1, + y: 2, + z: 3, + } + + toRecord + "# + )); + + expr_formats_to( + indoc!( + r#" + toRecord = \_ -> + { + x: 1, + y: 2, + z: 3, + } + + toRecord + "# + ), + indoc!( + r#" + toRecord = \_ -> { + x: 1, + y: 2, + z: 3, + } + + toRecord + "# + ), + ); + } + + #[test] + fn lambda_returns_list() { + expr_formats_same(indoc!( + r#" + toList = \_ -> [ + 1, + 2, + 3, + ] + + toList + "# + )); + + expr_formats_same(indoc!( + r#" + func = \_ -> + [ 1, 2, 3 ] + + func + "# + )); + + expr_formats_same(indoc!( + r#" + toList = \_ -> + val = 0 + + [ + 1, + 2, + 3, + ] + + toList + "# + )); + + expr_formats_to( + indoc!( + r#" + toList = \_ -> + [ + 1, + 2, + 3, + ] + + toList + "# + ), + indoc!( + r#" + toList = \_ -> [ + 1, + 2, + 3, + ] + + toList + "# + ), + ); + } + + #[test] + fn multiline_list_func_arg() { + expr_formats_same(indoc!( + r#" + result = func arg [ + 1, + 2, + 3, + ] + + result + "# + )); + + expr_formats_to( + indoc!( + r#" + result = func arg + [ 1, 2, 3 ] + + result + "# + ), + indoc!( + r#" + result = func + arg + [ 1, 2, 3 ] + + result + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + result = func arg [ + 1, + 2, + 3, + ] + + result + "# + ), + indoc!( + r#" + result = func arg [ + 1, + 2, + 3, + ] + + result + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + result = func [ + 1, + 2, + 3, + ] + arg + + result + "# + ), + indoc!( + r#" + result = func + [ + 1, + 2, + 3, + ] + arg + + result + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + result = func arg + [ + 1, + 2, + 3, + ] + + result + "# + ), + indoc!( + r#" + result = func arg [ + 1, + 2, + 3, + ] + + result + "# + ), + ); + + expr_formats_same(indoc!( + r#" + result = func + arg + [ + 1, + 2, + 3, + ] + + result + "# + )); + } + + #[test] + fn multiline_record_func_arg() { + expr_formats_same(indoc!( + r#" + result = func arg { + x: 1, + y: 2, + z: 3, + } + + result + "# + )); + + expr_formats_to( + indoc!( + r#" + result = func arg + { x: 1, y: 2, z: 3 } + + result + "# + ), + indoc!( + r#" + result = func + arg + { x: 1, y: 2, z: 3 } + + result + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + result = func arg { + x: 1, + y: 2, + z: 3, + } + + result + "# + ), + indoc!( + r#" + result = func arg { + x: 1, + y: 2, + z: 3, + } + + result + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + result = func { + x: 1, + y: 2, + z: 3, + } + arg + + result + "# + ), + indoc!( + r#" + result = func + { + x: 1, + y: 2, + z: 3, + } + arg + + result + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + result = func arg + { + x: 1, + y: 2, + z: 3, + } + + result + "# + ), + indoc!( + r#" + result = func arg { + x: 1, + y: 2, + z: 3, + } + + result + "# + ), + ); + + expr_formats_same(indoc!( + r#" + result = func + arg + { + x: 1, + y: 2, + z: 3, + } + + result + "# + )); + } + #[test] fn record_updating() { expr_formats_same(indoc!( @@ -1301,6 +1722,27 @@ mod test_fmt { fn multi_line_list_def() { expr_formats_same(indoc!( r#" + l = [ + 1, + 2, + ] + + l + "# + )); + + expr_formats_same(indoc!( + r#" + l = + [ 1, 2 ] + + l + "# + )); + + expr_formats_to( + indoc!( + r#" l = [ 1, @@ -1308,8 +1750,19 @@ mod test_fmt { ] l - "# - )); + "# + ), + indoc!( + r#" + l = [ + 1, + 2, + ] + + l + "# + ), + ); expr_formats_to( indoc!( @@ -1324,11 +1777,10 @@ mod test_fmt { ), indoc!( r#" - results = - [ - Ok 4, - Ok 5, - ] + results = [ + Ok 4, + Ok 5, + ] allOks results "# @@ -1417,18 +1869,69 @@ mod test_fmt { #[test] fn multi_line_record_def() { + expr_formats_same(indoc!( + r#" + pos = { + x: 4, + y: 11, + z: 16, + } + + pos + "# + )); + expr_formats_same(indoc!( r#" pos = + { x: 4, y: 11, z: 16 } + + pos + "# + )); + + expr_formats_same(indoc!( + r#" + myDef = + list = [ + a, + b, + ] + { + c, + d, + } + + myDef + "# + )); + + expr_formats_to( + indoc!( + r#" + pos = + { + x: 4, + y: 11, + z: 16, + } + + pos + "# + ), + indoc!( + r#" + pos = { x: 4, y: 11, z: 16, } - pos - "# - )); + pos + "# + ), + ); expr_formats_to( indoc!( @@ -1443,11 +1946,10 @@ mod test_fmt { ), indoc!( r#" - pos = - { - x: 5, - y: 10, - } + pos = { + x: 5, + y: 10, + } pos "# @@ -2537,7 +3039,7 @@ mod test_fmt { indoc!( r#" 2 % 3 - %% 5 + // 5 + 7 "# ), @@ -2545,7 +3047,7 @@ mod test_fmt { r#" 2 % 3 - %% 5 + // 5 + 7 "# ), @@ -2619,6 +3121,18 @@ mod test_fmt { )); } + #[test] + fn func_call_trailing_multiline_lambda() { + expr_formats_same(indoc!( + r#" + list = List.map [ 1, 2, 3 ] \x -> + x + 1 + + list + "# + )); + } + // MODULES #[test] diff --git a/compiler/gen_dev/Cargo.toml b/compiler/gen_dev/Cargo.toml index 0b5a08fe0d..00b08bd467 100644 --- a/compiler/gen_dev/Cargo.toml +++ b/compiler/gen_dev/Cargo.toml @@ -19,7 +19,7 @@ roc_mono = { path = "../mono" } roc_target = { path = "../roc_target" } roc_error_macros = { path = "../../error_macros" } bumpalo = { version = "3.8.0", features = ["collections"] } -target-lexicon = "0.12.2" +target-lexicon = "0.12.3" # TODO: Deal with the update of object to 0.27. # It looks like it breaks linking the generated objects. # Probably just need to specify an extra field that used to be implicit or something. diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index 1d6fe2ea2c..73bcd5c7df 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -913,10 +913,7 @@ trait Backend<'a> { TagName::Closure(sym) => { self.set_last_seen(*sym, stmt); } - TagName::Private(sym) => { - self.set_last_seen(*sym, stmt); - } - TagName::Global(_) => {} + TagName::Tag(_) => {} } for sym in *arguments { self.set_last_seen(*sym, stmt); diff --git a/compiler/gen_llvm/Cargo.toml b/compiler/gen_llvm/Cargo.toml index 26335be9ff..3a1ebe8a57 100644 --- a/compiler/gen_llvm/Cargo.toml +++ b/compiler/gen_llvm/Cargo.toml @@ -18,4 +18,4 @@ roc_std = { path = "../../roc_std", default-features = false } morphic_lib = { path = "../../vendor/morphic_lib" } bumpalo = { version = "3.8.0", features = ["collections"] } inkwell = { path = "../../vendor/inkwell" } -target-lexicon = "0.12.2" +target-lexicon = "0.12.3" diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 47aa702893..0f5d65231a 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -30,6 +30,7 @@ use crate::llvm::refcounting::{ }; use bumpalo::collections::Vec; use bumpalo::Bump; +use inkwell::attributes::{Attribute, AttributeLoc}; use inkwell::basic_block::BasicBlock; use inkwell::builder::Builder; use inkwell::context::Context; @@ -40,7 +41,7 @@ use inkwell::memory_buffer::MemoryBuffer; use inkwell::module::{Linkage, Module}; use inkwell::passes::{PassManager, PassManagerBuilder}; use inkwell::types::{ - BasicMetadataTypeEnum, BasicType, BasicTypeEnum, FunctionType, IntType, StructType, + AnyType, BasicMetadataTypeEnum, BasicType, BasicTypeEnum, FunctionType, IntType, StructType, }; use inkwell::values::BasicValueEnum::{self, *}; use inkwell::values::{ @@ -63,7 +64,7 @@ use roc_mono::ir::{ ModifyRc, OptLevel, ProcLayout, }; use roc_mono::layout::{Builtin, LambdaSet, Layout, LayoutIds, TagIdIntType, UnionLayout}; -use roc_target::TargetInfo; +use roc_target::{PtrWidth, TargetInfo}; use target_lexicon::{Architecture, OperatingSystem, Triple}; use super::convert::zig_with_overflow_roc_dec; @@ -469,7 +470,7 @@ fn add_float_intrinsic<'ctx, F>( if let Some(_) = module.get_function(full_name) { // zig defined this function already } else { - add_intrinsic(module, full_name, construct_type($typ)); + add_intrinsic(ctx, module, full_name, construct_type($typ)); } }; } @@ -494,7 +495,7 @@ fn add_int_intrinsic<'ctx, F>( if let Some(_) = module.get_function(full_name) { // zig defined this function already } else { - add_intrinsic(module, full_name, construct_type($typ)); + add_intrinsic(ctx, module, full_name, construct_type($typ)); } }; } @@ -516,12 +517,10 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) { // List of all supported LLVM intrinsics: // // https://releases.llvm.org/10.0.0/docs/LangRef.html#standard-c-library-intrinsics - let f64_type = ctx.f64_type(); let i1_type = ctx.bool_type(); let i8_type = ctx.i8_type(); let i8_ptr_type = i8_type.ptr_type(AddressSpace::Generic); let i32_type = ctx.i32_type(); - let i64_type = ctx.i64_type(); let void_type = ctx.void_type(); if let Some(func) = module.get_function("__muloti4") { @@ -529,37 +528,31 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) { } add_intrinsic( + ctx, module, LLVM_SETJMP, i32_type.fn_type(&[i8_ptr_type.into()], false), ); - if true { - add_intrinsic( - module, - LLVM_LONGJMP, - void_type.fn_type(&[i8_ptr_type.into()], false), - ); - } else { - add_intrinsic( - module, - LLVM_LONGJMP, - void_type.fn_type(&[i8_ptr_type.into(), i32_type.into()], false), - ); - } + add_intrinsic( + ctx, + module, + LLVM_LONGJMP, + void_type.fn_type(&[i8_ptr_type.into()], false), + ); add_intrinsic( + ctx, module, LLVM_FRAME_ADDRESS, i8_ptr_type.fn_type(&[i32_type.into()], false), ); - add_intrinsic(module, LLVM_STACK_SAVE, i8_ptr_type.fn_type(&[], false)); - add_intrinsic( + ctx, module, - LLVM_LROUND_I64_F64, - i64_type.fn_type(&[f64_type.into()], false), + LLVM_STACK_SAVE, + i8_ptr_type.fn_type(&[], false), ); add_float_intrinsic(ctx, module, &LLVM_LOG, |t| t.fn_type(&[t.into()], false)); @@ -613,9 +606,7 @@ static LLVM_FLOOR: IntrinsicName = float_intrinsic!("llvm.floor"); static LLVM_MEMSET_I64: &str = "llvm.memset.p0i8.i64"; static LLVM_MEMSET_I32: &str = "llvm.memset.p0i8.i32"; -static LLVM_LROUND_I64_F64: &str = "llvm.lround.i64.f64"; -// static LLVM_FRAME_ADDRESS: &str = "llvm.frameaddress"; static LLVM_FRAME_ADDRESS: &str = "llvm.frameaddress.p0i8"; static LLVM_STACK_SAVE: &str = "llvm.stacksave"; @@ -633,18 +624,17 @@ const LLVM_ADD_SATURATED: IntrinsicName = llvm_int_intrinsic!("llvm.sadd.sat", " const LLVM_SUB_SATURATED: IntrinsicName = llvm_int_intrinsic!("llvm.ssub.sat", "llvm.usub.sat"); fn add_intrinsic<'ctx>( + context: &Context, module: &Module<'ctx>, intrinsic_name: &str, fn_type: FunctionType<'ctx>, ) -> FunctionValue<'ctx> { add_func( + context, module, intrinsic_name, - fn_type, + FunctionSpec::intrinsic(fn_type), Linkage::External, - // LLVM intrinsics always use the C calling convention, because - // they are implemented in C libraries - C_CALL_CONV, ) } @@ -3256,32 +3246,29 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>( // let mut argument_types = roc_function.get_type().get_param_types(); let mut argument_types = cc_argument_types; - let c_function_type = match roc_function.get_type().get_return_type() { + match roc_function.get_type().get_return_type() { None => { // this function already returns by-pointer let output_type = roc_function.get_type().get_param_types().pop().unwrap(); argument_types.insert(0, output_type); - - env.context - .void_type() - .fn_type(&function_arguments(env, &argument_types), false) } Some(return_type) => { let output_type = return_type.ptr_type(AddressSpace::Generic); argument_types.insert(0, output_type.into()); - - env.context - .void_type() - .fn_type(&function_arguments(env, &argument_types), false) } - }; + } + // This is not actually a function that returns a value but then became + // return-by-pointer do to the calling convention. Instead, here we + // explicitly are forcing the passing of values via the first parameter + // pointer, since they are generic and hence opaque to anything outside roc. + let c_function_spec = FunctionSpec::cconv(env, CCReturn::Void, None, &argument_types); let c_function = add_func( + env.context, env.module, c_function_name, - c_function_type, + c_function_spec, Linkage::External, - C_CALL_CONV, ); let subprogram = env.new_subprogram(c_function_name); @@ -3394,20 +3381,18 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>( let mut argument_types = cc_argument_types; let return_type = wrapper_return_type; - let c_function_type = { + let c_function_spec = { let output_type = return_type.ptr_type(AddressSpace::Generic); argument_types.push(output_type.into()); - env.context - .void_type() - .fn_type(&function_arguments(env, &argument_types), false) + FunctionSpec::cconv(env, CCReturn::Void, None, &argument_types) }; let c_function = add_func( + env.context, env.module, c_function_name, - c_function_type, + c_function_spec, Linkage::External, - C_CALL_CONV, ); let subprogram = env.new_subprogram(c_function_name); @@ -3472,15 +3457,20 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>( builder.build_return(None); // STEP 3: build a {} -> u64 function that gives the size of the return type - let size_function_type = env.context.i64_type().fn_type(&[], false); + let size_function_spec = FunctionSpec::cconv( + env, + CCReturn::Return, + Some(env.context.i64_type().as_basic_type_enum()), + &[], + ); let size_function_name: String = format!("roc__{}_size", ident_string); let size_function = add_func( + env.context, env.module, size_function_name.as_str(), - size_function_type, + size_function_spec, Linkage::External, - C_CALL_CONV, ); let subprogram = env.new_subprogram(&size_function_name); @@ -3512,14 +3502,14 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>( let cc_return = to_cc_return(env, &return_layout); let roc_return = RocReturn::from_layout(env, &return_layout); - let c_function_type = cc_return.to_signature(env, return_type, argument_types.as_slice()); + let c_function_spec = FunctionSpec::cconv(env, cc_return, Some(return_type), &argument_types); let c_function = add_func( + env.context, env.module, c_function_name, - c_function_type, + c_function_spec, Linkage::External, - C_CALL_CONV, ); let subprogram = env.new_subprogram(c_function_name); @@ -3630,15 +3620,20 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( ); // STEP 3: build a {} -> u64 function that gives the size of the return type - let size_function_type = env.context.i64_type().fn_type(&[], false); + let size_function_spec = FunctionSpec::cconv( + env, + CCReturn::Return, + Some(env.context.i64_type().as_basic_type_enum()), + &[], + ); let size_function_name: String = format!("roc__{}_size", ident_string); let size_function = add_func( + env.context, env.module, size_function_name.as_str(), - size_function_type, + size_function_spec, Linkage::External, - C_CALL_CONV, ); let subprogram = env.new_subprogram(&size_function_name); @@ -3664,10 +3659,22 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( } pub fn get_sjlj_buffer<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValue<'ctx> { - let type_ = env - .context - .i8_type() - .array_type(5 * env.target_info.ptr_width() as u32); + // The size of jump_buf is platform-dependent. + // - AArch64 needs 3 machine-sized words + // - LLVM says the following about the SJLJ intrinsic: + // + // [It is] a five word buffer in which the calling context is saved. + // The front end places the frame pointer in the first word, and the + // target implementation of this intrinsic should place the destination + // address for a llvm.eh.sjlj.longjmp in the second word. + // The following three words are available for use in a target-specific manner. + // + // So, let's create a 5-word buffer. + let word_type = match env.target_info.ptr_width() { + PtrWidth::Bytes4 => env.context.i32_type(), + PtrWidth::Bytes8 => env.context.i64_type(), + }; + let type_ = word_type.array_type(5); let global = match env.module.get_global("roc_sjlj_buffer") { Some(global) => global, @@ -3679,12 +3686,85 @@ pub fn get_sjlj_buffer<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValu env.builder .build_bitcast( global.as_pointer_value(), - env.context.i8_type().ptr_type(AddressSpace::Generic), + env.context.i32_type().ptr_type(AddressSpace::Generic), "cast_sjlj_buffer", ) .into_pointer_value() } +pub fn build_setjmp_call<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValueEnum<'ctx> { + let jmp_buf = get_sjlj_buffer(env); + if cfg!(target_arch = "aarch64") { + // Due to https://github.com/rtfeldman/roc/issues/2965, we use a setjmp we linked in from Zig + call_bitcode_fn(env, &[jmp_buf.into()], bitcode::UTILS_SETJMP) + } else { + // Anywhere else, use the LLVM intrinsic. + // https://llvm.org/docs/ExceptionHandling.html#llvm-eh-sjlj-setjmp + + let jmp_buf_i8p_arr = env + .builder + .build_bitcast( + jmp_buf, + env.context + .i8_type() + .ptr_type(AddressSpace::Generic) + .array_type(5) + .ptr_type(AddressSpace::Generic), + "jmp_buf [5 x i8*]", + ) + .into_pointer_value(); + + // LLVM asks us to please store the frame pointer in the first word. + let frame_address = env.call_intrinsic( + LLVM_FRAME_ADDRESS, + &[env.context.i32_type().const_zero().into()], + ); + + let zero = env.context.i32_type().const_zero(); + let fa_index = env.context.i32_type().const_zero(); + let fa = unsafe { + env.builder.build_in_bounds_gep( + jmp_buf_i8p_arr, + &[zero, fa_index], + "frame address index", + ) + }; + env.builder.build_store(fa, frame_address); + + // LLVM says that the target implementation of the setjmp intrinsic will put the + // destination address at index 1, and that the remaining three words are for ad-hoc target + // usage. But for whatever reason, on x86, it appears we need a stacksave in those words. + let ss_index = env.context.i32_type().const_int(2, false); + let ss = unsafe { + env.builder + .build_in_bounds_gep(jmp_buf_i8p_arr, &[zero, ss_index], "name") + }; + let stack_save = env.call_intrinsic(LLVM_STACK_SAVE, &[]); + env.builder.build_store(ss, stack_save); + + let jmp_buf_i8p = env.builder.build_bitcast( + jmp_buf, + env.context.i8_type().ptr_type(AddressSpace::Generic), + "jmp_buf i8*", + ); + env.call_intrinsic(LLVM_SETJMP, &[jmp_buf_i8p]) + } +} + +/// Pointer to pointer of the panic message. +pub fn get_panic_msg_ptr<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValue<'ctx> { + let ptr_to_u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic); + + let global_name = "roc_panic_msg_ptr"; + let global = env.module.get_global(global_name).unwrap_or_else(|| { + let global = env.module.add_global(ptr_to_u8_ptr, None, global_name); + global.set_initializer(&ptr_to_u8_ptr.const_zero()); + global + }); + + global.as_pointer_value() +} + fn set_jump_and_catch_long_jump<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, @@ -3703,53 +3783,7 @@ fn set_jump_and_catch_long_jump<'a, 'ctx, 'env>( let catch_block = context.append_basic_block(parent, "catch_block"); let cont_block = context.append_basic_block(parent, "cont_block"); - let buffer = get_sjlj_buffer(env); - - let cast = env - .builder - .build_bitcast( - buffer, - env.context - .i8_type() - .ptr_type(AddressSpace::Generic) - .array_type(5) - .ptr_type(AddressSpace::Generic), - "to [5 x i8*]", - ) - .into_pointer_value(); - - let zero = env.context.i32_type().const_zero(); - - let index = env.context.i32_type().const_zero(); - let fa = unsafe { - env.builder - .build_in_bounds_gep(cast, &[zero, index], "name") - }; - - let index = env.context.i32_type().const_int(2, false); - let ss = unsafe { - env.builder - .build_in_bounds_gep(cast, &[zero, index], "name") - }; - - let index = env.context.i32_type().const_int(3, false); - let error_msg = unsafe { - env.builder - .build_in_bounds_gep(cast, &[zero, index], "name") - }; - - let frame_address = env.call_intrinsic( - LLVM_FRAME_ADDRESS, - &[env.context.i32_type().const_zero().into()], - ); - - env.builder.build_store(fa, frame_address); - - let stack_save = env.call_intrinsic(LLVM_STACK_SAVE, &[]); - - env.builder.build_store(ss, stack_save); - - let panicked_u32 = env.call_intrinsic(LLVM_SETJMP, &[buffer.into()]); + let panicked_u32 = build_setjmp_call(env); let panicked_bool = env.builder.build_int_compare( IntPredicate::NE, panicked_u32.into_int_value(), @@ -3779,19 +3813,10 @@ fn set_jump_and_catch_long_jump<'a, 'ctx, 'env>( let error_msg = { // u8** - let ptr_int_ptr = builder.build_bitcast( - error_msg, - env.context - .i8_type() - .ptr_type(AddressSpace::Generic) - .ptr_type(AddressSpace::Generic), - "cast", - ); + let ptr_int_ptr = get_panic_msg_ptr(env); // u8* again - let ptr_int = builder.build_load(ptr_int_ptr.into_pointer_value(), "ptr_int"); - - ptr_int + builder.build_load(ptr_int_ptr, "ptr_int") }; let return_value = { @@ -3919,16 +3944,20 @@ fn make_exception_catching_wrapper<'a, 'ctx, 'env>( // argument_types.push(wrapper_return_type.ptr_type(AddressSpace::Generic).into()); // let wrapper_function_type = env.context.void_type().fn_type(&argument_types, false); - let wrapper_function_type = - wrapper_return_type.fn_type(&function_arguments(env, &argument_types), false); + let wrapper_function_spec = FunctionSpec::cconv( + env, + CCReturn::Return, + Some(wrapper_return_type.as_basic_type_enum()), + &argument_types, + ); // Add main to the module. let wrapper_function = add_func( + env.context, env.module, wrapper_function_name, - wrapper_function_type, + wrapper_function_spec, Linkage::External, - C_CALL_CONV, ); let subprogram = env.new_subprogram(wrapper_function_name); @@ -4157,23 +4186,15 @@ fn build_proc_header<'a, 'ctx, 'env>( arg_basic_types.push(arg_type); } - let fn_type = match RocReturn::from_layout(env, &proc.ret_layout) { - RocReturn::Return => ret_type.fn_type(&function_arguments(env, &arg_basic_types), false), - RocReturn::ByPointer => { - // println!( "{:?} will return void instead of {:?}", symbol, proc.ret_layout); - arg_basic_types.push(ret_type.ptr_type(AddressSpace::Generic).into()); - env.context - .void_type() - .fn_type(&function_arguments(env, &arg_basic_types), false) - } - }; + let roc_return = RocReturn::from_layout(env, &proc.ret_layout); + let fn_spec = FunctionSpec::fastcc(env, roc_return, ret_type, arg_basic_types); let fn_val = add_func( + env.context, env.module, fn_name.as_str(), - fn_type, + fn_spec, Linkage::Internal, - FAST_CALL_CONV, ); let subprogram = env.new_subprogram(&fn_name); @@ -4191,8 +4212,6 @@ fn build_proc_header<'a, 'ctx, 'env>( } if false { - use inkwell::attributes::{Attribute, AttributeLoc}; - let kind_id = Attribute::get_named_enum_kind_id("alwaysinline"); debug_assert!(kind_id > 0); let enum_attr = env.context.create_enum_attribute(kind_id, 1); @@ -4200,8 +4219,6 @@ fn build_proc_header<'a, 'ctx, 'env>( } if false { - use inkwell::attributes::{Attribute, AttributeLoc}; - let kind_id = Attribute::get_named_enum_kind_id("noinline"); debug_assert!(kind_id > 0); let enum_attr = env.context.create_enum_attribute(kind_id, 1); @@ -4255,14 +4272,14 @@ pub fn build_closure_caller<'a, 'ctx, 'env>( alias_symbol.as_str(&env.interns) ); - let function_type = context.void_type().fn_type(&argument_types, false); + let function_spec = FunctionSpec::cconv(env, CCReturn::Void, None, &argument_types); let function_value = add_func( + env.context, env.module, function_name.as_str(), - function_type, + function_spec, Linkage::External, - C_CALL_CONV, ); // STEP 2: build function body @@ -4361,7 +4378,8 @@ fn build_host_exposed_alias_size_help<'a, 'ctx, 'env>( let builder = env.builder; let context = env.context; - let size_function_type = env.context.i64_type().fn_type(&[], false); + let i64 = env.context.i64_type().as_basic_type_enum(); + let size_function_spec = FunctionSpec::cconv(env, CCReturn::Return, Some(i64), &[]); let size_function_name: String = if let Some(label) = opt_label { format!( "roc__{}_{}_{}_size", @@ -4378,11 +4396,11 @@ fn build_host_exposed_alias_size_help<'a, 'ctx, 'env>( }; let size_function = add_func( + env.context, env.module, size_function_name.as_str(), - size_function_type, + size_function_spec, Linkage::External, - C_CALL_CONV, ); let entry = context.append_basic_block(size_function, "entry"); @@ -6083,6 +6101,13 @@ fn run_low_level<'a, 'ctx, 'env>( let key_layout = list_element_layout!(list_layout); set_from_list(env, layout_ids, list, key_layout) } + SetToDict => { + debug_assert_eq!(args.len(), 1); + + let (set, _set_layout) = load_symbol_and_layout(scope, &args[0]); + + set + } ExpectTrue => { debug_assert_eq!(args.len(), 1); @@ -6205,6 +6230,7 @@ fn to_cc_type_builtin<'a, 'ctx, 'env>( } } +#[derive(Clone, Copy)] enum RocReturn { /// Return as normal Return, @@ -6245,7 +6271,7 @@ impl RocReturn { } } -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub enum CCReturn { /// Return as normal Return, @@ -6257,32 +6283,113 @@ pub enum CCReturn { Void, } -impl CCReturn { - fn to_signature<'a, 'ctx, 'env>( - &self, +#[derive(Debug, Clone, Copy)] +pub struct FunctionSpec<'ctx> { + /// The function type + pub typ: FunctionType<'ctx>, + call_conv: u32, + + /// Index (0-based) of return-by-pointer parameter, if it exists. + /// We only care about this for C-call-conv functions, because this may take + /// ownership of a register due to the convention. For example, on AArch64, + /// values returned-by-pointer use the x8 register. + /// But for internal functions we don't need to worry about that and we don't + /// want the convention, since it might eat a register and cause a spill! + cconv_sret_parameter: Option, +} + +impl<'ctx> FunctionSpec<'ctx> { + fn attach_attributes(&self, ctx: &Context, fn_val: FunctionValue<'ctx>) { + fn_val.set_call_conventions(self.call_conv); + + if let Some(param_index) = self.cconv_sret_parameter { + // Indicate to LLVM that this argument holds the return value of the function. + let sret_attribute_id = Attribute::get_named_enum_kind_id("sret"); + debug_assert!(sret_attribute_id > 0); + let ret_typ = self.typ.get_param_types()[param_index as usize]; + let sret_attribute = + ctx.create_type_attribute(sret_attribute_id, ret_typ.as_any_type_enum()); + fn_val.add_attribute(AttributeLoc::Param(0), sret_attribute); + } + } + + /// C-calling convention + pub fn cconv<'a, 'env>( env: &Env<'a, 'ctx, 'env>, - return_type: BasicTypeEnum<'ctx>, + cc_return: CCReturn, + return_type: Option>, argument_types: &[BasicTypeEnum<'ctx>], - ) -> FunctionType<'ctx> { - match self { + ) -> FunctionSpec<'ctx> { + let (typ, opt_sret_parameter) = match cc_return { CCReturn::ByPointer => { // turn the output type into a pointer type. Make it the first argument to the function - let output_type = return_type.ptr_type(AddressSpace::Generic); + let output_type = return_type.unwrap().ptr_type(AddressSpace::Generic); + let mut arguments: Vec<'_, BasicTypeEnum> = bumpalo::vec![in env.arena; output_type.into()]; arguments.extend(argument_types); let arguments = function_arguments(env, &arguments); - env.context.void_type().fn_type(&arguments, false) + (env.context.void_type().fn_type(&arguments, false), Some(0)) } CCReturn::Return => { let arguments = function_arguments(env, argument_types); - return_type.fn_type(&arguments, false) + (return_type.unwrap().fn_type(&arguments, false), None) } CCReturn::Void => { let arguments = function_arguments(env, argument_types); - env.context.void_type().fn_type(&arguments, false) + (env.context.void_type().fn_type(&arguments, false), None) } + }; + + Self { + typ, + call_conv: C_CALL_CONV, + cconv_sret_parameter: opt_sret_parameter, + } + } + + /// Fastcc calling convention + fn fastcc<'a, 'env>( + env: &Env<'a, 'ctx, 'env>, + roc_return: RocReturn, + return_type: BasicTypeEnum<'ctx>, + mut argument_types: Vec>, + ) -> FunctionSpec<'ctx> { + let typ = match roc_return { + RocReturn::Return => { + return_type.fn_type(&function_arguments(env, &argument_types), false) + } + RocReturn::ByPointer => { + argument_types.push(return_type.ptr_type(AddressSpace::Generic).into()); + env.context + .void_type() + .fn_type(&function_arguments(env, &argument_types), false) + } + }; + + Self { + typ, + call_conv: FAST_CALL_CONV, + cconv_sret_parameter: None, + } + } + + pub fn known_fastcc(fn_type: FunctionType<'ctx>) -> FunctionSpec<'ctx> { + Self { + typ: fn_type, + call_conv: FAST_CALL_CONV, + cconv_sret_parameter: None, + } + } + + pub fn intrinsic(fn_type: FunctionType<'ctx>) -> Self { + // LLVM intrinsics always use the C calling convention, because + // they are implemented in C libraries + Self { + typ: fn_type, + call_conv: C_CALL_CONV, + cconv_sret_parameter: None, } } } @@ -6363,27 +6470,19 @@ fn build_foreign_symbol<'a, 'ctx, 'env>( arguments.push(value); } - let cc_type = cc_return.to_signature(env, return_type, cc_argument_types.as_slice()); + let cc_type = + FunctionSpec::cconv(env, cc_return, Some(return_type), &cc_argument_types); let cc_function = get_foreign_symbol(env, foreign.clone(), cc_type); - let fastcc_type = match roc_return { - RocReturn::Return => { - return_type.fn_type(&function_arguments(env, &fastcc_argument_types), false) - } - RocReturn::ByPointer => { - fastcc_argument_types.push(return_type.ptr_type(AddressSpace::Generic).into()); - env.context - .void_type() - .fn_type(&function_arguments(env, &fastcc_argument_types), false) - } - }; + let fastcc_type = + FunctionSpec::fastcc(env, roc_return, return_type, fastcc_argument_types); let fastcc_function = add_func( + env.context, env.module, &fastcc_function_name, fastcc_type, Linkage::Internal, - FAST_CALL_CONV, ); let old = builder.get_insert_block().unwrap(); @@ -6921,7 +7020,6 @@ fn build_float_binop<'a, 'ctx, 'env>( NumGte => bd.build_float_compare(OGE, lhs, rhs, "float_gte").into(), NumLt => bd.build_float_compare(OLT, lhs, rhs, "float_lt").into(), NumLte => bd.build_float_compare(OLE, lhs, rhs, "float_lte").into(), - NumRemUnchecked => bd.build_float_rem(lhs, rhs, "rem_float").into(), NumDivUnchecked => bd.build_float_div(lhs, rhs, "div_float").into(), NumPow => env.call_intrinsic(&LLVM_POW[float_width], &[lhs.into(), rhs.into()]), _ => { @@ -7510,7 +7608,7 @@ fn throw_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, message: &str) { fn get_foreign_symbol<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, foreign_symbol: roc_module::ident::ForeignSymbol, - function_type: FunctionType<'ctx>, + function_spec: FunctionSpec<'ctx>, ) -> FunctionValue<'ctx> { let module = env.module; @@ -7518,11 +7616,11 @@ fn get_foreign_symbol<'a, 'ctx, 'env>( Some(gvalue) => gvalue, None => { let foreign_function = add_func( + env.context, module, foreign_symbol.as_str(), - function_type, + function_spec, Linkage::External, - C_CALL_CONV, ); foreign_function @@ -7534,11 +7632,11 @@ fn get_foreign_symbol<'a, 'ctx, 'env>( /// We never want to define the same function twice in the same module! /// The result can be bugs that are difficult to track down. pub fn add_func<'ctx>( + ctx: &Context, module: &Module<'ctx>, name: &str, - typ: FunctionType<'ctx>, + spec: FunctionSpec<'ctx>, linkage: Linkage, - call_conv: u32, ) -> FunctionValue<'ctx> { if cfg!(debug_assertions) { if let Some(func) = module.get_function(name) { @@ -7546,9 +7644,9 @@ pub fn add_func<'ctx>( } } - let fn_val = module.add_function(name, typ, Some(linkage)); + let fn_val = module.add_function(name, spec.typ, Some(linkage)); - fn_val.set_call_conventions(call_conv); + spec.attach_attributes(ctx, fn_val); fn_val } diff --git a/compiler/gen_llvm/src/llvm/externs.rs b/compiler/gen_llvm/src/llvm/externs.rs index 889a0edd3b..b3c428958b 100644 --- a/compiler/gen_llvm/src/llvm/externs.rs +++ b/compiler/gen_llvm/src/llvm/externs.rs @@ -1,8 +1,13 @@ -use crate::llvm::build::Env; -use crate::llvm::build::{add_func, C_CALL_CONV}; +use crate::llvm::bitcode::call_void_bitcode_fn; +use crate::llvm::build::{add_func, get_panic_msg_ptr, C_CALL_CONV}; +use crate::llvm::build::{CCReturn, Env, FunctionSpec}; use inkwell::module::Linkage; +use inkwell::types::BasicType; use inkwell::values::BasicValue; use inkwell::AddressSpace; +use roc_builtins::bitcode; + +use super::build::{get_sjlj_buffer, LLVM_LONGJMP}; /// Define functions for roc_alloc, roc_realloc, and roc_dealloc /// which use libc implementations (malloc, realloc, and free) @@ -82,21 +87,18 @@ pub fn add_default_roc_externs(env: &Env<'_, '_, '_>) { // roc_realloc { let libc_realloc_val = { - let fn_val = add_func( - module, - "realloc", - i8_ptr_type.fn_type( - &[ - // ptr: *void - i8_ptr_type.into(), - // size: usize - usize_type.into(), - ], - false, - ), - Linkage::External, - C_CALL_CONV, + let fn_spec = FunctionSpec::cconv( + env, + CCReturn::Return, + Some(i8_ptr_type.as_basic_type_enum()), + &[ + // ptr: *void + i8_ptr_type.into(), + // size: usize + usize_type.into(), + ], ); + let fn_val = add_func(env.context, module, "realloc", fn_spec, Linkage::External); let mut params = fn_val.get_param_iter(); let ptr_arg = params.next().unwrap(); @@ -186,8 +188,6 @@ pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) { // roc_panic { - use crate::llvm::build::LLVM_LONGJMP; - // The type of this function (but not the implementation) should have // already been defined by the builtins, which rely on it. let fn_val = module.get_function("roc_panic").unwrap(); @@ -209,32 +209,10 @@ pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) { builder.position_at_end(entry); - let buffer = crate::llvm::build::get_sjlj_buffer(env); - // write our error message pointer - let index = env - .ptr_int() - .const_int(3 * env.target_info.ptr_width() as u64, false); - let message_buffer_raw = - unsafe { builder.build_gep(buffer, &[index], "raw_msg_buffer_ptr") }; - let message_buffer = builder.build_bitcast( - message_buffer_raw, - env.context - .i8_type() - .ptr_type(AddressSpace::Generic) - .ptr_type(AddressSpace::Generic), - "to **u8", - ); + env.builder.build_store(get_panic_msg_ptr(env), ptr_arg); - env.builder - .build_store(message_buffer.into_pointer_value(), ptr_arg); - - let tag = env.context.i32_type().const_int(1, false); - if true { - let _call = env.build_intrinsic_call(LLVM_LONGJMP, &[buffer.into()]); - } else { - let _call = env.build_intrinsic_call(LLVM_LONGJMP, &[buffer.into(), tag.into()]); - } + build_longjmp_call(env); builder.build_unreachable(); @@ -243,3 +221,21 @@ pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) { } } } + +pub fn build_longjmp_call(env: &Env) { + let jmp_buf = get_sjlj_buffer(env); + if cfg!(target_arch = "aarch64") { + // Call the Zig-linked longjmp: `void longjmp(i32*, i32)` + let tag = env.context.i32_type().const_int(1, false); + let _call = + call_void_bitcode_fn(env, &[jmp_buf.into(), tag.into()], bitcode::UTILS_LONGJMP); + } else { + // Call the LLVM-intrinsic longjmp: `void @llvm.eh.sjlj.longjmp(i8* %setjmp_buf)` + let jmp_buf_i8p = env.builder.build_bitcast( + jmp_buf, + env.context.i8_type().ptr_type(AddressSpace::Generic), + "jmp_buf i8*", + ); + let _call = env.build_intrinsic_call(LLVM_LONGJMP, &[jmp_buf_i8p]); + } +} diff --git a/compiler/gen_llvm/src/llvm/refcounting.rs b/compiler/gen_llvm/src/llvm/refcounting.rs index ce79180f75..efc9d9bfa5 100644 --- a/compiler/gen_llvm/src/llvm/refcounting.rs +++ b/compiler/gen_llvm/src/llvm/refcounting.rs @@ -18,7 +18,7 @@ use roc_module::symbol::Interns; use roc_module::symbol::Symbol; use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout}; -use super::build::load_roc_value; +use super::build::{load_roc_value, FunctionSpec}; use super::convert::{argument_type_from_layout, argument_type_from_union_layout}; pub struct PointerToRefcount<'ctx> { @@ -143,11 +143,11 @@ impl<'ctx> PointerToRefcount<'ctx> { ); let function_value = add_func( + env.context, env.module, fn_name, - fn_type, + FunctionSpec::known_fastcc(fn_type), Linkage::Internal, - FAST_CALL_CONV, // Because it's an internal-only function, it should use the fast calling convention. ); let subprogram = env.new_subprogram(fn_name); @@ -1138,11 +1138,11 @@ pub fn build_header_help<'a, 'ctx, 'env>( }; let fn_val = add_func( + env.context, env.module, fn_name, - fn_type, + FunctionSpec::known_fastcc(fn_type), Linkage::Private, - FAST_CALL_CONV, // Because it's an internal-only function, it should use the fast calling convention. ); let subprogram = env.new_subprogram(fn_name); @@ -1809,7 +1809,39 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>( for (i, field_layout) in field_layouts.iter().enumerate() { if let Layout::RecursivePointer = field_layout { - panic!("non-recursive tag unions cannot contain naked recursion pointers!"); + let recursive_union_layout = match when_recursive { + WhenRecursive::Unreachable => { + panic!("non-recursive tag unions cannot contain naked recursion pointers!"); + } + WhenRecursive::Loop(recursive_union_layout) => recursive_union_layout, + }; + + // This field is a pointer to the recursive pointer. + let field_ptr = env + .builder + .build_struct_gep(cast_tag_data_pointer, i as u32, "modify_tag_field") + .unwrap(); + + // This is the actual pointer to the recursive data. + let field_value = env.builder.build_load(field_ptr, "load_recursive_pointer"); + + debug_assert!(field_value.is_pointer_value()); + + // therefore we must cast it to our desired type + let union_type = + basic_type_from_layout(env, &Layout::Union(*recursive_union_layout)); + let recursive_ptr_field_value = + cast_basic_basic(env.builder, field_value, union_type); + + modify_refcount_layout_help( + env, + parent, + layout_ids, + mode.to_call_mode(fn_val), + when_recursive, + recursive_ptr_field_value, + &Layout::RecursivePointer, + ) } else if field_layout.contains_refcounted() { let field_ptr = env .builder diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index c898a279c9..109854d132 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -7,7 +7,7 @@ use roc_collections::all::MutMap; use roc_module::ident::Ident; use roc_module::low_level::{LowLevel, LowLevelWrapperType}; use roc_module::symbol::{Interns, Symbol}; -use roc_mono::code_gen_help::{CodeGenHelp, REFCOUNT_MAX}; +use roc_mono::code_gen_help::{CodeGenHelp, HelperOp, REFCOUNT_MAX}; use roc_mono::ir::{ BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, ModifyRc, Param, Proc, ProcLayout, Stmt, @@ -192,7 +192,7 @@ impl<'a> WasmBackend<'a> { .get_mut(&self.env.module_id) .unwrap(); - let ident_id = ident_ids.add(Ident::from(debug_name)); + let ident_id = ident_ids.add_ident(&Ident::from(debug_name)); Symbol::new(self.env.module_id, ident_id) } @@ -283,6 +283,13 @@ impl<'a> WasmBackend<'a> { self.storage.stack_frame_size, self.storage.stack_frame_pointer, ); + + if DEBUG_LOG_SETTINGS.storage_map { + println!("\nStorage:"); + for (sym, storage) in self.storage.symbol_storage_map.iter() { + println!("{:?} => {:?}", sym, storage); + } + } } fn append_proc_debug_name(&mut self, sym: Symbol) { @@ -1623,8 +1630,9 @@ impl<'a> WasmBackend<'a> { ); } - /// Generate a refcount increment procedure and return its Wasm function index - pub fn gen_refcount_inc_for_zig(&mut self, layout: Layout<'a>) -> u32 { + /// Generate a refcount helper procedure and return a pointer (table index) to it + /// This allows it to be indirectly called from Zig code + pub fn get_refcount_fn_ptr(&mut self, layout: Layout<'a>, op: HelperOp) -> i32 { let ident_ids = self .interns .all_ident_ids @@ -1633,7 +1641,7 @@ impl<'a> WasmBackend<'a> { let (proc_symbol, new_specializations) = self .helper_proc_gen - .gen_refcount_inc_proc(ident_ids, layout); + .gen_refcount_proc(ident_ids, layout, op); // If any new specializations were created, register their symbol data for (spec_sym, spec_layout) in new_specializations.into_iter() { @@ -1646,6 +1654,7 @@ impl<'a> WasmBackend<'a> { .position(|lookup| lookup.name == proc_symbol && lookup.layout.arguments[0] == layout) .unwrap(); - self.fn_index_offset + proc_index as u32 + let wasm_fn_index = self.fn_index_offset + proc_index as u32; + self.get_fn_table_index(wasm_fn_index) } } diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 6689680ac4..7cca526acb 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -253,6 +253,7 @@ pub struct WasmDebugLogSettings { helper_procs_ir: bool, let_stmt_ir: bool, instructions: bool, + storage_map: bool, pub keep_test_binary: bool, } @@ -262,5 +263,6 @@ pub const DEBUG_LOG_SETTINGS: WasmDebugLogSettings = WasmDebugLogSettings { helper_procs_ir: false && cfg!(debug_assertions), let_stmt_ir: false && cfg!(debug_assertions), instructions: false && cfg!(debug_assertions), + storage_map: false && cfg!(debug_assertions), keep_test_binary: false && cfg!(debug_assertions), }; diff --git a/compiler/gen_wasm/src/low_level.rs b/compiler/gen_wasm/src/low_level.rs index 6810b008ba..86933acfcb 100644 --- a/compiler/gen_wasm/src/low_level.rs +++ b/compiler/gen_wasm/src/low_level.rs @@ -3,6 +3,7 @@ use roc_builtins::bitcode::{self, FloatWidth, IntWidth}; use roc_error_macros::internal_error; use roc_module::low_level::LowLevel; use roc_module::symbol::Symbol; +use roc_mono::code_gen_help::HelperOp; use roc_mono::ir::{HigherOrderLowLevel, PassedFunction, ProcLayout}; use roc_mono::layout::{Builtin, Layout, UnionLayout}; use roc_mono::low_level::HigherOrder; @@ -304,7 +305,7 @@ impl<'a> LowLevelCall<'a> { DictSize | DictEmpty | DictInsert | DictRemove | DictContains | DictGetUnsafe | DictKeys | DictValues | DictUnion | DictIntersection | DictDifference - | SetFromList => { + | SetFromList | SetToDict => { todo!("{:?}", self.lowlevel); } @@ -1035,58 +1036,73 @@ pub fn call_higher_order_lowlevel<'a>( }; let wrapper_fn_idx = backend.register_helper_proc(wrapper_sym, wrapper_layout, source); - let inc_fn_idx = backend.gen_refcount_inc_for_zig(closure_data_layout); - let wrapper_fn_ptr = backend.get_fn_table_index(wrapper_fn_idx); - let inc_fn_ptr = backend.get_fn_table_index(inc_fn_idx); + let inc_fn_ptr = match closure_data_layout { + Layout::Struct { + field_layouts: &[], .. + } => { + // Our code gen would ignore the Unit arg, but the Zig builtin passes a pointer for it! + // That results in an exception (type signature mismatch in indirect call). + // The workaround is to use I32 layout, treating the (ignored) pointer as an integer. + backend.get_refcount_fn_ptr(Layout::Builtin(Builtin::Int(IntWidth::I32)), HelperOp::Inc) + } + _ => backend.get_refcount_fn_ptr(closure_data_layout, HelperOp::Inc), + }; match op { - // List.map : List elem_x, (elem_x -> elem_ret) -> List elem_ret - ListMap { xs } => { - let list_layout_in = backend.storage.symbol_layouts[xs]; + ListMap { xs } => list_map_n( + bitcode::LIST_MAP, + backend, + &[*xs], + return_sym, + *return_layout, + wrapper_fn_ptr, + inc_fn_ptr, + closure_data_exists, + *captured_environment, + *owns_captured_environment, + ), - let (elem_x, elem_ret) = match (list_layout_in, return_layout) { - ( - Layout::Builtin(Builtin::List(elem_x)), - Layout::Builtin(Builtin::List(elem_ret)), - ) => (elem_x, elem_ret), - _ => unreachable!("invalid layout for List.map arguments"), - }; - let elem_x_size = elem_x.stack_size(TARGET_INFO); - let (elem_ret_size, elem_ret_align) = elem_ret.stack_size_and_alignment(TARGET_INFO); + ListMap2 { xs, ys } => list_map_n( + bitcode::LIST_MAP2, + backend, + &[*xs, *ys], + return_sym, + *return_layout, + wrapper_fn_ptr, + inc_fn_ptr, + closure_data_exists, + *captured_environment, + *owns_captured_environment, + ), - let cb = &mut backend.code_builder; + ListMap3 { xs, ys, zs } => list_map_n( + bitcode::LIST_MAP3, + backend, + &[*xs, *ys, *zs], + return_sym, + *return_layout, + wrapper_fn_ptr, + inc_fn_ptr, + closure_data_exists, + *captured_environment, + *owns_captured_environment, + ), - // Load return pointer & argument values - // Wasm signature: (i32, i64, i64, i32, i32, i32, i32, i32, i32, i32) -> nil - backend.storage.load_symbols(cb, &[return_sym]); - backend.storage.load_symbol_zig(cb, *xs); // list with capacity = 2 x i64 args - cb.i32_const(wrapper_fn_ptr); - if closure_data_exists { - backend.storage.load_symbols(cb, &[*captured_environment]); - } else { - // Normally, a zero-size arg would be eliminated in code gen, but Zig expects one! - cb.i32_const(0); // null pointer - } - cb.i32_const(inc_fn_ptr); - cb.i32_const(*owns_captured_environment as i32); - cb.i32_const(elem_ret_align as i32); // used for allocating the new list - cb.i32_const(elem_x_size as i32); - cb.i32_const(elem_ret_size as i32); + ListMap4 { xs, ys, zs, ws } => list_map_n( + bitcode::LIST_MAP4, + backend, + &[*xs, *ys, *zs, *ws], + return_sym, + *return_layout, + wrapper_fn_ptr, + inc_fn_ptr, + closure_data_exists, + *captured_environment, + *owns_captured_environment, + ), - let num_wasm_args = 10; // 1 return pointer + 8 Zig args + list 2nd i64 - let has_return_val = false; - backend.call_zig_builtin_after_loading_args( - bitcode::LIST_MAP, - num_wasm_args, - has_return_val, - ); - } - - ListMap2 { .. } - | ListMap3 { .. } - | ListMap4 { .. } - | ListMapWithIndex { .. } + ListMapWithIndex { .. } | ListKeepIf { .. } | ListWalk { .. } | ListWalkUntil { .. } @@ -1100,3 +1116,71 @@ pub fn call_higher_order_lowlevel<'a>( | DictWalk { .. } => todo!("{:?}", op), } } + +fn unwrap_list_elem_layout(list_layout: Layout<'_>) -> &Layout<'_> { + match list_layout { + Layout::Builtin(Builtin::List(x)) => x, + e => internal_error!("expected List layout, got {:?}", e), + } +} + +#[allow(clippy::too_many_arguments)] +fn list_map_n<'a>( + zig_fn_name: &'static str, + backend: &mut WasmBackend<'a>, + arg_symbols: &[Symbol], + return_sym: Symbol, + return_layout: Layout<'a>, + wrapper_fn_ptr: i32, + inc_fn_ptr: i32, + closure_data_exists: bool, + captured_environment: Symbol, + owns_captured_environment: bool, +) { + let arg_elem_layouts = Vec::from_iter_in( + arg_symbols + .iter() + .map(|sym| *unwrap_list_elem_layout(backend.storage.symbol_layouts[sym])), + backend.env.arena, + ); + + let elem_ret = unwrap_list_elem_layout(return_layout); + let (elem_ret_size, elem_ret_align) = elem_ret.stack_size_and_alignment(TARGET_INFO); + + let cb = &mut backend.code_builder; + + backend.storage.load_symbols(cb, &[return_sym]); + + for s in arg_symbols { + backend.storage.load_symbol_zig(cb, *s); + } + cb.i32_const(wrapper_fn_ptr); + if closure_data_exists { + backend.storage.load_symbols(cb, &[captured_environment]); + } else { + // load_symbols assumes that a zero-size arg should be eliminated in code gen, + // but that's a specialization that our Zig code doesn't have! Pass a null pointer. + cb.i32_const(0); + } + cb.i32_const(inc_fn_ptr); + cb.i32_const(owns_captured_environment as i32); + cb.i32_const(elem_ret_align as i32); + for el in arg_elem_layouts.iter() { + cb.i32_const(el.stack_size(TARGET_INFO) as i32); + } + cb.i32_const(elem_ret_size as i32); + + // If we have lists of different lengths, we may need to decrement + let num_wasm_args = if arg_elem_layouts.len() > 1 { + for el in arg_elem_layouts.iter() { + let ptr = backend.get_refcount_fn_ptr(*el, HelperOp::Dec); + backend.code_builder.i32_const(ptr); + } + 7 + arg_elem_layouts.len() * 4 + } else { + 7 + arg_elem_layouts.len() * 3 + }; + + let has_return_val = false; + backend.call_zig_builtin_after_loading_args(zig_fn_name, num_wasm_args, has_return_val); +} diff --git a/compiler/gen_wasm/src/wasm_module/code_builder.rs b/compiler/gen_wasm/src/wasm_module/code_builder.rs index c6d7646a19..30db30702c 100644 --- a/compiler/gen_wasm/src/wasm_module/code_builder.rs +++ b/compiler/gen_wasm/src/wasm_module/code_builder.rs @@ -62,7 +62,7 @@ struct VmBlock<'a> { impl std::fmt::Debug for VmBlock<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("{:?}", self.opcode)) + f.write_fmt(format_args!("{:?} {:?}", self.opcode, self.value_stack)) } } @@ -608,7 +608,7 @@ impl<'a> CodeBuilder<'a> { log_instruction!( "{:10}\t\t{:?}", format!("{:?}", opcode), - self.current_stack() + self.vm_block_stack ); } @@ -635,7 +635,7 @@ impl<'a> CodeBuilder<'a> { "{:10}\t{}\t{:?}", format!("{:?}", opcode), immediate, - self.current_stack() + self.vm_block_stack ); } @@ -648,7 +648,7 @@ impl<'a> CodeBuilder<'a> { format!("{:?}", opcode), align, offset, - self.current_stack() + self.vm_block_stack ); } @@ -752,7 +752,7 @@ impl<'a> CodeBuilder<'a> { "{:10}\t{}\t{:?}", format!("{:?}", CALL), function_index, - self.current_stack() + self.vm_block_stack ); } @@ -823,7 +823,7 @@ impl<'a> CodeBuilder<'a> { "{:10}\t{}\t{:?}", format!("{:?}", opcode), x, - self.current_stack() + self.vm_block_stack ); } pub fn i32_const(&mut self, x: i32) { diff --git a/compiler/ident/src/lib.rs b/compiler/ident/src/lib.rs index e5f9bc62fa..f5c37713d1 100644 --- a/compiler/ident/src/lib.rs +++ b/compiler/ident/src/lib.rs @@ -30,7 +30,8 @@ impl IdentStr { // Reserve 1 byte for the discriminant const SMALL_STR_BYTES: usize = std::mem::size_of::() - 1; - pub fn len(&self) -> usize { + #[inline(always)] + pub const fn len(&self) -> usize { let bytes = self.length.to_ne_bytes(); let last_byte = bytes[mem::size_of::() - 1]; @@ -55,11 +56,11 @@ impl IdentStr { } } - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.length == 0 } - pub fn is_small_str(&self) -> bool { + pub const fn is_small_str(&self) -> bool { let bytes = self.length.to_ne_bytes(); let last_byte = bytes[mem::size_of::() - 1]; @@ -144,6 +145,7 @@ impl IdentStr { } } + #[inline(always)] pub fn as_slice(&self) -> &[u8] { use core::slice::from_raw_parts; @@ -156,6 +158,7 @@ impl IdentStr { } } + #[inline(always)] pub fn as_str(&self) -> &str { let slice = self.as_slice(); @@ -201,8 +204,19 @@ impl From<&str> for IdentStr { } impl From for IdentStr { - fn from(str: String) -> Self { - Self::from_str(&str) + fn from(string: String) -> Self { + if string.len() <= Self::SMALL_STR_BYTES { + Self::from_str(string.as_str()) + } else { + // Take over the string's heap allocation + let length = string.len(); + let elements = string.as_ptr(); + + // Make sure the existing string doesn't get dropped. + std::mem::forget(string); + + Self { elements, length } + } } } diff --git a/compiler/load/build.rs b/compiler/load/build.rs index 3484b412d1..ed424eb6eb 100644 --- a/compiler/load/build.rs +++ b/compiler/load/build.rs @@ -5,13 +5,13 @@ use roc_module::symbol::ModuleId; const MODULES: &[(ModuleId, &str)] = &[ (ModuleId::BOOL, "Bool.roc"), - // (ModuleId::RESULT, "Result.roc"), - // (ModuleId::LIST, "List.roc"), - // (ModuleId::STR, "Str.roc"), - // (ModuleId::DICT, "Dict.roc"), - // (ModuleId::SET, "Set.roc"), - // (ModuleId::BOX, "Box.roc"), - // (ModuleId::NUM, "Num.roc"), + (ModuleId::RESULT, "Result.roc"), + (ModuleId::NUM, "Num.roc"), + (ModuleId::LIST, "List.roc"), + (ModuleId::STR, "Str.roc"), + (ModuleId::DICT, "Dict.roc"), + (ModuleId::SET, "Set.roc"), + (ModuleId::BOX, "Box.roc"), ]; fn main() { diff --git a/compiler/load/src/lib.rs b/compiler/load/src/lib.rs index b867228cca..5d5c09daa4 100644 --- a/compiler/load/src/lib.rs +++ b/compiler/load/src/lib.rs @@ -35,6 +35,30 @@ fn load<'a>( ) } +/// Load using only a single thread; used when compiling to webassembly +pub fn load_single_threaded<'a>( + arena: &'a Bump, + load_start: LoadStart<'a>, + src_dir: &Path, + exposed_types: ExposedByModule, + goal_phase: Phase, + target_info: TargetInfo, + render: RenderTarget, +) -> Result, LoadingProblem<'a>> { + let cached_subs = read_cached_subs(); + + roc_load_internal::file::load_single_threaded( + arena, + load_start, + src_dir, + exposed_types, + goal_phase, + target_info, + cached_subs, + render, + ) +} + pub fn load_and_monomorphize_from_str<'a>( arena: &'a Bump, filename: PathBuf, @@ -114,14 +138,44 @@ pub fn load_and_typecheck<'a>( } } +pub fn load_and_typecheck_str<'a>( + arena: &'a Bump, + filename: PathBuf, + source: &'a str, + src_dir: &Path, + exposed_types: ExposedByModule, + target_info: TargetInfo, + render: RenderTarget, +) -> Result> { + use LoadResult::*; + + let load_start = LoadStart::from_str(arena, filename, source)?; + + // NOTE: this function is meant for tests, and so we use single-threaded + // solving so we don't use too many threads per-test. That gives higher + // throughput for the test run overall + match load_single_threaded( + arena, + load_start, + src_dir, + exposed_types, + Phase::SolveTypes, + target_info, + render, + )? { + Monomorphized(_) => unreachable!(""), + TypeChecked(module) => Ok(module), + } +} + const BOOL: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Bool.dat")) as &[_]; -// const RESULT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Result.dat")) as &[_]; -// const LIST: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/List.dat")) as &[_]; -// const STR: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Str.dat")) as &[_]; -// const DICT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Dict.dat")) as &[_]; -// const SET: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Set.dat")) as &[_]; -// const BOX: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Box.dat")) as &[_]; -// const NUM: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Num.dat")) as &[_]; +const RESULT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Result.dat")) as &[_]; +const LIST: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/List.dat")) as &[_]; +const STR: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Str.dat")) as &[_]; +const DICT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Dict.dat")) as &[_]; +const SET: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Set.dat")) as &[_]; +const BOX: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Box.dat")) as &[_]; +const NUM: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Num.dat")) as &[_]; fn deserialize_help(bytes: &[u8]) -> (Subs, Vec<(Symbol, Variable)>) { let (subs, slice) = Subs::deserialize(bytes); @@ -132,14 +186,20 @@ fn deserialize_help(bytes: &[u8]) -> (Subs, Vec<(Symbol, Variable)>) { fn read_cached_subs() -> MutMap)> { let mut output = MutMap::default(); - output.insert(ModuleId::BOOL, deserialize_help(BOOL)); - // output.insert(ModuleId::RESULT, deserialize_help(RESULT)); - // output.insert(ModuleId::LIST, deserialize_help(LIST)); - // output.insert(ModuleId::STR, deserialize_help(STR)); - // output.insert(ModuleId::DICT, deserialize_help(DICT)); - // output.insert(ModuleId::SET, deserialize_help(SET)); - // output.insert(ModuleId::BOX, deserialize_help(BOX)); - // output.insert(ModuleId::NUM, deserialize_help(NUM)); + // Wasm seems to re-order definitions between build time and runtime, but only in release mode. + // That is very strange, but we can solve it separately + if !cfg!(target_family = "wasm") { + output.insert(ModuleId::BOOL, deserialize_help(BOOL)); + output.insert(ModuleId::RESULT, deserialize_help(RESULT)); + output.insert(ModuleId::NUM, deserialize_help(NUM)); + + output.insert(ModuleId::LIST, deserialize_help(LIST)); + output.insert(ModuleId::STR, deserialize_help(STR)); + output.insert(ModuleId::DICT, deserialize_help(DICT)); + + output.insert(ModuleId::SET, deserialize_help(SET)); + output.insert(ModuleId::BOX, deserialize_help(BOX)); + } output } diff --git a/compiler/load_internal/.gitignore b/compiler/load_internal/.gitignore new file mode 100644 index 0000000000..cad2309100 --- /dev/null +++ b/compiler/load_internal/.gitignore @@ -0,0 +1 @@ +/tmp \ No newline at end of file diff --git a/compiler/load_internal/Cargo.toml b/compiler/load_internal/Cargo.toml index 3d876623f5..28641d1c1b 100644 --- a/compiler/load_internal/Cargo.toml +++ b/compiler/load_internal/Cargo.toml @@ -33,3 +33,4 @@ tempfile = "3.2.0" pretty_assertions = "1.0.0" maplit = "1.0.2" indoc = "1.0.3" +roc_test_utils = { path = "../../test_utils" } \ No newline at end of file diff --git a/compiler/load_internal/src/docs.rs b/compiler/load_internal/src/docs.rs index 0400a53533..890ddb46b0 100644 --- a/compiler/load_internal/src/docs.rs +++ b/compiler/load_internal/src/docs.rs @@ -1,7 +1,5 @@ use crate::docs::DocEntry::DetachedDoc; -use crate::docs::TypeAnnotation::{ - Apply, BoundVariable, Function, NoTypeAnn, ObscuredRecord, ObscuredTagUnion, Record, TagUnion, -}; +use crate::docs::TypeAnnotation::{Apply, BoundVariable, Function, NoTypeAnn, Record, TagUnion}; use crate::file::LoadedModule; use roc_can::scope::Scope; use roc_error_macros::todo_abilities; @@ -91,14 +89,13 @@ pub struct Tag { pub fn generate_module_docs<'a>( scope: Scope, module_name: ModuleName, - ident_ids: &'a IdentIds, parsed_defs: &'a [Loc>], ) -> ModuleDocumentation { let (entries, _) = parsed_defs .iter() .fold((vec![], None), |(acc, maybe_comments_after), def| { - generate_entry_doc(ident_ids, acc, maybe_comments_after, &def.value) + generate_entry_doc(&scope.ident_ids, acc, maybe_comments_after, &def.value) }); ModuleDocumentation { @@ -274,36 +271,20 @@ fn type_to_docs(in_func_type_ann: bool, type_annotation: ast::TypeAnnotation) -> ast::TypeAnnotation::TagUnion { tags, ext } => { let mut tags_to_render: Vec = Vec::new(); - let mut any_tags_are_private = false; - for tag in tags.iter() { - match tag_to_doc(in_func_type_ann, tag.value) { - None => { - any_tags_are_private = true; - break; - } - Some(tag_ann) => { - tags_to_render.push(tag_ann); - } + if let Some(tag_ann) = tag_to_doc(in_func_type_ann, tag.value) { + tags_to_render.push(tag_ann); } } - if any_tags_are_private { - if in_func_type_ann { - ObscuredTagUnion - } else { - NoTypeAnn - } - } else { - let extension = match ext { - None => NoTypeAnn, - Some(ext_type_ann) => type_to_docs(in_func_type_ann, ext_type_ann.value), - }; + let extension = match ext { + None => NoTypeAnn, + Some(ext_type_ann) => type_to_docs(in_func_type_ann, ext_type_ann.value), + }; - TagUnion { - tags: tags_to_render, - extension: Box::new(extension), - } + TagUnion { + tags: tags_to_render, + extension: Box::new(extension), } } ast::TypeAnnotation::BoundVariable(var_name) => BoundVariable(var_name.to_string()), @@ -328,35 +309,19 @@ fn type_to_docs(in_func_type_ann: bool, type_annotation: ast::TypeAnnotation) -> ast::TypeAnnotation::Record { fields, ext } => { let mut doc_fields = Vec::new(); - let mut any_fields_include_private_tags = false; - for field in fields.items { - match record_field_to_doc(in_func_type_ann, field.value) { - None => { - any_fields_include_private_tags = true; - break; - } - Some(doc_field) => { - doc_fields.push(doc_field); - } + if let Some(doc_field) = record_field_to_doc(in_func_type_ann, field.value) { + doc_fields.push(doc_field); } } - if any_fields_include_private_tags { - if in_func_type_ann { - ObscuredRecord - } else { - NoTypeAnn - } - } else { - let extension = match ext { - None => NoTypeAnn, - Some(ext_type_ann) => type_to_docs(in_func_type_ann, ext_type_ann.value), - }; + let extension = match ext { + None => NoTypeAnn, + Some(ext_type_ann) => type_to_docs(in_func_type_ann, ext_type_ann.value), + }; - Record { - fields: doc_fields, - extension: Box::new(extension), - } + Record { + fields: doc_fields, + extension: Box::new(extension), } } ast::TypeAnnotation::SpaceBefore(&sub_type_ann, _) => { @@ -404,11 +369,10 @@ fn record_field_to_doc( } } -// The Option here represents if it is private. Private tags -// evaluate to `None`. +// The Option here represents if it is malformed. fn tag_to_doc(in_func_ann: bool, tag: ast::Tag) -> Option { match tag { - ast::Tag::Global { name, args } => Some(Tag { + ast::Tag::Apply { name, args } => Some(Tag { name: name.value.to_string(), values: { let mut type_vars = Vec::new(); @@ -420,7 +384,6 @@ fn tag_to_doc(in_func_ann: bool, tag: ast::Tag) -> Option { type_vars }, }), - ast::Tag::Private { .. } => None, ast::Tag::SpaceBefore(&sub_tag, _) => tag_to_doc(in_func_ann, sub_tag), ast::Tag::SpaceAfter(&sub_tag, _) => tag_to_doc(in_func_ann, sub_tag), ast::Tag::Malformed(_) => None, diff --git a/compiler/load_internal/src/file.rs b/compiler/load_internal/src/file.rs index 5287d7721c..aea2867805 100644 --- a/compiler/load_internal/src/file.rs +++ b/compiler/load_internal/src/file.rs @@ -4,12 +4,13 @@ use crossbeam::channel::{bounded, Sender}; use crossbeam::deque::{Injector, Stealer, Worker}; use crossbeam::thread; use parking_lot::Mutex; +use roc_builtins::roc::module_source; use roc_builtins::std::borrow_stdlib; use roc_can::abilities::AbilitiesStore; 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_collections::{default_hasher, BumpMap, MutMap, MutSet, VecSet}; use roc_constrain::module::{ constrain_builtin_imports, constrain_module, ExposedByModule, ExposedForModule, ExposedModuleTypes, @@ -17,8 +18,8 @@ use roc_constrain::module::{ 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, + IdentIds, IdentIdsByModule, Interns, ModuleId, ModuleIds, PQModuleName, PackageModuleIds, + PackageQualified, Symbol, }; use roc_mono::ir::{ CapturedSymbols, EntryPoint, ExternalSpecializations, PartialProc, Proc, ProcLayout, Procs, @@ -38,9 +39,9 @@ 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, AliasCommon, TypeExtension}; +use roc_types::types::{Alias, AliasCommon, AliasKind, TypeExtension}; use std::collections::hash_map::Entry::{Occupied, Vacant}; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::io; use std::iter; use std::ops::ControlFlow; @@ -79,7 +80,7 @@ macro_rules! log { } /// Struct storing various intermediate stages by their ModuleId -#[derive(Debug, Default)] +#[derive(Debug)] struct ModuleCache<'a> { module_names: MutMap>, @@ -103,6 +104,72 @@ struct ModuleCache<'a> { sources: MutMap, } +impl Default for ModuleCache<'_> { + fn default() -> Self { + let mut module_names = MutMap::default(); + + module_names.insert( + ModuleId::RESULT, + PQModuleName::Unqualified(ModuleName::from(ModuleName::RESULT)), + ); + + module_names.insert( + ModuleId::LIST, + PQModuleName::Unqualified(ModuleName::from(ModuleName::LIST)), + ); + + module_names.insert( + ModuleId::STR, + PQModuleName::Unqualified(ModuleName::from(ModuleName::STR)), + ); + + module_names.insert( + ModuleId::DICT, + PQModuleName::Unqualified(ModuleName::from(ModuleName::DICT)), + ); + + module_names.insert( + ModuleId::SET, + PQModuleName::Unqualified(ModuleName::from(ModuleName::SET)), + ); + + module_names.insert( + ModuleId::BOOL, + PQModuleName::Unqualified(ModuleName::from(ModuleName::BOOL)), + ); + + module_names.insert( + ModuleId::NUM, + PQModuleName::Unqualified(ModuleName::from(ModuleName::NUM)), + ); + + module_names.insert( + ModuleId::BOX, + PQModuleName::Unqualified(ModuleName::from(ModuleName::BOX)), + ); + + Self { + module_names, + headers: Default::default(), + parsed: Default::default(), + aliases: Default::default(), + constrained: Default::default(), + typechecked: Default::default(), + found_specializations: Default::default(), + external_specializations_requested: Default::default(), + imports: Default::default(), + top_level_thunks: Default::default(), + documentation: Default::default(), + can_problems: Default::default(), + type_problems: Default::default(), + mono_problems: Default::default(), + sources: Default::default(), + } + } +} + +type SharedIdentIdsByModule = Arc>; + fn start_phase<'a>( module_id: ModuleId, phase: Phase, @@ -131,20 +198,24 @@ fn start_phase<'a>( let task = { match phase { Phase::LoadHeader => { - let dep_name = state - .module_cache - .module_names - .get(&module_id) - .expect("module id is present") - .clone(); + let opt_dep_name = state.module_cache.module_names.get(&module_id); - BuildTask::LoadModule { - module_name: dep_name, - // Provide mutexes of ModuleIds and IdentIds by module, - // so other modules can populate them as they load. - module_ids: Arc::clone(&state.arc_modules), - shorthands: Arc::clone(&state.arc_shorthands), - ident_ids_by_module: Arc::clone(&state.ident_ids_by_module), + match opt_dep_name { + None => { + panic!("Module {:?} is not in module_cache.module_names", module_id) + } + Some(dep_name) => { + let module_name = dep_name.clone(); + + BuildTask::LoadModule { + module_name, + // Provide mutexes of ModuleIds and IdentIds by module, + // so other modules can populate them as they load. + module_ids: Arc::clone(&state.arc_modules), + shorthands: Arc::clone(&state.arc_shorthands), + ident_ids_by_module: Arc::clone(&state.ident_ids_by_module), + } + } } } Phase::Parse => { @@ -159,8 +230,7 @@ fn start_phase<'a>( let deps_by_name = &parsed.deps_by_name; let num_deps = deps_by_name.len(); - let mut dep_idents: MutMap = - IdentIds::exposed_builtins(num_deps); + let mut dep_idents: IdentIdsByModule = IdentIds::exposed_builtins(num_deps); let State { ident_ids_by_module, @@ -222,12 +292,15 @@ fn start_phase<'a>( } } + let skip_constraint_gen = state.cached_subs.lock().contains_key(&module_id); + BuildTask::CanonicalizeAndConstrain { parsed, dep_idents, exposed_symbols, module_ids, aliases, + skip_constraint_gen, } } @@ -271,6 +344,7 @@ fn start_phase<'a>( solved_subs, decls, ident_ids, + abilities_store, } = typechecked; let mut imported_module_thunks = bumpalo::collections::Vec::new_in(arena); @@ -294,6 +368,7 @@ fn start_phase<'a>( decls, ident_ids, exposed_to_host: state.exposed_to_host.clone(), + abilities_store, } } Phase::MakeSpecializations => { @@ -316,6 +391,7 @@ fn start_phase<'a>( procs_base, layout_cache, module_timing, + abilities_store, } = found_specializations; BuildTask::MakeSpecializations { @@ -326,6 +402,7 @@ fn start_phase<'a>( layout_cache, specializations_we_must_make, module_timing, + abilities_store, } } } @@ -343,7 +420,7 @@ pub struct LoadedModule { pub type_problems: MutMap>, pub declarations_by_id: MutMap>, pub exposed_to_host: MutMap, - pub dep_idents: MutMap, + pub dep_idents: IdentIdsByModule, pub exposed_aliases: MutMap, pub exposed_values: Vec, pub sources: MutMap)>, @@ -370,7 +447,7 @@ impl LoadedModule { pub fn exposed_values_str(&self) -> Vec<&str> { self.exposed_values .iter() - .map(|symbol| symbol.ident_str(&self.interns).as_str()) + .map(|symbol| symbol.as_str(&self.interns)) .collect() } } @@ -407,7 +484,7 @@ struct ConstrainedModule { constraint: ConstraintSoa, ident_ids: IdentIds, var_store: VarStore, - dep_idents: MutMap, + dep_idents: IdentIdsByModule, module_timing: ModuleTiming, } @@ -419,6 +496,7 @@ pub struct TypeCheckedModule<'a> { pub solved_subs: Solved, pub decls: Vec, pub ident_ids: IdentIds, + pub abilities_store: AbilitiesStore, } #[derive(Debug)] @@ -429,6 +507,7 @@ struct FoundSpecializationsModule<'a> { procs_base: ProcsBase<'a>, subs: Subs, module_timing: ModuleTiming, + abilities_store: AbilitiesStore, } #[derive(Debug)] @@ -440,7 +519,6 @@ pub struct MonomorphizedModule<'a> { pub platform_path: Box, pub can_problems: MutMap>, pub type_problems: MutMap>, - pub mono_problems: MutMap>, pub procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, pub entry_point: EntryPoint<'a>, pub exposed_to_host: ExposedToHost, @@ -468,10 +546,6 @@ impl<'a> MonomorphizedModule<'a> { total += problems.len(); } - for problems in self.mono_problems.values() { - total += problems.len(); - } - total } } @@ -509,7 +583,7 @@ enum Msg<'a> { solved_module: SolvedModule, solved_subs: Solved, decls: Vec, - dep_idents: MutMap, + dep_idents: IdentIdsByModule, module_timing: ModuleTiming, abilities_store: AbilitiesStore, }, @@ -517,7 +591,7 @@ enum Msg<'a> { solved_subs: Solved, exposed_vars_by_symbol: Vec<(Symbol, Variable)>, exposed_aliases_by_symbol: MutMap, - dep_idents: MutMap, + dep_idents: IdentIdsByModule, documentation: MutMap, abilities_store: AbilitiesStore, }, @@ -529,6 +603,7 @@ enum Msg<'a> { problems: Vec, solved_subs: Solved, module_timing: ModuleTiming, + abilities_store: AbilitiesStore, }, MadeSpecializations { module_id: ModuleId, @@ -574,6 +649,7 @@ struct PlatformData { #[derive(Debug)] struct State<'a> { pub root_id: ModuleId, + pub root_subs: Option, pub platform_data: Option, pub goal_phase: Phase, pub exposed_types: ExposedByModule, @@ -588,17 +664,17 @@ struct State<'a> { /// This is the "final" list of IdentIds, after canonicalization and constraint gen /// have completed for a given module. - pub constrained_ident_ids: MutMap, + pub constrained_ident_ids: IdentIdsByModule, /// From now on, these will be used by multiple threads; time to make an Arc>! pub arc_modules: Arc>>, pub arc_shorthands: Arc>>>, - pub ident_ids_by_module: Arc>>, + pub ident_ids_by_module: SharedIdentIdsByModule, pub declarations_by_id: MutMap>, - pub exposed_symbols_by_module: MutMap>, + pub exposed_symbols_by_module: MutMap>, pub timings: MutMap, @@ -625,7 +701,7 @@ impl<'a> State<'a> { goal_phase: Phase, exposed_types: ExposedByModule, arc_modules: Arc>>, - ident_ids_by_module: Arc>>, + ident_ids_by_module: SharedIdentIdsByModule, cached_subs: MutMap)>, render: RenderTarget, ) -> Self { @@ -633,6 +709,7 @@ impl<'a> State<'a> { Self { root_id, + root_subs: None, target_info, platform_data: None, goal_phase, @@ -733,7 +810,7 @@ enum BuildTask<'a> { module_name: PQModuleName<'a>, module_ids: Arc>>, shorthands: Arc>>>, - ident_ids_by_module: Arc>>, + ident_ids_by_module: SharedIdentIdsByModule, }, Parse { header: ModuleHeader<'a>, @@ -741,9 +818,10 @@ enum BuildTask<'a> { CanonicalizeAndConstrain { parsed: ParsedModule<'a>, module_ids: ModuleIds, - dep_idents: MutMap, - exposed_symbols: MutSet, + dep_idents: IdentIdsByModule, + exposed_symbols: VecSet, aliases: MutMap, + skip_constraint_gen: bool, }, Solve { module: Module, @@ -755,7 +833,7 @@ enum BuildTask<'a> { constraint: ConstraintSoa, var_store: VarStore, declarations: Vec, - dep_idents: MutMap, + dep_idents: IdentIdsByModule, cached_subs: CachedSubs, }, BuildPendingSpecializations { @@ -767,6 +845,7 @@ enum BuildTask<'a> { ident_ids: IdentIds, decls: Vec, exposed_to_host: ExposedToHost, + abilities_store: AbilitiesStore, }, MakeSpecializations { module_id: ModuleId, @@ -776,6 +855,7 @@ enum BuildTask<'a> { layout_cache: LayoutCache<'a>, specializations_we_must_make: Vec, module_timing: ModuleTiming, + abilities_store: AbilitiesStore, }, } @@ -867,7 +947,7 @@ pub enum PrintTarget { pub struct LoadStart<'a> { arc_modules: Arc>>, - ident_ids_by_module: Arc>>, + ident_ids_by_module: SharedIdentIdsByModule, root_id: ModuleId, root_msg: Msg<'a>, } @@ -1053,7 +1133,7 @@ pub fn load<'a>( /// Load using only a single thread; used when compiling to webassembly #[allow(clippy::too_many_arguments)] -fn load_single_threaded<'a>( +pub fn load_single_threaded<'a>( arena: &'a Bump, load_start: LoadStart<'a>, src_dir: &Path, @@ -1200,7 +1280,6 @@ fn state_thread_step<'a>( // This is where most of the main thread's work gets done. // Everything up to this point has been setting up the threading // system which lets this logic work efficiently. - let constrained_ident_ids = state.constrained_ident_ids.clone(); let arc_modules = state.arc_modules.clone(); let render = state.render; @@ -1226,10 +1305,12 @@ fn state_thread_step<'a>( .into_inner() .into_module_ids(); + // if parsing failed, this module did not add anything to IdentIds + let root_exposed_ident_ids = IdentIds::exposed_builtins(0); let buf = to_parse_problem_report( problem, module_ids, - constrained_ident_ids, + root_exposed_ident_ids, render, ); Err(LoadingProblem::FormattedReport(buf)) @@ -1585,7 +1666,9 @@ fn report_unused_imported_modules<'a>( }; for (unused, region) in unused_imported_modules.drain() { - existing.push(roc_problem::can::Problem::UnusedImport(unused, region)); + if !unused.is_builtin() { + existing.push(roc_problem::can::Problem::UnusedImport(unused, region)); + } } } @@ -1651,7 +1734,7 @@ fn update<'a>( state.platform_path = PlatformPath::RootIsPkgConfig; } } - Interface => { + Builtin { .. } | Interface => { if header.is_root_module { debug_assert!(matches!(state.platform_path, PlatformPath::NotSpecified)); state.platform_path = PlatformPath::RootIsInterface; @@ -1671,8 +1754,7 @@ fn update<'a>( } // This was a dependency. Write it down and keep processing messages. - let mut exposed_symbols: MutSet = - HashSet::with_capacity_and_hasher(header.exposes.len(), default_hasher()); + let mut exposed_symbols: VecSet = VecSet::with_capacity(header.exposes.len()); // TODO can we avoid this loop by storing them as a Set in Header to begin with? for symbol in header.exposes.iter() { @@ -1687,6 +1769,132 @@ fn update<'a>( .exposed_symbols_by_module .insert(home, exposed_symbols); + // add the prelude + let mut header = header; + + if ![ModuleId::RESULT, ModuleId::BOOL].contains(&header.module_id) { + header + .package_qualified_imported_modules + .insert(PackageQualified::Unqualified(ModuleId::RESULT)); + + header + .imported_modules + .insert(ModuleId::RESULT, Region::zero()); + + header.exposed_imports.insert( + Ident::from("Result"), + (Symbol::RESULT_RESULT, Region::zero()), + ); + } + + if ![ModuleId::NUM, ModuleId::BOOL, ModuleId::RESULT].contains(&header.module_id) { + header + .package_qualified_imported_modules + .insert(PackageQualified::Unqualified(ModuleId::NUM)); + + header + .imported_modules + .insert(ModuleId::NUM, Region::zero()); + + let prelude_types = [ + (Ident::from("Num"), Symbol::NUM_NUM), + (Ident::from("Int"), Symbol::NUM_INT), + (Ident::from("Float"), Symbol::NUM_FLOAT), + (Ident::from("Integer"), Symbol::NUM_INTEGER), + (Ident::from("FloatingPoint"), Symbol::NUM_FLOATINGPOINT), + (Ident::from("Binary32"), Symbol::NUM_BINARY32), + (Ident::from("Binary64"), Symbol::NUM_BINARY64), + (Ident::from("Signed128"), Symbol::NUM_SIGNED128), + (Ident::from("Signed64"), Symbol::NUM_SIGNED64), + (Ident::from("Signed32"), Symbol::NUM_SIGNED32), + (Ident::from("Signed16"), Symbol::NUM_SIGNED16), + (Ident::from("Signed8"), Symbol::NUM_SIGNED8), + (Ident::from("Unsigned128"), Symbol::NUM_UNSIGNED128), + (Ident::from("Unsigned64"), Symbol::NUM_UNSIGNED64), + (Ident::from("Unsigned32"), Symbol::NUM_UNSIGNED32), + (Ident::from("Unsigned16"), Symbol::NUM_UNSIGNED16), + (Ident::from("Unsigned8"), Symbol::NUM_UNSIGNED8), + (Ident::from("Natural"), Symbol::NUM_NATURAL), + (Ident::from("Decimal"), Symbol::NUM_DECIMAL), + (Ident::from("Nat"), Symbol::NUM_NAT), + (Ident::from("I8"), Symbol::NUM_I8), + (Ident::from("I16"), Symbol::NUM_I16), + (Ident::from("I32"), Symbol::NUM_I32), + (Ident::from("I64"), Symbol::NUM_I64), + (Ident::from("I128"), Symbol::NUM_I128), + (Ident::from("U8"), Symbol::NUM_U8), + (Ident::from("U16"), Symbol::NUM_U16), + (Ident::from("U32"), Symbol::NUM_U32), + (Ident::from("U64"), Symbol::NUM_U64), + (Ident::from("U128"), Symbol::NUM_U128), + (Ident::from("F32"), Symbol::NUM_F32), + (Ident::from("F64"), Symbol::NUM_F64), + (Ident::from("Dec"), Symbol::NUM_DEC), + ]; + + for (ident, symbol) in prelude_types { + header + .exposed_imports + .insert(ident, (symbol, Region::zero())); + } + } + + if header.module_id != ModuleId::BOOL { + header + .package_qualified_imported_modules + .insert(PackageQualified::Unqualified(ModuleId::BOOL)); + + header + .imported_modules + .insert(ModuleId::BOOL, Region::zero()); + + header + .exposed_imports + .insert(Ident::from("Bool"), (Symbol::BOOL_BOOL, Region::zero())); + } + + if !header.module_id.is_builtin() { + header + .package_qualified_imported_modules + .insert(PackageQualified::Unqualified(ModuleId::BOX)); + + header + .imported_modules + .insert(ModuleId::BOX, Region::zero()); + + header + .package_qualified_imported_modules + .insert(PackageQualified::Unqualified(ModuleId::STR)); + + header + .imported_modules + .insert(ModuleId::STR, Region::zero()); + + header + .package_qualified_imported_modules + .insert(PackageQualified::Unqualified(ModuleId::DICT)); + + header + .imported_modules + .insert(ModuleId::DICT, Region::zero()); + + header + .package_qualified_imported_modules + .insert(PackageQualified::Unqualified(ModuleId::SET)); + + header + .imported_modules + .insert(ModuleId::SET, Region::zero()); + + header + .package_qualified_imported_modules + .insert(PackageQualified::Unqualified(ModuleId::LIST)); + + header + .imported_modules + .insert(ModuleId::LIST, Region::zero()); + } + state .module_cache .imports @@ -1880,6 +2088,7 @@ fn update<'a>( solved_subs, decls, ident_ids, + abilities_store, }; state @@ -1904,6 +2113,7 @@ fn update<'a>( layout_cache, problems, module_timing, + abilities_store, } => { log!("found specializations for {:?}", module_id); @@ -1925,6 +2135,7 @@ fn update<'a>( procs_base, subs, module_timing, + abilities_store, }; state @@ -2013,6 +2224,14 @@ fn update<'a>( existing.push(requested); } + // use the subs of the root module; + // this is used in the repl to find the type of `main` + let subs = if module_id == state.root_id { + subs + } else { + state.root_subs.clone().unwrap() + }; + msg_tx .send(Msg::FinishedAllSpecialization { subs, @@ -2025,6 +2244,12 @@ fn update<'a>( // the originally requested module, we're all done! return Ok(state); } else { + // record the subs of the root module; + // this is used in the repl to find the type of `main` + if module_id == state.root_id { + state.root_subs = Some(subs); + } + state.constrained_ident_ids.insert(module_id, ident_ids); for (module_id, requested) in external_specializations_requested { @@ -2091,7 +2316,6 @@ fn finish_specialization( } = state; let ModuleCache { - mono_problems, type_problems, can_problems, sources, @@ -2154,7 +2378,6 @@ fn finish_specialization( Ok(MonomorphizedModule { can_problems, - mono_problems, type_problems, output_path: output_path.unwrap_or(DEFAULT_APP_OUTPUT_PATH).into(), platform_path, @@ -2174,7 +2397,7 @@ fn finish( solved: Solved, exposed_aliases_by_symbol: MutMap, exposed_vars_by_symbol: Vec<(Symbol, Variable)>, - dep_idents: MutMap, + dep_idents: IdentIdsByModule, documentation: MutMap, abilities_store: AbilitiesStore, ) -> LoadedModule { @@ -2222,7 +2445,7 @@ fn load_pkg_config<'a>( shorthand: &'a str, app_module_id: ModuleId, module_ids: Arc>>, - ident_ids_by_module: Arc>>, + ident_ids_by_module: SharedIdentIdsByModule, ) -> Result, LoadingProblem<'a>> { let module_start_time = SystemTime::now(); @@ -2253,6 +2476,12 @@ fn load_pkg_config<'a>( header ))) } + Ok((ast::Module::Hosted { header }, _parse_state)) => { + Err(LoadingProblem::UnexpectedHeader(format!( + "expected platform/package module, got Hosted module with header\n{:?}", + header + ))) + } Ok((ast::Module::App { header }, _parse_state)) => { Err(LoadingProblem::UnexpectedHeader(format!( "expected platform/package module, got App with header\n{:?}", @@ -2276,12 +2505,6 @@ fn load_pkg_config<'a>( Ok(pkg_config_module_msg) } - Ok((ast::Module::Hosted { header }, _parse_state)) => { - Err(LoadingProblem::UnexpectedHeader(format!( - "expected platform/package module, got Hosted module with header\n{:?}", - header - ))) - } Err(fail) => Err(LoadingProblem::ParsingFailed( fail.map_problem(SyntaxError::Header) .into_file_error(filename), @@ -2296,6 +2519,68 @@ fn load_pkg_config<'a>( } } +fn load_builtin_module_help<'a>( + arena: &'a Bump, + filename: &str, + src_bytes: &'a str, +) -> (HeaderInfo<'a>, roc_parse::state::State<'a>) { + let is_root_module = false; + let opt_shorthand = None; + + let filename = PathBuf::from(filename); + + let parse_state = roc_parse::state::State::new(src_bytes.as_bytes()); + let parsed = roc_parse::module::parse_header(arena, parse_state.clone()); + + match parsed { + Ok((ast::Module::Interface { header }, parse_state)) => { + let info = HeaderInfo { + loc_name: Loc { + region: header.name.region, + value: ModuleNameEnum::Interface(header.name.value), + }, + filename, + is_root_module, + opt_shorthand, + packages: &[], + exposes: unspace(arena, header.exposes.items), + imports: unspace(arena, header.imports.items), + extra: HeaderFor::Builtin { + generates_with: &[], + }, + }; + + (info, parse_state) + } + Ok(_) => panic!("invalid header format for builtin module"), + Err(e) => panic!( + "Hit a parse error in the header of {:?}:\n{:?}", + filename, e + ), + } +} + +fn load_builtin_module<'a>( + arena: &'a Bump, + module_ids: Arc>>, + ident_ids_by_module: SharedIdentIdsByModule, + module_timing: ModuleTiming, + module_id: ModuleId, + module_name: &str, +) -> (ModuleId, Msg<'a>) { + let src_bytes = module_source(module_id); + + let (info, parse_state) = load_builtin_module_help(arena, module_name, src_bytes); + + send_header( + info, + parse_state, + module_ids, + ident_ids_by_module, + module_timing, + ) +} + /// Load a module by its module name, rather than by its filename fn load_module<'a>( arena: &'a Bump, @@ -2303,9 +2588,122 @@ fn load_module<'a>( module_name: PQModuleName<'a>, module_ids: Arc>>, arc_shorthands: Arc>>>, - ident_ids_by_module: Arc>>, + ident_ids_by_module: SharedIdentIdsByModule, ) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> { let module_start_time = SystemTime::now(); + + let parse_start = SystemTime::now(); + let parse_header_duration = parse_start.elapsed().unwrap(); + + // Insert the first entries for this module's timings + let mut module_timing = ModuleTiming::new(module_start_time); + + module_timing.read_roc_file = Default::default(); + module_timing.parse_header = parse_header_duration; + match module_name.as_inner().as_str() { + "Result" => { + return Ok(load_builtin_module( + arena, + module_ids, + ident_ids_by_module, + module_timing, + ModuleId::RESULT, + "Result.roc", + )); + } + "List" => { + return Ok(load_builtin_module( + arena, + module_ids, + ident_ids_by_module, + module_timing, + ModuleId::LIST, + "List.roc", + )); + } + "Str" => { + return Ok(load_builtin_module( + arena, + module_ids, + ident_ids_by_module, + module_timing, + ModuleId::STR, + "Str.roc", + )); + } + "Dict" => { + return Ok(load_builtin_module( + arena, + module_ids, + ident_ids_by_module, + module_timing, + ModuleId::DICT, + "Dict.roc", + )); + } + "Set" => { + return Ok(load_builtin_module( + arena, + module_ids, + ident_ids_by_module, + module_timing, + ModuleId::SET, + "Set.roc", + )); + } + "Num" => { + return Ok(load_builtin_module( + arena, + module_ids, + ident_ids_by_module, + module_timing, + ModuleId::NUM, + "Num.roc", + )); + } + "Bool" => { + return Ok(load_builtin_module( + arena, + module_ids, + ident_ids_by_module, + module_timing, + ModuleId::BOOL, + "Bool.roc", + )); + } + "Box" => { + return Ok(load_builtin_module( + arena, + module_ids, + ident_ids_by_module, + module_timing, + ModuleId::BOX, + "Box.roc", + )); + } + _ => { + // fall through + } + } + + let (filename, opt_shorthand) = module_name_to_path(src_dir, module_name, arc_shorthands); + + load_filename( + arena, + filename, + false, + opt_shorthand, + module_ids, + ident_ids_by_module, + module_start_time, + ) +} + +fn module_name_to_path<'a>( + src_dir: &Path, + module_name: PQModuleName<'a>, + arc_shorthands: Arc>>>, +) -> (PathBuf, Option<&'a str>) { let mut filename = PathBuf::new(); filename.push(src_dir); @@ -2340,15 +2738,7 @@ fn load_module<'a>( // End with .roc filename.set_extension(ROC_FILE_EXTENSION); - load_filename( - arena, - filename, - false, - opt_shorthand, - module_ids, - ident_ids_by_module, - module_start_time, - ) + (filename, opt_shorthand) } /// Find a task according to the following algorithm: @@ -2385,7 +2775,7 @@ fn parse_header<'a>( is_root_module: bool, opt_shorthand: Option<&'a str>, module_ids: Arc>>, - ident_ids_by_module: Arc>>, + ident_ids_by_module: SharedIdentIdsByModule, src_bytes: &'a [u8], start_time: SystemTime, ) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> { @@ -2573,7 +2963,7 @@ fn load_filename<'a>( is_root_module: bool, opt_shorthand: Option<&'a str>, module_ids: Arc>>, - ident_ids_by_module: Arc>>, + ident_ids_by_module: SharedIdentIdsByModule, module_start_time: SystemTime, ) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> { let file_io_start = SystemTime::now(); @@ -2607,7 +2997,7 @@ fn load_from_str<'a>( filename: PathBuf, src: &'a str, module_ids: Arc>>, - ident_ids_by_module: Arc>>, + ident_ids_by_module: SharedIdentIdsByModule, module_start_time: SystemTime, ) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> { let file_io_start = SystemTime::now(); @@ -2643,7 +3033,7 @@ fn send_header<'a>( info: HeaderInfo<'a>, parse_state: roc_parse::state::State<'a>, module_ids: Arc>>, - ident_ids_by_module: Arc>>, + ident_ids_by_module: SharedIdentIdsByModule, module_timing: ModuleTiming, ) -> (ModuleId, Msg<'a>) { use ModuleNameEnum::*; @@ -2706,9 +3096,7 @@ fn send_header<'a>( home = module_ids.get_or_insert(&name); // Ensure this module has an entry in the exposed_ident_ids map. - ident_ids_by_module - .entry(home) - .or_insert_with(IdentIds::default); + ident_ids_by_module.get_or_insert(home); // For each of our imports, add an entry to deps_by_name // @@ -2735,9 +3123,7 @@ fn send_header<'a>( // Add the new exposed idents to the dep module's IdentIds, so // once that module later gets loaded, its lookups will resolve // to the same symbols as the ones we're using here. - let ident_ids = ident_ids_by_module - .entry(module_id) - .or_insert_with(IdentIds::default); + let ident_ids = ident_ids_by_module.get_or_insert(module_id); for ident in exposed_idents { let ident_id = ident_ids.get_or_insert(&ident); @@ -2807,6 +3193,18 @@ fn send_header<'a>( } } + // make sure when we run the bulitin modules in /compiler/builtins/roc that we + // mark these modules as Builtin. Otherwise the builtin functions are not instantiated + // and we just have a bunch of definitions with runtime errors in their bodies + let extra = { + match extra { + HeaderFor::Interface if home.is_builtin() => HeaderFor::Builtin { + generates_with: &[], + }, + _ => extra, + } + }; + ( home, Msg::Header(ModuleHeader { @@ -2847,7 +3245,7 @@ fn send_header_two<'a>( info: PlatformHeaderInfo<'a>, parse_state: roc_parse::state::State<'a>, module_ids: Arc>>, - ident_ids_by_module: Arc>>, + ident_ids_by_module: SharedIdentIdsByModule, module_timing: ModuleTiming, ) -> (ModuleId, Msg<'a>) { let PlatformHeaderInfo { @@ -2906,9 +3304,7 @@ fn send_header_two<'a>( home = module_ids.get_or_insert(&name); // Ensure this module has an entry in the exposed_ident_ids map. - ident_ids_by_module - .entry(home) - .or_insert_with(IdentIds::default); + ident_ids_by_module.get_or_insert(home); // For each of our imports, add an entry to deps_by_name // @@ -2930,9 +3326,7 @@ fn send_header_two<'a>( // Add the new exposed idents to the dep module's IdentIds, so // once that module later gets loaded, its lookups will resolve // to the same symbols as the ones we're using here. - let ident_ids = ident_ids_by_module - .entry(module_id) - .or_insert_with(IdentIds::default); + let ident_ids = ident_ids_by_module.get_or_insert(module_id); for ident in exposed_idents { let ident_id = ident_ids.get_or_insert(&ident); @@ -2946,9 +3340,7 @@ fn send_header_two<'a>( } { - let ident_ids = ident_ids_by_module - .entry(app_module_id) - .or_insert_with(IdentIds::default); + let ident_ids = ident_ids_by_module.get_or_insert(app_module_id); for entry in requires { let entry = entry.value; @@ -3078,7 +3470,7 @@ impl<'a> BuildTask<'a> { var_store: VarStore, imported_modules: MutMap, exposed_types: &mut ExposedByModule, - dep_idents: MutMap, + dep_idents: IdentIdsByModule, declarations: Vec, cached_subs: CachedSubs, ) -> Self { @@ -3218,31 +3610,29 @@ fn run_solve_solve( solve_aliases.insert(*name, alias.clone()); } - let (solved_subs, solved_env, problems, abilities_store) = roc_solve::module::run_solve( - &constraints, - actual_constraint, - rigid_variables, - subs, - solve_aliases, - abilities_store, - ); + let (solved_subs, exposed_vars_by_symbol, problems, abilities_store) = { + let (solved_subs, solved_env, problems, abilities_store) = roc_solve::module::run_solve( + &constraints, + actual_constraint, + rigid_variables, + subs, + solve_aliases, + abilities_store, + ); - let solved_subs = if true { - solved_subs - } else { - panic!(); - // let mut serialized = Vec::new(); - // solved_subs.inner().serialize(&mut serialized).unwrap(); - // let subs = Subs::deserialize(&serialized); - // - // Solved(subs) + let exposed_vars_by_symbol: Vec<_> = solved_env + .vars_by_symbol() + .filter(|(k, _)| exposed_symbols.contains(k)) + .collect(); + + ( + solved_subs, + exposed_vars_by_symbol, + problems, + abilities_store, + ) }; - let exposed_vars_by_symbol: Vec<_> = solved_env - .vars_by_symbol() - .filter(|(k, _)| exposed_symbols.contains(k)) - .collect(); - ( solved_subs, exposed_vars_by_symbol, @@ -3262,7 +3652,7 @@ fn run_solve<'a>( constraint: ConstraintSoa, var_store: VarStore, decls: Vec, - dep_idents: MutMap, + dep_idents: IdentIdsByModule, cached_subs: CachedSubs, ) -> Msg<'a> { let solve_start = SystemTime::now(); @@ -3275,24 +3665,23 @@ fn run_solve<'a>( let (solved_subs, exposed_vars_by_symbol, problems, abilities_store) = { if module_id.is_builtin() { match cached_subs.lock().remove(&module_id) { - None => { - // this should never happen - run_solve_solve( - imported_builtins, - exposed_for_module, - constraints, - constraint, - var_store, - module, + None => run_solve_solve( + imported_builtins, + exposed_for_module, + constraints, + constraint, + var_store, + module, + ), + Some((subs, exposed_vars_by_symbol)) => { + ( + Solved(subs), + exposed_vars_by_symbol.to_vec(), + vec![], + // TODO(abilities) replace when we have abilities for builtins + AbilitiesStore::default(), ) } - Some((subs, exposed_vars_by_symbol)) => ( - Solved(subs), - exposed_vars_by_symbol.to_vec(), - vec![], - // TODO(abilities) replace when we have abilities for builtins - AbilitiesStore::default(), - ), } } else { run_solve_solve( @@ -3353,7 +3742,7 @@ fn fabricate_pkg_config_module<'a>( filename: PathBuf, parse_state: roc_parse::state::State<'a>, module_ids: Arc>>, - ident_ids_by_module: Arc>>, + ident_ids_by_module: SharedIdentIdsByModule, header: &PlatformHeader<'a>, module_timing: ModuleTiming, ) -> (ModuleId, Msg<'a>) { @@ -3386,10 +3775,11 @@ fn fabricate_pkg_config_module<'a>( fn canonicalize_and_constrain<'a>( arena: &'a Bump, module_ids: &ModuleIds, - dep_idents: MutMap, - exposed_symbols: MutSet, + dep_idents: IdentIdsByModule, + exposed_symbols: VecSet, aliases: MutMap, parsed: ParsedModule<'a>, + skip_constraint_gen: bool, ) -> Result, LoadingProblem<'a>> { let canonicalize_start = SystemTime::now(); @@ -3447,7 +3837,6 @@ fn canonicalize_and_constrain<'a>( let docs = crate::docs::generate_module_docs( module_output.scope.clone(), name.as_str().into(), - &module_output.ident_ids, parsed_defs, ); @@ -3459,12 +3848,16 @@ fn canonicalize_and_constrain<'a>( let mut constraints = Constraints::new(); - let constraint = constrain_module( - &mut constraints, - &module_output.scope.abilities_store, - &module_output.declarations, - module_id, - ); + let constraint = if skip_constraint_gen { + roc_can::constraint::Constraint::True + } else { + constrain_module( + &mut constraints, + &module_output.scope.abilities_store, + &module_output.declarations, + module_id, + ) + }; let after = roc_types::types::get_type_clone_count(); @@ -3514,7 +3907,7 @@ fn canonicalize_and_constrain<'a>( var_store, constraints, constraint, - ident_ids: module_output.ident_ids, + ident_ids: module_output.scope.ident_ids, dep_idents, module_timing, }; @@ -3642,6 +4035,7 @@ fn make_specializations<'a>( specializations_we_must_make: Vec, mut module_timing: ModuleTiming, target_info: TargetInfo, + abilities_store: AbilitiesStore, ) -> Msg<'a> { let make_specializations_start = SystemTime::now(); let mut mono_problems = Vec::new(); @@ -3657,6 +4051,7 @@ fn make_specializations<'a>( update_mode_ids: &mut update_mode_ids, // call_specialization_counter=0 is reserved call_specialization_counter: 1, + abilities_store: &abilities_store, }; let mut procs = Procs::new_in(arena); @@ -3727,6 +4122,7 @@ fn build_pending_specializations<'a>( target_info: TargetInfo, // TODO remove exposed_to_host: ExposedToHost, + abilities_store: AbilitiesStore, ) -> Msg<'a> { let find_specializations_start = SystemTime::now(); @@ -3753,6 +4149,7 @@ fn build_pending_specializations<'a>( update_mode_ids: &mut update_mode_ids, // call_specialization_counter=0 is reserved call_specialization_counter: 1, + abilities_store: &abilities_store, }; // Add modules' decls to Procs @@ -3806,6 +4203,7 @@ fn build_pending_specializations<'a>( procs_base, problems, module_timing, + abilities_store, } } @@ -3823,7 +4221,11 @@ fn add_def_to_module<'a>( use roc_can::pattern::Pattern::*; match def.loc_pattern.value { - Identifier(symbol) => { + Identifier(symbol) + | AbilityMemberSpecialization { + ident: symbol, + specializes: _, + } => { let is_host_exposed = exposed_to_host.contains_key(&symbol); match def.loc_expr.value { @@ -3833,10 +4235,15 @@ fn add_def_to_module<'a>( arguments: loc_args, loc_body, captured_symbols, + name, .. }) => { // this is a top-level definition, it should not capture anything - debug_assert!(captured_symbols.is_empty()); + debug_assert!( + captured_symbols.is_empty(), + "{:?}", + (symbol, name, symbol.module_id(), &captured_symbols) + ); // If this is an exposed symbol, we need to // register it as such. Otherwise, since it @@ -3877,7 +4284,6 @@ fn add_def_to_module<'a>( let partial_proc = PartialProc::from_named_function( mono_env, - layout_cache, annotation, loc_args, *loc_body, @@ -3984,6 +4390,7 @@ fn run_task<'a>( dep_idents, exposed_symbols, aliases, + skip_constraint_gen, } => canonicalize_and_constrain( arena, &module_ids, @@ -3991,6 +4398,7 @@ fn run_task<'a>( exposed_symbols, aliases, parsed, + skip_constraint_gen, ), Solve { module, @@ -4026,6 +4434,7 @@ fn run_task<'a>( solved_subs, imported_module_thunks, exposed_to_host, + abilities_store, } => Ok(build_pending_specializations( arena, solved_subs, @@ -4037,6 +4446,7 @@ fn run_task<'a>( layout_cache, target_info, exposed_to_host, + abilities_store, )), MakeSpecializations { module_id, @@ -4046,6 +4456,7 @@ fn run_task<'a>( layout_cache, specializations_we_must_make, module_timing, + abilities_store, } => Ok(make_specializations( arena, module_id, @@ -4056,6 +4467,7 @@ fn run_task<'a>( specializations_we_must_make, module_timing, target_info, + abilities_store, )), }?; @@ -4083,12 +4495,12 @@ fn to_file_problem_report(filename: &Path, error: io::ErrorKind) -> String { let report = match error { io::ErrorKind::NotFound => { - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am looking for this file, but it's not there:"), alloc .parser_suggestion(filename.to_str().unwrap()) .indent(4), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"Is the file supposed to be there? "), alloc.reflow("Maybe there is a typo in the file name?"), ]), @@ -4102,14 +4514,13 @@ fn to_file_problem_report(filename: &Path, error: io::ErrorKind) -> String { } } io::ErrorKind::PermissionDenied => { - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I don't have the required permissions to read this file:"), alloc .parser_suggestion(filename.to_str().unwrap()) .indent(4), - alloc.concat(vec![ - alloc.reflow(r"Is it the right file? Maybe change its permissions?") - ]), + alloc + .concat([alloc.reflow(r"Is it the right file? Maybe change its permissions?")]), ]); Report { @@ -4122,7 +4533,7 @@ fn to_file_problem_report(filename: &Path, error: io::ErrorKind) -> String { _ => { let error = std::io::Error::from(error); let formatted = format!("{}", error); - let doc = alloc.concat(vec![ + let doc = alloc.concat([ alloc.reflow(r"I tried to read this file, but ran into a "), alloc.text(formatted), alloc.reflow(r" problem."), @@ -4147,7 +4558,7 @@ fn to_file_problem_report(filename: &Path, error: io::ErrorKind) -> String { fn to_parse_problem_report<'a>( problem: FileError<'a, SyntaxError<'a>>, mut module_ids: ModuleIds, - all_ident_ids: MutMap, + all_ident_ids: IdentIdsByModule, render: RenderTarget, ) -> String { use roc_reporting::report::{parse_problem, RocDocAllocator, DEFAULT_PALETTE}; @@ -4201,7 +4612,7 @@ fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> Strin match other { Valid(_) => unreachable!(), NotSpecified => { - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow("I could not find a platform based on your input file."), alloc.reflow(r"Does the module header contain an entry that looks like this:"), alloc @@ -4218,9 +4629,9 @@ fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> Strin } } RootIsInterface => { - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"The input file is an interface module, but only app modules can be ran."), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"I will still parse and typecheck the input file and its dependencies, "), alloc.reflow(r"but won't output any executable."), ]) @@ -4234,9 +4645,9 @@ fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> Strin } } RootIsHosted => { - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"The input file is a hosted module, but only app modules can be ran."), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"I will still parse and typecheck the input file and its dependencies, "), alloc.reflow(r"but won't output any executable."), ]) @@ -4250,9 +4661,9 @@ fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> Strin } } RootIsPkgConfig => { - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"The input file is a package config file, but only app modules can be ran."), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"I will still parse and typecheck the input file and its dependencies, "), alloc.reflow(r"but won't output any executable."), ]) @@ -4286,50 +4697,35 @@ fn default_aliases() -> roc_solve::solve::Aliases { let mut var_store = VarStore::default(); + // Num range := range { 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, + typ: Type::Variable(tvar), kind: roc_types::types::AliasKind::Structural, }; solve_aliases.insert(symbol, alias); } - // FloatingPoint range : [ @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, + typ: Type::Variable(tvar), + kind: roc_types::types::AliasKind::Opaque, }; solve_aliases.insert(symbol, alias); @@ -4344,11 +4740,13 @@ fn default_aliases() -> roc_solve::solve::Aliases { symbol: Symbol::NUM_NUM, type_arguments: vec![( "range".into(), - Type::DelayedAlias(AliasCommon { + Type::Alias { symbol: Symbol::NUM_INTEGER, type_arguments: vec![("range".into(), Type::Variable(tvar))], lambda_set_variables: vec![], - }), + actual: Box::new(Type::Variable(tvar)), + kind: AliasKind::Opaque, + }, )], lambda_set_variables: vec![], }); @@ -4365,6 +4763,7 @@ fn default_aliases() -> roc_solve::solve::Aliases { solve_aliases.insert(symbol, alias); } + // Float range : Num (FloatingPoint range) { let symbol = Symbol::NUM_FLOAT; let tvar = var_store.fresh(); @@ -4373,11 +4772,13 @@ fn default_aliases() -> roc_solve::solve::Aliases { symbol: Symbol::NUM_NUM, type_arguments: vec![( "range".into(), - Type::DelayedAlias(AliasCommon { + Type::Alias { symbol: Symbol::NUM_FLOATINGPOINT, type_arguments: vec![("range".into(), Type::Variable(tvar))], lambda_set_variables: vec![], - }), + actual: Box::new(Type::Variable(tvar)), + kind: AliasKind::Opaque, + }, )], lambda_set_variables: vec![], }); @@ -4394,24 +4795,17 @@ fn default_aliases() -> roc_solve::solve::Aliases { solve_aliases.insert(symbol, alias); } + // Integer range := range { 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, + typ: Type::Variable(tvar), kind: roc_types::types::AliasKind::Structural, }; @@ -4425,8 +4819,8 @@ fn default_aliases() -> roc_solve::solve::Aliases { let typ = Type::TagUnion( vec![ - (TagName::Global("Ok".into()), vec![Type::Variable(tvar1)]), - (TagName::Global("Err".into()), vec![Type::Variable(tvar2)]), + (TagName::Tag("Ok".into()), vec![Type::Variable(tvar1)]), + (TagName::Tag("Err".into()), vec![Type::Variable(tvar2)]), ], TypeExtension::Closed, ); @@ -4446,38 +4840,33 @@ fn default_aliases() -> roc_solve::solve::Aliases { 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 mut zero_opaque = |alias_name: Symbol| { 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, + typ: Type::EmptyTagUnion, + kind: AliasKind::Opaque, }; 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); + zero_opaque(Symbol::NUM_SIGNED8); + zero_opaque(Symbol::NUM_SIGNED16); + zero_opaque(Symbol::NUM_SIGNED32); + zero_opaque(Symbol::NUM_SIGNED64); + zero_opaque(Symbol::NUM_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); + zero_opaque(Symbol::NUM_UNSIGNED8); + zero_opaque(Symbol::NUM_UNSIGNED16); + zero_opaque(Symbol::NUM_UNSIGNED32); + zero_opaque(Symbol::NUM_UNSIGNED64); + zero_opaque(Symbol::NUM_UNSIGNED128); - unit_function(Symbol::NUM_BINARY32, Symbol::NUM_AT_BINARY32); - unit_function(Symbol::NUM_BINARY64, Symbol::NUM_AT_BINARY64); + zero_opaque(Symbol::NUM_BINARY32); + zero_opaque(Symbol::NUM_BINARY64); solve_aliases } diff --git a/compiler/load_internal/tests/test_load.rs b/compiler/load_internal/tests/test_load.rs index 5aaace9f19..ae018333c1 100644 --- a/compiler/load_internal/tests/test_load.rs +++ b/compiler/load_internal/tests/test_load.rs @@ -89,11 +89,11 @@ mod test_load { buf } - fn multiple_modules(files: Vec<(&str, &str)>) -> Result { + fn multiple_modules(subdir: &str, files: Vec<(&str, &str)>) -> Result { let arena = Bump::new(); let arena = &arena; - match multiple_modules_help(arena, files) { + match multiple_modules_help(subdir, arena, files) { Err(io_error) => panic!("IO trouble: {:?}", io_error), Ok(Err(LoadingProblem::FormattedReport(buf))) => Err(buf), Ok(Err(loading_problem)) => Err(format!("{:?}", loading_problem)), @@ -112,13 +112,11 @@ mod test_load { )); } - assert_eq!( - loaded_module - .type_problems - .remove(&home) - .unwrap_or_default(), - Vec::new() - ); + assert!(loaded_module + .type_problems + .remove(&home) + .unwrap_or_default() + .is_empty(),); Ok(loaded_module) } @@ -126,18 +124,21 @@ mod test_load { } fn multiple_modules_help<'a>( + subdir: &str, arena: &'a Bump, mut files: Vec<(&str, &str)>, ) -> Result>, std::io::Error> { use std::fs::{self, File}; use std::io::Write; - use tempfile::tempdir; let mut file_handles: Vec<_> = Vec::new(); - // create a temporary directory - let dir = tempdir()?; + // Use a deterministic temporary directory. + // We can't have all tests use "tmp" because tests run in parallel, + // so append the test name to the tmp path. + let tmp = format!("tmp/{}", subdir); + let dir = roc_test_utils::TmpDir::new(&tmp); let app_module = files.pop().unwrap(); @@ -173,8 +174,6 @@ mod test_load { ) }; - dir.close()?; - Ok(result) } @@ -208,13 +207,11 @@ mod test_load { loaded_module.can_problems.remove(&home).unwrap_or_default(), Vec::new() ); - assert_eq!( - loaded_module - .type_problems - .remove(&home) - .unwrap_or_default(), - Vec::new() - ); + assert!(loaded_module + .type_problems + .remove(&home) + .unwrap_or_default() + .is_empty()); let expected_name = loaded_module .interns @@ -261,13 +258,11 @@ mod test_load { loaded_module.can_problems.remove(&home).unwrap_or_default(), Vec::new() ); - assert_eq!( - loaded_module - .type_problems - .remove(&home) - .unwrap_or_default(), - Vec::new() - ); + assert!(loaded_module + .type_problems + .remove(&home) + .unwrap_or_default() + .is_empty()); for decl in loaded_module.declarations_by_id.remove(&home).unwrap() { match decl { @@ -341,7 +336,7 @@ mod test_load { ), ]; - assert!(multiple_modules(modules).is_ok()); + assert!(multiple_modules("import_transitive_alias", modules).is_ok()); } #[test] @@ -365,13 +360,11 @@ mod test_load { loaded_module.can_problems.remove(&home).unwrap_or_default(), Vec::new() ); - assert_eq!( - loaded_module - .type_problems - .remove(&home) - .unwrap_or_default(), - Vec::new() - ); + assert!(loaded_module + .type_problems + .remove(&home) + .unwrap_or_default() + .is_empty(),); let def_count: usize = loaded_module .declarations_by_id @@ -584,12 +577,12 @@ mod test_load { ), )]; - match multiple_modules(modules) { + match multiple_modules("parse_problem", modules) { Err(report) => assert_eq!( report, indoc!( " - ── UNFINISHED LIST ───────────────────────────────────────────────────────────── + ── UNFINISHED LIST ──────────────────────────────────── tmp/parse_problem/Main ─ I cannot find the end of this list: @@ -651,10 +644,14 @@ mod test_load { ), )]; - match multiple_modules(modules) { + match multiple_modules("platform_does_not_exist", modules) { Err(report) => { - assert!(report.contains("FILE NOT FOUND")); - assert!(report.contains("zzz-does-not-exist/Package-Config.roc")); + assert!(report.contains("FILE NOT FOUND"), "report=({})", report); + assert!( + report.contains("zzz-does-not-exist/Package-Config.roc"), + "report=({})", + report + ); } Ok(_) => unreachable!("we expect failure here"), } @@ -694,7 +691,7 @@ mod test_load { ), ]; - match multiple_modules(modules) { + match multiple_modules("platform_parse_error", modules) { Err(report) => { assert!(report.contains("NOT END OF FILE")); assert!(report.contains("blah 1 2 3 # causing a parse error on purpose")); @@ -738,7 +735,7 @@ mod test_load { ), ]; - assert!(multiple_modules(modules).is_ok()); + assert!(multiple_modules("platform_exposes_main_return_by_pointer_issue", modules).is_ok()); } #[test] @@ -760,24 +757,25 @@ mod test_load { r#" interface Main exposes [ twenty, readAge ] imports [ Age.{ Age } ] - twenty = $Age 20 + twenty = @Age 20 - readAge = \$Age n -> n + readAge = \@Age n -> n "# ), ), ]; - let err = multiple_modules(modules).unwrap_err(); + let err = multiple_modules("opaque_wrapped_unwrapped_outside_defining_module", modules) + .unwrap_err(); assert_eq!( err, indoc!( r#" - ── OPAQUE TYPE DECLARED OUTSIDE SCOPE ────────────────────────────────────────── + ── OPAQUE TYPE DECLARED OUTSIDE SCOPE ─ ...rapped_outside_defining_module/Main ─ The unwrapped opaque type Age referenced here: - 3β”‚ twenty = $Age 20 + 3β”‚ twenty = @Age 20 ^^^^ is imported from another module: @@ -787,11 +785,11 @@ mod test_load { Note: Opaque types can only be wrapped and unwrapped in the module they are defined in! - ── OPAQUE TYPE DECLARED OUTSIDE SCOPE ────────────────────────────────────────── + ── OPAQUE TYPE DECLARED OUTSIDE SCOPE ─ ...rapped_outside_defining_module/Main ─ The unwrapped opaque type Age referenced here: - 5β”‚ readAge = \$Age n -> n + 5β”‚ readAge = \@Age n -> n ^^^^ is imported from another module: @@ -801,7 +799,7 @@ mod test_load { Note: Opaque types can only be wrapped and unwrapped in the module they are defined in! - ── UNUSED IMPORT ─────────────────────────────────────────────────────────────── + ── UNUSED IMPORT ─── tmp/opaque_wrapped_unwrapped_outside_defining_module/Main ─ Nothing from Age is used in this module. @@ -850,13 +848,13 @@ mod test_load { ), ]; - match multiple_modules(modules) { + match multiple_modules("issue_2863_module_type_does_not_exist", modules) { Err(report) => { assert_eq!( report, indoc!( " - ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + ── UNRECOGNIZED NAME ────────── tmp/issue_2863_module_type_does_not_exist/Main ─ I cannot find a `DoesNotExist` value @@ -868,7 +866,7 @@ mod test_load { Dict Result List - Nat + Box " ) ) diff --git a/compiler/module/src/called_via.rs b/compiler/module/src/called_via.rs index c83245d3e9..bc314e0eec 100644 --- a/compiler/module/src/called_via.rs +++ b/compiler/module/src/called_via.rs @@ -34,7 +34,6 @@ pub enum BinOp { Slash, DoubleSlash, Percent, - DoublePercent, Plus, Minus, Equals, @@ -58,8 +57,8 @@ impl BinOp { pub fn width(self) -> u16 { match self { Caret | Star | Slash | Percent | Plus | Minus | LessThan | GreaterThan => 1, - DoubleSlash | DoublePercent | Equals | NotEquals | LessThanOrEq | GreaterThanOrEq - | And | Or | Pizza => 2, + DoubleSlash | Equals | NotEquals | LessThanOrEq | GreaterThanOrEq | And | Or + | Pizza => 2, Assignment | IsAliasType | IsOpaqueType | Backpassing => unreachable!(), } } @@ -97,9 +96,7 @@ impl BinOp { use self::Associativity::*; match self { - Pizza | Star | Slash | DoubleSlash | DoublePercent | Percent | Plus | Minus => { - LeftAssociative - } + Pizza | Star | Slash | DoubleSlash | Percent | Plus | Minus => LeftAssociative, And | Or | Caret => RightAssociative, Equals | NotEquals | LessThan | GreaterThan | LessThanOrEq | GreaterThanOrEq => { NonAssociative @@ -111,7 +108,7 @@ impl BinOp { fn precedence(self) -> u8 { match self { Caret => 7, - Star | Slash | DoubleSlash | DoublePercent | Percent => 6, + Star | Slash | DoubleSlash | Percent => 6, Plus | Minus => 5, Equals | NotEquals | LessThan | GreaterThan | LessThanOrEq | GreaterThanOrEq => 4, And => 3, @@ -142,7 +139,6 @@ impl std::fmt::Display for BinOp { Slash => "/", DoubleSlash => "//", Percent => "%", - DoublePercent => "%%", Plus => "+", Minus => "-", Equals => "==", diff --git a/compiler/module/src/ident.rs b/compiler/module/src/ident.rs index 3f3c8d51f1..064f061974 100644 --- a/compiler/module/src/ident.rs +++ b/compiler/module/src/ident.rs @@ -3,13 +3,18 @@ pub use roc_ident::IdentStr; use std::fmt; /// This could be uppercase or lowercase, qualified or unqualified. -#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default)] pub struct Ident(pub IdentStr); impl Ident { pub fn as_inline_str(&self) -> &IdentStr { &self.0 } + + #[inline(always)] + pub fn as_str(&self) -> &str { + self.0.as_str() + } } pub struct QualifiedModuleName<'a> { @@ -44,18 +49,14 @@ pub type TagIdIntType = u16; #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum TagName { - /// Global tags have no module, but tend to be short strings (since they're + /// Tags have no module, but tend to be short strings (since they're /// never qualified), so we store them as ident strings. /// /// This is allows canonicalization to happen in parallel without locks. - /// If global tags had a Symbol representation, then each module would have to - /// deal with contention on a global mutex around translating global tag strings + /// If tags had a Symbol representation, then each module would have to + /// deal with contention on a global mutex around translating tag strings /// into integers. (Record field labels work the same way, for the same reason.) - Global(Uppercase), - - /// Private tags are associated with a specific module, and as such use a - /// Symbol just like all other module-specific identifiers. - Private(Symbol), + Tag(Uppercase), /// Used to connect the closure size to the function it corresponds to Closure(Symbol), @@ -68,10 +69,7 @@ roc_error_macros::assert_sizeof_default!(TagName, 24); impl TagName { pub fn as_ident_str(&self, interns: &Interns, home: ModuleId) -> IdentStr { match self { - TagName::Global(uppercase) => uppercase.as_ident_str().clone(), - TagName::Private(symbol) => { - symbol.fully_qualified(interns, home).as_ident_str().clone() - } + TagName::Tag(uppercase) => uppercase.as_ident_str().clone(), TagName::Closure(symbol) => { symbol.fully_qualified(interns, home).as_ident_str().clone() } @@ -90,6 +88,7 @@ impl ModuleName { pub const DICT: &'static str = "Dict"; pub const SET: &'static str = "Set"; pub const RESULT: &'static str = "Result"; + pub const BOX: &'static str = "Box"; pub fn as_str(&self) -> &str { self.0.as_str() @@ -203,6 +202,12 @@ impl<'a> From<&'a str> for Lowercase { } } +impl<'a> From<&'a Lowercase> for &'a str { + fn from(lowercase: &'a Lowercase) -> Self { + lowercase.as_str() + } +} + impl<'a> From for Lowercase { fn from(string: String) -> Self { Self(string.into()) diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index ef888bc878..36f0f0c77b 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -66,6 +66,7 @@ pub enum LowLevel { DictDifference, DictWalk, SetFromList, + SetToDict, NumAdd, NumAddWrap, NumAddChecked, @@ -243,22 +244,22 @@ impl LowLevelWrapperType { Symbol::LIST_JOIN => CanBeReplacedBy(ListJoin), Symbol::LIST_RANGE => CanBeReplacedBy(ListRange), Symbol::LIST_MAP => WrapperIsRequired, - Symbol::LIST_MAP2 => CanBeReplacedBy(ListMap2), - Symbol::LIST_MAP3 => CanBeReplacedBy(ListMap3), - Symbol::LIST_MAP4 => CanBeReplacedBy(ListMap4), - Symbol::LIST_MAP_WITH_INDEX => CanBeReplacedBy(ListMapWithIndex), - Symbol::LIST_KEEP_IF => CanBeReplacedBy(ListKeepIf), - Symbol::LIST_WALK => CanBeReplacedBy(ListWalk), - Symbol::LIST_WALK_UNTIL => CanBeReplacedBy(ListWalkUntil), - Symbol::LIST_WALK_BACKWARDS => CanBeReplacedBy(ListWalkBackwards), - Symbol::LIST_KEEP_OKS => CanBeReplacedBy(ListKeepOks), - Symbol::LIST_KEEP_ERRS => CanBeReplacedBy(ListKeepErrs), - Symbol::LIST_SORT_WITH => CanBeReplacedBy(ListSortWith), + Symbol::LIST_MAP2 => WrapperIsRequired, + Symbol::LIST_MAP3 => WrapperIsRequired, + Symbol::LIST_MAP4 => WrapperIsRequired, + Symbol::LIST_MAP_WITH_INDEX => WrapperIsRequired, + Symbol::LIST_KEEP_IF => WrapperIsRequired, + Symbol::LIST_WALK => WrapperIsRequired, + Symbol::LIST_WALK_UNTIL => WrapperIsRequired, + Symbol::LIST_WALK_BACKWARDS => WrapperIsRequired, + Symbol::LIST_KEEP_OKS => WrapperIsRequired, + Symbol::LIST_KEEP_ERRS => WrapperIsRequired, + Symbol::LIST_SORT_WITH => WrapperIsRequired, Symbol::LIST_SUBLIST => CanBeReplacedBy(ListSublist), Symbol::LIST_DROP_AT => CanBeReplacedBy(ListDropAt), Symbol::LIST_SWAP => CanBeReplacedBy(ListSwap), - Symbol::LIST_ANY => CanBeReplacedBy(ListAny), - Symbol::LIST_ALL => CanBeReplacedBy(ListAll), + Symbol::LIST_ANY => WrapperIsRequired, + Symbol::LIST_ALL => WrapperIsRequired, Symbol::LIST_FIND => WrapperIsRequired, Symbol::DICT_LEN => CanBeReplacedBy(DictSize), Symbol::DICT_EMPTY => CanBeReplacedBy(DictEmpty), @@ -271,7 +272,7 @@ impl LowLevelWrapperType { Symbol::DICT_UNION => CanBeReplacedBy(DictUnion), Symbol::DICT_INTERSECTION => CanBeReplacedBy(DictIntersection), Symbol::DICT_DIFFERENCE => CanBeReplacedBy(DictDifference), - Symbol::DICT_WALK => CanBeReplacedBy(DictWalk), + Symbol::DICT_WALK => WrapperIsRequired, Symbol::SET_FROM_LIST => CanBeReplacedBy(SetFromList), Symbol::NUM_ADD => CanBeReplacedBy(NumAdd), Symbol::NUM_ADD_WRAP => CanBeReplacedBy(NumAddWrap), @@ -293,14 +294,17 @@ impl LowLevelWrapperType { Symbol::NUM_DIV_FLOAT_CHECKED => WrapperIsRequired, Symbol::NUM_DIV_CEIL => CanBeReplacedBy(NumDivCeilUnchecked), Symbol::NUM_DIV_CEIL_CHECKED => WrapperIsRequired, - Symbol::NUM_REM => WrapperIsRequired, + Symbol::NUM_REM => CanBeReplacedBy(NumRemUnchecked), + Symbol::NUM_REM_CHECKED => WrapperIsRequired, Symbol::NUM_IS_MULTIPLE_OF => CanBeReplacedBy(NumIsMultipleOf), Symbol::NUM_ABS => CanBeReplacedBy(NumAbs), Symbol::NUM_NEG => CanBeReplacedBy(NumNeg), Symbol::NUM_SIN => CanBeReplacedBy(NumSin), Symbol::NUM_COS => CanBeReplacedBy(NumCos), - Symbol::NUM_SQRT => WrapperIsRequired, - Symbol::NUM_LOG => WrapperIsRequired, + Symbol::NUM_SQRT => CanBeReplacedBy(NumSqrtUnchecked), + Symbol::NUM_SQRT_CHECKED => WrapperIsRequired, + Symbol::NUM_LOG => CanBeReplacedBy(NumLogUnchecked), + Symbol::NUM_LOG_CHECKED => WrapperIsRequired, Symbol::NUM_ROUND => CanBeReplacedBy(NumRound), Symbol::NUM_TO_FLOAT => CanBeReplacedBy(NumToFloat), Symbol::NUM_POW => CanBeReplacedBy(NumPow), diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 5be160e1d8..462d44a8e4 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -1,6 +1,6 @@ use crate::ident::{Ident, ModuleName}; use crate::module_err::{IdentIdNotFound, ModuleIdNotFound, ModuleResult}; -use roc_collections::all::{default_hasher, MutMap, SendMap}; +use roc_collections::{default_hasher, MutMap, SendMap, SmallStringInterner, VecMap}; use roc_ident::IdentStr; use roc_region::all::Region; use snafu::OptionExt; @@ -68,10 +68,6 @@ impl Symbol { } pub fn as_str(self, interns: &Interns) -> &str { - self.ident_str(interns).as_str() - } - - pub fn ident_str(self, interns: &Interns) -> &IdentStr { let ident_ids = interns .all_ident_ids .get(&self.module_id()) @@ -83,16 +79,13 @@ impl Symbol { ) }); - ident_ids - .get_name(self.ident_id()) - .unwrap_or_else(|| { - panic!( - "ident_string's IdentIds did not contain an entry for {} in module {:?}", - self.ident_id().0, - self.module_id() - ) - }) - .into() + ident_ids.get_name(self.ident_id()).unwrap_or_else(|| { + panic!( + "ident_string's IdentIds did not contain an entry for {} in module {:?}", + self.ident_id().0, + self.module_id() + ) + }) } pub fn as_u64(self) -> u64 { @@ -103,13 +96,13 @@ impl Symbol { let module_id = self.module_id(); if module_id == home { - self.ident_str(interns).clone().into() + ModuleName::from(self.as_str(interns)) } else { // TODO do this without format! to avoid allocation for short strings format!( "{}.{}", self.module_string(interns).as_str(), - self.ident_str(interns) + self.as_str(interns) ) .into() } @@ -214,7 +207,7 @@ lazy_static! { #[derive(Debug, Default)] pub struct Interns { pub module_ids: ModuleIds, - pub all_ident_ids: MutMap, + pub all_ident_ids: IdentIdsByModule, } impl Interns { @@ -256,7 +249,7 @@ impl Interns { } pub fn get_module_ident_ids<'a>( - all_ident_ids: &'a MutMap, + all_ident_ids: &'a IdentIdsByModule, module_id: &ModuleId, ) -> ModuleResult<&'a IdentIds> { all_ident_ids @@ -268,7 +261,7 @@ pub fn get_module_ident_ids<'a>( } pub fn get_module_ident_ids_mut<'a>( - all_ident_ids: &'a mut MutMap, + all_ident_ids: &'a mut IdentIdsByModule, module_id: &ModuleId, ) -> ModuleResult<&'a mut IdentIds> { all_ident_ids @@ -529,89 +522,43 @@ impl ModuleIds { #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct IdentId(u32); -/// Stores a mapping between IdentId and InlinableString. -/// -/// Each module name is stored twice, for faster lookups. -/// Since these are interned strings, this shouldn't result in many total allocations in practice. +impl IdentId { + pub const fn index(self) -> usize { + self.0 as usize + } +} + +/// Stores a mapping between Ident and IdentId. #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct IdentIds { - /// Each IdentId is an index into this Vec - by_id: Vec, - - next_generated_name: u32, + interner: SmallStringInterner, } impl IdentIds { - pub fn idents(&self) -> impl Iterator { - self.by_id + pub fn ident_strs(&self) -> impl Iterator { + self.interner .iter() .enumerate() .map(|(index, ident)| (IdentId(index as u32), ident)) } - pub fn add(&mut self, ident_name: Ident) -> IdentId { - let by_id = &mut self.by_id; - let ident_id = IdentId(by_id.len() as u32); - - by_id.push(ident_name); - - ident_id + pub fn add_ident(&mut self, ident_name: &Ident) -> IdentId { + IdentId(self.interner.insert(ident_name.as_str()) as u32) } pub fn get_or_insert(&mut self, name: &Ident) -> IdentId { match self.get_id(name) { Some(id) => id, - None => { - let ident_id = IdentId(self.by_id.len() as u32); - - self.by_id.push(name.clone()); - - ident_id - } + None => self.add_ident(name), } } // necessary when the name of a value is changed in the editor // TODO fix when same ident_name is present multiple times, see issue #2548 - pub fn update_key( - &mut self, - old_ident_name: &str, - new_ident_name: &str, - ) -> Result { - let old_ident: Ident = old_ident_name.into(); - - let ident_id_ref_opt = self.get_id(&old_ident); - - match ident_id_ref_opt { - Some(ident_id_ref) => { - let ident_id = ident_id_ref; - - let by_id = &mut self.by_id; - let key_index_opt = by_id.iter().position(|x| *x == old_ident); - - if let Some(key_index) = key_index_opt { - if let Some(vec_elt) = by_id.get_mut(key_index) { - *vec_elt = new_ident_name.into(); - } else { - // we get the index from by_id - unreachable!() - } - - Ok(ident_id) - } else { - Err( - format!( - "Tried to find position of key {:?} in IdentIds.by_id but I could not find the key. IdentIds.by_id: {:?}", - old_ident_name, - self.by_id - ) - ) - } - } - None => Err(format!( - "Tried to update key in IdentIds ({:?}) but I could not find the key ({}).", - self.by_id, old_ident_name - )), + pub fn update_key(&mut self, old_name: &str, new_name: &str) -> Result { + match self.interner.find_and_update(old_name, new_name) { + Some(index) => Ok(IdentId(index as u32)), + None => Err(format!("The identifier {:?} is not in IdentIds", old_name)), } } @@ -623,49 +570,136 @@ impl IdentIds { /// This is used, for example, during canonicalization of an Expr::Closure /// to generate a unique symbol to refer to that closure. pub fn gen_unique(&mut self) -> IdentId { - use std::fmt::Write; - - let index: u32 = self.next_generated_name; - self.next_generated_name += 1; - - // "4294967296" is 10 characters - let mut buffer: arrayvec::ArrayString<10> = arrayvec::ArrayString::new(); - - write!(buffer, "{}", index).unwrap(); - let ident = Ident(IdentStr::from_str(buffer.as_str())); - - self.add(ident) + IdentId(self.interner.insert_index_str() as u32) } #[inline(always)] pub fn get_id(&self, ident_name: &Ident) -> Option { - for (id, ident) in self.idents() { - if ident_name == ident { - return Some(id); - } - } - - None + self.interner + .find_index(ident_name.as_str()) + .map(|i| IdentId(i as u32)) } - pub fn get_name(&self, id: IdentId) -> Option<&Ident> { - self.by_id.get(id.0 as usize) + pub fn get_name(&self, id: IdentId) -> Option<&str> { + self.interner.try_get(id.0 as usize) } pub fn get_name_str_res(&self, ident_id: IdentId) -> ModuleResult<&str> { - Ok(self - .get_name(ident_id) - .with_context(|| IdentIdNotFound { - ident_id, - ident_ids_str: format!("{:?}", self), - })? - .as_inline_str() - .as_str()) + self.get_name(ident_id).with_context(|| IdentIdNotFound { + ident_id, + ident_ids_str: format!("{:?}", self), + }) + } + + pub fn len(&self) -> usize { + self.interner.len() + } + + pub fn is_empty(&self) -> bool { + self.interner.is_empty() + } +} + +#[derive(Debug, Default)] +pub struct IdentIdsByModule(VecMap); + +impl IdentIdsByModule { + pub fn get_or_insert(&mut self, module_id: ModuleId) -> &mut IdentIds { + self.0.get_or_insert(module_id, IdentIds::default) + } + + pub fn get_mut(&mut self, key: &ModuleId) -> Option<&mut IdentIds> { + self.0.get_mut(key) + } + + pub fn get(&self, key: &ModuleId) -> Option<&IdentIds> { + self.0.get(key) + } + + pub fn insert(&mut self, key: ModuleId, value: IdentIds) -> Option { + self.0.insert(key, value) + } + + pub fn keys(&self) -> impl Iterator { + self.0.keys() + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() } } // BUILTINS +const fn offset_helper(mut array: [u32; N]) -> [u32; N] { + let mut sum = 0u32; + + let mut i = 0; + while i < N { + // In rust 1.60 change to: (array[i], sum) = (sum, sum + array[i]); + let temp = array[i]; + array[i] = sum; + sum += temp; + + i += 1; + } + + array +} + +const fn byte_slice_equality(a: &[u8], b: &[u8]) -> bool { + if a.len() != b.len() { + return false; + } + + let mut i = 0; + while i < a.len() { + if a[i] != b[i] { + return false; + } + + i += 1; + } + + true +} + +const fn find_duplicates(array: [&str; N]) -> Option<(usize, usize)> { + let mut i = 0; + while i < N { + let needle = array[i]; + let mut j = i + 1; + while j < N { + if byte_slice_equality(needle.as_bytes(), array[j].as_bytes()) { + return Some((i, j)); + } + + j += 1; + } + + i += 1; + } + + None +} + +const fn check_indices(array: [u32; N]) -> Option<(u32, usize)> { + let mut i = 0; + while i < N { + if array[i] as usize != i { + return Some((array[i], i)); + } + + i += 1; + } + + None +} + macro_rules! define_builtins { { $( @@ -678,52 +712,51 @@ macro_rules! define_builtins { num_modules: $total:literal } => { impl IdentIds { - pub fn exposed_builtins(extra_capacity: usize) -> MutMap { - let mut exposed_idents_by_module = HashMap::with_capacity_and_hasher(extra_capacity + $total, default_hasher()); + pub fn exposed_builtins(extra_capacity: usize) -> IdentIdsByModule { + let mut exposed_idents_by_module = VecMap::with_capacity(extra_capacity + $total); $( - debug_assert!(!exposed_idents_by_module.contains_key(&ModuleId($module_id)), "Error setting up Builtins: when setting up module {} {:?} - the module ID {} is already present in the map. Check the map for duplicate module IDs!", $module_id, $module_name, $module_id); + debug_assert!(!exposed_idents_by_module.contains_key(&ModuleId($module_id)), r"Error setting up Builtins: when setting up module {} {:?} - the module ID {} is already present in the map. Check the map for duplicate module IDs!", $module_id, $module_name, $module_id); - let mut by_id : Vec = Vec::new(); let ident_ids = { - $( - debug_assert!(by_id.len() == $ident_id, "Error setting up Builtins: when inserting {} …: {:?} into module {} …: {:?} - this entry was assigned an ID of {}, but based on insertion order, it should have had an ID of {} instead! To fix this, change it from {} …: {:?} to {} …: {:?} instead.", $ident_id, $ident_name, $module_id, $module_name, $ident_id, by_id.len(), $ident_id, $ident_name, by_id.len(), $ident_name); + const TOTAL : usize = [ $($ident_name),+ ].len(); + const NAMES : [ &str; TOTAL] = [ $($ident_name),+ ]; + const LENGTHS: [ u16; TOTAL] = [ $($ident_name.len() as u16),+ ]; + const OFFSETS: [ u32; TOTAL] = offset_helper([ $($ident_name.len() as u32),+ ]); + const BUFFER: &str = concat!($($ident_name),+); - by_id.push($ident_name.into()); - )+ + const LENGTH_CHECK: Option<(u32, usize)> = check_indices([ $($ident_id),+ ]); + const DUPLICATE_CHECK: Option<(usize, usize)> = find_duplicates(NAMES); - #[cfg(debug_assertions)] - { - let mut cloned = by_id.clone(); - let before = cloned.len(); - cloned.sort(); - cloned.dedup(); - let after = cloned.len(); - - - if before != after { - let mut duplicates : Vec<&Ident> = Vec::new(); - let mut temp : Vec<&Ident> = Vec::new(); - - for symbol in cloned.iter() { - if temp.contains(&&symbol) { - duplicates.push(symbol); - } - - temp.push(&symbol); - } - - - panic!("duplicate symbols in IdentIds for module {:?}: {:?}", $module_name, duplicates); - } - } - - IdentIds { - by_id, - next_generated_name: 0, + if cfg!(debug_assertions) { + match LENGTH_CHECK { + None => (), + Some((given, expected)) => panic!( + "Symbol {} : {} should have index {} based on the insertion order, try {} : {} instead", + given, NAMES[expected], expected, expected, NAMES[expected], + ), } }; + if cfg!(debug_assertions) { + match DUPLICATE_CHECK { + None => (), + Some((first, second)) => panic!( + "Symbol {} : {} is duplicated at position {}, try removing the duplicate", + first, NAMES[first], second + ), + } + }; + + let interner = SmallStringInterner::from_parts ( + BUFFER.as_bytes().to_vec(), + LENGTHS.to_vec(), + OFFSETS.to_vec(), + ); + + IdentIds{ interner } + }; + if cfg!(debug_assertions) { let module_id = ModuleId($module_id); @@ -732,6 +765,7 @@ macro_rules! define_builtins { module_id.register_debug_idents(&ident_ids); } + exposed_idents_by_module.insert( ModuleId($module_id), ident_ids @@ -740,7 +774,7 @@ macro_rules! define_builtins { debug_assert!(exposed_idents_by_module.len() == $total, "Error setting up Builtins: `total:` is set to the wrong amount. It was set to {} but {} modules were set up.", $total, exposed_idents_by_module.len()); - exposed_idents_by_module + IdentIdsByModule(exposed_idents_by_module) } } @@ -906,172 +940,151 @@ define_builtins! { 30 DEV_TMP5: "#dev_tmp5" } 1 NUM: "Num" => { - 0 NUM_NUM: "Num" imported // the Num.Num type alias - 1 NUM_AT_NUM: "@Num" // the Num.@Num private tag - 2 NUM_I128: "I128" imported // the Num.I128 type alias - 3 NUM_U128: "U128" imported // the Num.U128 type alias - 4 NUM_I64: "I64" imported // the Num.I64 type alias - 5 NUM_U64: "U64" imported // the Num.U64 type alias - 6 NUM_I32: "I32" imported // the Num.I32 type alias - 7 NUM_U32: "U32" imported // the Num.U32 type alias - 8 NUM_I16: "I16" imported // the Num.I16 type alias - 9 NUM_U16: "U16" imported // the Num.U16 type alias - 10 NUM_I8: "I8" imported // the Num.I8 type alias - 11 NUM_U8: "U8" imported // the Num.U8 type alias - 12 NUM_INTEGER: "Integer" imported // Int : Num Integer - 13 NUM_AT_INTEGER: "@Integer" // the Int.@Integer private tag - 14 NUM_F64: "F64" imported // the Num.F64 type alias - 15 NUM_F32: "F32" imported // the Num.F32 type alias - 16 NUM_FLOATINGPOINT: "FloatingPoint" imported // Float : Num FloatingPoint - 17 NUM_AT_FLOATINGPOINT: "@FloatingPoint" // the Float.@FloatingPoint private tag - 18 NUM_MAX_FLOAT: "maxFloat" - 19 NUM_MIN_FLOAT: "minFloat" - 20 NUM_ABS: "abs" - 21 NUM_NEG: "neg" - 22 NUM_ADD: "add" - 23 NUM_SUB: "sub" - 24 NUM_MUL: "mul" - 25 NUM_LT: "isLt" - 26 NUM_LTE: "isLte" - 27 NUM_GT: "isGt" - 28 NUM_GTE: "isGte" - 29 NUM_TO_FLOAT: "toFloat" - 30 NUM_SIN: "sin" - 31 NUM_COS: "cos" - 32 NUM_TAN: "tan" - 33 NUM_IS_ZERO: "isZero" - 34 NUM_IS_EVEN: "isEven" - 35 NUM_IS_ODD: "isOdd" - 36 NUM_IS_POSITIVE: "isPositive" - 37 NUM_IS_NEGATIVE: "isNegative" - 38 NUM_REM: "rem" - 39 NUM_REM_CHECKED: "remChecked" - 40 NUM_DIV_FLOAT: "div" - 41 NUM_DIV_FLOAT_CHECKED: "divChecked" - 42 NUM_DIV_FLOOR: "divFloor" - 43 NUM_DIV_FLOOR_CHECKED: "divFloorChecked" - 44 NUM_MOD_INT: "modInt" - 45 NUM_MOD_INT_CHECKED: "modIntChecked" - 46 NUM_MOD_FLOAT: "modFloat" - 47 NUM_MOD_FLOAT_CHECKED: "modFloatChecked" - 48 NUM_SQRT: "sqrt" - 49 NUM_SQRT_CHECKED: "sqrtChecked" - 50 NUM_LOG: "log" - 51 NUM_LOG_CHECKED: "logChecked" - 52 NUM_ROUND: "round" - 53 NUM_COMPARE: "compare" - 54 NUM_POW: "pow" - 55 NUM_CEILING: "ceiling" - 56 NUM_POW_INT: "powInt" - 57 NUM_FLOOR: "floor" - 58 NUM_ADD_WRAP: "addWrap" - 59 NUM_ADD_CHECKED: "addChecked" - 60 NUM_ADD_SATURATED: "addSaturated" - 61 NUM_ATAN: "atan" - 62 NUM_ACOS: "acos" - 63 NUM_ASIN: "asin" - 64 NUM_AT_SIGNED128: "@Signed128" - 65 NUM_SIGNED128: "Signed128" imported - 66 NUM_AT_SIGNED64: "@Signed64" - 67 NUM_SIGNED64: "Signed64" imported - 68 NUM_AT_SIGNED32: "@Signed32" - 69 NUM_SIGNED32: "Signed32" imported - 70 NUM_AT_SIGNED16: "@Signed16" - 71 NUM_SIGNED16: "Signed16" imported - 72 NUM_AT_SIGNED8: "@Signed8" - 73 NUM_SIGNED8: "Signed8" imported - 74 NUM_AT_UNSIGNED128: "@Unsigned128" - 75 NUM_UNSIGNED128: "Unsigned128" imported - 76 NUM_AT_UNSIGNED64: "@Unsigned64" - 77 NUM_UNSIGNED64: "Unsigned64" imported - 78 NUM_AT_UNSIGNED32: "@Unsigned32" - 79 NUM_UNSIGNED32: "Unsigned32" imported - 80 NUM_AT_UNSIGNED16: "@Unsigned16" - 81 NUM_UNSIGNED16: "Unsigned16" imported - 82 NUM_AT_UNSIGNED8: "@Unsigned8" - 83 NUM_UNSIGNED8: "Unsigned8" imported - 84 NUM_AT_BINARY64: "@Binary64" - 85 NUM_BINARY64: "Binary64" imported - 86 NUM_AT_BINARY32: "@Binary32" - 87 NUM_BINARY32: "Binary32" imported - 88 NUM_BITWISE_AND: "bitwiseAnd" - 89 NUM_BITWISE_XOR: "bitwiseXor" - 90 NUM_BITWISE_OR: "bitwiseOr" - 91 NUM_SHIFT_LEFT: "shiftLeftBy" - 92 NUM_SHIFT_RIGHT: "shiftRightBy" - 93 NUM_SHIFT_RIGHT_ZERO_FILL: "shiftRightZfBy" - 94 NUM_SUB_WRAP: "subWrap" - 95 NUM_SUB_CHECKED: "subChecked" - 96 NUM_SUB_SATURATED: "subSaturated" - 97 NUM_MUL_WRAP: "mulWrap" - 98 NUM_MUL_CHECKED: "mulChecked" - 99 NUM_INT: "Int" imported - 100 NUM_FLOAT: "Float" imported - 101 NUM_AT_NATURAL: "@Natural" - 102 NUM_NATURAL: "Natural" imported - 103 NUM_NAT: "Nat" imported - 104 NUM_INT_CAST: "intCast" - 105 NUM_IS_MULTIPLE_OF: "isMultipleOf" - 106 NUM_AT_DECIMAL: "@Decimal" - 107 NUM_DECIMAL: "Decimal" imported - 108 NUM_DEC: "Dec" imported // the Num.Dectype alias - 109 NUM_BYTES_TO_U16: "bytesToU16" - 110 NUM_BYTES_TO_U32: "bytesToU32" - 111 NUM_CAST_TO_NAT: "#castToNat" - 112 NUM_DIV_CEIL: "divCeil" - 113 NUM_DIV_CEIL_CHECKED: "divCeilChecked" - 114 NUM_TO_STR: "toStr" - 115 NUM_MIN_I8: "minI8" - 116 NUM_MAX_I8: "maxI8" - 117 NUM_MIN_U8: "minU8" - 118 NUM_MAX_U8: "maxU8" - 119 NUM_MIN_I16: "minI16" - 120 NUM_MAX_I16: "maxI16" - 121 NUM_MIN_U16: "minU16" - 122 NUM_MAX_U16: "maxU16" - 123 NUM_MIN_I32: "minI32" - 124 NUM_MAX_I32: "maxI32" - 125 NUM_MIN_U32: "minU32" - 126 NUM_MAX_U32: "maxU32" - 127 NUM_MIN_I64: "minI64" - 128 NUM_MAX_I64: "maxI64" - 129 NUM_MIN_U64: "minU64" - 130 NUM_MAX_U64: "maxU64" - 131 NUM_MIN_I128: "minI128" - 132 NUM_MAX_I128: "maxI128" - 133 NUM_TO_I8: "toI8" - 134 NUM_TO_I8_CHECKED: "toI8Checked" - 135 NUM_TO_I16: "toI16" - 136 NUM_TO_I16_CHECKED: "toI16Checked" - 137 NUM_TO_I32: "toI32" - 138 NUM_TO_I32_CHECKED: "toI32Checked" - 139 NUM_TO_I64: "toI64" - 140 NUM_TO_I64_CHECKED: "toI64Checked" - 141 NUM_TO_I128: "toI128" - 142 NUM_TO_I128_CHECKED: "toI128Checked" - 143 NUM_TO_U8: "toU8" - 144 NUM_TO_U8_CHECKED: "toU8Checked" - 145 NUM_TO_U16: "toU16" - 146 NUM_TO_U16_CHECKED: "toU16Checked" - 147 NUM_TO_U32: "toU32" - 148 NUM_TO_U32_CHECKED: "toU32Checked" - 149 NUM_TO_U64: "toU64" - 150 NUM_TO_U64_CHECKED: "toU64Checked" - 151 NUM_TO_U128: "toU128" - 152 NUM_TO_U128_CHECKED: "toU128Checked" - 153 NUM_TO_NAT: "toNat" - 154 NUM_TO_NAT_CHECKED: "toNatChecked" - 155 NUM_TO_F32: "toF32" - 156 NUM_TO_F32_CHECKED: "toF32Checked" - 157 NUM_TO_F64: "toF64" - 158 NUM_TO_F64_CHECKED: "toF64Checked" + 0 NUM_NUM: "Num" // the Num.Num type alias + 1 NUM_I128: "I128" // the Num.I128 type alias + 2 NUM_U128: "U128" // the Num.U128 type alias + 3 NUM_I64: "I64" // the Num.I64 type alias + 4 NUM_U64: "U64" // the Num.U64 type alias + 5 NUM_I32: "I32" // the Num.I32 type alias + 6 NUM_U32: "U32" // the Num.U32 type alias + 7 NUM_I16: "I16" // the Num.I16 type alias + 8 NUM_U16: "U16" // the Num.U16 type alias + 9 NUM_I8: "I8" // the Num.I8 type alias + 10 NUM_U8: "U8" // the Num.U8 type alias + 11 NUM_INTEGER: "Integer" // Int : Num Integer + 12 NUM_F64: "F64" // the Num.F64 type alias + 13 NUM_F32: "F32" // the Num.F32 type alias + 14 NUM_FLOATINGPOINT: "FloatingPoint" // Float : Num FloatingPoint + 15 NUM_MAX_FLOAT: "maxFloat" + 16 NUM_MIN_FLOAT: "minFloat" + 17 NUM_ABS: "abs" + 18 NUM_NEG: "neg" + 19 NUM_ADD: "add" + 20 NUM_SUB: "sub" + 21 NUM_MUL: "mul" + 22 NUM_LT: "isLt" + 23 NUM_LTE: "isLte" + 24 NUM_GT: "isGt" + 25 NUM_GTE: "isGte" + 26 NUM_TO_FLOAT: "toFloat" + 27 NUM_SIN: "sin" + 28 NUM_COS: "cos" + 29 NUM_TAN: "tan" + 30 NUM_IS_ZERO: "isZero" + 31 NUM_IS_EVEN: "isEven" + 32 NUM_IS_ODD: "isOdd" + 33 NUM_IS_POSITIVE: "isPositive" + 34 NUM_IS_NEGATIVE: "isNegative" + 35 NUM_REM: "rem" + 36 NUM_REM_CHECKED: "remChecked" + 37 NUM_DIV_FLOAT: "div" + 38 NUM_DIV_FLOAT_CHECKED: "divChecked" + 39 NUM_DIV_TRUNC: "divTrunc" + 40 NUM_DIV_TRUNC_CHECKED: "divTruncChecked" + 41 NUM_SQRT: "sqrt" + 42 NUM_SQRT_CHECKED: "sqrtChecked" + 43 NUM_LOG: "log" + 44 NUM_LOG_CHECKED: "logChecked" + 45 NUM_ROUND: "round" + 46 NUM_COMPARE: "compare" + 47 NUM_POW: "pow" + 48 NUM_CEILING: "ceiling" + 49 NUM_POW_INT: "powInt" + 50 NUM_FLOOR: "floor" + 51 NUM_ADD_WRAP: "addWrap" + 52 NUM_ADD_CHECKED: "addChecked" + 53 NUM_ADD_SATURATED: "addSaturated" + 54 NUM_ATAN: "atan" + 55 NUM_ACOS: "acos" + 56 NUM_ASIN: "asin" + 57 NUM_SIGNED128: "Signed128" + 58 NUM_SIGNED64: "Signed64" + 59 NUM_SIGNED32: "Signed32" + 60 NUM_SIGNED16: "Signed16" + 61 NUM_SIGNED8: "Signed8" + 62 NUM_UNSIGNED128: "Unsigned128" + 63 NUM_UNSIGNED64: "Unsigned64" + 64 NUM_UNSIGNED32: "Unsigned32" + 65 NUM_UNSIGNED16: "Unsigned16" + 66 NUM_UNSIGNED8: "Unsigned8" + 67 NUM_BINARY64: "Binary64" + 68 NUM_BINARY32: "Binary32" + 69 NUM_BITWISE_AND: "bitwiseAnd" + 70 NUM_BITWISE_XOR: "bitwiseXor" + 71 NUM_BITWISE_OR: "bitwiseOr" + 72 NUM_SHIFT_LEFT: "shiftLeftBy" + 73 NUM_SHIFT_RIGHT: "shiftRightBy" + 74 NUM_SHIFT_RIGHT_ZERO_FILL: "shiftRightZfBy" + 75 NUM_SUB_WRAP: "subWrap" + 76 NUM_SUB_CHECKED: "subChecked" + 77 NUM_SUB_SATURATED: "subSaturated" + 78 NUM_MUL_WRAP: "mulWrap" + 79 NUM_MUL_CHECKED: "mulChecked" + 80 NUM_INT: "Int" + 81 NUM_FLOAT: "Float" + 82 NUM_NATURAL: "Natural" + 83 NUM_NAT: "Nat" + 84 NUM_INT_CAST: "intCast" + 85 NUM_IS_MULTIPLE_OF: "isMultipleOf" + 86 NUM_DECIMAL: "Decimal" + 87 NUM_DEC: "Dec" // the Num.Dectype alias + 88 NUM_BYTES_TO_U16: "bytesToU16" + 89 NUM_BYTES_TO_U32: "bytesToU32" + 90 NUM_CAST_TO_NAT: "#castToNat" + 91 NUM_DIV_CEIL: "divCeil" + 92 NUM_DIV_CEIL_CHECKED: "divCeilChecked" + 93 NUM_TO_STR: "toStr" + 94 NUM_MIN_I8: "minI8" + 95 NUM_MAX_I8: "maxI8" + 96 NUM_MIN_U8: "minU8" + 97 NUM_MAX_U8: "maxU8" + 98 NUM_MIN_I16: "minI16" + 99 NUM_MAX_I16: "maxI16" + 100 NUM_MIN_U16: "minU16" + 101 NUM_MAX_U16: "maxU16" + 102 NUM_MIN_I32: "minI32" + 103 NUM_MAX_I32: "maxI32" + 104 NUM_MIN_U32: "minU32" + 105 NUM_MAX_U32: "maxU32" + 106 NUM_MIN_I64: "minI64" + 107 NUM_MAX_I64: "maxI64" + 108 NUM_MIN_U64: "minU64" + 109 NUM_MAX_U64: "maxU64" + 110 NUM_MIN_I128: "minI128" + 111 NUM_MAX_I128: "maxI128" + 112 NUM_TO_I8: "toI8" + 113 NUM_TO_I8_CHECKED: "toI8Checked" + 114 NUM_TO_I16: "toI16" + 115 NUM_TO_I16_CHECKED: "toI16Checked" + 116 NUM_TO_I32: "toI32" + 117 NUM_TO_I32_CHECKED: "toI32Checked" + 118 NUM_TO_I64: "toI64" + 119 NUM_TO_I64_CHECKED: "toI64Checked" + 120 NUM_TO_I128: "toI128" + 121 NUM_TO_I128_CHECKED: "toI128Checked" + 122 NUM_TO_U8: "toU8" + 123 NUM_TO_U8_CHECKED: "toU8Checked" + 124 NUM_TO_U16: "toU16" + 125 NUM_TO_U16_CHECKED: "toU16Checked" + 126 NUM_TO_U32: "toU32" + 127 NUM_TO_U32_CHECKED: "toU32Checked" + 128 NUM_TO_U64: "toU64" + 129 NUM_TO_U64_CHECKED: "toU64Checked" + 130 NUM_TO_U128: "toU128" + 131 NUM_TO_U128_CHECKED: "toU128Checked" + 132 NUM_TO_NAT: "toNat" + 133 NUM_TO_NAT_CHECKED: "toNatChecked" + 134 NUM_TO_F32: "toF32" + 135 NUM_TO_F32_CHECKED: "toF32Checked" + 136 NUM_TO_F64: "toF64" + 137 NUM_TO_F64_CHECKED: "toF64Checked" } 2 BOOL: "Bool" => { - 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias + 0 BOOL_BOOL: "Bool" // the Bool.Bool type alias 1 BOOL_FALSE: "False" imported // Bool.Bool = [ False, True ] - // NB: not strictly needed; used for finding global tag names in error suggestions + // NB: not strictly needed; used for finding tag names in error suggestions 2 BOOL_TRUE: "True" imported // Bool.Bool = [ False, True ] - // NB: not strictly needed; used for finding global tag names in error suggestions + // NB: not strictly needed; used for finding tag names in error suggestions 3 BOOL_AND: "and" 4 BOOL_OR: "or" 5 BOOL_NOT: "not" @@ -1081,108 +1094,106 @@ define_builtins! { } 3 STR: "Str" => { 0 STR_STR: "Str" imported // the Str.Str type alias - 1 STR_AT_STR: "@Str" // the Str.@Str private tag - 2 STR_IS_EMPTY: "isEmpty" - 3 STR_APPEND: "append" - 4 STR_CONCAT: "concat" - 5 STR_JOIN_WITH: "joinWith" - 6 STR_SPLIT: "split" - 7 STR_COUNT_GRAPHEMES: "countGraphemes" - 8 STR_STARTS_WITH: "startsWith" - 9 STR_ENDS_WITH: "endsWith" - 10 STR_FROM_UTF8: "fromUtf8" - 11 STR_UT8_PROBLEM: "Utf8Problem" // the Utf8Problem type alias - 12 STR_UT8_BYTE_PROBLEM: "Utf8ByteProblem" // the Utf8ByteProblem type alias - 13 STR_TO_UTF8: "toUtf8" - 14 STR_STARTS_WITH_CODE_PT: "startsWithCodePt" - 15 STR_ALIAS_ANALYSIS_STATIC: "#aliasAnalysisStatic" // string with the static lifetime - 16 STR_FROM_UTF8_RANGE: "fromUtf8Range" - 17 STR_REPEAT: "repeat" - 18 STR_TRIM: "trim" - 19 STR_TRIM_LEFT: "trimLeft" - 20 STR_TRIM_RIGHT: "trimRight" - 21 STR_TO_DEC: "toDec" - 22 STR_TO_F64: "toF64" - 23 STR_TO_F32: "toF32" - 24 STR_TO_NAT: "toNat" - 25 STR_TO_U128: "toU128" - 26 STR_TO_I128: "toI128" - 27 STR_TO_U64: "toU64" - 28 STR_TO_I64: "toI64" - 29 STR_TO_U32: "toU32" - 30 STR_TO_I32: "toI32" - 31 STR_TO_U16: "toU16" - 32 STR_TO_I16: "toI16" - 33 STR_TO_U8: "toU8" - 34 STR_TO_I8: "toI8" + 1 STR_IS_EMPTY: "isEmpty" + 2 STR_APPEND: "#append" // unused + 3 STR_CONCAT: "concat" + 4 STR_JOIN_WITH: "joinWith" + 5 STR_SPLIT: "split" + 6 STR_COUNT_GRAPHEMES: "countGraphemes" + 7 STR_STARTS_WITH: "startsWith" + 8 STR_ENDS_WITH: "endsWith" + 9 STR_FROM_UTF8: "fromUtf8" + 10 STR_UT8_PROBLEM: "Utf8Problem" // the Utf8Problem type alias + 11 STR_UT8_BYTE_PROBLEM: "Utf8ByteProblem" // the Utf8ByteProblem type alias + 12 STR_TO_UTF8: "toUtf8" + 13 STR_STARTS_WITH_CODE_PT: "startsWithCodePt" + 14 STR_ALIAS_ANALYSIS_STATIC: "#aliasAnalysisStatic" // string with the static lifetime + 15 STR_FROM_UTF8_RANGE: "fromUtf8Range" + 16 STR_REPEAT: "repeat" + 17 STR_TRIM: "trim" + 18 STR_TRIM_LEFT: "trimLeft" + 19 STR_TRIM_RIGHT: "trimRight" + 20 STR_TO_DEC: "toDec" + 21 STR_TO_F64: "toF64" + 22 STR_TO_F32: "toF32" + 23 STR_TO_NAT: "toNat" + 24 STR_TO_U128: "toU128" + 25 STR_TO_I128: "toI128" + 26 STR_TO_U64: "toU64" + 27 STR_TO_I64: "toI64" + 28 STR_TO_U32: "toU32" + 29 STR_TO_I32: "toI32" + 30 STR_TO_U16: "toU16" + 31 STR_TO_I16: "toI16" + 32 STR_TO_U8: "toU8" + 33 STR_TO_I8: "toI8" } 4 LIST: "List" => { 0 LIST_LIST: "List" imported // the List.List type alias - 1 LIST_AT_LIST: "@List" // the List.@List private tag - 2 LIST_IS_EMPTY: "isEmpty" - 3 LIST_GET: "get" - 4 LIST_SET: "set" - 5 LIST_APPEND: "append" - 6 LIST_MAP: "map" - 7 LIST_LEN: "len" - 8 LIST_WALK_BACKWARDS: "walkBackwards" - 9 LIST_CONCAT: "concat" - 10 LIST_FIRST: "first" - 11 LIST_SINGLE: "single" - 12 LIST_REPEAT: "repeat" - 13 LIST_REVERSE: "reverse" - 14 LIST_PREPEND: "prepend" - 15 LIST_JOIN: "join" - 16 LIST_KEEP_IF: "keepIf" - 17 LIST_CONTAINS: "contains" - 18 LIST_SUM: "sum" - 19 LIST_WALK: "walk" - 20 LIST_LAST: "last" - 21 LIST_KEEP_OKS: "keepOks" - 22 LIST_KEEP_ERRS: "keepErrs" - 23 LIST_MAP_WITH_INDEX: "mapWithIndex" - 24 LIST_MAP2: "map2" - 25 LIST_MAP3: "map3" - 26 LIST_PRODUCT: "product" - 27 LIST_WALK_UNTIL: "walkUntil" - 28 LIST_RANGE: "range" - 29 LIST_SORT_WITH: "sortWith" - 30 LIST_DROP: "drop" - 31 LIST_SWAP: "swap" - 32 LIST_DROP_AT: "dropAt" - 33 LIST_DROP_LAST: "dropLast" - 34 LIST_MIN: "min" - 35 LIST_MIN_LT: "#minlt" - 36 LIST_MAX: "max" - 37 LIST_MAX_GT: "#maxGt" - 38 LIST_MAP4: "map4" - 39 LIST_DROP_FIRST: "dropFirst" - 40 LIST_JOIN_MAP: "joinMap" - 41 LIST_JOIN_MAP_CONCAT: "#joinMapConcat" - 42 LIST_ANY: "any" - 43 LIST_TAKE_FIRST: "takeFirst" - 44 LIST_TAKE_LAST: "takeLast" - 45 LIST_FIND: "find" - 46 LIST_FIND_RESULT: "#find_result" // symbol used in the definition of List.find - 47 LIST_SUBLIST: "sublist" - 48 LIST_INTERSPERSE: "intersperse" - 49 LIST_INTERSPERSE_CLOS: "#intersperseClos" - 50 LIST_SPLIT: "split" - 51 LIST_SPLIT_CLOS: "#splitClos" - 52 LIST_ALL: "all" - 53 LIST_DROP_IF: "dropIf" - 54 LIST_DROP_IF_PREDICATE: "#dropIfPred" - 55 LIST_SORT_ASC: "sortAsc" - 56 LIST_SORT_DESC: "sortDesc" - 57 LIST_SORT_DESC_COMPARE: "#sortDescCompare" - 58 LIST_REPLACE: "replace" + 1 LIST_IS_EMPTY: "isEmpty" + 2 LIST_GET: "get" + 3 LIST_SET: "set" + 4 LIST_APPEND: "append" + 5 LIST_MAP: "map" + 6 LIST_LEN: "len" + 7 LIST_WALK_BACKWARDS: "walkBackwards" + 8 LIST_CONCAT: "concat" + 9 LIST_FIRST: "first" + 10 LIST_SINGLE: "single" + 11 LIST_REPEAT: "repeat" + 12 LIST_REVERSE: "reverse" + 13 LIST_PREPEND: "prepend" + 14 LIST_JOIN: "join" + 15 LIST_KEEP_IF: "keepIf" + 16 LIST_CONTAINS: "contains" + 17 LIST_SUM: "sum" + 18 LIST_WALK: "walk" + 19 LIST_LAST: "last" + 20 LIST_KEEP_OKS: "keepOks" + 21 LIST_KEEP_ERRS: "keepErrs" + 22 LIST_MAP_WITH_INDEX: "mapWithIndex" + 23 LIST_MAP2: "map2" + 24 LIST_MAP3: "map3" + 25 LIST_PRODUCT: "product" + 26 LIST_WALK_UNTIL: "walkUntil" + 27 LIST_RANGE: "range" + 28 LIST_SORT_WITH: "sortWith" + 29 LIST_DROP: "drop" + 30 LIST_SWAP: "swap" + 31 LIST_DROP_AT: "dropAt" + 32 LIST_DROP_LAST: "dropLast" + 33 LIST_MIN: "min" + 34 LIST_MIN_LT: "#minlt" + 35 LIST_MAX: "max" + 36 LIST_MAX_GT: "#maxGt" + 37 LIST_MAP4: "map4" + 38 LIST_DROP_FIRST: "dropFirst" + 39 LIST_JOIN_MAP: "joinMap" + 40 LIST_JOIN_MAP_CONCAT: "#joinMapConcat" + 41 LIST_ANY: "any" + 42 LIST_TAKE_FIRST: "takeFirst" + 43 LIST_TAKE_LAST: "takeLast" + 44 LIST_FIND: "find" + 45 LIST_FIND_RESULT: "#find_result" // symbol used in the definition of List.find + 46 LIST_SUBLIST: "sublist" + 47 LIST_INTERSPERSE: "intersperse" + 48 LIST_INTERSPERSE_CLOS: "#intersperseClos" + 49 LIST_SPLIT: "split" + 50 LIST_SPLIT_CLOS: "#splitClos" + 51 LIST_ALL: "all" + 52 LIST_DROP_IF: "dropIf" + 53 LIST_DROP_IF_PREDICATE: "#dropIfPred" + 54 LIST_SORT_ASC: "sortAsc" + 55 LIST_SORT_DESC: "sortDesc" + 56 LIST_SORT_DESC_COMPARE: "#sortDescCompare" + 57 LIST_REPLACE: "replace" } 5 RESULT: "Result" => { - 0 RESULT_RESULT: "Result" imported // the Result.Result type alias + 0 RESULT_RESULT: "Result" // the Result.Result type alias 1 RESULT_OK: "Ok" imported // Result.Result a e = [ Ok a, Err e ] - // NB: not strictly needed; used for finding global tag names in error suggestions + // NB: not strictly needed; used for finding tag names in error suggestions 2 RESULT_ERR: "Err" imported // Result.Result a e = [ Ok a, Err e ] - // NB: not strictly needed; used for finding global tag names in error suggestions + // NB: not strictly needed; used for finding tag names in error suggestions 3 RESULT_MAP: "map" 4 RESULT_MAP_ERR: "mapErr" 5 RESULT_WITH_DEFAULT: "withDefault" @@ -1192,40 +1203,39 @@ define_builtins! { } 6 DICT: "Dict" => { 0 DICT_DICT: "Dict" imported // the Dict.Dict type alias - 1 DICT_AT_DICT: "@Dict" // the Dict.@Dict private tag - 2 DICT_EMPTY: "empty" - 3 DICT_SINGLE: "single" - 4 DICT_GET: "get" - 5 DICT_GET_RESULT: "#get_result" // symbol used in the definition of Dict.get - 6 DICT_WALK: "walk" - 7 DICT_INSERT: "insert" - 8 DICT_LEN: "len" + 1 DICT_EMPTY: "empty" + 2 DICT_SINGLE: "single" + 3 DICT_GET: "get" + 4 DICT_GET_RESULT: "#get_result" // symbol used in the definition of Dict.get + 5 DICT_WALK: "walk" + 6 DICT_INSERT: "insert" + 7 DICT_LEN: "len" - 9 DICT_REMOVE: "remove" - 10 DICT_CONTAINS: "contains" - 11 DICT_KEYS: "keys" - 12 DICT_VALUES: "values" + 8 DICT_REMOVE: "remove" + 9 DICT_CONTAINS: "contains" + 10 DICT_KEYS: "keys" + 11 DICT_VALUES: "values" - 13 DICT_UNION: "union" - 14 DICT_INTERSECTION: "intersection" - 15 DICT_DIFFERENCE: "difference" + 12 DICT_UNION: "union" + 13 DICT_INTERSECTION: "intersection" + 14 DICT_DIFFERENCE: "difference" } 7 SET: "Set" => { 0 SET_SET: "Set" imported // the Set.Set type alias - 1 SET_AT_SET: "@Set" // the Set.@Set private tag - 2 SET_EMPTY: "empty" - 3 SET_SINGLE: "single" - 4 SET_LEN: "len" - 5 SET_INSERT: "insert" - 6 SET_REMOVE: "remove" - 7 SET_UNION: "union" - 8 SET_DIFFERENCE: "difference" - 9 SET_INTERSECTION: "intersection" - 10 SET_TO_LIST: "toList" - 11 SET_FROM_LIST: "fromList" - 12 SET_WALK: "walk" - 13 SET_WALK_USER_FUNCTION: "#walk_user_function" - 14 SET_CONTAINS: "contains" + 1 SET_EMPTY: "empty" + 2 SET_SINGLE: "single" + 3 SET_LEN: "len" + 4 SET_INSERT: "insert" + 5 SET_REMOVE: "remove" + 6 SET_UNION: "union" + 7 SET_DIFFERENCE: "difference" + 8 SET_INTERSECTION: "intersection" + 9 SET_TO_LIST: "toList" + 10 SET_FROM_LIST: "fromList" + 11 SET_WALK: "walk" + 12 SET_WALK_USER_FUNCTION: "#walk_user_function" + 13 SET_CONTAINS: "contains" + 14 SET_TO_DICT: "toDict" } 8 BOX: "Box" => { 0 BOX_BOX_TYPE: "Box" imported // the Box.Box opaque type diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index 7fd09d0bfc..a883797efb 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -1027,6 +1027,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { DictWalk => arena.alloc_slice_copy(&[owned, owned, function, closure_data]), SetFromList => arena.alloc_slice_copy(&[owned]), + SetToDict => arena.alloc_slice_copy(&[owned]), ExpectTrue => arena.alloc_slice_copy(&[irrelevant]), diff --git a/compiler/mono/src/code_gen_help/mod.rs b/compiler/mono/src/code_gen_help/mod.rs index 2ffdd2494d..68361fd267 100644 --- a/compiler/mono/src/code_gen_help/mod.rs +++ b/compiler/mono/src/code_gen_help/mod.rs @@ -25,7 +25,7 @@ const ARG_2: Symbol = Symbol::ARG_2; pub const REFCOUNT_MAX: usize = 0; #[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum HelperOp { +pub enum HelperOp { Inc, Dec, DecRef(JoinPointId), @@ -185,16 +185,16 @@ impl<'a> CodeGenHelp<'a> { /// Generate a refcount increment procedure, *without* a Call expression. /// *This method should be rarely used* - only when the proc is to be called from Zig. /// Otherwise you want to generate the Proc and the Call together, using another method. - /// This only supports the 'inc' operation, as it's the only real use case we have. - pub fn gen_refcount_inc_proc( + pub fn gen_refcount_proc( &mut self, ident_ids: &mut IdentIds, layout: Layout<'a>, + op: HelperOp, ) -> (Symbol, Vec<'a, (Symbol, ProcLayout<'a>)>) { let mut ctx = Context { new_linker_data: Vec::new_in(self.arena), recursive_union: None, - op: HelperOp::Inc, + op, }; let proc_name = self.find_or_create_proc(ident_ids, &mut ctx, layout); @@ -396,7 +396,7 @@ impl<'a> CodeGenHelp<'a> { } fn create_symbol(&self, ident_ids: &mut IdentIds, debug_name: &str) -> Symbol { - let ident_id = ident_ids.add(Ident::from(debug_name)); + let ident_id = ident_ids.add_ident(&Ident::from(debug_name)); Symbol::new(self.home, ident_id) } diff --git a/compiler/mono/src/code_gen_help/refcount.rs b/compiler/mono/src/code_gen_help/refcount.rs index ef46ad8564..176f875d41 100644 --- a/compiler/mono/src/code_gen_help/refcount.rs +++ b/compiler/mono/src/code_gen_help/refcount.rs @@ -107,7 +107,9 @@ pub fn refcount_generic<'a>( match layout { Layout::Builtin(Builtin::Int(_) | Builtin::Float(_) | Builtin::Bool | Builtin::Decimal) => { - unreachable!("Not refcounted: {:?}", layout) + // Generate a dummy function that immediately returns Unit + // Some higher-order Zig builtins *always* call an RC function on List elements. + rc_return_stmt(root, ident_ids, ctx) } Layout::Builtin(Builtin::Str) => refcount_str(root, ident_ids, ctx), Layout::Builtin(Builtin::List(elem_layout)) => { diff --git a/compiler/mono/src/decision_tree.rs b/compiler/mono/src/decision_tree.rs index 2d9718c417..8e3f3816b8 100644 --- a/compiler/mono/src/decision_tree.rs +++ b/compiler/mono/src/decision_tree.rs @@ -4,7 +4,7 @@ use crate::ir::{ use crate::layout::{Builtin, Layout, LayoutCache, TagIdIntType, UnionLayout}; use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_collections::all::{MutMap, MutSet}; -use roc_exhaustive::{Ctor, RenderAs, TagId, Union}; +use roc_exhaustive::{Ctor, CtorName, RenderAs, TagId, Union}; use roc_module::ident::TagName; use roc_module::low_level::LowLevel; use roc_module::symbol::Symbol; @@ -82,7 +82,7 @@ enum GuardedTest<'a> { enum Test<'a> { IsCtor { tag_id: TagIdIntType, - tag_name: TagName, + ctor_name: CtorName, union: roc_exhaustive::Union, arguments: Vec<(Pattern<'a>, Layout<'a>)>, }, @@ -512,7 +512,7 @@ fn test_at_path<'a>( render_as: RenderAs::Tag, alternatives: vec![Ctor { tag_id: TagId(0), - name: TagName::Global(RECORD_TAG_NAME.into()), + name: CtorName::Tag(TagName::Tag(RECORD_TAG_NAME.into())), arity: destructs.len(), }], }; @@ -532,7 +532,7 @@ fn test_at_path<'a>( IsCtor { tag_id: 0, - tag_name: TagName::Global(RECORD_TAG_NAME.into()), + ctor_name: CtorName::Tag(TagName::Tag(RECORD_TAG_NAME.into())), union, arguments, } @@ -543,11 +543,12 @@ fn test_at_path<'a>( arguments, } => { let tag_id = 0; - let union = Union::newtype_wrapper(tag_name.clone(), arguments.len()); + let union = + Union::newtype_wrapper(CtorName::Tag(tag_name.clone()), arguments.len()); IsCtor { tag_id, - tag_name: tag_name.clone(), + ctor_name: CtorName::Tag(tag_name.clone()), union, arguments: arguments.to_vec(), } @@ -561,7 +562,7 @@ fn test_at_path<'a>( .. } => IsCtor { tag_id: *tag_id, - tag_name: tag_name.clone(), + ctor_name: CtorName::Tag(tag_name.clone()), union: union.clone(), arguments: arguments.to_vec(), }, @@ -571,14 +572,14 @@ fn test_at_path<'a>( render_as: RenderAs::Tag, alternatives: vec![Ctor { tag_id: TagId(0), - name: TagName::Private(*opaque), + name: CtorName::Opaque(*opaque), arity: 1, }], }; IsCtor { tag_id: 0, - tag_name: TagName::Private(*opaque), + ctor_name: CtorName::Opaque(*opaque), union, arguments: vec![(**argument).clone()], } @@ -680,11 +681,11 @@ fn to_relevant_branch_help<'a>( RecordDestructure(destructs, _) => match test { IsCtor { - tag_name: test_name, + ctor_name: test_name, tag_id, .. } => { - debug_assert!(test_name == &TagName::Global(RECORD_TAG_NAME.into())); + debug_assert!(test_name == &CtorName::Tag(TagName::Tag(RECORD_TAG_NAME.into()))); let sub_positions = destructs.into_iter().enumerate().map(|(index, destruct)| { let pattern = match destruct.typ { DestructType::Guard(guard) => guard.clone(), @@ -713,11 +714,11 @@ fn to_relevant_branch_help<'a>( OpaqueUnwrap { opaque, argument } => match test { IsCtor { - tag_name: test_opaque_tag_name, + ctor_name: test_opaque_tag_name, tag_id, .. } => { - debug_assert_eq!(test_opaque_tag_name, &TagName::Private(opaque)); + debug_assert_eq!(test_opaque_tag_name, &CtorName::Opaque(opaque)); let (argument, _) = *argument; @@ -744,10 +745,10 @@ fn to_relevant_branch_help<'a>( .. } => match test { IsCtor { - tag_name: test_name, + ctor_name: test_name, tag_id: test_id, .. - } if &tag_name == test_name => { + } if test_name.is_tag(&tag_name) => { let tag_id = 0; debug_assert_eq!(tag_id, *test_id); @@ -785,10 +786,10 @@ fn to_relevant_branch_help<'a>( } => { match test { IsCtor { - tag_name: test_name, + ctor_name: test_name, tag_id: test_id, .. - } if &tag_name == test_name => { + } if test_name.is_tag(&tag_name) => { debug_assert_eq!(tag_id, *test_id); // the test matches the constructor of this pattern diff --git a/compiler/mono/src/exhaustive.rs b/compiler/mono/src/exhaustive.rs index bfcb3b3e90..79b54d446e 100644 --- a/compiler/mono/src/exhaustive.rs +++ b/compiler/mono/src/exhaustive.rs @@ -1,7 +1,7 @@ use crate::ir::DestructType; use roc_collections::all::HumanIndex; use roc_exhaustive::{ - is_useful, Context, Ctor, Error, Guard, Literal, Pattern, RenderAs, TagId, Union, + is_useful, Context, Ctor, CtorName, Error, Guard, Literal, Pattern, RenderAs, TagId, Union, }; use roc_module::ident::{TagIdIntType, TagName}; use roc_region::all::{Loc, Region}; @@ -45,7 +45,7 @@ fn simplify(pattern: &crate::ir::Pattern) -> Pattern { let union = Union { render_as: RenderAs::Record(field_names), alternatives: vec![Ctor { - name: TagName::Global("#Record".into()), + name: CtorName::Tag(TagName::Tag("#Record".into())), tag_id, arity: destructures.len(), }], @@ -62,7 +62,7 @@ fn simplify(pattern: &crate::ir::Pattern) -> Pattern { let simplified_args: std::vec::Vec<_> = arguments.iter().map(|v| simplify(&v.0)).collect(); Ctor( - Union::newtype_wrapper(tag_name.clone(), arguments.len()), + Union::newtype_wrapper(CtorName::Tag(tag_name.clone()), arguments.len()), TagId(tag_id), simplified_args, ) @@ -87,7 +87,7 @@ fn simplify(pattern: &crate::ir::Pattern) -> Pattern { let union = Union { render_as: RenderAs::Opaque, alternatives: vec![Ctor { - name: TagName::Private(*opaque), + name: CtorName::Opaque(*opaque), tag_id, arity: 1, }], @@ -113,7 +113,7 @@ pub fn check( } } -pub fn check_patterns<'a>( +fn check_patterns<'a>( region: Region, context: Context, patterns: &[(Loc>, Guard)], @@ -169,7 +169,7 @@ fn to_nonredundant_rows( render_as: RenderAs::Guard, alternatives: vec![Ctor { tag_id, - name: TagName::Global("#Guard".into()), + name: CtorName::Tag(TagName::Tag("#Guard".into())), arity: 2, }], }; diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 8409b19b01..1b0875c14a 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -7,9 +7,10 @@ use crate::layout::{ use bumpalo::collections::Vec; use bumpalo::Bump; use roc_builtins::bitcode::{FloatWidth, IntWidth}; -use roc_can::expr::{ClosureData, IntValue}; +use roc_can::abilities::AbilitiesStore; +use roc_can::expr::{AnnotatedMark, ClosureData, IntValue}; use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap}; -use roc_exhaustive::{Ctor, Guard, RenderAs, TagId}; +use roc_exhaustive::{Ctor, CtorName, Guard, RenderAs, TagId}; use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::low_level::LowLevel; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; @@ -17,7 +18,10 @@ use roc_problem::can::{RuntimeError, ShadowKind}; use roc_region::all::{Loc, Region}; use roc_std::RocDec; use roc_target::TargetInfo; -use roc_types::subs::{Content, FlatType, StorageSubs, Subs, Variable, VariableSubsSlice}; +use roc_types::subs::{ + Content, ExhaustiveMark, FlatType, RedundantMark, StorageSubs, Subs, Variable, + VariableSubsSlice, +}; use std::collections::HashMap; use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder}; @@ -107,7 +111,7 @@ pub struct EntryPoint<'a> { #[derive(Clone, Copy, Debug)] pub struct PartialProcId(usize); -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct PartialProcs<'a> { /// maps a function name (symbol) to an index symbols: Vec<'a, Symbol>, @@ -189,7 +193,7 @@ impl<'a> PartialProcs<'a> { } } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct PartialProc<'a> { pub annotation: Variable, pub pattern_symbols: &'a [Symbol], @@ -203,9 +207,8 @@ impl<'a> PartialProc<'a> { #[allow(clippy::too_many_arguments)] pub fn from_named_function( env: &mut Env<'a, '_>, - layout_cache: &mut LayoutCache<'a>, annotation: Variable, - loc_args: std::vec::Vec<(Variable, Loc)>, + loc_args: std::vec::Vec<(Variable, AnnotatedMark, Loc)>, loc_body: Loc, captured_symbols: CapturedSymbols<'a>, is_self_recursive: bool, @@ -213,7 +216,7 @@ impl<'a> PartialProc<'a> { ) -> PartialProc<'a> { let number_of_arguments = loc_args.len(); - match patterns_to_when(env, layout_cache, loc_args, ret_var, loc_body) { + match patterns_to_when(env, loc_args, ret_var, loc_body) { Ok((_, pattern_symbols, body)) => { // a named closure. Since these aren't specialized by the surrounding // context, we can't add pending specializations for them yet. @@ -251,11 +254,22 @@ impl<'a> PartialProc<'a> { } #[derive(Clone, Debug)] -enum PartialExprLink { - Aliases(Symbol), +enum PolymorphicExpr { + /// A root ability member, which must be specialized at a call site, for example + /// "hash" which must be specialized to an exact symbol implementing "hash" for a type. + AbilityMember(Symbol), + /// A polymorphic expression we inline at the usage site. Expr(roc_can::expr::Expr, Variable), } +#[derive(Clone, Debug)] +enum PartialExprLink { + /// The root polymorphic expression + Sink(PolymorphicExpr), + /// A hop in a partial expression alias chain + Aliases(Symbol), +} + /// A table of symbols to polymorphic expressions. For example, in the program /// /// n = 1 @@ -281,8 +295,8 @@ impl PartialExprs { Self(BumpMap::new_in(arena)) } - fn insert(&mut self, symbol: Symbol, expr: roc_can::expr::Expr, expr_var: Variable) { - self.0.insert(symbol, PartialExprLink::Expr(expr, expr_var)); + fn insert(&mut self, symbol: Symbol, expr: PolymorphicExpr) { + self.0.insert(symbol, PartialExprLink::Sink(expr)); } fn insert_alias(&mut self, symbol: Symbol, aliases: Symbol) { @@ -293,7 +307,7 @@ impl PartialExprs { self.0.contains_key(&symbol) } - fn get(&mut self, mut symbol: Symbol) -> Option<(&roc_can::expr::Expr, Variable)> { + fn get(&mut self, mut symbol: Symbol) -> Option<&PolymorphicExpr> { // In practice the alias chain is very short loop { match self.0.get(&symbol) { @@ -303,8 +317,8 @@ impl PartialExprs { Some(&PartialExprLink::Aliases(real_symbol)) => { symbol = real_symbol; } - Some(PartialExprLink::Expr(expr, var)) => { - return Some((expr, *var)); + Some(PartialExprLink::Sink(expr)) => { + return Some(expr); } } } @@ -843,7 +857,7 @@ impl<'a> Procs<'a> { env: &mut Env<'a, '_>, symbol: Symbol, annotation: Variable, - loc_args: std::vec::Vec<(Variable, Loc)>, + loc_args: std::vec::Vec<(Variable, AnnotatedMark, Loc)>, loc_body: Loc, captured_symbols: CapturedSymbols<'a>, ret_var: Variable, @@ -862,7 +876,7 @@ impl<'a> Procs<'a> { _ => false, }; - match patterns_to_when(env, layout_cache, loc_args, ret_var, loc_body) { + match patterns_to_when(env, loc_args, ret_var, loc_body) { Ok((_, pattern_symbols, body)) => { // an anonymous closure. These will always be specialized already // by the surrounding context, so we can add pending specializations @@ -1119,6 +1133,7 @@ pub struct Env<'a, 'i> { pub target_info: TargetInfo, pub update_mode_ids: &'i mut UpdateModeIds, pub call_specialization_counter: u32, + pub abilities_store: &'i AbilitiesStore, } impl<'a, 'i> Env<'a, 'i> { @@ -1143,7 +1158,7 @@ impl<'a, 'i> Env<'a, 'i> { } pub fn is_imported_symbol(&self, symbol: Symbol) -> bool { - symbol.module_id() != self.home && !symbol.is_builtin() + symbol.module_id() != self.home } } @@ -1642,8 +1657,7 @@ impl<'a> Expr<'a> { .. } => { let doc_tag = match tag_name { - TagName::Global(s) => alloc.text(s.as_str()), - TagName::Private(s) => symbol_to_doc(alloc, *s), + TagName::Tag(s) => alloc.text(s.as_str()), TagName::Closure(s) => alloc .text("ClosureTag(") .append(symbol_to_doc(alloc, *s)) @@ -1664,8 +1678,7 @@ impl<'a> Expr<'a> { .. } => { let doc_tag = match tag_name { - TagName::Global(s) => alloc.text(s.as_str()), - TagName::Private(s) => alloc.text(format!("{}", s)), + TagName::Tag(s) => alloc.text(s.as_str()), TagName::Closure(s) => alloc .text("ClosureTag(") .append(symbol_to_doc(alloc, *s)) @@ -1924,8 +1937,7 @@ impl<'a> Stmt<'a> { #[allow(clippy::type_complexity)] fn patterns_to_when<'a>( env: &mut Env<'a, '_>, - layout_cache: &mut LayoutCache<'a>, - patterns: std::vec::Vec<(Variable, Loc)>, + patterns: std::vec::Vec<(Variable, AnnotatedMark, Loc)>, body_var: Variable, body: Loc, ) -> Result<(Vec<'a, Variable>, Vec<'a, Symbol>, Loc), Loc> { @@ -1940,66 +1952,26 @@ fn patterns_to_when<'a>( // NOTE this fails if the pattern contains rigid variables, // see https://github.com/rtfeldman/roc/issues/786 // this must be fixed when moving exhaustiveness checking to the new canonical AST - for (pattern_var, pattern) in patterns.into_iter() { - let context = roc_exhaustive::Context::BadArg; - let mono_pattern = match from_can_pattern(env, layout_cache, &pattern.value) { - Ok((pat, _assignments)) => { - // Don't apply any assignments (e.g. to initialize optional variables) yet. - // We'll take care of that later when expanding the new "when" branch. - pat - } - Err(runtime_error) => { - // Even if the body was Ok, replace it with this Err. - // If it was already an Err, leave it at that Err, so the first - // RuntimeError we encountered remains the first. - body = body.and({ - Err(Loc { - region: pattern.region, - value: runtime_error, - }) - }); + for (pattern_var, annotated_mark, pattern) in patterns.into_iter() { + if annotated_mark.exhaustive.is_non_exhaustive(env.subs) { + // Even if the body was Ok, replace it with this Err. + // If it was already an Err, leave it at that Err, so the first + // RuntimeError we encountered remains the first. + let value = RuntimeError::UnsupportedPattern(pattern.region); + body = body.and({ + Err(Loc { + region: pattern.region, + value, + }) + }); + } else if let Ok(unwrapped_body) = body { + let (new_symbol, new_body) = + pattern_to_when(env, pattern_var, pattern, body_var, unwrapped_body); - continue; - } - }; + symbols.push(new_symbol); + arg_vars.push(pattern_var); - match crate::exhaustive::check( - pattern.region, - &[( - Loc::at(pattern.region, mono_pattern), - roc_exhaustive::Guard::NoGuard, - )], - context, - ) { - Ok(_) => { - // Replace the body with a new one, but only if it was Ok. - if let Ok(unwrapped_body) = body { - let (new_symbol, new_body) = - pattern_to_when(env, pattern_var, pattern, body_var, unwrapped_body); - - symbols.push(new_symbol); - arg_vars.push(pattern_var); - - body = Ok(new_body) - } - } - Err(errors) => { - for error in errors { - env.problems.push(MonoProblem::PatternProblem(error)) - } - - let value = RuntimeError::UnsupportedPattern(pattern.region); - - // Even if the body was Ok, replace it with this Err. - // If it was already an Err, leave it at that Err, so the first - // RuntimeError we encountered remains the first. - body = body.and({ - Err(Loc { - region: pattern.region, - value, - }) - }); - } + body = Ok(new_body) } } @@ -2079,7 +2051,12 @@ fn pattern_to_when<'a>( patterns: vec![pattern], value: body, guard: None, + // If this type-checked, it's non-redundant + redundant: RedundantMark::known_non_redundant(), }], + branches_cond_var: pattern_var, + // If this type-checked, it's exhaustive + exhaustive: ExhaustiveMark::known_exhaustive(), }; (symbol, Loc::at_zero(wrapped_body)) @@ -3247,7 +3224,7 @@ pub fn with_hole<'a>( LetNonRec(def, cont, _) => { if let roc_can::pattern::Pattern::Identifier(symbol) = def.loc_pattern.value { if let Closure(closure_data) = def.loc_expr.value { - register_noncapturing_closure(env, procs, layout_cache, symbol, closure_data); + register_noncapturing_closure(env, procs, symbol, closure_data); return with_hole( env, @@ -3333,24 +3310,6 @@ pub fn with_hole<'a>( } }; - let context = roc_exhaustive::Context::BadDestruct; - match crate::exhaustive::check( - def.loc_pattern.region, - &[( - Loc::at(def.loc_pattern.region, mono_pattern.clone()), - roc_exhaustive::Guard::NoGuard, - )], - context, - ) { - Ok(_) => {} - Err(errors) => { - for error in errors { - env.problems.push(MonoProblem::PatternProblem(error)) - } - } // TODO make all variables bound in the pattern evaluate to a runtime error - // return Stmt::RuntimeError("TODO non-exhaustive pattern"); - } - let mut hole = hole; for (symbol, variable, expr) in assignments { @@ -3390,13 +3349,7 @@ pub fn with_hole<'a>( for def in defs.into_iter() { if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value { if let Closure(closure_data) = def.loc_expr.value { - register_noncapturing_closure( - env, - procs, - layout_cache, - *symbol, - closure_data, - ); + register_noncapturing_closure(env, procs, *symbol, closure_data); continue; } @@ -3486,15 +3439,24 @@ pub fn with_hole<'a>( OpaqueRef { argument, .. } => { let (arg_var, loc_arg_expr) = *argument; - with_hole( - env, - loc_arg_expr.value, - arg_var, - procs, - layout_cache, - assigned, - hole, - ) + + match can_reuse_symbol(env, procs, &loc_arg_expr.value) { + // Opaques decay to their argument. + ReuseSymbol::Value(real_name) => { + let mut result = hole.clone(); + substitute_in_exprs(arena, &mut result, assigned, real_name); + result + } + _ => with_hole( + env, + loc_arg_expr.value, + arg_var, + procs, + layout_cache, + assigned, + hole, + ), + } } Record { @@ -3730,9 +3692,11 @@ pub fn with_hole<'a>( When { cond_var, expr_var, - region, + region: _, loc_cond, branches, + branches_cond_var: _, + exhaustive, } => { let cond_symbol = possible_reuse_symbol(env, procs, &loc_cond.value); @@ -3742,9 +3706,9 @@ pub fn with_hole<'a>( env, cond_var, expr_var, - region, cond_symbol, branches, + exhaustive, layout_cache, procs, Some(id), @@ -4218,10 +4182,14 @@ pub fn with_hole<'a>( // a proc in this module, or an imported symbol procs.partial_procs.contains_key(key) || (env.is_imported_symbol(key) && !procs.is_imported_module_thunk(key)) + || env.abilities_store.is_ability_member_name(key) }; match loc_expr.value { roc_can::expr::Expr::Var(proc_name) if is_known(proc_name) => { + // This might be an ability member - if so, use the appropriate specialization. + let proc_name = get_specialization(env, fn_var, proc_name).unwrap_or(proc_name); + // a call by a known name call_by_name( env, @@ -4330,47 +4298,68 @@ pub fn with_hole<'a>( unreachable!("calling a non-closure layout") } }, - UnspecializedExpr(symbol) => match full_layout { - RawFunctionLayout::Function(arg_layouts, lambda_set, ret_layout) => { - let closure_data_symbol = env.unique_symbol(); + UnspecializedExpr(symbol) => match procs.partial_exprs.get(symbol).unwrap() + { + &PolymorphicExpr::AbilityMember(member) => { + let proc_name = get_specialization(env, fn_var, member).expect("Recorded as an ability member, but it doesn't have a specialization"); - result = match_on_lambda_set( + // a call by a known name + return call_by_name( env, - lambda_set, - closure_data_symbol, - arg_symbols, - arg_layouts, - ret_layout, + procs, + fn_var, + proc_name, + loc_args, + layout_cache, assigned, hole, ); - - let (lambda_expr, lambda_expr_var) = - procs.partial_exprs.get(symbol).unwrap(); - - let snapshot = env.subs.snapshot(); - let cache_snapshot = layout_cache.snapshot(); - let _unified = roc_unify::unify::unify( - env.subs, - fn_var, - lambda_expr_var, - roc_unify::unify::Mode::EQ, - ); - - result = with_hole( - env, - lambda_expr.clone(), - fn_var, - procs, - layout_cache, - closure_data_symbol, - env.arena.alloc(result), - ); - env.subs.rollback_to(snapshot); - layout_cache.rollback_to(cache_snapshot); } - RawFunctionLayout::ZeroArgumentThunk(_) => { - unreachable!("calling a non-closure layout") + PolymorphicExpr::Expr(lambda_expr, lambda_expr_var) => { + match full_layout { + RawFunctionLayout::Function( + arg_layouts, + lambda_set, + ret_layout, + ) => { + let closure_data_symbol = env.unique_symbol(); + + result = match_on_lambda_set( + env, + lambda_set, + closure_data_symbol, + arg_symbols, + arg_layouts, + ret_layout, + assigned, + hole, + ); + + let snapshot = env.subs.snapshot(); + let cache_snapshot = layout_cache.snapshot(); + let _unified = roc_unify::unify::unify( + env.subs, + fn_var, + *lambda_expr_var, + roc_unify::unify::Mode::EQ, + ); + + result = with_hole( + env, + lambda_expr.clone(), + fn_var, + procs, + layout_cache, + closure_data_symbol, + env.arena.alloc(result), + ); + env.subs.rollback_to(snapshot); + layout_cache.rollback_to(cache_snapshot); + } + RawFunctionLayout::ZeroArgumentThunk(_) => { + unreachable!("calling a non-closure layout") + } + } } }, NotASymbol => { @@ -4705,6 +4694,45 @@ pub fn with_hole<'a>( } } +#[inline(always)] +fn get_specialization<'a>( + env: &mut Env<'a, '_>, + symbol_var: Variable, + symbol: Symbol, +) -> Option { + use roc_solve::ability::type_implementing_member; + use roc_solve::solve::instantiate_rigids; + use roc_unify::unify::unify; + + match env.abilities_store.member_def(symbol) { + None => { + // This is not an ability member, it doesn't need specialization. + None + } + Some(member) => { + let snapshot = env.subs.snapshot(); + instantiate_rigids(env.subs, member.signature_var); + let (_, must_implement_ability) = unify( + env.subs, + symbol_var, + member.signature_var, + roc_unify::unify::Mode::EQ, + ) + .expect_success("This typechecked previously"); + env.subs.rollback_to(snapshot); + let specializing_type = + type_implementing_member(&must_implement_ability, member.parent_ability); + + let specialization = env + .abilities_store + .get_specialization(symbol, specializing_type) + .expect("No specialization is recorded - I thought there would only be a type error here."); + + Some(specialization.symbol) + } + } +} + #[allow(clippy::too_many_arguments)] fn construct_closure_data<'a>( env: &mut Env<'a, '_>, @@ -5090,7 +5118,7 @@ fn tag_union_to_function<'a>( let loc_expr = Loc::at_zero(roc_can::expr::Expr::Var(arg_symbol)); - loc_pattern_args.push((arg_var, loc_pattern)); + loc_pattern_args.push((arg_var, AnnotatedMark::known_exhaustive(), loc_pattern)); loc_expr_args.push((arg_var, loc_expr)); } @@ -5192,7 +5220,6 @@ fn sorted_field_symbols<'a>( fn register_noncapturing_closure<'a>( env: &mut Env<'a, '_>, procs: &mut Procs<'a>, - layout_cache: &mut LayoutCache<'a>, closure_name: Symbol, closure_data: ClosureData, ) { @@ -5220,7 +5247,6 @@ fn register_noncapturing_closure<'a>( let partial_proc = PartialProc::from_named_function( env, - layout_cache, function_type, arguments, loc_body, @@ -5304,7 +5330,6 @@ fn register_capturing_closure<'a>( let partial_proc = PartialProc::from_named_function( env, - layout_cache, function_type, arguments, loc_body, @@ -5356,9 +5381,11 @@ pub fn from_can<'a>( When { cond_var, expr_var, - region, + region: _, loc_cond, branches, + branches_cond_var: _, + exhaustive, } => { let cond_symbol = possible_reuse_symbol(env, procs, &loc_cond.value); @@ -5366,9 +5393,9 @@ pub fn from_can<'a>( env, cond_var, expr_var, - region, cond_symbol, branches, + exhaustive, layout_cache, procs, None, @@ -5494,7 +5521,6 @@ pub fn from_can<'a>( register_noncapturing_closure( env, procs, - layout_cache, *symbol, accessor_data.to_closure_data(fresh_record_symbol), ); @@ -5621,9 +5647,10 @@ pub fn from_can<'a>( // At the definition site `n = 1` we only know `1` to have the type `[Int *]`, // which won't be refined until the call `asU8 n`. Add it as a partial expression // that will be specialized at each concrete usage site. - procs - .partial_exprs - .insert(*symbol, def.loc_expr.value, def.expr_var); + procs.partial_exprs.insert( + *symbol, + PolymorphicExpr::Expr(def.loc_expr.value, def.expr_var), + ); let result = from_can(env, variable, cont.value, procs, layout_cache); @@ -5675,25 +5702,6 @@ pub fn from_can<'a>( hole, ) } else { - let context = roc_exhaustive::Context::BadDestruct; - match crate::exhaustive::check( - def.loc_pattern.region, - &[( - Loc::at(def.loc_pattern.region, mono_pattern.clone()), - roc_exhaustive::Guard::NoGuard, - )], - context, - ) { - Ok(_) => {} - Err(errors) => { - for error in errors { - env.problems.push(MonoProblem::PatternProblem(error)) - } - - return Stmt::RuntimeError("TODO non-exhaustive pattern"); - } - } - // convert the continuation let mut stmt = from_can(env, variable, cont.value, procs, layout_cache); @@ -5734,8 +5742,8 @@ pub fn from_can<'a>( fn to_opt_branches<'a>( env: &mut Env<'a, '_>, - region: Region, branches: std::vec::Vec, + exhaustive_mark: ExhaustiveMark, layout_cache: &mut LayoutCache<'a>, ) -> std::vec::Vec<( Pattern<'a>, @@ -5754,12 +5762,17 @@ fn to_opt_branches<'a>( Guard::NoGuard }; + if when_branch.redundant.is_redundant(env.subs) { + // Don't codegen this branch since it's redundant. + continue; + } + for loc_pattern in when_branch.patterns { match from_can_pattern(env, layout_cache, &loc_pattern.value) { Ok((mono_pattern, assignments)) => { loc_branches.push(( Loc::at(loc_pattern.region, mono_pattern.clone()), - exhaustive_guard.clone(), + exhaustive_guard, )); let mut loc_expr = when_branch.value.clone(); @@ -5789,7 +5802,7 @@ fn to_opt_branches<'a>( Err(runtime_error) => { loc_branches.push(( Loc::at(loc_pattern.region, Pattern::Underscore), - exhaustive_guard.clone(), + exhaustive_guard, )); // TODO remove clone? @@ -5803,45 +5816,14 @@ fn to_opt_branches<'a>( } } - // NOTE exhaustiveness is checked after the construction of all the branches - // In contrast to elm (currently), we still do codegen even if a pattern is non-exhaustive. - // So we not only report exhaustiveness errors, but also correct them - let context = roc_exhaustive::Context::BadCase; - match crate::exhaustive::check(region, &loc_branches, context) { - Ok(_) => {} - Err(errors) => { - use roc_exhaustive::Error::*; - let mut is_not_exhaustive = false; - let mut overlapping_branches = std::vec::Vec::new(); - - for error in errors { - match &error { - Incomplete(_, _, _) => { - is_not_exhaustive = true; - } - Redundant { index, .. } => { - overlapping_branches.push(index.to_zero_based()); - } - } - env.problems.push(MonoProblem::PatternProblem(error)) - } - - overlapping_branches.sort_unstable(); - - for i in overlapping_branches.into_iter().rev() { - opt_branches.remove(i); - } - - if is_not_exhaustive { - opt_branches.push(( - Pattern::Underscore, - None, - roc_can::expr::Expr::RuntimeError( - roc_problem::can::RuntimeError::NonExhaustivePattern, - ), - )); - } - } + if exhaustive_mark.is_non_exhaustive(env.subs) { + // In contrast to elm (currently), we still do codegen even if a pattern is non-exhaustive. + // So we not only report exhaustiveness errors, but also correct them + opt_branches.push(( + Pattern::Underscore, + None, + roc_can::expr::Expr::RuntimeError(roc_problem::can::RuntimeError::NonExhaustivePattern), + )); } opt_branches @@ -5852,9 +5834,9 @@ fn from_can_when<'a>( env: &mut Env<'a, '_>, cond_var: Variable, expr_var: Variable, - region: Region, cond_symbol: Symbol, branches: std::vec::Vec, + exhaustive_mark: ExhaustiveMark, layout_cache: &mut LayoutCache<'a>, procs: &mut Procs<'a>, join_point: Option, @@ -5864,7 +5846,7 @@ fn from_can_when<'a>( // We can't know what to return! return Stmt::RuntimeError("Hit a 0-branch when expression"); } - let opt_branches = to_opt_branches(env, region, branches, layout_cache); + let opt_branches = to_opt_branches(env, branches, exhaustive_mark, layout_cache); let cond_layout = return_on_layout_error!(env, layout_cache.from_var(env.arena, cond_var, env.subs)); @@ -6329,7 +6311,7 @@ fn store_pattern_help<'a>( match can_pat { Identifier(symbol) => { - if let Some((_, var)) = procs.partial_exprs.get(outer_symbol) { + if let Some(&PolymorphicExpr::Expr(_, var)) = procs.partial_exprs.get(outer_symbol) { // It might be the case that symbol we're storing hasn't been reified to a value // yet, if it's polymorphic. Do that now. stmt = specialize_symbol( @@ -6690,7 +6672,19 @@ fn can_reuse_symbol<'a>( if let roc_can::expr::Expr::Var(symbol) = expr { let symbol = *symbol; - if env.is_imported_symbol(symbol) { + let arguments = [ + Symbol::ARG_1, + Symbol::ARG_2, + Symbol::ARG_3, + Symbol::ARG_4, + Symbol::ARG_5, + Symbol::ARG_6, + Symbol::ARG_7, + ]; + + if arguments.contains(&symbol) { + Value(symbol) + } else if env.is_imported_symbol(symbol) { Imported(symbol) } else if procs.partial_procs.contains_key(symbol) { LocalFunction(symbol) @@ -6727,6 +6721,13 @@ fn handle_variable_aliasing<'a, BuildRest>( where BuildRest: FnOnce(&mut Env<'a, '_>, &mut Procs<'a>, &mut LayoutCache<'a>) -> Stmt<'a>, { + if env.abilities_store.is_ability_member_name(right) { + procs + .partial_exprs + .insert(left, PolymorphicExpr::AbilityMember(right)); + return build_rest(env, procs, layout_cache); + } + if procs.partial_exprs.contains(right) { // If `right` links to a partial expression, make sure we link `left` to it as well, so // that usages of it will be specialized when building the rest of the program. @@ -6804,7 +6805,7 @@ fn specialize_symbol<'a>( result: Stmt<'a>, original: Symbol, ) -> Stmt<'a> { - if let Some((expr, expr_var)) = procs.partial_exprs.get(original) { + if let Some(PolymorphicExpr::Expr(expr, expr_var)) = procs.partial_exprs.get(original) { // Specialize the expression type now, based off the `arg_var` we've been given. // TODO: cache the specialized result let snapshot = env.subs.snapshot(); @@ -6812,14 +6813,14 @@ fn specialize_symbol<'a>( let _unified = roc_unify::unify::unify( env.subs, arg_var.unwrap(), - expr_var, + *expr_var, roc_unify::unify::Mode::EQ, ); let result = with_hole( env, expr.clone(), - expr_var, + *expr_var, procs, layout_cache, symbol, @@ -6837,9 +6838,10 @@ fn specialize_symbol<'a>( None => { match arg_var { Some(arg_var) if env.is_imported_symbol(original) => { - let raw = layout_cache - .raw_from_var(env.arena, arg_var, env.subs) - .expect("creating layout does not fail"); + let raw = match layout_cache.raw_from_var(env.arena, arg_var, env.subs) { + Ok(v) => v, + Err(e) => return_on_layout_error_help!(env, e), + }; if procs.is_imported_module_thunk(original) { let layout = match raw { @@ -7315,6 +7317,7 @@ fn call_by_name_help<'a>( add_needed_external(procs, env, original_fn_var, proc_name); debug_assert_ne!(proc_name.module_id(), ModuleId::ATTR); + if procs.is_imported_module_thunk(proc_name) { force_thunk( env, @@ -7323,13 +7326,23 @@ fn call_by_name_help<'a>( assigned, hole, ) - } else { - debug_assert!( - !field_symbols.is_empty(), - "{} should be in the list of imported_module_thunks", - proc_name - ); + } else if field_symbols.is_empty() { + // this is a case like `Str.concat`, an imported standard function, applied to zero arguments + // imported symbols cannot capture anything + let captured = &[]; + + construct_closure_data( + env, + procs, + layout_cache, + lambda_set, + proc_name, + captured, + assigned, + hole, + ) + } else { debug_assert_eq!( argument_layouts.len(), field_symbols.len(), @@ -7484,8 +7497,6 @@ fn call_by_name_module_thunk<'a>( assigned: Symbol, hole: &'a Stmt<'a>, ) -> Stmt<'a> { - debug_assert!(!env.is_imported_symbol(proc_name)); - let top_level_layout = ProcLayout::new(env.arena, &[], *ret_layout); let inner_layout = *ret_layout; @@ -7923,7 +7934,7 @@ fn from_can_pattern_help<'a>( render_as: RenderAs::Tag, alternatives: vec![Ctor { tag_id: TagId(0), - name: tag_name.clone(), + name: CtorName::Tag(tag_name.clone()), arity: 0, }], }, @@ -7936,12 +7947,12 @@ fn from_can_pattern_help<'a>( alternatives: vec![ Ctor { tag_id: TagId(0), - name: ffalse, + name: CtorName::Tag(ffalse), arity: 0, }, Ctor { tag_id: TagId(1), - name: ttrue, + name: CtorName::Tag(ttrue), arity: 0, }, ], @@ -7957,7 +7968,7 @@ fn from_can_pattern_help<'a>( for (i, tag_name) in tag_names.into_iter().enumerate() { ctors.push(Ctor { tag_id: TagId(i as _), - name: tag_name, + name: CtorName::Tag(tag_name), arity: 0, }) } @@ -8048,7 +8059,7 @@ fn from_can_pattern_help<'a>( for (i, (tag_name, args)) in tags.iter().enumerate() { ctors.push(Ctor { tag_id: TagId(i as _), - name: tag_name.clone(), + name: CtorName::Tag(tag_name.clone()), arity: args.len(), }) } @@ -8099,7 +8110,7 @@ fn from_can_pattern_help<'a>( for (i, (tag_name, args)) in tags.iter().enumerate() { ctors.push(Ctor { tag_id: TagId(i as _), - name: tag_name.clone(), + name: CtorName::Tag(tag_name.clone()), // don't include tag discriminant in arity arity: args.len() - 1, }) @@ -8144,7 +8155,7 @@ fn from_can_pattern_help<'a>( ctors.push(Ctor { tag_id: TagId(0), - name: tag_name.clone(), + name: CtorName::Tag(tag_name.clone()), arity: fields.len(), }); @@ -8191,7 +8202,7 @@ fn from_can_pattern_help<'a>( if i == nullable_id as usize { ctors.push(Ctor { tag_id: TagId(i as _), - name: nullable_name.clone(), + name: CtorName::Tag(nullable_name.clone()), // don't include tag discriminant in arity arity: 0, }); @@ -8201,7 +8212,7 @@ fn from_can_pattern_help<'a>( ctors.push(Ctor { tag_id: TagId(i as _), - name: tag_name.clone(), + name: CtorName::Tag(tag_name.clone()), // don't include tag discriminant in arity arity: args.len() - 1, }); @@ -8212,7 +8223,7 @@ fn from_can_pattern_help<'a>( if i == nullable_id as usize { ctors.push(Ctor { tag_id: TagId(i as _), - name: nullable_name.clone(), + name: CtorName::Tag(nullable_name.clone()), // don't include tag discriminant in arity arity: 0, }); @@ -8262,13 +8273,13 @@ fn from_can_pattern_help<'a>( ctors.push(Ctor { tag_id: TagId(nullable_id as _), - name: nullable_name.clone(), + name: CtorName::Tag(nullable_name.clone()), arity: 0, }); ctors.push(Ctor { tag_id: TagId(!nullable_id as _), - name: nullable_name.clone(), + name: CtorName::Tag(nullable_name.clone()), // FIXME drop tag arity: other_fields.len() - 1, }); @@ -8524,9 +8535,9 @@ pub fn num_argument_to_int_or_float( num_argument_to_int_or_float(subs, target_info, var, true) } - Symbol::NUM_DECIMAL | Symbol::NUM_AT_DECIMAL => IntOrFloat::DecimalFloatType, + Symbol::NUM_DECIMAL => IntOrFloat::DecimalFloatType, - Symbol::NUM_NAT | Symbol::NUM_NATURAL | Symbol::NUM_AT_NATURAL => { + Symbol::NUM_NAT | Symbol::NUM_NATURAL => { let int_width = match target_info.ptr_width() { roc_target::PtrWidth::Bytes4 => IntWidth::U32, roc_target::PtrWidth::Bytes8 => IntWidth::U64, diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index eef312cab3..79e193e1e5 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -311,6 +311,50 @@ impl<'a> UnionLayout<'a> { .append(alloc.intersperse(tags_doc, ", ")) .append(alloc.text("]")) } + Recursive(tags) => { + let tags_doc = tags.iter().map(|fields| { + alloc.text("C ").append(alloc.intersperse( + fields.iter().map(|x| x.to_doc(alloc, Parens::InTypeParam)), + " ", + )) + }); + alloc + .text("[") + .append(alloc.intersperse(tags_doc, ", ")) + .append(alloc.text("]")) + } + NonNullableUnwrapped(fields) => { + let fields_doc = alloc.text("C ").append(alloc.intersperse( + fields.iter().map(|x| x.to_doc(alloc, Parens::InTypeParam)), + " ", + )); + alloc + .text("[") + .append(fields_doc) + .append(alloc.text("]")) + } + NullableUnwrapped { + nullable_id, + other_fields, + } => { + let fields_doc = alloc.text("C ").append( + alloc.intersperse( + other_fields + .iter() + .map(|x| x.to_doc(alloc, Parens::InTypeParam)), + " ", + ), + ); + let tags_doc = if nullable_id { + alloc.concat(vec![alloc.text(", "), fields_doc]) + } else { + alloc.concat(vec![fields_doc, alloc.text(", ")]) + }; + alloc + .text("[") + .append(tags_doc) + .append(alloc.text("]")) + } _ => alloc.text("TODO"), } } @@ -939,6 +983,16 @@ pub const fn round_up_to_alignment(width: u32, alignment: u32) -> u32 { } } +#[inline(always)] +pub fn is_unresolved_var(subs: &Subs, var: Variable) -> bool { + use Content::*; + let content = subs.get_content_without_compacting(var); + matches!( + content, + FlexVar(..) | RigidVar(..) | FlexAbleVar(..) | RigidAbleVar(..), + ) +} + impl<'a> Layout<'a> { pub const VOID: Self = Layout::Union(UnionLayout::NonRecursive(&[])); pub const UNIT: Self = Layout::Struct { @@ -971,12 +1025,24 @@ impl<'a> Layout<'a> { } match symbol { - Symbol::NUM_DECIMAL | Symbol::NUM_AT_DECIMAL => { - return Ok(Layout::Builtin(Builtin::Decimal)) + Symbol::NUM_DECIMAL => return Ok(Layout::Builtin(Builtin::Decimal)), + + Symbol::NUM_NAT | Symbol::NUM_NATURAL => { + return Ok(Layout::usize(env.target_info)) } - Symbol::NUM_NAT | Symbol::NUM_NATURAL | Symbol::NUM_AT_NATURAL => { - return Ok(Layout::usize(env.target_info)) + Symbol::NUM_NUM | Symbol::NUM_INT | Symbol::NUM_INTEGER + if is_unresolved_var(env.subs, actual_var) => + { + // default to i64 + return Ok(Layout::i64()); + } + + Symbol::NUM_FLOAT | Symbol::NUM_FLOATINGPOINT + if is_unresolved_var(env.subs, actual_var) => + { + // default to f64 + return Ok(Layout::f64()); } _ => Self::from_var(env, actual_var), @@ -1645,7 +1711,7 @@ fn layout_from_flat_type<'a>( Ok(Layout::f32()) } - Symbol::NUM_NUM | Symbol::NUM_AT_NUM => { + Symbol::NUM_NUM => { // Num.Num should only ever have 1 argument, e.g. Num.Num Int.Integer debug_assert_eq!(args.len(), 1); @@ -1731,7 +1797,7 @@ fn layout_from_flat_type<'a>( debug_assert!(ext_var_is_empty_tag_union(subs, ext_var)); - Ok(layout_from_tag_union(arena, &tags, subs, env.target_info)) + Ok(layout_from_tag_union(env, &tags)) } FunctionOrTagUnion(tag_name, _, ext_var) => { debug_assert!( @@ -1742,7 +1808,7 @@ fn layout_from_flat_type<'a>( let union_tags = UnionTags::from_tag_name_index(tag_name); let (tags, _) = union_tags.unsorted_tags_and_ext(subs, ext_var); - Ok(layout_from_tag_union(arena, &tags, subs, env.target_info)) + Ok(layout_from_tag_union(env, &tags)) } RecursiveTagUnion(rec_var, tags, ext_var) => { let (tags, ext_var) = tags.unsorted_tags_and_ext(subs, ext_var); @@ -2071,23 +2137,14 @@ fn is_recursive_tag_union(layout: &Layout) -> bool { } fn union_sorted_tags_help_new<'a>( - arena: &'a Bump, + env: &mut Env<'a, '_>, tags_list: &[(&'_ TagName, &[Variable])], opt_rec_var: Option, - subs: &Subs, - target_info: TargetInfo, ) -> UnionVariant<'a> { // sort up front; make sure the ordering stays intact! - let mut tags_list = Vec::from_iter_in(tags_list.iter(), arena); + let mut tags_list = Vec::from_iter_in(tags_list.iter(), env.arena); tags_list.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); - let mut env = Env { - arena, - subs, - seen: Vec::new_in(arena), - target_info, - }; - match tags_list.len() { 0 => { // trying to instantiate a type with no values @@ -2098,39 +2155,29 @@ fn union_sorted_tags_help_new<'a>( let tag_name = tag_name.clone(); // just one tag in the union (but with arguments) can be a struct - let mut layouts = Vec::with_capacity_in(tags_list.len(), arena); + let mut layouts = Vec::with_capacity_in(tags_list.len(), env.arena); - // special-case NUM_AT_NUM: if its argument is a FlexVar, make it Int - match tag_name { - TagName::Private(Symbol::NUM_AT_NUM) => { - let var = arguments[0]; - layouts - .push(unwrap_num_tag(subs, var, target_info).expect("invalid num layout")); - } - _ => { - for &var in arguments { - match Layout::from_var(&mut env, var) { - Ok(layout) => { - layouts.push(layout); - } - Err(LayoutProblem::UnresolvedTypeVar(_)) => { - // If we encounter an unbound type var (e.g. `Ok *`) - // then it's zero-sized; In the future we may drop this argument - // completely, but for now we represent it with the empty tag union - layouts.push(Layout::VOID) - } - Err(LayoutProblem::Erroneous) => { - // An erroneous type var will code gen to a runtime - // error, so we don't need to store any data for it. - } - } + for &var in arguments { + match Layout::from_var(env, var) { + Ok(layout) => { + layouts.push(layout); + } + Err(LayoutProblem::UnresolvedTypeVar(_)) => { + // If we encounter an unbound type var (e.g. `Ok *`) + // then it's zero-sized; In the future we may drop this argument + // completely, but for now we represent it with the empty tag union + layouts.push(Layout::VOID) + } + Err(LayoutProblem::Erroneous) => { + // An erroneous type var will code gen to a runtime + // error, so we don't need to store any data for it. } } } layouts.sort_by(|layout1, layout2| { - let size1 = layout1.alignment_bytes(target_info); - let size2 = layout2.alignment_bytes(target_info); + let size1 = layout1.alignment_bytes(env.target_info); + let size2 = layout2.alignment_bytes(env.target_info); size2.cmp(&size1) }); @@ -2151,7 +2198,7 @@ fn union_sorted_tags_help_new<'a>( } num_tags => { // default path - let mut answer = Vec::with_capacity_in(tags_list.len(), arena); + let mut answer = Vec::with_capacity_in(tags_list.len(), env.arena); let mut has_any_arguments = false; let mut nullable: Option<(TagIdIntType, TagName)> = None; @@ -2174,17 +2221,19 @@ fn union_sorted_tags_help_new<'a>( continue; } - let mut arg_layouts = Vec::with_capacity_in(arguments.len() + 1, arena); + let mut arg_layouts = Vec::with_capacity_in(arguments.len() + 1, env.arena); for &var in arguments { - match Layout::from_var(&mut env, var) { + match Layout::from_var(env, var) { Ok(layout) => { has_any_arguments = true; // make sure to not unroll recursive types! let self_recursion = opt_rec_var.is_some() - && subs.get_root_key_without_compacting(var) - == subs.get_root_key_without_compacting(opt_rec_var.unwrap()) + && env.subs.get_root_key_without_compacting(var) + == env + .subs + .get_root_key_without_compacting(opt_rec_var.unwrap()) && is_recursive_tag_union(&layout); if self_recursion { @@ -2207,8 +2256,8 @@ fn union_sorted_tags_help_new<'a>( } arg_layouts.sort_by(|layout1, layout2| { - let size1 = layout1.alignment_bytes(target_info); - let size2 = layout2.alignment_bytes(target_info); + let size1 = layout1.alignment_bytes(env.target_info); + let size2 = layout2.alignment_bytes(env.target_info); size2.cmp(&size1) }); @@ -2229,7 +2278,7 @@ fn union_sorted_tags_help_new<'a>( 3..=MAX_ENUM_SIZE if !has_any_arguments => { // type can be stored in a byte // needs the sorted tag names to determine the tag_id - let mut tag_names = Vec::with_capacity_in(answer.len(), arena); + let mut tag_names = Vec::with_capacity_in(answer.len(), env.arena); for (tag_name, _) in answer { tag_names.push(tag_name); @@ -2303,37 +2352,26 @@ pub fn union_sorted_tags_help<'a>( let mut layouts = Vec::with_capacity_in(tags_vec.len(), arena); let mut contains_zero_sized = false; - // special-case NUM_AT_NUM: if its argument is a FlexVar, make it Int - match tag_name { - TagName::Private(Symbol::NUM_AT_NUM) => { - layouts.push( - unwrap_num_tag(subs, arguments[0], target_info) - .expect("invalid num layout"), - ); - } - _ => { - for var in arguments { - match Layout::from_var(&mut env, var) { - Ok(layout) => { - // Drop any zero-sized arguments like {} - if !layout.is_dropped_because_empty() { - layouts.push(layout); - } else { - contains_zero_sized = true; - } - } - Err(LayoutProblem::UnresolvedTypeVar(_)) => { - // If we encounter an unbound type var (e.g. `Ok *`) - // then it's zero-sized; In the future we may drop this argument - // completely, but for now we represent it with the empty tag union - layouts.push(Layout::VOID) - } - Err(LayoutProblem::Erroneous) => { - // An erroneous type var will code gen to a runtime - // error, so we don't need to store any data for it. - } + for var in arguments { + match Layout::from_var(&mut env, var) { + Ok(layout) => { + // Drop any zero-sized arguments like {} + if !layout.is_dropped_because_empty() { + layouts.push(layout); + } else { + contains_zero_sized = true; } } + Err(LayoutProblem::UnresolvedTypeVar(_)) => { + // If we encounter an unbound type var (e.g. `Ok *`) + // then it's zero-sized; In the future we may drop this argument + // completely, but for now we represent it with the empty tag union + layouts.push(Layout::VOID) + } + Err(LayoutProblem::Erroneous) => { + // An erroneous type var will code gen to a runtime + // error, so we don't need to store any data for it. + } } } @@ -2488,128 +2526,95 @@ pub fn union_sorted_tags_help<'a>( } } -fn layout_from_newtype<'a>( - arena: &'a Bump, - tags: &UnsortedUnionTags, - subs: &Subs, - target_info: TargetInfo, -) -> Layout<'a> { - debug_assert!(tags.is_newtype_wrapper(subs)); +fn layout_from_newtype<'a>(env: &mut Env<'a, '_>, tags: &UnsortedUnionTags) -> Layout<'a> { + debug_assert!(tags.is_newtype_wrapper(env.subs)); - let (tag_name, var) = tags.get_newtype(subs); + let (_tag_name, var) = tags.get_newtype(env.subs); - if tag_name == &TagName::Private(Symbol::NUM_AT_NUM) { - unwrap_num_tag(subs, var, target_info).expect("invalid Num argument") - } else { - let mut env = Env { - arena, - subs, - seen: Vec::new_in(arena), - target_info, - }; - - match Layout::from_var(&mut env, var) { - Ok(layout) => layout, - Err(LayoutProblem::UnresolvedTypeVar(_)) => { - // If we encounter an unbound type var (e.g. `Ok *`) - // then it's zero-sized; In the future we may drop this argument - // completely, but for now we represent it with the empty tag union - Layout::VOID - } - Err(LayoutProblem::Erroneous) => { - // An erroneous type var will code gen to a runtime - // error, so we don't need to store any data for it. - todo!() - } + match Layout::from_var(env, var) { + Ok(layout) => layout, + Err(LayoutProblem::UnresolvedTypeVar(_)) => { + // If we encounter an unbound type var (e.g. `Ok *`) + // then it's zero-sized; In the future we may drop this argument + // completely, but for now we represent it with the empty tag union + Layout::VOID + } + Err(LayoutProblem::Erroneous) => { + // An erroneous type var will code gen to a runtime + // error, so we don't need to store any data for it. + todo!() } } } -fn layout_from_tag_union<'a>( - arena: &'a Bump, - tags: &UnsortedUnionTags, - subs: &Subs, - target_info: TargetInfo, -) -> Layout<'a> { +fn layout_from_tag_union<'a>(env: &mut Env<'a, '_>, tags: &UnsortedUnionTags) -> Layout<'a> { use UnionVariant::*; - if tags.is_newtype_wrapper(subs) { - return layout_from_newtype(arena, tags, subs, target_info); + if tags.is_newtype_wrapper(env.subs) { + return layout_from_newtype(env, tags); } let tags_vec = &tags.tags; - match tags_vec.get(0) { - Some((tag_name, arguments)) if *tag_name == &TagName::Private(Symbol::NUM_AT_NUM) => { - debug_assert_eq!(arguments.len(), 1); + let opt_rec_var = None; + let variant = union_sorted_tags_help_new(env, tags_vec, opt_rec_var); - let &var = arguments.iter().next().unwrap(); + match variant { + Never => Layout::VOID, + Unit | UnitWithArguments => Layout::UNIT, + BoolUnion { .. } => Layout::bool(), + ByteUnion(_) => Layout::u8(), + Newtype { + arguments: field_layouts, + .. + } => { + let answer1 = if field_layouts.len() == 1 { + field_layouts[0] + } else { + Layout::struct_no_name_order(field_layouts.into_bump_slice()) + }; - unwrap_num_tag(subs, var, target_info).expect("invalid Num argument") + answer1 } - _ => { - let opt_rec_var = None; - let variant = - union_sorted_tags_help_new(arena, tags_vec, opt_rec_var, subs, target_info); + Wrapped(variant) => { + use WrappedVariant::*; match variant { - Never => Layout::VOID, - Unit | UnitWithArguments => Layout::UNIT, - BoolUnion { .. } => Layout::bool(), - ByteUnion(_) => Layout::u8(), - Newtype { - arguments: field_layouts, - .. + NonRecursive { + sorted_tag_layouts: tags, } => { - let answer1 = if field_layouts.len() == 1 { - field_layouts[0] - } else { - Layout::struct_no_name_order(field_layouts.into_bump_slice()) - }; + let mut tag_layouts = Vec::with_capacity_in(tags.len(), env.arena); + tag_layouts.extend(tags.iter().map(|r| r.1)); - answer1 + Layout::Union(UnionLayout::NonRecursive(tag_layouts.into_bump_slice())) } - Wrapped(variant) => { - use WrappedVariant::*; - match variant { - NonRecursive { - sorted_tag_layouts: tags, - } => { - let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena); - tag_layouts.extend(tags.iter().map(|r| r.1)); + Recursive { + sorted_tag_layouts: tags, + } => { + let mut tag_layouts = Vec::with_capacity_in(tags.len(), env.arena); + tag_layouts.extend(tags.iter().map(|r| r.1)); - Layout::Union(UnionLayout::NonRecursive(tag_layouts.into_bump_slice())) - } - - Recursive { - sorted_tag_layouts: tags, - } => { - let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena); - tag_layouts.extend(tags.iter().map(|r| r.1)); - - debug_assert!(tag_layouts.len() > 1); - Layout::Union(UnionLayout::Recursive(tag_layouts.into_bump_slice())) - } - - NullableWrapped { - nullable_id, - nullable_name: _, - sorted_tag_layouts: tags, - } => { - let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena); - tag_layouts.extend(tags.iter().map(|r| r.1)); - - Layout::Union(UnionLayout::NullableWrapped { - nullable_id, - other_tags: tag_layouts.into_bump_slice(), - }) - } - - NullableUnwrapped { .. } => todo!(), - NonNullableUnwrapped { .. } => todo!(), - } + debug_assert!(tag_layouts.len() > 1); + Layout::Union(UnionLayout::Recursive(tag_layouts.into_bump_slice())) } + + NullableWrapped { + nullable_id, + nullable_name: _, + sorted_tag_layouts: tags, + } => { + let mut tag_layouts = Vec::with_capacity_in(tags.len(), env.arena); + tag_layouts.extend(tags.iter().map(|r| r.1)); + + Layout::Union(UnionLayout::NullableWrapped { + nullable_id, + other_tags: tag_layouts.into_bump_slice(), + }) + } + + NullableUnwrapped { .. } => todo!(), + NonNullableUnwrapped { .. } => todo!(), } } } @@ -2707,88 +2712,6 @@ fn layout_from_num_content<'a>( } } -fn unwrap_num_tag<'a>( - subs: &Subs, - var: Variable, - target_info: TargetInfo, -) -> Result, LayoutProblem> { - match subs.get_content_without_compacting(var) { - Content::Alias(Symbol::NUM_INTEGER, args, _, _) => { - debug_assert!(args.len() == 1); - - let precision_var = subs[args.all_variables().into_iter().next().unwrap()]; - - let precision = subs.get_content_without_compacting(precision_var); - - match precision { - Content::Alias(symbol, args, _, _) => { - debug_assert!(args.is_empty()); - - let layout = match *symbol { - Symbol::NUM_SIGNED128 => Layout::i128(), - Symbol::NUM_SIGNED64 => Layout::i64(), - Symbol::NUM_SIGNED32 => Layout::i32(), - Symbol::NUM_SIGNED16 => Layout::i16(), - Symbol::NUM_SIGNED8 => Layout::i8(), - Symbol::NUM_UNSIGNED128 => Layout::u128(), - Symbol::NUM_UNSIGNED64 => Layout::u64(), - Symbol::NUM_UNSIGNED32 => Layout::u32(), - Symbol::NUM_UNSIGNED16 => Layout::u16(), - Symbol::NUM_UNSIGNED8 => Layout::u8(), - Symbol::NUM_NATURAL => Layout::usize(target_info), - - _ => unreachable!("not a valid int variant: {:?} {:?}", symbol, args), - }; - - Ok(layout) - } - Content::FlexVar(_) | Content::RigidVar(_) => { - // default to i64 - Ok(Layout::i64()) - } - _ => unreachable!("not a valid int variant: {:?}", precision), - } - } - Content::Alias(Symbol::NUM_FLOATINGPOINT, args, _, _) => { - debug_assert!(args.len() == 1); - - let precision_var = subs[args.all_variables().into_iter().next().unwrap()]; - - let precision = subs.get_content_without_compacting(precision_var); - - match precision { - Content::Alias(Symbol::NUM_BINARY32, args, _, _) => { - debug_assert!(args.is_empty()); - - Ok(Layout::f32()) - } - Content::Alias(Symbol::NUM_BINARY64, args, _, _) => { - debug_assert!(args.is_empty()); - - Ok(Layout::f64()) - } - Content::Alias(Symbol::NUM_DECIMAL, args, _, _) => { - debug_assert!(args.is_empty()); - - Ok(Layout::Builtin(Builtin::Decimal)) - } - Content::FlexVar(_) | Content::RigidVar(_) => { - // default to f64 - Ok(Layout::f64()) - } - _ => unreachable!("not a valid float variant: {:?}", precision), - } - } - Content::FlexVar(_) | Content::RigidVar(_) => { - // If this was still a (Num *) then default to compiling it to i64 - Ok(Layout::default_integer()) - } - other => { - todo!("TODO non structure Num.@Num flat_type {:?}", other); - } - } -} - fn dict_layout_from_key_value<'a>( env: &mut Env<'a, '_>, key_var: Variable, @@ -2851,7 +2774,7 @@ impl LayoutId { // Returns something like "foo#1" when given a symbol that interns to "foo" // and a LayoutId of 1. pub fn to_symbol_string(self, symbol: Symbol, interns: &Interns) -> String { - let ident_string = symbol.ident_str(interns); + let ident_string = symbol.as_str(interns); let module_string = interns.module_ids.get_name(symbol.module_id()).unwrap(); format!("{}_{}_{}", module_string, ident_string, self.0) } diff --git a/compiler/mono/src/layout_soa.rs b/compiler/mono/src/layout_soa.rs index ed813d0c0a..f5477a5976 100644 --- a/compiler/mono/src/layout_soa.rs +++ b/compiler/mono/src/layout_soa.rs @@ -343,8 +343,7 @@ impl LambdaSet { TagName::Closure(symbol) => { layouts.symbols.push(*symbol); } - TagName::Global(_) => unreachable!("lambda set tags must be closure tags"), - TagName::Private(_) => unreachable!("lambda set tags must be closure tags"), + TagName::Tag(_) => unreachable!("lambda set tags must be closure tags"), } } @@ -678,11 +677,9 @@ impl Layout { } match symbol { - Symbol::NUM_DECIMAL | Symbol::NUM_AT_DECIMAL => Ok(Layout::Decimal), + Symbol::NUM_DECIMAL => Ok(Layout::Decimal), - Symbol::NUM_NAT | Symbol::NUM_NATURAL | Symbol::NUM_AT_NATURAL => { - Ok(layouts.usize()) - } + Symbol::NUM_NAT | Symbol::NUM_NATURAL => Ok(layouts.usize()), _ => { // at this point we throw away alias information diff --git a/compiler/parse/fuzz/dict.txt b/compiler/parse/fuzz/dict.txt index c22976ccb2..c2e458bbda 100644 --- a/compiler/parse/fuzz/dict.txt +++ b/compiler/parse/fuzz/dict.txt @@ -30,7 +30,6 @@ ">=" ">" "^" -"%%" "%" "->" \ No newline at end of file diff --git a/compiler/parse/src/ast.rs b/compiler/parse/src/ast.rs index f616f07d7f..a47f2da392 100644 --- a/compiler/parse/src/ast.rs +++ b/compiler/parse/src/ast.rs @@ -188,11 +188,9 @@ pub enum Expr<'a> { Underscore(&'a str), // Tags - GlobalTag(&'a str), - PrivateTag(&'a str), + Tag(&'a str), - // Reference to an opaque type, e.g. $Opaq - // TODO(opaques): $->@ in the above comment + // Reference to an opaque type, e.g. @Opaq OpaqueRef(&'a str), // Pattern Matching @@ -278,6 +276,12 @@ pub struct AbilityMember<'a> { pub typ: Loc>, } +impl AbilityMember<'_> { + pub fn region(&self) -> Region { + Region::across_all([self.name.region, self.typ.region].iter()) + } +} + #[derive(Debug, Clone, Copy, PartialEq)] pub enum TypeDef<'a> { /// A type alias. This is like a standalone annotation, except the pattern @@ -435,12 +439,7 @@ pub enum TypeAnnotation<'a> { #[derive(Debug, Clone, Copy, PartialEq)] pub enum Tag<'a> { - Global { - name: Loc<&'a str>, - args: &'a [Loc>], - }, - - Private { + Apply { name: Loc<&'a str>, args: &'a [Loc>], }, @@ -516,8 +515,7 @@ pub enum Pattern<'a> { // Identifier Identifier(&'a str), - GlobalTag(&'a str), - PrivateTag(&'a str), + Tag(&'a str), OpaqueRef(&'a str), @@ -572,8 +570,7 @@ pub enum Base { impl<'a> Pattern<'a> { pub fn from_ident(arena: &'a Bump, ident: Ident<'a>) -> Pattern<'a> { match ident { - Ident::GlobalTag(string) => Pattern::GlobalTag(string), - Ident::PrivateTag(string) => Pattern::PrivateTag(string), + Ident::Tag(string) => Pattern::Tag(string), Ident::OpaqueRef(string) => Pattern::OpaqueRef(string), Ident::Access { module_name, parts } => { if parts.len() == 1 { @@ -622,8 +619,7 @@ impl<'a> Pattern<'a> { match (self, other) { (Identifier(x), Identifier(y)) => x == y, - (GlobalTag(x), GlobalTag(y)) => x == y, - (PrivateTag(x), PrivateTag(y)) => x == y, + (Tag(x), Tag(y)) => x == y, (Apply(constructor_x, args_x), Apply(constructor_y, args_y)) => { let equivalent_args = args_x .iter() @@ -705,7 +701,7 @@ impl<'a, T> Collection<'a, T> { } } - pub fn with_items(items: &'a [T]) -> Collection<'a, T> { + pub const fn with_items(items: &'a [T]) -> Collection<'a, T> { Collection { items, final_comments: None, @@ -921,7 +917,7 @@ impl<'a> Expr<'a> { } pub fn is_tag(&self) -> bool { - matches!(self, Expr::GlobalTag(_) | Expr::PrivateTag(_)) + matches!(self, Expr::Tag(_)) } } diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index 4d0654b31f..2ede367506 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -196,6 +196,7 @@ fn parse_loc_term_or_underscore<'a>( ) -> ParseResult<'a, Loc>, EExpr<'a>> { one_of!( loc_expr_in_parens_etc_help(min_indent), + loc!(specialize(EExpr::If, if_expr_help(min_indent, options))), loc!(specialize(EExpr::Str, string_literal_help())), loc!(specialize(EExpr::SingleQuote, single_quote_literal_help())), loc!(specialize(EExpr::Number, positive_number_literal_help())), @@ -603,7 +604,7 @@ fn append_body_definition<'a>( // UserId x = UserId 42 // We optimistically parsed the first line as an alias; we now turn it // into an annotation. - let loc_name = arena.alloc(header.name.map(|x| Pattern::GlobalTag(x))); + let loc_name = arena.alloc(header.name.map(|x| Pattern::Tag(x))); let ann_pattern = Pattern::Apply(loc_name, header.vars); let vars_region = Region::across_all(header.vars.iter().map(|v| &v.region)); let region_ann_pattern = Region::span_across(&loc_name.region, &vars_region); @@ -697,7 +698,7 @@ fn append_annotation_definition<'a>( match &loc_pattern.value { Pattern::Apply( Loc { - value: Pattern::GlobalTag(name), + value: Pattern::Tag(name), .. }, alias_arguments, @@ -711,7 +712,7 @@ fn append_annotation_definition<'a>( loc_ann, kind, ), - Pattern::GlobalTag(name) => append_type_definition( + Pattern::Tag(name) => append_type_definition( arena, defs, region, @@ -872,12 +873,12 @@ fn parse_defs_end<'a>( { Pattern::Apply( Loc { - value: Pattern::GlobalTag(name), + value: Pattern::Tag(name), region, }, args, ) => Some((name, *region, args)), - Pattern::GlobalTag(name) => Some((name, loc_pattern.region, &[])), + Pattern::Tag(name) => Some((name, loc_pattern.region, &[])), _ => None, }; @@ -1019,7 +1020,7 @@ fn finish_parsing_alias_or_opaque<'a>( .map_err(|fail| (MadeProgress, fail, state.clone()))?; let (loc_def, state) = match &expr.value { - Expr::GlobalTag(name) => { + Expr::Tag(name) => { let mut type_arguments = Vec::with_capacity_in(arguments.len(), arena); for argument in arguments { @@ -1509,8 +1510,8 @@ fn parse_expr_operator<'a>( } } } - Err((NoProgress, _, _)) => { - todo!() + Err((NoProgress, expr, e)) => { + todo!("{:?} {:?}", expr, e) } }, } @@ -1542,11 +1543,11 @@ fn parse_expr_end<'a>( .. }, state, - )) if matches!(expr_state.expr.value, Expr::GlobalTag(..)) => { + )) if matches!(expr_state.expr.value, Expr::Tag(..)) => { // This is an ability definition, `Ability arg1 ... has ...`. let name = expr_state.expr.map_owned(|e| match e { - Expr::GlobalTag(name) => name, + Expr::Tag(name) => name, _ => unreachable!(), }); @@ -1762,8 +1763,7 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result Ok(Pattern::Underscore(opt_name)), - Expr::GlobalTag(value) => Ok(Pattern::GlobalTag(value)), - Expr::PrivateTag(value) => Ok(Pattern::PrivateTag(value)), + Expr::Tag(value) => Ok(Pattern::Tag(value)), Expr::OpaqueRef(value) => Ok(Pattern::OpaqueRef(value)), Expr::Apply(loc_val, loc_args, _) => { let region = loc_val.region; @@ -2436,8 +2436,7 @@ where fn ident_to_expr<'a>(arena: &'a Bump, src: Ident<'a>) -> Expr<'a> { match src { - Ident::GlobalTag(string) => Expr::GlobalTag(string), - Ident::PrivateTag(string) => Expr::PrivateTag(string), + Ident::Tag(string) => Expr::Tag(string), Ident::OpaqueRef(string) => Expr::OpaqueRef(string), Ident::Access { module_name, parts } => { let mut iter = parts.iter(); @@ -2762,7 +2761,6 @@ where "&&" => good!(BinOp::And, 2), "||" => good!(BinOp::Or, 2), "//" => good!(BinOp::DoubleSlash, 2), - "%%" => good!(BinOp::DoublePercent, 2), "->" => { // makes no progress, so it does not interfere with `_ if isGood -> ...` Err((NoProgress, to_error("->", state.pos()), state)) diff --git a/compiler/parse/src/header.rs b/compiler/parse/src/header.rs index 1a1b301f9f..fcd534e0d6 100644 --- a/compiler/parse/src/header.rs +++ b/compiler/parse/src/header.rs @@ -6,6 +6,7 @@ use crate::parser::{specialize, word1, EPackageEntry, EPackageName, Parser}; use crate::state::State; use crate::string_literal; use bumpalo::collections::Vec; +use roc_module::symbol::Symbol; use roc_region::all::Loc; #[derive(Debug)] @@ -17,6 +18,10 @@ pub enum HeaderFor<'a> { generates: UppercaseIdent<'a>, generates_with: &'a [Loc>], }, + /// Only created during canonicalization, never actually parsed from source + Builtin { + generates_with: &'a [Symbol], + }, PkgConfig { /// usually `pf` config_shorthand: &'a str, @@ -60,11 +65,11 @@ impl<'a> From> for &'a str { } impl<'a> ModuleName<'a> { - pub fn new(name: &'a str) -> Self { + pub const fn new(name: &'a str) -> Self { ModuleName(name) } - pub fn as_str(&'a self) -> &'a str { + pub const fn as_str(&'a self) -> &'a str { self.0 } } @@ -88,7 +93,7 @@ impl<'a> From> for &'a str { } impl<'a> ExposedName<'a> { - pub fn new(name: &'a str) -> Self { + pub const fn new(name: &'a str) -> Self { ExposedName(name) } diff --git a/compiler/parse/src/ident.rs b/compiler/parse/src/ident.rs index c2afb90038..131cf4c465 100644 --- a/compiler/parse/src/ident.rs +++ b/compiler/parse/src/ident.rs @@ -5,7 +5,7 @@ use bumpalo::collections::vec::Vec; use bumpalo::Bump; use roc_region::all::Position; -/// A global tag, for example. Must start with an uppercase letter +/// A tag, for example. Must start with an uppercase letter /// and then contain only letters and numbers afterwards - no dots allowed! #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct UppercaseIdent<'a>(&'a str); @@ -35,11 +35,8 @@ impl<'a> From<&'a UppercaseIdent<'a>> for &'a str { #[derive(Debug, Clone, PartialEq, Eq)] pub enum Ident<'a> { /// Foo or Bar - GlobalTag(&'a str), + Tag(&'a str), /// @Foo or @Bar - PrivateTag(&'a str), - /// $Foo or $Bar - // TODO(opaques): $->@ in the above comment OpaqueRef(&'a str), /// foo or foo.bar or Foo.Bar.baz.qux Access { @@ -57,7 +54,7 @@ impl<'a> Ident<'a> { use self::Ident::*; match self { - GlobalTag(string) | PrivateTag(string) | OpaqueRef(string) => string.len(), + Tag(string) | OpaqueRef(string) => string.len(), Access { module_name, parts } => { let mut len = if module_name.is_empty() { 0 @@ -101,31 +98,14 @@ pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str, ()> { } pub fn tag_name<'a>() -> impl Parser<'a, &'a str, ()> { - move |arena, state: State<'a>| { - if state.bytes().starts_with(b"@") { - match chomp_private_tag_or_opaque( - /* private tag */ true, - state.bytes(), - state.pos(), - ) { - Err(BadIdent::Start(_)) => Err((NoProgress, (), state)), - Err(_) => Err((MadeProgress, (), state)), - Ok(ident) => { - let width = ident.len(); - Ok((MadeProgress, ident, state.advance(width))) - } - } - } else { - uppercase_ident().parse(arena, state) - } - } + move |arena, state: State<'a>| uppercase_ident().parse(arena, state) } /// This could be: /// /// * A module name /// * A type name -/// * A global tag +/// * A tag pub fn uppercase<'a>() -> impl Parser<'a, UppercaseIdent<'a>, ()> { move |_, state: State<'a>| match chomp_uppercase_part(state.bytes()) { Err(progress) => Err((progress, (), state)), @@ -140,7 +120,7 @@ pub fn uppercase<'a>() -> impl Parser<'a, UppercaseIdent<'a>, ()> { /// /// * A module name /// * A type name -/// * A global tag +/// * A tag pub fn uppercase_ident<'a>() -> impl Parser<'a, &'a str, ()> { move |_, state: State<'a>| match chomp_uppercase_part(state.bytes()) { Err(progress) => Err((progress, (), state)), @@ -242,7 +222,6 @@ pub enum BadIdent { WeirdDotAccess(Position), WeirdDotQualified(Position), StrayDot(Position), - BadPrivateTag(Position), BadOpaqueRef(Position), } @@ -311,21 +290,13 @@ fn chomp_accessor(buffer: &[u8], pos: Position) -> Result<&str, BadIdent> { } } -/// a `@Token` private tag -fn chomp_private_tag_or_opaque( - private_tag: bool, // If false, opaque - buffer: &[u8], - pos: Position, -) -> Result<&str, BadIdent> { +/// a `@Token` opaque +fn chomp_opaque_ref(buffer: &[u8], pos: Position) -> Result<&str, BadIdent> { // assumes the leading `@` has NOT been chomped already - debug_assert_eq!(buffer.get(0), Some(if private_tag { &b'@' } else { &b'$' })); + debug_assert_eq!(buffer.get(0), Some(&b'@')); use encode_unicode::CharExt; - let bad_ident = if private_tag { - BadIdent::BadPrivateTag - } else { - BadIdent::BadOpaqueRef - }; + let bad_ident = BadIdent::BadOpaqueRef; match chomp_uppercase_part(&buffer[1..]) { Ok(name) => { @@ -362,15 +333,11 @@ fn chomp_identifier_chain<'a>( } Err(fail) => return Err((1, fail)), }, - c @ ('@' | '$') => match chomp_private_tag_or_opaque(c == '@', buffer, pos) { + '@' => match chomp_opaque_ref(buffer, pos) { Ok(tagname) => { let bytes_parsed = tagname.len(); - let ident = if c == '@' { - Ident::PrivateTag - } else { - Ident::OpaqueRef - }; + let ident = Ident::OpaqueRef; return Ok((bytes_parsed as u32, ident(tagname))); } @@ -451,9 +418,9 @@ fn chomp_identifier_chain<'a>( BadIdent::Underscore(pos.bump_column(chomped as u32 + 1)), )) } else if first_is_uppercase { - // just one segment, starting with an uppercase letter; that's a global tag + // just one segment, starting with an uppercase letter; that's a tag let value = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) }; - Ok((chomped as u32, Ident::GlobalTag(value))) + Ok((chomped as u32, Ident::Tag(value))) } else { // just one segment, starting with a lowercase letter; that's a normal identifier let value = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) }; diff --git a/compiler/parse/src/pattern.rs b/compiler/parse/src/pattern.rs index 09e676075f..df09c1cb73 100644 --- a/compiler/parse/src/pattern.rs +++ b/compiler/parse/src/pattern.rs @@ -213,10 +213,10 @@ fn loc_ident_pattern_help<'a>( specialize(|_, pos| EPattern::Start(pos), loc!(parse_ident)).parse(arena, state)?; match loc_ident.value { - Ident::GlobalTag(tag) => { + Ident::Tag(tag) => { let loc_tag = Loc { region: loc_ident.region, - value: Pattern::GlobalTag(tag), + value: Pattern::Tag(tag), }; // Make sure `Foo Bar 1` is parsed as `Foo (Bar) 1`, and not `Foo (Bar 1)` @@ -240,14 +240,10 @@ fn loc_ident_pattern_help<'a>( Ok((MadeProgress, loc_tag, state)) } } - Ident::PrivateTag(name) | Ident::OpaqueRef(name) => { + Ident::OpaqueRef(name) => { let loc_pat = Loc { region: loc_ident.region, - value: if matches!(loc_ident.value, Ident::PrivateTag(..)) { - Pattern::PrivateTag(name) - } else { - Pattern::OpaqueRef(name) - }, + value: Pattern::OpaqueRef(name), }; // Make sure `@Foo Bar 1` is parsed as `@Foo (Bar) 1`, and not `@Foo (Bar 1)` diff --git a/compiler/parse/src/type_annotation.rs b/compiler/parse/src/type_annotation.rs index 45b34b7157..61d5a50a22 100644 --- a/compiler/parse/src/type_annotation.rs +++ b/compiler/parse/src/type_annotation.rs @@ -214,16 +214,9 @@ fn tag_type<'a>(min_indent: u32) -> impl Parser<'a, Tag<'a>, ETypeTagUnion<'a>> let (_, args, state) = specialize_ref(ETypeTagUnion::Type, loc_applied_args_e(min_indent)) .parse(arena, state)?; - let result = if name.value.starts_with('@') { - Tag::Private { - name, - args: args.into_bump_slice(), - } - } else { - Tag::Global { - name, - args: args.into_bump_slice(), - } + let result = Tag::Apply { + name, + args: args.into_bump_slice(), }; Ok((MadeProgress, result, state)) diff --git a/compiler/parse/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast b/compiler/parse/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast index 54d3d56502..eec49d33ac 100644 --- a/compiler/parse/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast @@ -3,7 +3,7 @@ Defs( @26-46 Value( AnnotatedBody { ann_pattern: @0-8 Apply( - @0-6 GlobalTag( + @0-6 Tag( "UserId", ), [ @@ -15,7 +15,7 @@ Defs( ann_type: @11-25 TagUnion { ext: None, tags: [ - @13-23 Global { + @13-23 Apply { name: @13-19 "UserId", args: [ @20-23 Apply( @@ -29,7 +29,7 @@ Defs( }, comment: None, body_pattern: @26-34 Apply( - @26-32 GlobalTag( + @26-32 Tag( "UserId", ), [ @@ -39,7 +39,7 @@ Defs( ], ), body_expr: @37-46 Apply( - @37-43 GlobalTag( + @37-43 Tag( "UserId", ), [ diff --git a/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_parenthetical_tag_args.expr.result-ast similarity index 92% rename from compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.result-ast rename to compiler/parse/tests/snapshots/pass/apply_parenthetical_tag_args.expr.result-ast index 483292bdcf..af657692b6 100644 --- a/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_parenthetical_tag_args.expr.result-ast @@ -1,5 +1,5 @@ Apply( - @0-4 GlobalTag( + @0-4 Tag( "Whee", ), [ diff --git a/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.roc b/compiler/parse/tests/snapshots/pass/apply_parenthetical_tag_args.expr.roc similarity index 100% rename from compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.roc rename to compiler/parse/tests/snapshots/pass/apply_parenthetical_tag_args.expr.roc diff --git a/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.result-ast deleted file mode 100644 index 185e681f5b..0000000000 --- a/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.result-ast +++ /dev/null @@ -1,14 +0,0 @@ -Apply( - @0-5 PrivateTag( - "@Whee", - ), - [ - @6-8 Num( - "12", - ), - @9-11 Num( - "34", - ), - ], - Space, -) diff --git a/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.roc b/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.roc deleted file mode 100644 index ba23819345..0000000000 --- a/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.roc +++ /dev/null @@ -1 +0,0 @@ -@Whee 12 34 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_tag.expr.result-ast similarity index 88% rename from compiler/parse/tests/snapshots/pass/apply_global_tag.expr.result-ast rename to compiler/parse/tests/snapshots/pass/apply_tag.expr.result-ast index f939664412..6557b3805f 100644 --- a/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_tag.expr.result-ast @@ -1,5 +1,5 @@ Apply( - @0-4 GlobalTag( + @0-4 Tag( "Whee", ), [ diff --git a/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.roc b/compiler/parse/tests/snapshots/pass/apply_tag.expr.roc similarity index 100% rename from compiler/parse/tests/snapshots/pass/apply_global_tag.expr.roc rename to compiler/parse/tests/snapshots/pass/apply_tag.expr.roc diff --git a/compiler/parse/tests/snapshots/pass/basic_private_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/basic_private_tag.expr.result-ast deleted file mode 100644 index 1761f76aa1..0000000000 --- a/compiler/parse/tests/snapshots/pass/basic_private_tag.expr.result-ast +++ /dev/null @@ -1,3 +0,0 @@ -PrivateTag( - "@Whee", -) diff --git a/compiler/parse/tests/snapshots/pass/basic_private_tag.expr.roc b/compiler/parse/tests/snapshots/pass/basic_private_tag.expr.roc deleted file mode 100644 index 476a77dfc2..0000000000 --- a/compiler/parse/tests/snapshots/pass/basic_private_tag.expr.roc +++ /dev/null @@ -1 +0,0 @@ -@Whee \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/basic_global_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/basic_tag.expr.result-ast similarity index 56% rename from compiler/parse/tests/snapshots/pass/basic_global_tag.expr.result-ast rename to compiler/parse/tests/snapshots/pass/basic_tag.expr.result-ast index 8c0a66666f..bed3b6ff1d 100644 --- a/compiler/parse/tests/snapshots/pass/basic_global_tag.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/basic_tag.expr.result-ast @@ -1,3 +1,3 @@ -GlobalTag( +Tag( "Whee", ) diff --git a/compiler/parse/tests/snapshots/pass/basic_global_tag.expr.roc b/compiler/parse/tests/snapshots/pass/basic_tag.expr.roc similarity index 100% rename from compiler/parse/tests/snapshots/pass/basic_global_tag.expr.roc rename to compiler/parse/tests/snapshots/pass/basic_tag.expr.roc diff --git a/compiler/parse/tests/snapshots/pass/destructure_tag_assignment.expr.result-ast b/compiler/parse/tests/snapshots/pass/destructure_tag_assignment.expr.result-ast index 4cdfd0b739..95c8fc9192 100644 --- a/compiler/parse/tests/snapshots/pass/destructure_tag_assignment.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/destructure_tag_assignment.expr.result-ast @@ -3,7 +3,7 @@ Defs( @0-36 Value( Body( @0-5 Apply( - @0-5 GlobalTag( + @0-5 Tag( "Email", ), [ @@ -13,7 +13,7 @@ Defs( ], ), @12-36 Apply( - @12-17 GlobalTag( + @12-17 Tag( "Email", ), [ diff --git a/compiler/parse/tests/snapshots/pass/opaque_reference_expr.expr.result-ast b/compiler/parse/tests/snapshots/pass/opaque_reference_expr.expr.result-ast index 10d11945aa..4dfbe2dc65 100644 --- a/compiler/parse/tests/snapshots/pass/opaque_reference_expr.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/opaque_reference_expr.expr.result-ast @@ -1,3 +1,3 @@ OpaqueRef( - "$Age", + "@Age", ) diff --git a/compiler/parse/tests/snapshots/pass/opaque_reference_expr.expr.roc b/compiler/parse/tests/snapshots/pass/opaque_reference_expr.expr.roc index fd75aeaae1..6783cae16a 100644 --- a/compiler/parse/tests/snapshots/pass/opaque_reference_expr.expr.roc +++ b/compiler/parse/tests/snapshots/pass/opaque_reference_expr.expr.roc @@ -1 +1 @@ -$Age +@Age diff --git a/compiler/parse/tests/snapshots/pass/opaque_reference_expr_with_arguments.expr.result-ast b/compiler/parse/tests/snapshots/pass/opaque_reference_expr_with_arguments.expr.result-ast index b9db6ccfa8..c42661df16 100644 --- a/compiler/parse/tests/snapshots/pass/opaque_reference_expr_with_arguments.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/opaque_reference_expr_with_arguments.expr.result-ast @@ -1,6 +1,6 @@ Apply( @0-4 OpaqueRef( - "$Age", + "@Age", ), [ @5-6 Var { diff --git a/compiler/parse/tests/snapshots/pass/opaque_reference_expr_with_arguments.expr.roc b/compiler/parse/tests/snapshots/pass/opaque_reference_expr_with_arguments.expr.roc index e8b6053bba..c7a719c935 100644 --- a/compiler/parse/tests/snapshots/pass/opaque_reference_expr_with_arguments.expr.roc +++ b/compiler/parse/tests/snapshots/pass/opaque_reference_expr_with_arguments.expr.roc @@ -1 +1 @@ -$Age m n +@Age m n diff --git a/compiler/parse/tests/snapshots/pass/opaque_reference_pattern.expr.result-ast b/compiler/parse/tests/snapshots/pass/opaque_reference_pattern.expr.result-ast index 41f8660339..0dd1a3462e 100644 --- a/compiler/parse/tests/snapshots/pass/opaque_reference_pattern.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/opaque_reference_pattern.expr.result-ast @@ -8,7 +8,7 @@ When( patterns: [ @12-16 SpaceBefore( OpaqueRef( - "$Age", + "@Age", ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/opaque_reference_pattern.expr.roc b/compiler/parse/tests/snapshots/pass/opaque_reference_pattern.expr.roc index 49693af6b1..c18631c5fc 100644 --- a/compiler/parse/tests/snapshots/pass/opaque_reference_pattern.expr.roc +++ b/compiler/parse/tests/snapshots/pass/opaque_reference_pattern.expr.roc @@ -1,2 +1,2 @@ when n is - $Age -> 1 + @Age -> 1 diff --git a/compiler/parse/tests/snapshots/pass/opaque_reference_pattern_with_arguments.expr.result-ast b/compiler/parse/tests/snapshots/pass/opaque_reference_pattern_with_arguments.expr.result-ast index 6f159172e8..a572677762 100644 --- a/compiler/parse/tests/snapshots/pass/opaque_reference_pattern_with_arguments.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/opaque_reference_pattern_with_arguments.expr.result-ast @@ -9,7 +9,7 @@ When( @12-20 SpaceBefore( Apply( @12-16 OpaqueRef( - "$Add", + "@Add", ), [ @17-18 Identifier( diff --git a/compiler/parse/tests/snapshots/pass/opaque_reference_pattern_with_arguments.expr.roc b/compiler/parse/tests/snapshots/pass/opaque_reference_pattern_with_arguments.expr.roc index d5fcf3be4b..8ee3928e79 100644 --- a/compiler/parse/tests/snapshots/pass/opaque_reference_pattern_with_arguments.expr.roc +++ b/compiler/parse/tests/snapshots/pass/opaque_reference_pattern_with_arguments.expr.roc @@ -1,2 +1,2 @@ when n is - $Add n m -> n + m + @Add n m -> n + m diff --git a/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast b/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast index d45bbdf656..9395ab1ab7 100644 --- a/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast @@ -1,12 +1,12 @@ When( @5-22 Apply( - @5-11 GlobalTag( + @5-11 Tag( "Delmin", ), [ @13-19 ParensAround( Apply( - @13-16 GlobalTag( + @13-16 Tag( "Del", ), [ @@ -29,12 +29,12 @@ When( patterns: [ @30-48 SpaceBefore( Apply( - @30-36 GlobalTag( + @30-36 Tag( "Delmin", ), [ @38-44 Apply( - @38-41 GlobalTag( + @38-41 Tag( "Del", ), [ @@ -54,17 +54,17 @@ When( ), ], value: @52-73 Apply( - @52-56 GlobalTag( + @52-56 Tag( "Node", ), [ - @57-62 GlobalTag( + @57-62 Tag( "Black", ), @63-64 Num( "0", ), - @65-70 GlobalTag( + @65-70 Tag( "False", ), @71-73 Var { diff --git a/compiler/parse/tests/snapshots/pass/plus_if.expr.result-ast b/compiler/parse/tests/snapshots/pass/plus_if.expr.result-ast new file mode 100644 index 0000000000..e8a3539470 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/plus_if.expr.result-ast @@ -0,0 +1,25 @@ +BinOps( + [ + ( + @0-1 Num( + "1", + ), + @2-3 Star, + ), + ], + @4-25 If( + [ + ( + @7-11 Tag( + "True", + ), + @17-18 Num( + "1", + ), + ), + ], + @24-25 Num( + "1", + ), + ), +) diff --git a/compiler/parse/tests/snapshots/pass/plus_if.expr.roc b/compiler/parse/tests/snapshots/pass/plus_if.expr.roc new file mode 100644 index 0000000000..50a84b0a4d --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/plus_if.expr.roc @@ -0,0 +1 @@ +1 * if True then 1 else 1 diff --git a/compiler/parse/tests/snapshots/pass/private_qualified_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/private_qualified_tag.expr.result-ast deleted file mode 100644 index b770fe6077..0000000000 --- a/compiler/parse/tests/snapshots/pass/private_qualified_tag.expr.result-ast +++ /dev/null @@ -1,6 +0,0 @@ -MalformedIdent( - "@One.Two.Whee", - BadPrivateTag( - @4, - ), -) diff --git a/compiler/parse/tests/snapshots/pass/private_qualified_tag.expr.roc b/compiler/parse/tests/snapshots/pass/private_qualified_tag.expr.roc deleted file mode 100644 index e5c825a346..0000000000 --- a/compiler/parse/tests/snapshots/pass/private_qualified_tag.expr.roc +++ /dev/null @@ -1 +0,0 @@ -@One.Two.Whee \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/qualified_global_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/qualified_tag.expr.result-ast similarity index 100% rename from compiler/parse/tests/snapshots/pass/qualified_global_tag.expr.result-ast rename to compiler/parse/tests/snapshots/pass/qualified_tag.expr.result-ast diff --git a/compiler/parse/tests/snapshots/pass/qualified_global_tag.expr.roc b/compiler/parse/tests/snapshots/pass/qualified_tag.expr.roc similarity index 100% rename from compiler/parse/tests/snapshots/pass/qualified_global_tag.expr.roc rename to compiler/parse/tests/snapshots/pass/qualified_tag.expr.roc diff --git a/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast b/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast index 1776ed2ff8..5301d17702 100644 --- a/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast @@ -6,7 +6,7 @@ Record( @5-26 If( [ ( - @8-12 GlobalTag( + @8-12 Tag( "True", ), @18-19 Num( diff --git a/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast b/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast index 93bc481021..0b8a1d1b48 100644 --- a/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast @@ -1,6 +1,6 @@ Closure( [ - @1-6 GlobalTag( + @1-6 Tag( "Thing", ), ], diff --git a/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast index e0f8d41ece..bdd18adf88 100644 --- a/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast @@ -50,7 +50,7 @@ When( WhenBranch { patterns: [ @54-56 SpaceBefore( - GlobalTag( + Tag( "Ok", ), [ diff --git a/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast index 9192d1f1b6..2844887e56 100644 --- a/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast @@ -8,7 +8,7 @@ ParensAround( WhenBranch { patterns: [ @15-17 SpaceBefore( - GlobalTag( + Tag( "Ok", ), [ diff --git a/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast index 6f74fb5e4d..419e8c07ad 100644 --- a/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast @@ -9,7 +9,7 @@ ParensAround( WhenBranch { patterns: [ @15-17 SpaceBefore( - GlobalTag( + Tag( "Ok", ), [ diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index 759276523c..1fc6341287 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -77,7 +77,7 @@ mod test_parse { let pass_or_fail_names = list(&base); let mut extra_test_files = std::collections::HashSet::new(); for res in pass_or_fail_names { - assert!(res == "pass" || res == "fail"); + assert!(res == "pass" || res == "fail", "a pass or fail filename was neither \"pass\" nor \"fail\", but rather: {:?}", res); let res_dir = base.join(&res); for file in list(&res_dir) { let test = if let Some(test) = file.strip_suffix(".roc") { @@ -122,6 +122,7 @@ mod test_parse { snapshot_tests! { fail/type_argument_no_arrow.expr, fail/type_double_comma.expr, + pass/plus_if.expr, pass/list_closing_indent_not_enough.expr, pass/ability_single_line.expr, pass/ability_multi_line.expr, @@ -131,9 +132,8 @@ mod test_parse { pass/add_with_spaces.expr, pass/annotated_record_destructure.expr, pass/annotated_tag_destructure.expr, - pass/apply_global_tag.expr, - pass/apply_parenthetical_global_tag_args.expr, - pass/apply_private_tag.expr, + pass/apply_tag.expr, + pass/apply_parenthetical_tag_args.expr, pass/apply_three_args.expr, pass/apply_two_args.expr, pass/apply_unary_negation.expr, @@ -141,8 +141,7 @@ mod test_parse { pass/basic_apply.expr, pass/basic_docs.expr, pass/basic_field.expr, - pass/basic_global_tag.expr, - pass/basic_private_tag.expr, + pass/basic_tag.expr, pass/basic_var.expr, pass/closure_with_underscores.expr, pass/comment_after_def.module, @@ -232,10 +231,9 @@ mod test_parse { pass/pos_inf_float.expr, pass/positive_float.expr, pass/positive_int.expr, - pass/private_qualified_tag.expr, pass/provides_type.header, pass/qualified_field.expr, - pass/qualified_global_tag.expr, + pass/qualified_tag.expr, pass/qualified_var.expr, pass/record_destructure_def.expr, pass/record_func_type_decl.expr, @@ -316,7 +314,17 @@ mod test_parse { if std::env::var("ROC_PARSER_SNAPSHOT_TEST_OVERWRITE").is_ok() { std::fs::write(&result_path, actual_result).unwrap(); } else { - let expected_result = std::fs::read_to_string(&result_path).unwrap(); + let expected_result = std::fs::read_to_string(&result_path).unwrap_or_else(|e| { + panic!( + "Error opening test output file {}:\n\ + {:?} + Supposing the file is missing, consider running the tests with:\n\ + `env ROC_PARSER_SNAPSHOT_TEST_OVERWRITE=1 cargo test ...`\n\ + and committing the file that creates.", + result_path.display(), + e + ); + }); assert_multiline_str_eq!(expected_result, actual_result); } @@ -646,125 +654,17 @@ mod test_parse { // } // #[test] - // fn ann_private_open_union() { + // fn ann_open_union() { // let arena = Bump::new(); // let newline = bumpalo::vec![in &arena; Newline]; // let newlines = bumpalo::vec![in &arena; Newline, Newline]; - // let tag1 = Tag::Private { - // name: Located::new(0, 0, 8, 13, "@True"), - // args: &[], - // }; - // let tag2arg1 = Located::new(0, 0, 24, 27, TypeAnnotation::Apply("", "Two", &[])); - // let tag2arg2 = Located::new(0, 0, 28, 34, TypeAnnotation::Apply("", "Things", &[])); - // let tag2args = bumpalo::vec![in &arena; tag2arg1, tag2arg2]; - // let tag2 = Tag::Private { - // name: Located::new(0, 0, 15, 23, "@Perhaps"), - // args: tag2args.into_bump_slice(), - // }; - // let tags = bumpalo::vec![in &arena; - // Located::new(0, 0, 8, 13, tag1), - // Located::new(0, 0, 15, 34, tag2) - // ]; - // let loc_wildcard = Located::new(0, 0, 36, 37, TypeAnnotation::Wildcard); - // let applied_ann = TypeAnnotation::TagUnion { - // tags: tags.into_bump_slice(), - // ext: Some(arena.alloc(loc_wildcard)), - // }; - // let signature = Def::Annotation( - // Located::new(0, 0, 0, 3, Identifier("foo")), - // Located::new(0, 0, 6, 37, applied_ann), - // ); - // let def = Def::Body( - // arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))), - // arena.alloc(Located::new(1, 1, 6, 10, Expr::GlobalTag("True"))), - // ); - // let spaced_def = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice()); - // let loc_def = &*arena.alloc(Located::new(1, 1, 0, 10, spaced_def)); - - // let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature)); - // let defs = &[loc_ann, loc_def]; - // let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); - // let loc_ret = Located::new(3, 3, 0, 2, ret); - // let expected = Defs(defs, arena.alloc(loc_ret)); - - // assert_parses_to( - // indoc!( - // r#" - // foo : [ @True, @Perhaps Two Things ]* - // foo = True - - // 42 - // "# - // ), - // expected, - // ); - // } - - // #[test] - // fn ann_private_closed_union() { - // let arena = Bump::new(); - // let newline = bumpalo::vec![in &arena; Newline]; - // let newlines = bumpalo::vec![in &arena; Newline, Newline]; - // let tag1 = Tag::Private { - // name: Located::new(0, 0, 8, 13, "@True"), - // args: &[], - // }; - // let tag2arg = Located::new(0, 0, 24, 29, TypeAnnotation::Apply("", "Thing", &[])); - // let tag2args = bumpalo::vec![in &arena; tag2arg]; - // let tag2 = Tag::Private { - // name: Located::new(0, 0, 15, 23, "@Perhaps"), - // args: tag2args.into_bump_slice(), - // }; - // let tags = bumpalo::vec![in &arena; - // Located::new(0, 0, 8, 13, tag1), - // Located::new(0, 0, 15, 29, tag2) - // ]; - // let applied_ann = TypeAnnotation::TagUnion { - // tags: tags.into_bump_slice(), - // ext: None, - // }; - // let signature = Def::Annotation( - // Located::new(0, 0, 0, 3, Identifier("foo")), - // Located::new(0, 0, 6, 31, applied_ann), - // ); - // let def = Def::Body( - // arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))), - // arena.alloc(Located::new(1, 1, 6, 10, Expr::GlobalTag("True"))), - // ); - // let spaced_def = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice()); - // let loc_def = &*arena.alloc(Located::new(1, 1, 0, 10, spaced_def)); - - // let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature)); - // let defs = &[loc_ann, loc_def]; - // let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); - // let loc_ret = Located::new(3, 3, 0, 2, ret); - // let expected = Defs(defs, arena.alloc(loc_ret)); - - // assert_parses_to( - // indoc!( - // r#" - // foo : [ @True, @Perhaps Thing ] - // foo = True - - // 42 - // "# - // ), - // expected, - // ); - // } - - // #[test] - // fn ann_global_open_union() { - // let arena = Bump::new(); - // let newline = bumpalo::vec![in &arena; Newline]; - // let newlines = bumpalo::vec![in &arena; Newline, Newline]; - // let tag1 = Tag::Global { + // let tag1 = Tag::Apply { // name: Located::new(0, 0, 8, 12, "True"), // args: &[], // }; // let tag2arg = Located::new(0, 0, 22, 27, TypeAnnotation::Apply("", "Thing", &[])); // let tag2args = bumpalo::vec![in &arena; tag2arg]; - // let tag2 = Tag::Global { + // let tag2 = Tag::Apply { // name: Located::new(0, 0, 14, 21, "Perhaps"), // args: tag2args.into_bump_slice(), // }; @@ -783,7 +683,7 @@ mod test_parse { // ); // let def = Def::Body( // arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))), - // arena.alloc(Located::new(1, 1, 6, 10, Expr::GlobalTag("True"))), + // arena.alloc(Located::new(1, 1, 6, 10, Expr::Tag("True"))), // ); // let spaced_def = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice()); // let loc_def = &*arena.alloc(Located::new(1, 1, 0, 10, spaced_def)); @@ -808,17 +708,17 @@ mod test_parse { // } // #[test] - // fn ann_global_closed_union() { + // fn ann_closed_union() { // let arena = Bump::new(); // let newline = bumpalo::vec![in &arena; Newline]; // let newlines = bumpalo::vec![in &arena; Newline, Newline]; - // let tag1 = Tag::Global { + // let tag1 = Tag::Apply { // name: Located::new(0, 0, 8, 12, "True"), // args: &[], // }; // let tag2arg = Located::new(0, 0, 22, 27, TypeAnnotation::Apply("", "Thing", &[])); // let tag2args = bumpalo::vec![in &arena; tag2arg]; - // let tag2 = Tag::Global { + // let tag2 = Tag::Apply { // name: Located::new(0, 0, 14, 21, "Perhaps"), // args: tag2args.into_bump_slice(), // }; @@ -836,7 +736,7 @@ mod test_parse { // ); // let def = Def::Body( // arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))), - // arena.alloc(Located::new(1, 1, 6, 10, Expr::GlobalTag("True"))), + // arena.alloc(Located::new(1, 1, 6, 10, Expr::Tag("True"))), // ); // let spaced_def = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice()); // let loc_def = &*arena.alloc(Located::new(1, 1, 0, 10, spaced_def)); diff --git a/compiler/problem/src/can.rs b/compiler/problem/src/can.rs index f8d1684418..e28177345f 100644 --- a/compiler/problem/src/can.rs +++ b/compiler/problem/src/can.rs @@ -135,6 +135,10 @@ pub enum Problem { loc_name: Loc, ability: Symbol, }, + AbilityNotOnToplevel { + region: Region, + }, + AbilityUsedAsType(Lowercase, Symbol, Region), } #[derive(Clone, Debug, PartialEq)] diff --git a/compiler/region/src/all.rs b/compiler/region/src/all.rs index a22e325f60..3eed5060d5 100644 --- a/compiler/region/src/all.rs +++ b/compiler/region/src/all.rs @@ -279,16 +279,16 @@ pub struct Loc { } impl Loc { - pub fn new(start: u32, end: u32, value: T) -> Loc { + pub const fn new(start: u32, end: u32, value: T) -> Loc { let region = Region::new(Position::new(start), Position::new(end)); Loc { region, value } } - pub fn at(region: Region, value: T) -> Loc { + pub const fn at(region: Region, value: T) -> Loc { Loc { region, value } } - pub fn at_zero(value: T) -> Loc { + pub const fn at_zero(value: T) -> Loc { let region = Region::zero(); Loc { region, value } } @@ -377,6 +377,17 @@ impl LineInfo { end: self.convert_pos(region.end()), } } + + pub fn convert_line_column(&self, lc: LineColumn) -> Position { + let offset = self.line_offsets[lc.line as usize] + lc.column; + Position::new(offset) + } + + pub fn convert_line_column_region(&self, lc_region: LineColumnRegion) -> Region { + let start = self.convert_line_column(lc_region.start); + let end = self.convert_line_column(lc_region.end); + Region::new(start, end) + } } #[test] diff --git a/compiler/roc_target/Cargo.toml b/compiler/roc_target/Cargo.toml index 28211dbdc9..5ef3aba2d9 100644 --- a/compiler/roc_target/Cargo.toml +++ b/compiler/roc_target/Cargo.toml @@ -6,4 +6,4 @@ license = "UPL-1.0" edition = "2018" [dependencies] -target-lexicon = "0.12.2" +target-lexicon = "0.12.3" diff --git a/compiler/solve/Cargo.toml b/compiler/solve/Cargo.toml index b78faa8421..6b32bc14a8 100644 --- a/compiler/solve/Cargo.toml +++ b/compiler/solve/Cargo.toml @@ -8,6 +8,7 @@ edition = "2018" [dependencies] roc_collections = { path = "../collections" } roc_error_macros = { path = "../../error_macros" } +roc_exhaustive = { path = "../exhaustive" } roc_region = { path = "../region" } roc_module = { path = "../module" } roc_types = { path = "../types" } @@ -28,3 +29,5 @@ pretty_assertions = "1.0.0" indoc = "1.0.3" tempfile = "3.2.0" bumpalo = { version = "3.8.0", features = ["collections"] } +regex = "1.5.5" +lazy_static = "1.4.0" diff --git a/compiler/solve/src/ability.rs b/compiler/solve/src/ability.rs index 3aaf27d32d..a9796590cf 100644 --- a/compiler/solve/src/ability.rs +++ b/compiler/solve/src/ability.rs @@ -1,9 +1,11 @@ use roc_can::abilities::AbilitiesStore; +use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; use roc_types::subs::Subs; use roc_types::subs::Variable; use roc_types::types::{Category, PatternCategory}; use roc_unify::unify::MustImplementAbility; +use roc_unify::unify::MustImplementConstraints; use crate::solve::{IncompleteAbilityImplementation, TypeError}; @@ -18,17 +20,14 @@ pub enum AbilityImplError { } #[derive(Default)] -pub struct DeferredMustImplementAbility(Vec<(Vec, AbilityImplError)>); +pub struct DeferredMustImplementAbility(Vec<(MustImplementConstraints, AbilityImplError)>); impl DeferredMustImplementAbility { - pub fn add(&mut self, must_implement: Vec, on_error: AbilityImplError) { + pub fn add(&mut self, must_implement: MustImplementConstraints, on_error: AbilityImplError) { self.0.push((must_implement, on_error)); } pub fn check(self, subs: &mut Subs, abilities_store: &AbilitiesStore) -> Vec { - // Two passes here. First up let's build up records of what types fully implement - // abilities, and what specializations are available/missing for the ones that don't. - // Use a vec since these lists should usually be pretty small. let mut good = vec![]; let mut bad = vec![]; @@ -45,8 +44,21 @@ impl DeferredMustImplementAbility { }; } - for (mias, _) in self.0.iter() { - for &mia @ MustImplementAbility { typ, ability } in mias { + let mut problems = vec![]; + + // Keep track of which types that have an incomplete ability were reported as part of + // another type error (from an expression or pattern). If we reported an error for a type + // that doesn't implement an ability in that context, we don't want to repeat the error + // message. + let mut reported_in_context = vec![]; + let mut incomplete_not_in_context = vec![]; + + for (constraints, on_error) in self.0.into_iter() { + let must_implement = constraints.get_unique(); + + // First off, make sure we populate information about which of the "must implement" + // constraints are met, and which aren't. + for &mia @ MustImplementAbility { typ, ability } in must_implement.iter() { if is_good!(&mia) || get_bad!(mia).is_some() { continue; } @@ -80,22 +92,16 @@ impl DeferredMustImplementAbility { )); } } - } - // Now figure out what errors we need to report. - let mut problems = vec![]; - - // Keep track of which types that have an incomplete ability were reported as part of - // another type error (from an expression or pattern). If we reported an error for a type - // that doesn't implement an ability in that context, we don't want to repeat the error - // message. - let mut reported_in_context = vec![]; - let mut incomplete_not_in_context = vec![]; - - for (must_implement, on_error) in self.0.into_iter() { + // Now, figure out what errors we need to report. use AbilityImplError::*; match on_error { IncompleteAbility => { + // These aren't attached to another type error, so if these must_implement + // constraints aren't met, we'll emit a generic "this type doesn't implement an + // ability" error message at the end. We only want to do this if it turns out + // the "must implement" constraint indeed wasn't part of a more specific type + // error. incomplete_not_in_context.extend(must_implement); } BadExpr(region, category, var) => { @@ -138,9 +144,11 @@ impl DeferredMustImplementAbility { reported_in_context.extend(must_implement); } } - }; + } } + // Go through and attach generic "type does not implement ability" errors, if they were not + // part of a larger context. for mia in incomplete_not_in_context.into_iter() { if let Some(must_implement) = get_bad!(mia) { if !reported_in_context.contains(&mia) { @@ -154,3 +162,29 @@ impl DeferredMustImplementAbility { problems } } + +/// Determines what type implements an ability member of a specialized signature, given the +/// [MustImplementAbility] constraints of the signature. +pub fn type_implementing_member( + specialization_must_implement_constraints: &MustImplementConstraints, + ability: Symbol, +) -> Symbol { + debug_assert_eq!({ + let ability_implementations_for_specialization = + specialization_must_implement_constraints + .clone() + .get_unique(); + + ability_implementations_for_specialization.len() + }, + 1, + "Multiple variables bound to an ability - this is ambiguous and should have been caught in canonicalization: {:?}", + specialization_must_implement_constraints + ); + + specialization_must_implement_constraints + .iter_for_ability(ability) + .next() + .unwrap() + .typ +} diff --git a/compiler/solve/src/lib.rs b/compiler/solve/src/lib.rs index 06f9e2fd5c..d0e42eaf42 100644 --- a/compiler/solve/src/lib.rs +++ b/compiler/solve/src/lib.rs @@ -2,6 +2,6 @@ // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. #![allow(clippy::large_enum_variant)] -mod ability; +pub mod ability; pub mod module; pub mod solve; diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 2902eaefda..ae67dbf36c 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -1,10 +1,11 @@ -use crate::ability::{AbilityImplError, DeferredMustImplementAbility}; +use crate::ability::{type_implementing_member, AbilityImplError, DeferredMustImplementAbility}; use bumpalo::Bump; use roc_can::abilities::{AbilitiesStore, MemberSpecialization}; use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::{Constraints, LetConstraint}; use roc_can::expected::{Expected, PExpected}; use roc_collections::all::MutMap; +use roc_error_macros::internal_error; use roc_module::ident::TagName; use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; @@ -79,7 +80,7 @@ pub struct IncompleteAbilityImplementation { pub missing_members: Vec>, } -#[derive(PartialEq, Debug, Clone)] +#[derive(Debug, Clone)] pub enum TypeError { BadExpr(Region, Category, ErrorType, Expected), BadPattern(Region, PatternCategory, ErrorType, PExpected), @@ -99,6 +100,7 @@ pub enum TypeError { ErrorType, Vec, ), + Exhaustive(roc_exhaustive::Error), } use roc_types::types::Alias; @@ -195,20 +197,20 @@ impl Aliases { register(subs, rank, pools, content) } - /// Instantiate an alias of the form `Foo a : [ @Foo a ]` - fn instantiate_num_at_alias( + /// Build an alias of the form `Num range := range` + fn build_num_opaque( subs: &mut Subs, rank: Rank, pools: &mut Pools, - tag_name_slice: SubsSlice, - range_slice: SubsSlice, + symbol: Symbol, + range_var: Variable, ) -> 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); + let content = Content::Alias( + symbol, + AliasVariables::insert_into_subs(subs, [range_var], []), + range_var, + AliasKind::Opaque, + ); register(subs, rank, pools, content) } @@ -227,126 +229,46 @@ impl Aliases { 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_NUM | Symbol::NUM_FLOATINGPOINT | Symbol::NUM_INTEGER => { + // These are opaque types Num range := range (respectively for FloatingPoint and + // Integer). They should not have been built as DelayedAliases! + internal_error!("Attempting to build delayed instantiation of opaque num"); } Symbol::NUM_INT => { - // [ @Integer range ] - let integer_content_var = Self::instantiate_builtin_aliases( - self, + // Int range : Num (Integer range) + // + // build `Integer range := range` + let integer_content_var = Self::build_num_opaque( 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, + subs.variables[alias_variables.variables_start as usize], ); - Some(register(subs, rank, pools, num_content)) + // build `Num (Integer range) := Integer range` + let num_content_var = + Self::build_num_opaque(subs, rank, pools, Symbol::NUM_NUM, integer_content_var); + + Some(num_content_var) } Symbol::NUM_FLOAT => { - // [ @FloatingPoint range ] - let fpoint_content_var = Self::instantiate_builtin_aliases( - self, + // Float range : Num (FloatingPoint range) + // + // build `FloatingPoint range := range` + let fpoint_content_var = Self::build_num_opaque( 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, + subs.variables[alias_variables.variables_start as usize], ); - Some(register(subs, rank, pools, num_content)) + // build `Num (FloatingPoint range) := FloatingPoint range` + let num_content_var = + Self::build_num_opaque(subs, rank, pools, Symbol::NUM_NUM, fpoint_content_var); + + Some(num_content_var) } _ => None, } @@ -703,7 +625,6 @@ fn solve( check_ability_specialization( arena, subs, - &new_env, pools, rank, abilities_store, @@ -757,6 +678,8 @@ fn solve( pools.get_mut(next_rank).extend(pool_variables); debug_assert_eq!( + // Check that no variable ended up in a higher rank than the next rank.. that + // would mean we generalized one level more than we need to! { let offenders = pools .get(next_rank) @@ -783,7 +706,7 @@ fn solve( pools.get_mut(next_rank).clear(); // check that things went well - debug_assert!({ + if cfg!(debug_assertions) && std::env::var("ROC_VERIFY_RIGID_RANKS").is_ok() { let rigid_vars = &constraints.variables[let_con.rigid_vars.indices()]; // NOTE the `subs.redundant` check does not come from elm. @@ -799,20 +722,15 @@ fn solve( let failing: Vec<_> = it.collect(); println!("Rigids {:?}", &rigid_vars); println!("Failing {:?}", failing); - - // nicer error message - failing.is_empty() - } else { - true + debug_assert!(false); } - }); + } let mut new_env = env.clone(); for (symbol, loc_var) in local_def_vars.iter() { check_ability_specialization( arena, subs, - &new_env, pools, rank, abilities_store, @@ -856,7 +774,7 @@ fn solve( copy } - Eq(type_index, expectation_index, category_index, region) => { + Eq(roc_can::constraint::Eq(type_index, expectation_index, category_index, region)) => { let category = &constraints.categories[category_index.index()]; let actual = @@ -1185,25 +1103,9 @@ fn solve( let actual = either_type_index_to_var(constraints, subs, rank, pools, aliases, *type_index); - let mut new_desc = subs.get(actual); - match new_desc.content { - Content::Structure(FlatType::TagUnion(tags, _)) => { - let new_ext = subs.fresh_unnamed_flex_var(); - let new_union = Content::Structure(FlatType::TagUnion(tags, new_ext)); - new_desc.content = new_union; - subs.set(actual, new_desc); - state - } - _ => { - // Today, an "open" constraint doesn't affect any types - // other than tag unions. Recursive tag unions are constructed - // at a later time (during occurs checks after tag unions are - // resolved), so that's not handled here either. - // NB: Handle record types here if we add presence constraints - // to their type inference as well. - state - } - } + open_tag_union(subs, actual); + + state } IncludesTag(index) => { let includes_tag = &constraints.includes_tags[index.index()]; @@ -1268,12 +1170,217 @@ fn solve( } } } + &Exhaustive(eq, sketched_rows, context, exhaustive_mark) => { + // A few cases: + // 1. Either condition or branch types already have a type error. In this case just + // propagate it. + // 2. Types are correct, but there are redundancies. In this case we want + // exhaustiveness checking to pull those out. + // 3. Condition and branch types are "almost equal", that is one or the other is + // only missing a few more tags. In this case we want to run + // exhaustiveness checking both ways, to see which one is missing tags. + // 4. Condition and branch types aren't "almost equal", this is just a normal type + // error. + + let (real_var, real_region, expected_type, category_and_expected) = match eq { + Ok(eq) => { + let roc_can::constraint::Eq(real_var, expected, category, real_region) = + constraints.eq[eq.index()]; + let expected = &constraints.expectations[expected.index()]; + ( + real_var, + real_region, + expected.get_type_ref(), + Ok((category, expected)), + ) + } + Err(peq) => { + let roc_can::constraint::PatternEq( + real_var, + expected, + category, + real_region, + ) = constraints.pattern_eq[peq.index()]; + let expected = &constraints.pattern_expectations[expected.index()]; + ( + real_var, + real_region, + expected.get_type_ref(), + Err((category, expected)), + ) + } + }; + + let real_var = + either_type_index_to_var(constraints, subs, rank, pools, aliases, real_var); + + let branches_var = type_to_var(subs, rank, pools, aliases, expected_type); + + let real_content = subs.get_content_without_compacting(real_var); + let branches_content = subs.get_content_without_compacting(branches_var); + let already_have_error = matches!( + (real_content, branches_content), + ( + Content::Error | Content::Structure(FlatType::Erroneous(_)), + _ + ) | ( + _, + Content::Error | Content::Structure(FlatType::Erroneous(_)) + ) + ); + + let snapshot = subs.snapshot(); + let outcome = unify(subs, real_var, branches_var, Mode::EQ); + + let should_check_exhaustiveness; + match outcome { + Success { + vars, + must_implement_ability, + } => { + subs.commit_snapshot(snapshot); + + introduce(subs, rank, pools, &vars); + if !must_implement_ability.is_empty() { + internal_error!("Didn't expect ability vars to land here"); + } + + // Case 1: unify error types, but don't check exhaustiveness. + // Case 2: run exhaustiveness to check for redundant branches. + should_check_exhaustiveness = !already_have_error; + } + Failure(..) => { + // Rollback and check for almost-equality. + subs.rollback_to(snapshot); + + let almost_eq_snapshot = subs.snapshot(); + // TODO: turn this on for bidirectional exhaustiveness checking + // open_tag_union(subs, real_var); + open_tag_union(subs, branches_var); + let almost_eq = matches!( + unify(subs, real_var, branches_var, Mode::EQ), + Success { .. } + ); + + subs.rollback_to(almost_eq_snapshot); + + if almost_eq { + // Case 3: almost equal, check exhaustiveness. + should_check_exhaustiveness = true; + } else { + // Case 4: incompatible types, report type error. + // Re-run first failed unification to get the type diff. + match unify(subs, real_var, branches_var, Mode::EQ) { + Failure(vars, actual_type, expected_type, _bad_impls) => { + introduce(subs, rank, pools, &vars); + + // Figure out the problem - it might be pattern or value + // related. + let problem = match category_and_expected { + Ok((category, expected)) => { + let real_category = + constraints.categories[category.index()].clone(); + TypeError::BadExpr( + real_region, + real_category, + actual_type, + expected.replace_ref(expected_type), + ) + } + + Err((category, expected)) => { + let real_category = constraints.pattern_categories + [category.index()] + .clone(); + TypeError::BadPattern( + real_region, + real_category, + expected_type, + expected.replace_ref(actual_type), + ) + } + }; + + problems.push(problem); + should_check_exhaustiveness = false; + } + _ => internal_error!("Must be failure"), + } + } + } + BadType(vars, problem) => { + subs.commit_snapshot(snapshot); + + introduce(subs, rank, pools, &vars); + + problems.push(TypeError::BadType(problem)); + + should_check_exhaustiveness = false; + } + } + + let sketched_rows = constraints.sketched_rows[sketched_rows.index()].clone(); + + if should_check_exhaustiveness { + use roc_can::exhaustive::{check, ExhaustiveSummary}; + + let ExhaustiveSummary { + errors, + exhaustive, + redundancies, + } = check(subs, sketched_rows, context); + + // Store information about whether the "when" is exhaustive, and + // which (if any) of its branches are redundant. Codegen may use + // this for branch-fixing and redundant elimination. + if !exhaustive { + exhaustive_mark.set_non_exhaustive(subs); + } + for redundant_mark in redundancies { + redundant_mark.set_redundant(subs); + } + + // Store the errors. + problems.extend(errors.into_iter().map(TypeError::Exhaustive)); + } + + state + } }; } state } +fn open_tag_union(subs: &mut Subs, var: Variable) { + let mut stack = vec![var]; + while let Some(var) = stack.pop() { + use {Content::*, FlatType::*}; + + let mut desc = subs.get(var); + if let Structure(TagUnion(tags, ext)) = desc.content { + if let Structure(EmptyTagUnion) = subs.get_content_without_compacting(ext) { + let new_ext = subs.fresh_unnamed_flex_var(); + subs.set_rank(new_ext, desc.rank); + let new_union = Structure(TagUnion(tags, new_ext)); + desc.content = new_union; + subs.set(var, desc); + } + + // Also open up all nested tag unions. + let all_vars = tags.variables().into_iter(); + stack.extend(all_vars.flat_map(|slice| subs[slice]).map(|var| subs[var])); + } + + // Today, an "open" constraint doesn't affect any types + // other than tag unions. Recursive tag unions are constructed + // at a later time (during occurs checks after tag unions are + // resolved), so that's not handled here either. + // NB: Handle record types here if we add presence constraints + // to their type inference as well. + } +} + /// If a symbol claims to specialize an ability member, check that its solved type in fact /// does specialize the ability, and record the specialization. #[allow(clippy::too_many_arguments)] @@ -1282,7 +1389,6 @@ fn solve( fn check_ability_specialization( arena: &Bump, subs: &mut Subs, - env: &Env, pools: &mut Pools, rank: Rank, abilities_store: &mut AbilitiesStore, @@ -1295,9 +1401,7 @@ fn check_ability_specialization( // inferred type for the specialization actually aligns with the expected // implementation. if let Some((root_symbol, root_data)) = abilities_store.root_name_and_def(symbol) { - let root_signature_var = env - .get_var_by_symbol(&root_symbol) - .expect("Ability should be registered in env by now!"); + let root_signature_var = root_data.signature_var; // Check if they unify - if they don't, then the claimed specialization isn't really one, // and that's a type error! @@ -1351,16 +1455,8 @@ fn check_ability_specialization( // First, figure out and register for what type does this symbol specialize // the ability member. - let mut ability_implementations_for_specialization = must_implement_ability - .iter() - .filter(|mia| mia.ability == root_data.parent_ability) - .collect::>(); - ability_implementations_for_specialization.dedup(); - - debug_assert!(ability_implementations_for_specialization.len() == 1, "Multiple variables bound to an ability - this is ambiguous and should have been caught in canonicalization"); - - // This is a valid specialization! Record it. - let specialization_type = ability_implementations_for_specialization[0].typ; + let specialization_type = + type_implementing_member(&must_implement_ability, root_data.parent_ability); let specialization = MemberSpecialization { symbol, region: symbol_loc_var.region, @@ -2131,7 +2227,7 @@ fn insert_tags_fast_path<'a>( tags: &'a [(TagName, Vec)], stack: &mut bumpalo::collections::Vec<'_, TypeToVar<'a>>, ) -> UnionTags { - if let [(TagName::Global(tag_name), arguments)] = tags { + if let [(TagName::Tag(tag_name), arguments)] = tags { let variable_slice = register_tag_arguments(subs, rank, pools, arena, stack, arguments); let new_variable_slices = SubsSlice::extend_new(&mut subs.variable_slices, [variable_slice]); diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index e08c2e049b..7ca9f461c7 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -10,16 +10,54 @@ mod helpers; #[cfg(test)] mod solve_expr { use crate::helpers::with_larger_debug_stack; + use lazy_static::lazy_static; + use regex::Regex; + use roc_can::traverse::find_type_at; use roc_load::LoadedModule; + use roc_module::symbol::{Interns, ModuleId}; + use roc_problem::can::Problem; + use roc_region::all::{LineColumn, LineColumnRegion, LineInfo, Region}; + use roc_reporting::report::{can_problem, type_problem, RocDocAllocator}; + use roc_solve::solve::TypeError; use roc_types::pretty_print::{content_to_string, name_all_type_vars}; + use std::path::PathBuf; // HELPERS - fn run_load_and_infer(src: &str) -> Result { + lazy_static! { + static ref RE_TYPE_QUERY: Regex = Regex::new(r#"^\s*#\s*(?P\^+)\s*$"#).unwrap(); + } + + #[derive(Debug, Clone, Copy)] + struct TypeQuery(Region); + + fn parse_queries(src: &str) -> Vec { + let line_info = LineInfo::new(src); + let mut queries = vec![]; + for (i, line) in src.lines().enumerate() { + if let Some(capture) = RE_TYPE_QUERY.captures(line) { + let wher = capture.name("where").unwrap(); + let (start, end) = (wher.start() as u32, wher.end() as u32); + let last_line = i as u32 - 1; + let start_lc = LineColumn { + line: last_line, + column: start, + }; + let end_lc = LineColumn { + line: last_line, + column: end, + }; + let lc_region = LineColumnRegion::new(start_lc, end_lc); + let region = line_info.convert_line_column_region(lc_region); + + queries.push(TypeQuery(region)); + } + } + queries + } + + fn run_load_and_infer(src: &str) -> Result<(LoadedModule, String), std::io::Error> { use bumpalo::Bump; - use std::fs::File; - use std::io::Write; - use std::path::PathBuf; use tempfile::tempdir; let arena = &Bump::new(); @@ -40,13 +78,10 @@ mod solve_expr { let dir = tempdir()?; let filename = PathBuf::from("Test.roc"); let file_path = dir.path().join(filename); - let full_file_path = file_path.clone(); - let mut file = File::create(file_path)?; - writeln!(file, "{}", module_src)?; - drop(file); - let result = roc_load::load_and_typecheck( + let result = roc_load::load_and_typecheck_str( arena, - full_file_path, + file_path, + module_src, dir.path(), exposed_types, roc_target::TargetInfo::default_x86_64(), @@ -59,51 +94,78 @@ mod solve_expr { }; let loaded = loaded.expect("failed to load module"); - Ok(loaded) + Ok((loaded, module_src.to_string())) } - fn infer_eq_help( + fn format_problems( src: &str, - ) -> Result< - ( - Vec, - Vec, - String, - ), - std::io::Error, - > { - let LoadedModule { - module_id: home, - mut can_problems, - mut type_problems, - interns, - mut solved, - exposed_to_host, - .. - } = run_load_and_infer(src)?; + home: ModuleId, + interns: &Interns, + can_problems: Vec, + type_problems: Vec, + ) -> (String, String) { + let filename = PathBuf::from("test.roc"); + let src_lines: Vec<&str> = src.split('\n').collect(); + let lines = LineInfo::new(src); + let alloc = RocDocAllocator::new(&src_lines, home, &interns); + + let mut can_reports = vec![]; + let mut type_reports = vec![]; + + for problem in can_problems { + let report = can_problem(&alloc, &lines, filename.clone(), problem.clone()); + can_reports.push(report.pretty(&alloc)); + } + + for problem in type_problems { + if let Some(report) = type_problem(&alloc, &lines, filename.clone(), problem.clone()) { + type_reports.push(report.pretty(&alloc)); + } + } + + let mut can_reports_buf = String::new(); + let mut type_reports_buf = String::new(); + use roc_reporting::report::CiWrite; + alloc + .stack(can_reports) + .1 + .render_raw(70, &mut CiWrite::new(&mut can_reports_buf)) + .unwrap(); + alloc + .stack(type_reports) + .1 + .render_raw(70, &mut CiWrite::new(&mut type_reports_buf)) + .unwrap(); + + (can_reports_buf, type_reports_buf) + } + + fn infer_eq_help(src: &str) -> Result<(String, String, String), std::io::Error> { + let ( + LoadedModule { + module_id: home, + mut can_problems, + mut type_problems, + interns, + mut solved, + exposed_to_host, + .. + }, + src, + ) = run_load_and_infer(src)?; let mut can_problems = can_problems.remove(&home).unwrap_or_default(); let type_problems = type_problems.remove(&home).unwrap_or_default(); + // Disregard UnusedDef problems, because those are unavoidable when + // returning a function from the test expression. + can_problems.retain(|prob| !matches!(prob, roc_problem::can::Problem::UnusedDef(_, _))); + + let (can_problems, type_problems) = + format_problems(&src, home, &interns, can_problems, type_problems); + let subs = solved.inner_mut(); - // assert!(can_problems.is_empty()); - // assert!(type_problems.is_empty()); - // let CanExprOut { - // output, - // var_store, - // var, - // constraint, - // home, - // interns, - // problems: mut can_problems, - // .. - // } = can_expr(src); - // let mut subs = Subs::new(var_store.into()); - - // TODO fix this - // assert_correct_variable_usage(&constraint); - // name type vars for var in exposed_to_host.values() { name_all_type_vars(*var, subs); @@ -117,16 +179,19 @@ mod solve_expr { let actual_str = content_to_string(content, subs, home, &interns); - // Disregard UnusedDef problems, because those are unavoidable when - // returning a function from the test expression. - can_problems.retain(|prob| !matches!(prob, roc_problem::can::Problem::UnusedDef(_, _))); - Ok((type_problems, can_problems, actual_str)) } fn promote_expr_to_module(src: &str) -> String { - let mut buffer = - String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n"); + let mut buffer = String::from(indoc!( + r#" + app "test" + imports [] + provides [ main ] to "./platform" + + main = + "# + )); for line in src.lines() { // indent the body! @@ -141,7 +206,11 @@ mod solve_expr { fn infer_eq(src: &str, expected: &str) { let (_, can_problems, actual) = infer_eq_help(src).unwrap(); - assert_eq!(can_problems, Vec::new(), "Canonicalization problems: "); + assert!( + can_problems.is_empty(), + "Canonicalization problems: {}", + can_problems + ); assert_eq!(actual, expected.to_string()); } @@ -149,16 +218,73 @@ mod solve_expr { fn infer_eq_without_problem(src: &str, expected: &str) { let (type_problems, can_problems, actual) = infer_eq_help(src).unwrap(); - assert_eq!(can_problems, Vec::new(), "Canonicalization problems: "); + assert!( + can_problems.is_empty(), + "Canonicalization problems: {}", + can_problems + ); if !type_problems.is_empty() { // fail with an assert, but print the problems normally so rust doesn't try to diff // an empty vec with the problems. - panic!("expected:\n{:?}\ninferred:\n{:?}", expected, actual); + panic!( + "expected:\n{:?}\ninferred:\n{:?}\nproblems:\n{}", + expected, actual, type_problems, + ); } assert_eq!(actual, expected.to_string()); } + fn infer_queries(src: &str, expected: &[&'static str]) { + let ( + LoadedModule { + module_id: home, + mut can_problems, + mut type_problems, + mut declarations_by_id, + mut solved, + interns, + .. + }, + src, + ) = run_load_and_infer(src).unwrap(); + + let decls = declarations_by_id.remove(&home).unwrap(); + let subs = solved.inner_mut(); + + let can_problems = can_problems.remove(&home).unwrap_or_default(); + let type_problems = type_problems.remove(&home).unwrap_or_default(); + + let (can_problems, type_problems) = + format_problems(&src, home, &interns, can_problems, type_problems); + + assert!( + can_problems.is_empty(), + "Canonicalization problems: {}", + can_problems + ); + assert!(type_problems.is_empty(), "Type problems: {}", type_problems); + + let queries = parse_queries(&src); + assert!(!queries.is_empty(), "No queries provided!"); + + let mut solved_queries = Vec::with_capacity(queries.len()); + for TypeQuery(region) in queries.into_iter() { + let start = region.start().offset; + let end = region.end().offset; + let text = &src[start as usize..end as usize]; + let var = find_type_at(region, &decls).expect(&format!("No type for {}!", &text)); + + name_all_type_vars(var, subs); + let content = subs.get_content_without_compacting(var); + let actual_str = content_to_string(content, subs, home, &interns); + + solved_queries.push(format!("{} : {}", text, actual_str)); + } + + assert_eq!(solved_queries, expected) + } + fn check_inferred_abilities<'a, I>(src: &'a str, expected_specializations: I) where I: IntoIterator, @@ -170,7 +296,7 @@ mod solve_expr { interns, abilities_store, .. - } = run_load_and_infer(src).unwrap(); + } = run_load_and_infer(src).unwrap().0; let can_problems = can_problems.remove(&home).unwrap_or_default(); let type_problems = type_problems.remove(&home).unwrap_or_default(); @@ -188,11 +314,11 @@ mod solve_expr { .into_iter() .map(|(member, typ)| { let member_data = abilities_store.member_def(member).unwrap(); - let member_str = member.ident_str(&interns).as_str(); - let ability_str = member_data.parent_ability.ident_str(&interns).as_str(); + let member_str = member.as_str(&interns); + let ability_str = member_data.parent_ability.as_str(&interns); ( format!("{}:{}", ability_str, member_str), - typ.ident_str(&interns).as_str(), + typ.as_str(&interns), ) }) .collect::>(); @@ -1381,18 +1507,6 @@ mod solve_expr { ); } - #[test] - fn single_private_tag_pattern() { - infer_eq( - indoc!( - r#" - \@Foo -> 42 - "# - ), - "[ @Foo ] -> Num *", - ); - } - #[test] fn two_tag_pattern() { infer_eq( @@ -1420,18 +1534,6 @@ mod solve_expr { ); } - #[test] - fn private_tag_application() { - infer_eq( - indoc!( - r#" - @Foo "happy" 2020 - "# - ), - "[ @Foo Str (Num *) ]*", - ); - } - #[test] fn record_extraction() { infer_eq( @@ -1486,7 +1588,7 @@ mod solve_expr { } #[test] - fn global_tag_with_field() { + fn tag_with_field() { infer_eq( indoc!( r#" @@ -1498,19 +1600,6 @@ mod solve_expr { ); } - #[test] - fn private_tag_with_field() { - infer_eq( - indoc!( - r#" - when @Foo "blah" is - @Foo x -> x - "# - ), - "Str", - ); - } - #[test] fn qualified_annotation_num_integer() { infer_eq( @@ -2471,11 +2560,10 @@ mod solve_expr { } // this test is related to a bug where ext_var would have an incorrect rank. - // This match has duplicate cases, but that's not important because exhaustiveness happens - // after inference. + // This match has duplicate cases, but we ignore that. #[test] fn to_bit_record() { - infer_eq_without_problem( + infer_eq( indoc!( r#" foo = \rec -> @@ -3402,11 +3490,11 @@ mod solve_expr { } #[test] - fn div_floor() { + fn div_trunc() { infer_eq_without_problem( indoc!( r#" - Num.divFloor + Num.divTrunc "# ), "Int a, Int a -> Int a", @@ -3414,11 +3502,11 @@ mod solve_expr { } #[test] - fn div_floor_checked() { + fn div_trunc_checked() { infer_eq_without_problem( indoc!( r#" - Num.divFloorChecked + Num.divTruncChecked "# ), "Int a, Int a -> Result (Int a) [ DivByZero ]*", @@ -3603,7 +3691,7 @@ mod solve_expr { infer_eq_without_problem( indoc!( r#" - app "test" provides [ main ] to "./platform" + app "test" imports [ Result.{ Result } ] provides [ main ] to "./platform" boom = \_ -> boom {} @@ -4088,7 +4176,7 @@ mod solve_expr { } #[test] - fn double_tag_application_pattern_global() { + fn double_tag_application_pattern() { infer_eq_without_problem( indoc!( r#" @@ -4113,31 +4201,6 @@ mod solve_expr { ); } - #[test] - fn double_tag_application_pattern_private() { - infer_eq_without_problem( - indoc!( - r#" - app "test" provides [ main ] to "./platform" - - Foo : [ @Foo [ @Bar ] I64, @Empty ] - - foo : Foo - foo = @Foo @Bar 1 - - main = - when foo is - @Foo @Bar 1 -> - @Foo @Bar 2 - - x -> - x - "# - ), - "[ @Empty, @Foo [ @Bar ] I64 ]", - ); - } - #[test] fn recursive_function_with_rigid() { infer_eq_without_problem( @@ -4828,7 +4891,7 @@ mod solve_expr { infer_eq_without_problem( indoc!( r#" - canIGo : _ -> Result _ _ + canIGo : _ -> Result.Result _ _ canIGo = \color -> when color is "green" -> Ok "go!" @@ -5197,95 +5260,123 @@ mod solve_expr { { u8: (\n -> when n is - 123u8 -> n), + 123u8 -> n + _ -> n), u16: (\n -> when n is - 123u16 -> n), + 123u16 -> n + _ -> n), u32: (\n -> when n is - 123u32 -> n), + 123u32 -> n + _ -> n), u64: (\n -> when n is - 123u64 -> n), + 123u64 -> n + _ -> n), u128: (\n -> when n is - 123u128 -> n), + 123u128 -> n + _ -> n), i8: (\n -> when n is - 123i8 -> n), + 123i8 -> n + _ -> n), i16: (\n -> when n is - 123i16 -> n), + 123i16 -> n + _ -> n), i32: (\n -> when n is - 123i32 -> n), + 123i32 -> n + _ -> n), i64: (\n -> when n is - 123i64 -> n), + 123i64 -> n + _ -> n), i128: (\n -> when n is - 123i128 -> n), + 123i128 -> n + _ -> n), nat: (\n -> when n is - 123nat -> n), + 123nat -> n + _ -> n), bu8: (\n -> when n is - 0b11u8 -> n), + 0b11u8 -> n + _ -> n), bu16: (\n -> when n is - 0b11u16 -> n), + 0b11u16 -> n + _ -> n), bu32: (\n -> when n is - 0b11u32 -> n), + 0b11u32 -> n + _ -> n), bu64: (\n -> when n is - 0b11u64 -> n), + 0b11u64 -> n + _ -> n), bu128: (\n -> when n is - 0b11u128 -> n), + 0b11u128 -> n + _ -> n), bi8: (\n -> when n is - 0b11i8 -> n), + 0b11i8 -> n + _ -> n), bi16: (\n -> when n is - 0b11i16 -> n), + 0b11i16 -> n + _ -> n), bi32: (\n -> when n is - 0b11i32 -> n), + 0b11i32 -> n + _ -> n), bi64: (\n -> when n is - 0b11i64 -> n), + 0b11i64 -> n + _ -> n), bi128: (\n -> when n is - 0b11i128 -> n), + 0b11i128 -> n + _ -> n), bnat: (\n -> when n is - 0b11nat -> n), + 0b11nat -> n + _ -> n), dec: (\n -> when n is - 123.0dec -> n), + 123.0dec -> n + _ -> n), f32: (\n -> when n is - 123.0f32 -> n), + 123.0f32 -> n + _ -> n), f64: (\n -> when n is - 123.0f64 -> n), + 123.0f64 -> n + _ -> n), fdec: (\n -> when n is - 123dec -> n), + 123dec -> n + _ -> n), ff32: (\n -> when n is - 123f32 -> n), + 123f32 -> n + _ -> n), ff64: (\n -> when n is - 123f64 -> n), + 123f64 -> n + _ -> n), } "# ), @@ -5311,6 +5402,24 @@ mod solve_expr { ) } + #[test] + fn issue_2458_swapped_order() { + infer_eq_without_problem( + indoc!( + r#" + Bar a : Foo a + Foo a : [ Blah (Result (Bar a) { val: a }) ] + + v : Bar U8 + v = Blah (Ok (Blah (Err { val: 1 }))) + + v + "# + ), + "Bar U8", + ) + } + // https://github.com/rtfeldman/roc/issues/2379 #[test] fn copy_vars_referencing_copied_vars() { @@ -5398,7 +5507,7 @@ mod solve_expr { r#" Age := U32 - $Age 21 + @Age 21 "# ), r#"Age"#, @@ -5413,7 +5522,7 @@ mod solve_expr { Age := U32 a : Age - a = $Age 21 + a = @Age 21 a "# @@ -5429,7 +5538,7 @@ mod solve_expr { r#" Id n := [ Id U32 n ] - $Id (Id 21 "sasha") + @Id (Id 21 "sasha") "# ), r#"Id Str"#, @@ -5444,7 +5553,7 @@ mod solve_expr { Id n := [ Id U32 n ] a : Id Str - a = $Id (Id 21 "sasha") + a = @Id (Id 21 "sasha") a "# @@ -5462,8 +5571,8 @@ mod solve_expr { condition : Bool if condition - then $Id (Id 21 (Y "sasha")) - else $Id (Id 21 (Z "felix")) + then @Id (Id 21 (Y "sasha")) + else @Id (Id 21 (Z "felix")) "# ), r#"Id [ Y Str, Z Str ]*"#, @@ -5481,8 +5590,8 @@ mod solve_expr { v : Id [ Y Str, Z Str ] v = if condition - then $Id (Id 21 (Y "sasha")) - else $Id (Id 21 (Z "felix")) + then @Id (Id 21 (Y "sasha")) + else @Id (Id 21 (Z "felix")) v "# @@ -5498,7 +5607,7 @@ mod solve_expr { r#" Age := U32 - \$Age n -> n + \@Age n -> n "# ), r#"Age -> U32"#, @@ -5513,7 +5622,7 @@ mod solve_expr { Age := U32 v : Age -> U32 - v = \$Age n -> n + v = \@Age n -> n v "# ), @@ -5528,7 +5637,7 @@ mod solve_expr { r#" Id n := [ Id U32 n ] - \$Id (Id _ n) -> n + \@Id (Id _ n) -> n "# ), r#"Id a -> a"#, @@ -5543,7 +5652,7 @@ mod solve_expr { Id n := [ Id U32 n ] v : Id a -> a - v = \$Id (Id _ n) -> n + v = \@Id (Id _ n) -> n v "# @@ -5561,7 +5670,7 @@ mod solve_expr { strToBool : Str -> Bool - \$Id (Id _ n) -> strToBool n + \@Id (Id _ n) -> strToBool n "# ), r#"Id Str -> Bool"#, @@ -5578,7 +5687,7 @@ mod solve_expr { strToBool : Str -> Bool v : Id Str -> Bool - v = \$Id (Id _ n) -> strToBool n + v = \@Id (Id _ n) -> strToBool n v "# @@ -5596,9 +5705,10 @@ mod solve_expr { \id -> when id is - $Id (Id _ A) -> "" - $Id (Id _ B) -> "" - $Id (Id _ (C { a: "" })) -> "" + @Id (Id _ A) -> "" + @Id (Id _ B) -> "" + @Id (Id _ (C { a: "" })) -> "" + @Id (Id _ (C { a: _ })) -> "" # any other string, for exhautiveness "# ), r#"Id [ A, B, C { a : Str }* ] -> Str"#, @@ -5615,9 +5725,10 @@ mod solve_expr { f : Id [ A, B, C { a : Str }e ] -> Str f = \id -> when id is - $Id (Id _ A) -> "" - $Id (Id _ B) -> "" - $Id (Id _ (C { a: "" })) -> "" + @Id (Id _ A) -> "" + @Id (Id _ B) -> "" + @Id (Id _ (C { a: "" })) -> "" + @Id (Id _ (C { a: _ })) -> "" # any other string, for exhautiveness f "# @@ -5633,7 +5744,7 @@ mod solve_expr { r#" app "test" provides [ effectAlways ] to "./platform" - Effect a : [ @Effect ({} -> a) ] + Effect a := {} -> a effectAlways : a -> Effect a effectAlways = \x -> @@ -5701,8 +5812,8 @@ mod solve_expr { insert : Outer k, k -> Outer k insert = \m, var -> when m is - $Outer Empty -> $Outer (Wrapped var) - $Outer (Wrapped _) -> $Outer (Wrapped var) + @Outer Empty -> @Outer (Wrapped var) + @Outer (Wrapped _) -> @Outer (Wrapped var) insert "# @@ -5718,9 +5829,9 @@ mod solve_expr { r#" Outer k := [ Empty, Wrapped k ] - when ($Outer Empty) is - $Outer Empty -> $Outer (Wrapped "") - $Outer (Wrapped k) -> $Outer (Wrapped k) + when (@Outer Empty) is + @Outer Empty -> @Outer (Wrapped "") + @Outer (Wrapped k) -> @Outer (Wrapped k) "# ), r#"Outer Str"#, @@ -5734,9 +5845,9 @@ mod solve_expr { r#" Outer := [ A, B ] - when ($Outer A) is - $Outer A -> $Outer A - $Outer B -> $Outer B + when (@Outer A) is + @Outer A -> @Outer A + @Outer B -> @Outer B "# ), r#"Outer"#, @@ -5793,7 +5904,7 @@ mod solve_expr { Id := U64 - hash = \$Id n -> n + hash = \@Id n -> n "# ), [("Hash:hash", "Id")], @@ -5813,8 +5924,8 @@ mod solve_expr { Id := U64 - hash = \$Id n -> n - hash32 = \$Id n -> Num.toU32 n + hash = \@Id n -> n + hash32 = \@Id n -> Num.toU32 n "# ), [("Hash:hash", "Id"), ("Hash:hash32", "Id")], @@ -5838,11 +5949,11 @@ mod solve_expr { Id := U64 - hash = \$Id n -> n - hash32 = \$Id n -> Num.toU32 n + hash = \@Id n -> n + hash32 = \@Id n -> Num.toU32 n - eq = \$Id m, $Id n -> m == n - le = \$Id m, $Id n -> m < n + eq = \@Id m, @Id n -> m == n + le = \@Id m, @Id n -> m < n "# ), [ @@ -5867,7 +5978,7 @@ mod solve_expr { Id := U64 hash : Id -> U64 - hash = \$Id n -> n + hash = \@Id n -> n "# ), [("Hash:hash", "Id")], @@ -5905,12 +6016,222 @@ mod solve_expr { Id := U64 - hash = \$Id n -> n + hash = \@Id n -> n - zero = hash ($Id 0) + zero = hash (@Id 0) "# ), "U64", ) } + + #[test] + fn alias_ability_member() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [ thething ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + thething = + itis = hash + itis + "# + ), + "a -> U64 | a has Hash", + ) + } + + #[test] + fn when_branch_and_body_flipflop() { + infer_eq_without_problem( + indoc!( + r#" + func = \record -> + when record.tag is + A -> { record & tag: B } + B -> { record & tag: A } + + func + "# + ), + "{ tag : [ A, B ] }a -> { tag : [ A, B ] }a", + ) + } + + #[test] + fn ability_constrained_in_non_member_check() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [ hashEq ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + hashEq : a, a -> Bool | a has Hash + hashEq = \x, y -> hash x == hash y + "# + ), + "a, a -> Bool | a has Hash", + ) + } + + #[test] + fn ability_constrained_in_non_member_infer() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [ hashEq ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + hashEq = \x, y -> hash x == hash y + "# + ), + "a, a -> Bool | a has Hash", + ) + } + + #[test] + fn ability_constrained_in_non_member_infer_usage() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [ result ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + hashEq = \x, y -> hash x == hash y + + Id := U64 + hash = \@Id n -> n + + result = hashEq (@Id 100) (@Id 101) + "# + ), + "Bool", + ) + } + + #[test] + fn ability_constrained_in_non_member_multiple_specializations() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [ result ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + mulHashes = \x, y -> hash x * hash y + + Id := U64 + hash = \@Id n -> n + + Three := {} + hash = \@Three _ -> 3 + + result = mulHashes (@Id 100) (@Three {}) + "# + ), + "U64", + ) + } + + #[test] + fn intermediate_branch_types() { + infer_queries( + indoc!( + r#" + app "test" provides [ foo ] to "./platform" + + foo : Bool -> Str + foo = \ob -> + # ^^ + when ob is + # ^^ + True -> "A" + # ^^^^ + False -> "B" + # ^^^^^ + "# + ), + &[ + "ob : Bool", + "ob : Bool", + "True : [ False, True ]", + "False : [ False, True ]", + ], + ) + } + + #[test] + fn nested_open_tag_union() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [ go ] to "./platform" + + Expr : [ + Wrap Expr, + Val I64, + ] + + go : Expr -> Expr + go = \e -> + when P e is + P (Wrap (Val _)) -> Wrap e + + # This branch should force the first argument to `P` and + # the first argument to `Wrap` to be an open tag union. + # This tests checks that we don't regress on that. + P y1 -> Wrap y1 + "# + ), + indoc!(r#"Expr -> Expr"#), + ) + } + + #[test] + fn opaque_and_alias_unify() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [ always ] to "./platform" + + Effect a := {} -> a + + Task a err : Effect (Result a err) + + always : a -> Task a * + always = \x -> @Effect (\{} -> Ok x) + "# + ), + "a -> Task a *", + ); + } + + #[test] + fn export_rigid_to_lower_rank() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [ foo ] to "./platform" + + F a : { foo : a } + + foo = \arg -> + x : F b + x = arg + x.foo + "# + ), + "F b -> b", + ) + } } diff --git a/compiler/test_gen/Cargo.toml b/compiler/test_gen/Cargo.toml index 203b669d48..e1adaa8ef4 100644 --- a/compiler/test_gen/Cargo.toml +++ b/compiler/test_gen/Cargo.toml @@ -37,7 +37,7 @@ bumpalo = { version = "3.8.0", features = ["collections"] } either = "1.6.1" libc = "0.2.106" inkwell = { path = "../../vendor/inkwell" } -target-lexicon = "0.12.2" +target-lexicon = "0.12.3" libloading = "0.7.1" wasmer-wasi = "2.0.0" tempfile = "3.2.0" diff --git a/compiler/test_gen/src/gen_abilities.rs b/compiler/test_gen/src/gen_abilities.rs new file mode 100644 index 0000000000..4da1464de1 --- /dev/null +++ b/compiler/test_gen/src/gen_abilities.rs @@ -0,0 +1,218 @@ +#[cfg(feature = "gen-llvm")] +use crate::helpers::llvm::assert_evals_to; + +#[cfg(feature = "gen-dev")] +use crate::helpers::dev::assert_evals_to; + +#[cfg(feature = "gen-wasm")] +use crate::helpers::wasm::assert_evals_to; + +#[cfg(test)] +use indoc::indoc; + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn hash_specialization() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + Id := U64 + + hash = \@Id n -> n + + main = hash (@Id 1234) + "# + ), + 1234, + u64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn hash_specialization_multiple_add() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + Id := U64 + + hash = \@Id n -> n + + One := {} + + hash = \@One _ -> 1 + + main = hash (@Id 1234) + hash (@One {}) + "# + ), + 1235, + u64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn alias_member_specialization() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + Id := U64 + + hash = \@Id n -> n + + main = + aliasedHash = hash + aliasedHash (@Id 1234) + "# + ), + 1234, + u64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn ability_constrained_in_non_member_usage() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ result ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + mulHashes : a, a -> U64 | a has Hash + mulHashes = \x, y -> hash x * hash y + + Id := U64 + hash = \@Id n -> n + + result = mulHashes (@Id 5) (@Id 7) + "# + ), + 35, + u64 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn ability_constrained_in_non_member_usage_inferred() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ result ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + mulHashes = \x, y -> hash x * hash y + + Id := U64 + hash = \@Id n -> n + + result = mulHashes (@Id 5) (@Id 7) + "# + ), + 35, + u64 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn ability_constrained_in_non_member_multiple_specializations() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ result ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + mulHashes : a, b -> U64 | a has Hash, b has Hash + mulHashes = \x, y -> hash x * hash y + + Id := U64 + hash = \@Id n -> n + + Three := {} + hash = \@Three _ -> 3 + + result = mulHashes (@Id 100) (@Three {}) + "# + ), + 300, + u64 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn ability_constrained_in_non_member_multiple_specializations_inferred() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ result ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + mulHashes = \x, y -> hash x * hash y + + Id := U64 + hash = \@Id n -> n + + Three := {} + hash = \@Three _ -> 3 + + result = mulHashes (@Id 100) (@Three {}) + "# + ), + 300, + u64 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn ability_used_as_type_still_compiles() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ result ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + mulHashes : Hash, Hash -> U64 + mulHashes = \x, y -> hash x * hash y + + Id := U64 + hash = \@Id n -> n + + Three := {} + hash = \@Three _ -> 3 + + result = mulHashes (@Id 100) (@Three {}) + "# + ), + 300, + u64 + ) +} diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index 52b72ae7f0..c492936129 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -1104,7 +1104,7 @@ fn list_map_closure() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn list_map4_group() { assert_evals_to!( indoc!( @@ -1112,13 +1112,13 @@ fn list_map4_group() { List.map4 [1,2,3] [3,2,1] [2,1,3] [3,1,2] (\a, b, c, d -> Group a b c d) "# ), - RocList::from_slice(&[(1, 3, 2, 3), (2, 2, 1, 1), (3, 1, 3, 2)]), - RocList<(i64, i64, i64, i64)> + RocList::from_slice(&[[1, 3, 2, 3], [2, 2, 1, 1], [3, 1, 3, 2]]), + RocList<[i64; 4]> ); } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn list_map4_different_length() { assert_evals_to!( indoc!( @@ -1137,7 +1137,7 @@ fn list_map4_different_length() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn list_map3_group() { assert_evals_to!( indoc!( @@ -1151,7 +1151,7 @@ fn list_map3_group() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn list_map3_different_length() { assert_evals_to!( indoc!( @@ -1169,7 +1169,7 @@ fn list_map3_different_length() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn list_map2_pair() { assert_evals_to!( indoc!( @@ -1184,13 +1184,13 @@ fn list_map2_pair() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn list_map2_different_lengths() { assert_evals_to!( indoc!( r#" List.map2 - ["a", "b", "lllllllllllllongnggg" ] + ["a", "b", "lllllllllllllooooooooongnggg" ] ["b"] (\a, b -> Str.concat a b) "# @@ -2534,7 +2534,7 @@ fn list_keep_oks() { RocList ); assert_evals_to!( - "List.keepOks [1,2] (\\x -> x % 2)", + "List.keepOks [1,2] (\\x -> Num.remChecked x 2)", RocList::from_slice(&[1, 0]), RocList ); @@ -2561,7 +2561,7 @@ fn list_keep_errs() { assert_evals_to!( indoc!( r#" - List.keepErrs [0,1,2] (\x -> x % 0 |> Result.mapErr (\_ -> 32)) + List.keepErrs [0,1,2] (\x -> Num.remChecked x 0 |> Result.mapErr (\_ -> 32)) "# ), RocList::from_slice(&[32, 32, 32]), diff --git a/compiler/test_gen/src/gen_num.rs b/compiler/test_gen/src/gen_num.rs index 62f9f21eb4..9e17b80111 100644 --- a/compiler/test_gen/src/gen_num.rs +++ b/compiler/test_gen/src/gen_num.rs @@ -19,7 +19,7 @@ fn nat_alias() { assert_evals_to!( indoc!( r#" - i : Nat + i : Num.Nat i = 1 i @@ -473,7 +473,7 @@ fn f64_sqrt() { assert_evals_to!( indoc!( r#" - when Num.sqrt 100 is + when Num.sqrtChecked 100 is Ok val -> val Err _ -> -1 "# @@ -489,9 +489,7 @@ fn f64_log() { assert_evals_to!( indoc!( r#" - when Num.log 7.38905609893 is - Ok val -> val - Err _ -> -1 + Num.log 7.38905609893 "# ), 1.999999999999912, @@ -501,11 +499,11 @@ fn f64_log() { #[test] #[cfg(any(feature = "gen-llvm"))] -fn f64_log_one() { +fn f64_log_checked_one() { assert_evals_to!( indoc!( r#" - when Num.log 1 is + when Num.logChecked 1 is Ok val -> val Err _ -> -1 "# @@ -521,7 +519,7 @@ fn f64_sqrt_zero() { assert_evals_to!( indoc!( r#" - when Num.sqrt 0 is + when Num.sqrtChecked 0 is Ok val -> val Err _ -> -1 "# @@ -533,11 +531,11 @@ fn f64_sqrt_zero() { #[test] #[cfg(any(feature = "gen-llvm"))] -fn f64_sqrt_negative() { +fn f64_sqrt_checked_negative() { assert_evals_to!( indoc!( r#" - when Num.sqrt -1 is + when Num.sqrtChecked -1 is Err _ -> 42 Ok val -> val "# @@ -549,11 +547,11 @@ fn f64_sqrt_negative() { #[test] #[cfg(any(feature = "gen-llvm"))] -fn f64_log_zero() { +fn f64_log_checked_zero() { assert_evals_to!( indoc!( r#" - when Num.log 0 is + when Num.logChecked 0 is Err _ -> 42 Ok val -> val "# @@ -569,13 +567,12 @@ fn f64_log_negative() { assert_evals_to!( indoc!( r#" - when Num.log -1 is - Err _ -> 42 - Ok val -> val + Num.log -1 "# ), - 42.0, - f64 + true, + f64, + |f: f64| f.is_nan() ); } @@ -1050,7 +1047,7 @@ fn gen_div_checked_i64() { assert_evals_to!( indoc!( r#" - when Num.divFloorChecked 1000 10 is + when Num.divTruncChecked 1000 10 is Ok val -> val Err _ -> -1 "# @@ -1066,7 +1063,7 @@ fn gen_div_checked_by_zero_i64() { assert_evals_to!( indoc!( r#" - when Num.divFloorChecked 1000 0 is + when Num.divTruncChecked 1000 0 is Err DivByZero -> 99 _ -> -24 "# @@ -1082,9 +1079,7 @@ fn gen_rem_i64() { assert_evals_to!( indoc!( r#" - when Num.rem 8 3 is - Ok val -> val - Err _ -> -1 + Num.rem 8 3 "# ), 2, @@ -1094,11 +1089,11 @@ fn gen_rem_i64() { #[test] #[cfg(any(feature = "gen-llvm"))] -fn gen_rem_div_by_zero_i64() { +fn gen_rem_checked_div_by_zero_i64() { assert_evals_to!( indoc!( r#" - when Num.rem 8 0 is + when Num.remChecked 8 0 is Err DivByZero -> 4 Ok _ -> -23 "# diff --git a/compiler/test_gen/src/gen_primitives.rs b/compiler/test_gen/src/gen_primitives.rs index 4c4f020755..593096087b 100644 --- a/compiler/test_gen/src/gen_primitives.rs +++ b/compiler/test_gen/src/gen_primitives.rs @@ -593,6 +593,27 @@ fn top_level_constant() { ); } +#[test] +#[ignore] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] +fn top_level_destructure() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + {a, b} = { a: 1, b: 2 } + + main = + + a + b + "# + ), + 3, + i64 + ); +} + #[test] #[cfg(any(feature = "gen-llvm"))] fn linked_list_len_0() { @@ -1113,7 +1134,7 @@ fn io_poc_effect() { r#" app "test" provides [ main ] to "./platform" - Effect a : [ @Effect ({} -> a) ] + Effect a := {} -> a succeed : a -> Effect a succeed = \x -> @Effect \{} -> x @@ -1172,7 +1193,7 @@ fn return_wrapped_function_pointer() { r#" app "test" provides [ main ] to "./platform" - Effect a : [ @Effect ({} -> a) ] + Effect a := {} -> a foo : Effect {} foo = @Effect \{} -> {} @@ -1217,7 +1238,7 @@ fn return_wrapped_closure() { r#" app "test" provides [ main ] to "./platform" - Effect a : [ @Effect ({} -> a) ] + Effect a := {} -> a foo : Effect {} foo = @@ -1843,7 +1864,7 @@ fn task_always_twice() { r#" app "test" provides [ main ] to "./platform" - Effect a : [ @Effect ({} -> a) ] + Effect a := {} -> a effectAlways : a -> Effect a effectAlways = \x -> @@ -1888,7 +1909,7 @@ fn wildcard_rigid() { r#" app "test" provides [ main ] to "./platform" - Effect a : [ @Effect ({} -> a) ] + Effect a := {} -> a Task a err : Effect (Result a err) @@ -1918,7 +1939,7 @@ fn alias_of_alias_with_type_arguments() { r#" app "test" provides [ main ] to "./platform" - Effect a : [ @Effect a ] + Effect a := a Task a err : Effect (Result a err) @@ -1948,7 +1969,7 @@ fn todo_bad_error_message() { r#" app "test" provides [ main ] to "./platform" - Effect a : [ @Effect ({} -> a) ] + Effect a := {} -> a effectAlways : a -> Effect a effectAlways = \x -> @@ -2482,7 +2503,7 @@ fn backpassing_result() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm"))] #[should_panic(expected = "Shadowing { original_region: @57-58, shadow: @74-75 Ident")] fn function_malformed_pattern() { assert_evals_to!( @@ -2972,6 +2993,7 @@ fn mix_function_and_closure_level_of_indirection() { #[test] #[cfg(any(feature = "gen-llvm"))] +#[cfg_attr(debug_assertions, ignore)] // this test stack-overflows the compiler in debug mode fn do_pass_bool_byte_closure_layout() { // see https://github.com/rtfeldman/roc/pull/1706 // the distinction is actually important, dropping that info means some functions just get @@ -3079,7 +3101,7 @@ fn nested_rigid_alias() { r#" app "test" provides [ main ] to "./platform" - Identity a : [ @Identity a ] + Identity a := a foo : Identity a -> Identity a foo = \list -> @@ -3106,15 +3128,15 @@ fn nested_rigid_tag_union() { r#" app "test" provides [ main ] to "./platform" - foo : [ @Identity a ] -> [ @Identity a ] + foo : [ Identity a ] -> [ Identity a ] foo = \list -> - p2 : [ @Identity a ] + p2 : [ Identity a ] p2 = list p2 main = - when foo (@Identity "foo") is + when foo (Identity "foo") is _ -> "hello world" "# ), @@ -3200,7 +3222,7 @@ fn recursively_build_effect() { always {} |> after \_ -> nestHelp (m - 1) - XEffect a : [ @XEffect ({} -> a) ] + XEffect a := {} -> a always : a -> XEffect a always = \x -> @XEffect (\{} -> x) @@ -3266,7 +3288,9 @@ 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")) + Str.concat "Leverage " "agile frameworks to provide a robust synopsis for high level overviews" + |> Box.box + |> Box.unbox "# ), RocStr::from( @@ -3312,6 +3336,7 @@ fn box_and_unbox_tag_union() { r#" v : [ A U8, B U8 ] # usually stack allocated v = B 27u8 + Box.unbox (Box.box v) "# ), diff --git a/compiler/test_gen/src/gen_refcount.rs b/compiler/test_gen/src/gen_refcount.rs index 5b857a1fbc..25ade5e934 100644 --- a/compiler/test_gen/src/gen_refcount.rs +++ b/compiler/test_gen/src/gen_refcount.rs @@ -301,8 +301,8 @@ fn refcount_different_rosetrees_inc() { (Pointer, Pointer), &[ Live(2), // s - Live(2), // s1 Live(3), // i1 + Live(2), // s1 Live(1), // [i1, i1] Live(1), // i2 Live(1), // [s1, s1] diff --git a/compiler/test_gen/src/gen_tags.rs b/compiler/test_gen/src/gen_tags.rs index eefcb272cf..25648b37e4 100644 --- a/compiler/test_gen/src/gen_tags.rs +++ b/compiler/test_gen/src/gen_tags.rs @@ -7,10 +7,9 @@ use crate::helpers::dev::assert_evals_to; #[cfg(feature = "gen-wasm")] use crate::helpers::wasm::assert_evals_to; -// use crate::assert_wasm_evals_to as assert_evals_to; -#[allow(unused_imports)] +#[cfg(test)] use indoc::indoc; -#[allow(unused_imports)] +#[cfg(test)] use roc_std::{RocList, RocStr}; #[test] @@ -1055,10 +1054,10 @@ fn phantom_polymorphic() { r"# Point coordinate : [ Point coordinate I64 I64 ] - World : [ @World ] + World := {} zero : Point World - zero = Point @World 0 0 + zero = Point (@World {}) 0 0 add : Point a -> Point a add = \(Point c x y) -> (Point c x y) @@ -1201,6 +1200,7 @@ fn applied_tag_function_result() { #[test] #[cfg(any(feature = "gen-llvm"))] +#[ignore = "This test has incorrect refcounts: https://github.com/rtfeldman/roc/issues/2968"] fn applied_tag_function_linked_list() { assert_evals_to!( indoc!( @@ -1220,6 +1220,27 @@ fn applied_tag_function_linked_list() { ); } +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn applied_tag_function_pair() { + assert_evals_to!( + indoc!( + r#" + Pair a : [ Pair a a ] + + x : List (Pair Str) + x = List.map2 [ "a", "b" ] [ "c", "d" ] Pair + + when List.first x is + Ok (Pair "a" "c") -> 1 + _ -> 0 + "# + ), + 1, + i64 + ); +} + #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[should_panic(expected = "")] // TODO: this only panics because it returns 0 instead of 1! @@ -1580,3 +1601,105 @@ fn issue_2725_alias_polymorphic_lambda() { i64 ) } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn opaque_assign_to_symbol() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ out ] to "./platform" + + Variable := U8 + + fromUtf8 : U8 -> Result Variable [ InvalidVariableUtf8 ] + fromUtf8 = \char -> + Ok (@Variable char) + + out = + when fromUtf8 98 is + Ok (@Variable n) -> n + _ -> 1 + "# + ), + 98, + u8 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn issue_2777_default_branch_codegen() { + assert_evals_to!( + indoc!( + r#" + f1 = \color -> + when color is + Red -> "red" + Yellow -> "yellow" + _ -> "unknown" + + r1 = Red |> f1 |> Str.concat (f1 Orange) + + f2 = \color -> + when color is + Red -> "red" + Yellow -> "yellow" + Green -> "green" + _ -> "unknown" + + r2 = Red |> f2 |> Str.concat (f2 Orange) + + f3 = \color -> + when color is + Red -> "red" + Yellow -> "yellow" + Green -> "green" + _ -> "unknown" + + r3 = Orange |> f3 |> Str.concat (f3 Red) + + f4 = \color -> + when color is + Red -> "red" + Yellow | Gold -> "yellow" + _ -> "unknown" + + r4 = Red |> f4 |> Str.concat (f4 Orange) + + [r1, r2, r3, r4] + "# + ), + RocList::from_slice(&[ + RocStr::from("redunknown"), + RocStr::from("redunknown"), + RocStr::from("unknownred"), + RocStr::from("redunknown"), + ]), + RocList + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[should_panic(expected = "Erroneous")] +fn issue_2900_unreachable_pattern() { + assert_evals_to!( + indoc!( + r#" + foo : [ Foo, Bar, Baz, Blah ] -> Str + foo = \arg -> + when arg is + Foo -> "foo" + AnUnreachableTag -> "blah" + _ -> "other" + + foo Foo + "# + ), + RocStr::from("foo"), + RocStr, + |x| x, + true // ignore type errors + ) +} diff --git a/compiler/test_gen/src/helpers/dev.rs b/compiler/test_gen/src/helpers/dev.rs index 0ecd1bb37c..1210ce98e3 100644 --- a/compiler/test_gen/src/helpers/dev.rs +++ b/compiler/test_gen/src/helpers/dev.rs @@ -107,15 +107,12 @@ pub fn helper( let mut delayed_errors = Vec::new(); for (home, (module_path, src)) in loaded.sources { - use roc_reporting::report::{ - can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE, - }; + use roc_reporting::report::{can_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE}; let can_problems = loaded.can_problems.remove(&home).unwrap_or_default(); let type_problems = loaded.type_problems.remove(&home).unwrap_or_default(); - let mono_problems = loaded.mono_problems.remove(&home).unwrap_or_default(); - let error_count = can_problems.len() + type_problems.len() + mono_problems.len(); + let error_count = can_problems.len() + type_problems.len(); if error_count == 0 { continue; @@ -156,15 +153,6 @@ pub fn helper( lines.push(buf); } } - - for problem in mono_problems { - let report = mono_problem(&alloc, &line_info, module_path.clone(), problem); - let mut buf = String::new(); - - report.render_color_terminal(&mut buf, &alloc, &palette); - - lines.push(buf); - } } if !lines.is_empty() { diff --git a/compiler/test_gen/src/helpers/llvm.rs b/compiler/test_gen/src/helpers/llvm.rs index 03bc6b7a1e..e67a612c28 100644 --- a/compiler/test_gen/src/helpers/llvm.rs +++ b/compiler/test_gen/src/helpers/llvm.rs @@ -83,15 +83,12 @@ fn create_llvm_module<'a>( let mut delayed_errors = Vec::new(); for (home, (module_path, src)) in loaded.sources { - use roc_reporting::report::{ - can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE, - }; + use roc_reporting::report::{can_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE}; let can_problems = loaded.can_problems.remove(&home).unwrap_or_default(); let type_problems = loaded.type_problems.remove(&home).unwrap_or_default(); - let mono_problems = loaded.mono_problems.remove(&home).unwrap_or_default(); - let error_count = can_problems.len() + type_problems.len() + mono_problems.len(); + let error_count = can_problems.len() + type_problems.len(); if error_count == 0 { continue; @@ -106,8 +103,8 @@ fn create_llvm_module<'a>( use roc_problem::can::Problem::*; for problem in can_problems.into_iter() { - // Ignore "unused" problems match problem { + // Ignore "unused" problems UnusedDef(_, _) | UnusedArgument(_, _, _) | UnusedImport(_, _) @@ -122,6 +119,8 @@ fn create_llvm_module<'a>( delayed_errors.push(buf.clone()); lines.push(buf); } + // We should be able to compile even when abilities are used as types + AbilityUsedAsType(..) => {} _ => { let report = can_problem(&alloc, &line_info, module_path.clone(), problem); let mut buf = String::new(); @@ -142,16 +141,6 @@ fn create_llvm_module<'a>( lines.push(buf); } } - - for problem in mono_problems { - let report = mono_problem(&alloc, &line_info, module_path.clone(), problem); - let mut buf = String::new(); - - report.render_color_terminal(&mut buf, &alloc, &palette); - - delayed_errors.push(buf.clone()); - lines.push(buf); - } } if !lines.is_empty() { diff --git a/compiler/test_gen/src/tests.rs b/compiler/test_gen/src/tests.rs index 41923811a5..d17c610bc4 100644 --- a/compiler/test_gen/src/tests.rs +++ b/compiler/test_gen/src/tests.rs @@ -4,6 +4,7 @@ // we actually want to compare against the literal float bits #![allow(clippy::float_cmp)] +pub mod gen_abilities; pub mod gen_compare; pub mod gen_dict; pub mod gen_list; diff --git a/compiler/test_mono/generated/closure_in_list.txt b/compiler/test_mono/generated/closure_in_list.txt index 18496e4156..4687f8cc52 100644 --- a/compiler/test_mono/generated/closure_in_list.txt +++ b/compiler/test_mono/generated/closure_in_list.txt @@ -1,21 +1,21 @@ -procedure List.7 (#Attr.2): - let Test.8 : U64 = lowlevel ListLen #Attr.2; - ret Test.8; +procedure List.6 (#Attr.2): + let List.139 : U64 = lowlevel ListLen #Attr.2; + ret List.139; procedure Test.1 (Test.5): let Test.2 : I64 = 41i64; - let Test.12 : {I64} = Struct {Test.2}; - let Test.11 : List {I64} = Array [Test.12]; - ret Test.11; + let Test.11 : {I64} = Struct {Test.2}; + let Test.10 : List {I64} = Array [Test.11]; + ret Test.10; -procedure Test.3 (Test.10, #Attr.12): +procedure Test.3 (Test.9, #Attr.12): let Test.2 : I64 = StructAtIndex 0 #Attr.12; let Test.2 : I64 = 41i64; ret Test.2; procedure Test.0 (): - let Test.9 : {} = Struct {}; - let Test.7 : List {I64} = CallByName Test.1 Test.9; - let Test.6 : U64 = CallByName List.7 Test.7; + let Test.8 : {} = Struct {}; + let Test.7 : List {I64} = CallByName Test.1 Test.8; + let Test.6 : U64 = CallByName List.6 Test.7; dec Test.7; ret Test.6; diff --git a/compiler/test_mono/generated/dict.txt b/compiler/test_mono/generated/dict.txt index 4244fb0e0c..31ae33d719 100644 --- a/compiler/test_mono/generated/dict.txt +++ b/compiler/test_mono/generated/dict.txt @@ -1,13 +1,13 @@ -procedure Dict.2 (): - let Test.4 : Dict [] [] = lowlevel DictEmpty ; - ret Test.4; +procedure Dict.1 (): + let Dict.28 : Dict [] [] = lowlevel DictEmpty ; + ret Dict.28; -procedure Dict.8 (#Attr.2): - let Test.3 : U64 = lowlevel DictSize #Attr.2; +procedure Dict.7 (#Attr.2): + let Dict.27 : U64 = lowlevel DictSize #Attr.2; dec #Attr.2; - ret Test.3; + ret Dict.27; procedure Test.0 (): - let Test.2 : Dict [] [] = CallByName Dict.2; - let Test.1 : U64 = CallByName Dict.8 Test.2; + let Test.2 : Dict [] [] = CallByName Dict.1; + let Test.1 : U64 = CallByName Dict.7 Test.2; ret Test.1; diff --git a/compiler/test_mono/generated/empty_list_of_function_type.txt b/compiler/test_mono/generated/empty_list_of_function_type.txt index d25fa169f9..ae00ee7d57 100644 --- a/compiler/test_mono/generated/empty_list_of_function_type.txt +++ b/compiler/test_mono/generated/empty_list_of_function_type.txt @@ -1,23 +1,23 @@ -procedure List.3 (#Attr.2, #Attr.3): - let Test.20 : U64 = lowlevel ListLen #Attr.2; - let Test.17 : Int1 = lowlevel NumLt #Attr.3 Test.20; - if Test.17 then - let Test.19 : {} = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - let Test.18 : [C {}, C {}] = Ok Test.19; - ret Test.18; +procedure List.2 (#Attr.2, #Attr.3): + let List.144 : U64 = lowlevel ListLen #Attr.2; + let List.141 : Int1 = lowlevel NumLt #Attr.3 List.144; + if List.141 then + let List.143 : {} = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + let List.142 : [C {}, C {}] = Ok List.143; + ret List.142; else - let Test.16 : {} = Struct {}; - let Test.15 : [C {}, C {}] = Err Test.16; - ret Test.15; + let List.140 : {} = Struct {}; + let List.139 : [C {}, C {}] = Err List.140; + ret List.139; procedure Test.2 (Test.6): - let Test.24 : Str = "bar"; - ret Test.24; + let Test.18 : Str = "bar"; + ret Test.18; procedure Test.0 (): - joinpoint Test.22 Test.3: + joinpoint Test.16 Test.3: let Test.14 : U64 = 0i64; - let Test.7 : [C {}, C {}] = CallByName List.3 Test.3 Test.14; + let Test.7 : [C {}, C {}] = CallByName List.2 Test.3 Test.14; dec Test.3; let Test.11 : U8 = 1i64; let Test.12 : U8 = GetTagId Test.7; @@ -32,11 +32,11 @@ procedure Test.0 (): let Test.10 : Str = "bad!"; ret Test.10; in - let Test.25 : Int1 = false; - if Test.25 then + let Test.19 : Int1 = false; + if Test.19 then let Test.1 : List {} = Array []; - jump Test.22 Test.1; + jump Test.16 Test.1; else - let Test.23 : {} = Struct {}; - let Test.21 : List {} = Array [Test.23]; - jump Test.22 Test.21; + let Test.17 : {} = Struct {}; + let Test.15 : List {} = Array [Test.17]; + jump Test.16 Test.15; diff --git a/compiler/test_mono/generated/factorial.txt b/compiler/test_mono/generated/factorial.txt index f318bd7aa3..449fcd3072 100644 --- a/compiler/test_mono/generated/factorial.txt +++ b/compiler/test_mono/generated/factorial.txt @@ -1,24 +1,24 @@ -procedure Num.23 (#Attr.2, #Attr.3): - let Test.14 : I64 = lowlevel NumSub #Attr.2 #Attr.3; - ret Test.14; +procedure Num.20 (#Attr.2, #Attr.3): + let Num.229 : I64 = lowlevel NumSub #Attr.2 #Attr.3; + ret Num.229; -procedure Num.24 (#Attr.2, #Attr.3): - let Test.12 : I64 = lowlevel NumMul #Attr.2 #Attr.3; - ret Test.12; +procedure Num.21 (#Attr.2, #Attr.3): + let Num.228 : I64 = lowlevel NumMul #Attr.2 #Attr.3; + ret Num.228; -procedure Test.1 (Test.17, Test.18): +procedure Test.1 (Test.15, Test.16): joinpoint Test.7 Test.2 Test.3: - let Test.15 : I64 = 0i64; - let Test.16 : Int1 = lowlevel Eq Test.15 Test.2; - if Test.16 then + let Test.13 : I64 = 0i64; + let Test.14 : Int1 = lowlevel Eq Test.13 Test.2; + if Test.14 then ret Test.3; else - let Test.13 : I64 = 1i64; - let Test.10 : I64 = CallByName Num.23 Test.2 Test.13; - let Test.11 : I64 = CallByName Num.24 Test.2 Test.3; + let Test.12 : I64 = 1i64; + let Test.10 : I64 = CallByName Num.20 Test.2 Test.12; + let Test.11 : I64 = CallByName Num.21 Test.2 Test.3; jump Test.7 Test.10 Test.11; in - jump Test.7 Test.17 Test.18; + jump Test.7 Test.15 Test.16; procedure Test.0 (): let Test.5 : I64 = 10i64; diff --git a/compiler/test_mono/generated/has_none.txt b/compiler/test_mono/generated/has_none.txt index 5cd44fc90f..19a2c3d273 100644 --- a/compiler/test_mono/generated/has_none.txt +++ b/compiler/test_mono/generated/has_none.txt @@ -15,7 +15,7 @@ procedure Test.3 (Test.29): let Test.15 : Int1 = true; ret Test.15; else - let Test.7 : TODO = UnionAtIndex (Id 0) (Index 1) Test.4; + let Test.7 : [, C [C I64, C ] *self] = UnionAtIndex (Id 0) (Index 1) Test.4; jump Test.13 Test.7; in jump Test.13 Test.29; @@ -23,8 +23,8 @@ procedure Test.3 (Test.29): procedure Test.0 (): let Test.28 : I64 = 3i64; let Test.26 : [C I64, C ] = Just Test.28; - let Test.27 : TODO = Nil ; - let Test.12 : TODO = Cons Test.26 Test.27; + let Test.27 : [, C [C I64, C ] *self] = Nil ; + let Test.12 : [, C [C I64, C ] *self] = Cons Test.26 Test.27; let Test.11 : Int1 = CallByName Test.3 Test.12; dec Test.12; ret Test.11; diff --git a/compiler/test_mono/generated/if_guard_bind_variable_false.txt b/compiler/test_mono/generated/if_guard_bind_variable_false.txt index abd10f26af..cf492ae71d 100644 --- a/compiler/test_mono/generated/if_guard_bind_variable_false.txt +++ b/compiler/test_mono/generated/if_guard_bind_variable_false.txt @@ -1,16 +1,16 @@ procedure Bool.7 (#Attr.2, #Attr.3): - let Test.11 : Int1 = lowlevel Eq #Attr.2 #Attr.3; - ret Test.11; + let Bool.14 : Int1 = lowlevel Eq #Attr.2 #Attr.3; + ret Bool.14; procedure Test.1 (Test.3): let Test.6 : I64 = 10i64; - joinpoint Test.8 Test.13: - if Test.13 then + joinpoint Test.8 Test.12: + if Test.12 then let Test.7 : I64 = 0i64; ret Test.7; else - let Test.12 : I64 = 42i64; - ret Test.12; + let Test.11 : I64 = 42i64; + ret Test.11; in let Test.10 : I64 = 5i64; let Test.9 : Int1 = CallByName Bool.7 Test.6 Test.10; diff --git a/compiler/test_mono/generated/ir_int_add.txt b/compiler/test_mono/generated/ir_int_add.txt index bee729da6d..76ed6d9e5d 100644 --- a/compiler/test_mono/generated/ir_int_add.txt +++ b/compiler/test_mono/generated/ir_int_add.txt @@ -1,19 +1,19 @@ -procedure List.7 (#Attr.2): - let Test.7 : U64 = lowlevel ListLen #Attr.2; - ret Test.7; +procedure List.6 (#Attr.2): + let List.139 : U64 = lowlevel ListLen #Attr.2; + ret List.139; -procedure Num.22 (#Attr.2, #Attr.3): - let Test.5 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.5; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.230 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.230; procedure Test.0 (): - let Test.10 : U64 = 5i64; - let Test.11 : U64 = 4i64; - let Test.8 : U64 = CallByName Num.22 Test.10 Test.11; - let Test.9 : U64 = 3i64; - let Test.3 : U64 = CallByName Num.22 Test.8 Test.9; - let Test.6 : List I64 = Array [1i64, 2i64]; - let Test.4 : U64 = CallByName List.7 Test.6; - dec Test.6; - let Test.2 : U64 = CallByName Num.22 Test.3 Test.4; + let Test.8 : U64 = 5i64; + let Test.9 : U64 = 4i64; + let Test.6 : U64 = CallByName Num.19 Test.8 Test.9; + let Test.7 : U64 = 3i64; + let Test.3 : U64 = CallByName Num.19 Test.6 Test.7; + let Test.5 : List I64 = Array [1i64, 2i64]; + let Test.4 : U64 = CallByName List.6 Test.5; + dec Test.5; + let Test.2 : U64 = CallByName Num.19 Test.3 Test.4; ret Test.2; diff --git a/compiler/test_mono/generated/ir_plus.txt b/compiler/test_mono/generated/ir_plus.txt index 3bcafd0029..141f90d4a3 100644 --- a/compiler/test_mono/generated/ir_plus.txt +++ b/compiler/test_mono/generated/ir_plus.txt @@ -1,9 +1,9 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.4 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.4; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.228 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.228; procedure Test.0 (): let Test.2 : I64 = 1i64; let Test.3 : I64 = 2i64; - let Test.1 : I64 = CallByName Num.22 Test.2 Test.3; + let Test.1 : I64 = CallByName Num.19 Test.2 Test.3; ret Test.1; diff --git a/compiler/test_mono/generated/ir_round.txt b/compiler/test_mono/generated/ir_round.txt index a62c2c4a09..6686a4e849 100644 --- a/compiler/test_mono/generated/ir_round.txt +++ b/compiler/test_mono/generated/ir_round.txt @@ -1,6 +1,6 @@ procedure Num.45 (#Attr.2): - let Test.3 : I64 = lowlevel NumRound #Attr.2; - ret Test.3; + let Num.228 : I64 = lowlevel NumRound #Attr.2; + ret Num.228; procedure Test.0 (): let Test.2 : Float64 = 3.6f64; diff --git a/compiler/test_mono/generated/ir_two_defs.txt b/compiler/test_mono/generated/ir_two_defs.txt index 39d09c2318..a27db2c903 100644 --- a/compiler/test_mono/generated/ir_two_defs.txt +++ b/compiler/test_mono/generated/ir_two_defs.txt @@ -1,9 +1,9 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.6 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.6; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.228 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.228; procedure Test.0 (): let Test.4 : I64 = 3i64; let Test.5 : I64 = 4i64; - let Test.3 : I64 = CallByName Num.22 Test.4 Test.5; + let Test.3 : I64 = CallByName Num.19 Test.4 Test.5; ret Test.3; diff --git a/compiler/test_mono/generated/ir_when_idiv.txt b/compiler/test_mono/generated/ir_when_idiv.txt index 999d7f762e..b1fb84e522 100644 --- a/compiler/test_mono/generated/ir_when_idiv.txt +++ b/compiler/test_mono/generated/ir_when_idiv.txt @@ -1,14 +1,14 @@ procedure Num.40 (#Attr.2, #Attr.3): - let Test.15 : I64 = 0i64; - let Test.12 : Int1 = lowlevel NotEq #Attr.3 Test.15; - if Test.12 then - let Test.14 : I64 = lowlevel NumDivUnchecked #Attr.2 #Attr.3; - let Test.13 : [C {}, C I64] = Ok Test.14; - ret Test.13; + let Num.233 : I64 = 0i64; + let Num.230 : Int1 = lowlevel NotEq #Attr.3 Num.233; + if Num.230 then + let Num.232 : I64 = lowlevel NumDivUnchecked #Attr.2 #Attr.3; + let Num.231 : [C {}, C I64] = Ok Num.232; + ret Num.231; else - let Test.11 : {} = Struct {}; - let Test.10 : [C {}, C I64] = Err Test.11; - ret Test.10; + let Num.229 : {} = Struct {}; + let Num.228 : [C {}, C I64] = Err Num.229; + ret Num.228; procedure Test.0 (): let Test.8 : I64 = 1000i64; diff --git a/compiler/test_mono/generated/ir_when_just.txt b/compiler/test_mono/generated/ir_when_just.txt index c3e60612d4..e11182583b 100644 --- a/compiler/test_mono/generated/ir_when_just.txt +++ b/compiler/test_mono/generated/ir_when_just.txt @@ -1,18 +1,18 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.6 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.6; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.228 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.228; procedure Test.0 (): - let Test.11 : I64 = 41i64; - let Test.1 : [C I64, C ] = Just Test.11; - let Test.8 : U8 = 0i64; - let Test.9 : U8 = GetTagId Test.1; - let Test.10 : Int1 = lowlevel Eq Test.8 Test.9; - if Test.10 then + let Test.10 : I64 = 41i64; + let Test.1 : [C I64, C ] = Just Test.10; + let Test.7 : U8 = 0i64; + let Test.8 : U8 = GetTagId Test.1; + let Test.9 : Int1 = lowlevel Eq Test.7 Test.8; + if Test.9 then let Test.3 : I64 = UnionAtIndex (Id 0) (Index 0) Test.1; let Test.5 : I64 = 1i64; - let Test.4 : I64 = CallByName Num.22 Test.3 Test.5; + let Test.4 : I64 = CallByName Num.19 Test.3 Test.5; ret Test.4; else - let Test.7 : I64 = 1i64; - ret Test.7; + let Test.6 : I64 = 1i64; + ret Test.6; diff --git a/compiler/test_mono/generated/is_nil.txt b/compiler/test_mono/generated/is_nil.txt index 72af4853cd..c12eed1f71 100644 --- a/compiler/test_mono/generated/is_nil.txt +++ b/compiler/test_mono/generated/is_nil.txt @@ -11,8 +11,8 @@ procedure Test.2 (Test.3): procedure Test.0 (): let Test.15 : I64 = 2i64; - let Test.16 : TODO = Nil ; - let Test.9 : TODO = Cons Test.15 Test.16; + let Test.16 : [, C I64 *self] = Nil ; + let Test.9 : [, C I64 *self] = Cons Test.15 Test.16; let Test.8 : Int1 = CallByName Test.2 Test.9; dec Test.9; ret Test.8; diff --git a/compiler/test_mono/generated/issue_2583_specialize_errors_behind_unified_branches.txt b/compiler/test_mono/generated/issue_2583_specialize_errors_behind_unified_branches.txt index 1cb2cc555f..95aed66add 100644 --- a/compiler/test_mono/generated/issue_2583_specialize_errors_behind_unified_branches.txt +++ b/compiler/test_mono/generated/issue_2583_specialize_errors_behind_unified_branches.txt @@ -1,40 +1,40 @@ -procedure List.10 (#Attr.2): - let Test.20 : U64 = 0i64; - let Test.21 : U64 = lowlevel ListLen #Attr.2; - let Test.16 : Int1 = lowlevel NotEq Test.20 Test.21; - if Test.16 then - let Test.19 : U64 = 0i64; - let Test.18 : I64 = lowlevel ListGetUnsafe #Attr.2 Test.19; - let Test.17 : [C Int1, C I64] = Ok Test.18; - ret Test.17; +procedure List.9 (#Attr.2): + let List.145 : U64 = 0i64; + let List.146 : U64 = lowlevel ListLen #Attr.2; + let List.141 : Int1 = lowlevel NotEq List.145 List.146; + if List.141 then + let List.144 : U64 = 0i64; + let List.143 : I64 = lowlevel ListGetUnsafe #Attr.2 List.144; + let List.142 : [C Int1, C I64] = Ok List.143; + ret List.142; else - let Test.15 : Int1 = true; - let Test.14 : [C Int1, C I64] = Err Test.15; - ret Test.14; + let List.140 : Int1 = true; + let List.139 : [C Int1, C I64] = Err List.140; + ret List.139; -procedure Str.28 (#Attr.2): +procedure Str.27 (#Attr.2): let #Attr.3 : {I64, U8} = lowlevel StrToNum #Attr.2; - let Test.9 : U8 = StructAtIndex 1 #Attr.3; - let Test.10 : U8 = 0i64; - let Test.6 : Int1 = lowlevel NumGt Test.9 Test.10; - if Test.6 then - let Test.8 : Int1 = false; - let Test.7 : [C Int1, C I64] = Err Test.8; - ret Test.7; + let Str.69 : U8 = StructAtIndex 1 #Attr.3; + let Str.70 : U8 = 0i64; + let Str.66 : Int1 = lowlevel NumGt Str.69 Str.70; + if Str.66 then + let Str.68 : Int1 = false; + let Str.67 : [C Int1, C I64] = Err Str.68; + ret Str.67; else - let Test.5 : I64 = StructAtIndex 0 #Attr.3; - let Test.4 : [C Int1, C I64] = Ok Test.5; - ret Test.4; + let Str.65 : I64 = StructAtIndex 0 #Attr.3; + let Str.64 : [C Int1, C I64] = Ok Str.65; + ret Str.64; procedure Test.0 (): - let Test.11 : Int1 = true; - if Test.11 then - let Test.13 : List I64 = Array []; - let Test.12 : [C Int1, C I64] = CallByName List.10 Test.13; - dec Test.13; - ret Test.12; + let Test.4 : Int1 = true; + if Test.4 then + let Test.6 : List I64 = Array []; + let Test.5 : [C Int1, C I64] = CallByName List.9 Test.6; + dec Test.6; + ret Test.5; else let Test.3 : Str = ""; - let Test.2 : [C Int1, C I64] = CallByName Str.28 Test.3; + let Test.2 : [C Int1, C I64] = CallByName Str.27 Test.3; dec Test.3; ret Test.2; diff --git a/compiler/test_mono/generated/issue_2810.txt b/compiler/test_mono/generated/issue_2810.txt new file mode 100644 index 0000000000..60660e287c --- /dev/null +++ b/compiler/test_mono/generated/issue_2810.txt @@ -0,0 +1,6 @@ +procedure Test.0 (): + let Test.16 : [C [C [C *self, C ]], C ] = SystemTool ; + let Test.14 : [C [C *self, C ]] = Job Test.16; + let Test.13 : [C [C [C *self, C ]], C ] = FromJob Test.14; + let Test.4 : [C [C *self, C ]] = Job Test.13; + ret Test.4; diff --git a/compiler/test_mono/generated/linked_list_length_twice.txt b/compiler/test_mono/generated/linked_list_length_twice.txt index 3b1d0984f3..4d99868290 100644 --- a/compiler/test_mono/generated/linked_list_length_twice.txt +++ b/compiler/test_mono/generated/linked_list_length_twice.txt @@ -1,25 +1,25 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.10 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.10; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.229 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.229; procedure Test.3 (Test.5): - let Test.16 : Int1 = 1i64; - let Test.17 : Int1 = GetTagId Test.5; - let Test.18 : Int1 = lowlevel Eq Test.16 Test.17; - if Test.18 then - let Test.12 : I64 = 0i64; - ret Test.12; + let Test.15 : Int1 = 1i64; + let Test.16 : Int1 = GetTagId Test.5; + let Test.17 : Int1 = lowlevel Eq Test.15 Test.16; + if Test.17 then + let Test.11 : I64 = 0i64; + ret Test.11; else - let Test.6 : TODO = UnionAtIndex (Id 0) (Index 1) Test.5; - let Test.14 : I64 = 1i64; - let Test.15 : I64 = CallByName Test.3 Test.6; - let Test.13 : I64 = CallByName Num.22 Test.14 Test.15; - ret Test.13; + let Test.6 : [, C I64 *self] = UnionAtIndex (Id 0) (Index 1) Test.5; + let Test.13 : I64 = 1i64; + let Test.14 : I64 = CallByName Test.3 Test.6; + let Test.12 : I64 = CallByName Num.19 Test.13 Test.14; + ret Test.12; procedure Test.0 (): - let Test.2 : TODO = Nil ; + let Test.2 : [, C I64 *self] = Nil ; let Test.8 : I64 = CallByName Test.3 Test.2; let Test.9 : I64 = CallByName Test.3 Test.2; dec Test.2; - let Test.7 : I64 = CallByName Num.22 Test.8 Test.9; + let Test.7 : I64 = CallByName Num.19 Test.8 Test.9; ret Test.7; diff --git a/compiler/test_mono/generated/list_append.txt b/compiler/test_mono/generated/list_append.txt index c899f2abd7..93bb4798f8 100644 --- a/compiler/test_mono/generated/list_append.txt +++ b/compiler/test_mono/generated/list_append.txt @@ -1,9 +1,9 @@ -procedure List.5 (#Attr.2, #Attr.3): - let Test.4 : List I64 = lowlevel ListAppend #Attr.2 #Attr.3; - ret Test.4; +procedure List.4 (#Attr.2, #Attr.3): + let List.139 : List I64 = lowlevel ListAppend #Attr.2 #Attr.3; + ret List.139; procedure Test.0 (): let Test.2 : List I64 = Array [1i64]; let Test.3 : I64 = 2i64; - let Test.1 : List I64 = CallByName List.5 Test.2 Test.3; + let Test.1 : List I64 = CallByName List.4 Test.2 Test.3; ret Test.1; diff --git a/compiler/test_mono/generated/list_append_closure.txt b/compiler/test_mono/generated/list_append_closure.txt index 43227f7089..80d4aae9c5 100644 --- a/compiler/test_mono/generated/list_append_closure.txt +++ b/compiler/test_mono/generated/list_append_closure.txt @@ -1,10 +1,10 @@ -procedure List.5 (#Attr.2, #Attr.3): - let Test.7 : List I64 = lowlevel ListAppend #Attr.2 #Attr.3; - ret Test.7; +procedure List.4 (#Attr.2, #Attr.3): + let List.139 : List I64 = lowlevel ListAppend #Attr.2 #Attr.3; + ret List.139; procedure Test.1 (Test.2): let Test.6 : I64 = 42i64; - let Test.5 : List I64 = CallByName List.5 Test.2 Test.6; + let Test.5 : List I64 = CallByName List.4 Test.2 Test.6; ret Test.5; procedure Test.0 (): diff --git a/compiler/test_mono/generated/list_cannot_update_inplace.txt b/compiler/test_mono/generated/list_cannot_update_inplace.txt index 42ba0d2a8e..fd3a3616e8 100644 --- a/compiler/test_mono/generated/list_cannot_update_inplace.txt +++ b/compiler/test_mono/generated/list_cannot_update_inplace.txt @@ -1,50 +1,45 @@ -procedure List.4 (#Attr.2, #Attr.3, #Attr.4): - let Test.24 : U64 = lowlevel ListLen #Attr.2; - let Test.17 : Int1 = lowlevel NumLt #Attr.3 Test.24; - if Test.17 then - 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; +procedure List.3 (List.63, List.64, List.65): + let List.142 : {List I64, I64} = CallByName List.57 List.63 List.64 List.65; + let List.141 : List I64 = StructAtIndex 0 List.142; + inc List.141; + dec List.142; + ret List.141; + +procedure List.57 (#Attr.2, #Attr.3, #Attr.4): + let List.146 : U64 = lowlevel ListLen #Attr.2; + let List.144 : Int1 = lowlevel NumLt #Attr.3 List.146; + if List.144 then + let List.145 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; + ret List.145; else - ret #Attr.2; + let List.143 : {List I64, I64} = Struct {#Attr.2, #Attr.4}; + ret List.143; -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.6 (#Attr.2): + let List.140 : U64 = lowlevel ListLen #Attr.2; + ret List.140; -procedure List.7 (#Attr.2): - let Test.9 : U64 = lowlevel ListLen #Attr.2; - ret Test.9; - -procedure Num.22 (#Attr.2, #Attr.3): - let Test.7 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.7; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.228 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.228; procedure Test.1 (): - let Test.10 : List I64 = Array [1i64, 2i64, 3i64]; - ret Test.10; + let Test.8 : List I64 = Array [1i64, 2i64, 3i64]; + ret Test.8; procedure Test.2 (Test.3): - let Test.14 : U64 = 0i64; - let Test.15 : I64 = 0i64; - let Test.13 : List I64 = CallByName List.4 Test.3 Test.14 Test.15; - ret Test.13; + let Test.12 : U64 = 0i64; + let Test.13 : I64 = 0i64; + let Test.11 : List I64 = CallByName List.3 Test.3 Test.12 Test.13; + ret Test.11; procedure Test.0 (): - let Test.12 : List I64 = CallByName Test.1; - let Test.11 : List I64 = CallByName Test.2 Test.12; - let Test.5 : U64 = CallByName List.7 Test.11; - dec Test.11; - let Test.8 : List I64 = CallByName Test.1; - let Test.6 : U64 = CallByName List.7 Test.8; - dec Test.8; - let Test.4 : U64 = CallByName Num.22 Test.5 Test.6; + let Test.10 : List I64 = CallByName Test.1; + let Test.9 : List I64 = CallByName Test.2 Test.10; + let Test.5 : U64 = CallByName List.6 Test.9; + dec Test.9; + let Test.7 : List I64 = CallByName Test.1; + let Test.6 : U64 = CallByName List.6 Test.7; + dec Test.7; + let Test.4 : U64 = CallByName Num.19 Test.5 Test.6; ret Test.4; diff --git a/compiler/test_mono/generated/list_get.txt b/compiler/test_mono/generated/list_get.txt index b8d8636c97..7e5e29fff8 100644 --- a/compiler/test_mono/generated/list_get.txt +++ b/compiler/test_mono/generated/list_get.txt @@ -1,19 +1,19 @@ -procedure List.3 (#Attr.2, #Attr.3): - let Test.13 : U64 = lowlevel ListLen #Attr.2; - let Test.10 : Int1 = lowlevel NumLt #Attr.3 Test.13; - if Test.10 then - let Test.12 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - let Test.11 : [C {}, C I64] = Ok Test.12; - ret Test.11; +procedure List.2 (#Attr.2, #Attr.3): + let List.144 : U64 = lowlevel ListLen #Attr.2; + let List.141 : Int1 = lowlevel NumLt #Attr.3 List.144; + if List.141 then + let List.143 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + let List.142 : [C {}, C I64] = Ok List.143; + ret List.142; else - let Test.9 : {} = Struct {}; - let Test.8 : [C {}, C I64] = Err Test.9; - ret Test.8; + let List.140 : {} = Struct {}; + let List.139 : [C {}, C I64] = Err List.140; + ret List.139; procedure Test.1 (Test.2): let Test.6 : List I64 = Array [1i64, 2i64, 3i64]; let Test.7 : U64 = 0i64; - let Test.5 : [C {}, C I64] = CallByName List.3 Test.6 Test.7; + let Test.5 : [C {}, C I64] = CallByName List.2 Test.6 Test.7; dec Test.6; ret Test.5; diff --git a/compiler/test_mono/generated/list_len.txt b/compiler/test_mono/generated/list_len.txt index 13d10a54ce..9735510540 100644 --- a/compiler/test_mono/generated/list_len.txt +++ b/compiler/test_mono/generated/list_len.txt @@ -1,21 +1,21 @@ -procedure List.7 (#Attr.2): - let Test.10 : U64 = lowlevel ListLen #Attr.2; - ret Test.10; +procedure List.6 (#Attr.2): + let List.139 : U64 = lowlevel ListLen #Attr.2; + ret List.139; -procedure List.7 (#Attr.2): - let Test.8 : U64 = lowlevel ListLen #Attr.2; - ret Test.8; +procedure List.6 (#Attr.2): + let List.140 : U64 = lowlevel ListLen #Attr.2; + ret List.140; -procedure Num.22 (#Attr.2, #Attr.3): - let Test.6 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.6; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.228 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.228; procedure Test.0 (): - let Test.9 : List I64 = Array [1i64, 2i64, 3i64]; - let Test.4 : U64 = CallByName List.7 Test.9; - dec Test.9; - let Test.7 : List Float64 = Array [1f64]; - let Test.5 : U64 = CallByName List.7 Test.7; + let Test.7 : List I64 = Array [1i64, 2i64, 3i64]; + let Test.4 : U64 = CallByName List.6 Test.7; dec Test.7; - let Test.3 : U64 = CallByName Num.22 Test.4 Test.5; + let Test.6 : List Float64 = Array [1f64]; + let Test.5 : U64 = CallByName List.6 Test.6; + dec Test.6; + let Test.3 : U64 = CallByName Num.19 Test.4 Test.5; ret Test.3; diff --git a/compiler/test_mono/generated/list_pass_to_function.txt b/compiler/test_mono/generated/list_pass_to_function.txt index 99a6e73255..a53e9f973a 100644 --- a/compiler/test_mono/generated/list_pass_to_function.txt +++ b/compiler/test_mono/generated/list_pass_to_function.txt @@ -1,29 +1,24 @@ -procedure List.4 (#Attr.2, #Attr.3, #Attr.4): - let Test.16 : U64 = lowlevel ListLen #Attr.2; - let Test.9 : Int1 = lowlevel NumLt #Attr.3 Test.16; - if Test.9 then - 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.3 (List.63, List.64, List.65): + let List.140 : {List I64, I64} = CallByName List.57 List.63 List.64 List.65; + let List.139 : List I64 = StructAtIndex 0 List.140; + inc List.139; + dec List.140; + ret List.139; -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; +procedure List.57 (#Attr.2, #Attr.3, #Attr.4): + let List.144 : U64 = lowlevel ListLen #Attr.2; + let List.142 : Int1 = lowlevel NumLt #Attr.3 List.144; + if List.142 then + let List.143 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; + ret List.143; else - let Test.12 : {List I64, I64} = Struct {#Attr.2, #Attr.4}; - ret Test.12; + let List.141 : {List I64, I64} = Struct {#Attr.2, #Attr.4}; + ret List.141; procedure Test.2 (Test.3): let Test.6 : U64 = 0i64; let Test.7 : I64 = 0i64; - let Test.5 : List I64 = CallByName List.4 Test.3 Test.6 Test.7; + let Test.5 : List I64 = CallByName List.3 Test.3 Test.6 Test.7; ret Test.5; procedure Test.0 (): diff --git a/compiler/test_mono/generated/monomorphized_ints_aliased.txt b/compiler/test_mono/generated/monomorphized_ints_aliased.txt index b461c2a04e..f65562e3fd 100644 --- a/compiler/test_mono/generated/monomorphized_ints_aliased.txt +++ b/compiler/test_mono/generated/monomorphized_ints_aliased.txt @@ -1,21 +1,21 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.12 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.12; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.228 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.228; procedure Test.5 (Test.7, Test.8): - let Test.18 : U64 = 1i64; - ret Test.18; + let Test.17 : U64 = 1i64; + ret Test.17; procedure Test.6 (Test.7, Test.8): - let Test.15 : U64 = 1i64; - ret Test.15; + let Test.14 : U64 = 1i64; + ret Test.14; procedure Test.0 (): - 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.6 Test.13 Test.14; - let Test.9 : U64 = CallByName Num.22 Test.10 Test.11; + let Test.15 : U8 = 100i64; + let Test.16 : U32 = 100i64; + let Test.10 : U64 = CallByName Test.5 Test.15 Test.16; + let Test.12 : U32 = 100i64; + let Test.13 : U8 = 100i64; + let Test.11 : U64 = CallByName Test.6 Test.12 Test.13; + let Test.9 : U64 = CallByName Num.19 Test.10 Test.11; ret Test.9; diff --git a/compiler/test_mono/generated/nested_pattern_match.txt b/compiler/test_mono/generated/nested_pattern_match.txt index 6bb9eebf35..061f72e7c9 100644 --- a/compiler/test_mono/generated/nested_pattern_match.txt +++ b/compiler/test_mono/generated/nested_pattern_match.txt @@ -1,30 +1,30 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.8 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.8; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.228 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.228; procedure Test.0 (): - let Test.20 : I64 = 41i64; - let Test.19 : [C I64, C ] = Just Test.20; - let Test.2 : [C [C I64, C ], C ] = Just Test.19; - joinpoint Test.16: - let Test.9 : I64 = 1i64; - ret Test.9; + let Test.19 : I64 = 41i64; + let Test.18 : [C I64, C ] = Just Test.19; + let Test.2 : [C [C I64, C ], C ] = Just Test.18; + joinpoint Test.15: + let Test.8 : I64 = 1i64; + ret Test.8; in - let Test.14 : U8 = 0i64; - let Test.15 : U8 = GetTagId Test.2; - let Test.18 : Int1 = lowlevel Eq Test.14 Test.15; - if Test.18 then - let Test.11 : [C I64, C ] = UnionAtIndex (Id 0) (Index 0) Test.2; - let Test.12 : U8 = 0i64; - let Test.13 : U8 = GetTagId Test.11; - let Test.17 : Int1 = lowlevel Eq Test.12 Test.13; - if Test.17 then - let Test.10 : [C I64, C ] = UnionAtIndex (Id 0) (Index 0) Test.2; - let Test.5 : I64 = UnionAtIndex (Id 0) (Index 0) Test.10; + let Test.13 : U8 = 0i64; + let Test.14 : U8 = GetTagId Test.2; + let Test.17 : Int1 = lowlevel Eq Test.13 Test.14; + if Test.17 then + let Test.10 : [C I64, C ] = UnionAtIndex (Id 0) (Index 0) Test.2; + let Test.11 : U8 = 0i64; + let Test.12 : U8 = GetTagId Test.10; + let Test.16 : Int1 = lowlevel Eq Test.11 Test.12; + if Test.16 then + let Test.9 : [C I64, C ] = UnionAtIndex (Id 0) (Index 0) Test.2; + let Test.5 : I64 = UnionAtIndex (Id 0) (Index 0) Test.9; let Test.7 : I64 = 1i64; - let Test.6 : I64 = CallByName Num.22 Test.5 Test.7; + let Test.6 : I64 = CallByName Num.19 Test.5 Test.7; ret Test.6; else - jump Test.16; + jump Test.15; else - jump Test.16; + jump Test.15; diff --git a/compiler/test_mono/generated/opaque_assign_to_symbol.txt b/compiler/test_mono/generated/opaque_assign_to_symbol.txt new file mode 100644 index 0000000000..23eee2ceea --- /dev/null +++ b/compiler/test_mono/generated/opaque_assign_to_symbol.txt @@ -0,0 +1,8 @@ +procedure Test.3 (Test.4): + let Test.8 : [C {}, C U8] = Ok Test.4; + ret Test.8; + +procedure Test.0 (): + let Test.7 : U8 = 98i64; + let Test.6 : [C {}, C U8] = CallByName Test.3 Test.7; + ret Test.6; diff --git a/compiler/test_mono/generated/optional_when.txt b/compiler/test_mono/generated/optional_when.txt index b2649e0a96..25ab447db4 100644 --- a/compiler/test_mono/generated/optional_when.txt +++ b/compiler/test_mono/generated/optional_when.txt @@ -1,11 +1,11 @@ -procedure Num.24 (#Attr.2, #Attr.3): - let Test.18 : I64 = lowlevel NumMul #Attr.2 #Attr.3; - ret Test.18; +procedure Num.21 (#Attr.2, #Attr.3): + let Num.230 : I64 = lowlevel NumMul #Attr.2 #Attr.3; + ret Num.230; procedure Test.1 (Test.6): - let Test.22 : Int1 = false; - let Test.23 : Int1 = lowlevel Eq Test.22 Test.6; - if Test.23 then + let Test.21 : Int1 = false; + let Test.22 : Int1 = lowlevel Eq Test.21 Test.6; + if Test.22 then let Test.8 : I64 = 3i64; ret Test.8; else @@ -13,10 +13,10 @@ procedure Test.1 (Test.6): ret Test.10; procedure Test.1 (Test.6): - let Test.30 : Int1 = StructAtIndex 1 Test.6; - let Test.31 : Int1 = false; - let Test.32 : Int1 = lowlevel Eq Test.31 Test.30; - if Test.32 then + let Test.29 : Int1 = StructAtIndex 1 Test.6; + let Test.30 : Int1 = false; + let Test.31 : Int1 = lowlevel Eq Test.30 Test.29; + if Test.31 then let Test.8 : I64 = StructAtIndex 0 Test.6; ret Test.8; else @@ -24,19 +24,19 @@ procedure Test.1 (Test.6): ret Test.10; procedure Test.0 (): - let Test.40 : I64 = 7i64; - let Test.41 : Int1 = false; - let Test.39 : {I64, Int1} = Struct {Test.40, Test.41}; - let Test.35 : I64 = CallByName Test.1 Test.39; - let Test.38 : Int1 = false; - let Test.36 : I64 = CallByName Test.1 Test.38; - let Test.25 : I64 = CallByName Num.24 Test.35 Test.36; - let Test.33 : I64 = 11i64; - let Test.34 : Int1 = true; - let Test.27 : {I64, Int1} = Struct {Test.33, Test.34}; - let Test.26 : I64 = CallByName Test.1 Test.27; - let Test.16 : I64 = CallByName Num.24 Test.25 Test.26; - let Test.24 : Int1 = true; - let Test.17 : I64 = CallByName Test.1 Test.24; - let Test.15 : I64 = CallByName Num.24 Test.16 Test.17; + let Test.39 : I64 = 7i64; + let Test.40 : Int1 = false; + let Test.38 : {I64, Int1} = Struct {Test.39, Test.40}; + let Test.34 : I64 = CallByName Test.1 Test.38; + let Test.37 : Int1 = false; + let Test.35 : I64 = CallByName Test.1 Test.37; + let Test.24 : I64 = CallByName Num.21 Test.34 Test.35; + let Test.32 : I64 = 11i64; + let Test.33 : Int1 = true; + let Test.26 : {I64, Int1} = Struct {Test.32, Test.33}; + let Test.25 : I64 = CallByName Test.1 Test.26; + let Test.16 : I64 = CallByName Num.21 Test.24 Test.25; + let Test.23 : Int1 = true; + let Test.17 : I64 = CallByName Test.1 Test.23; + let Test.15 : I64 = CallByName Num.21 Test.16 Test.17; ret Test.15; diff --git a/compiler/test_mono/generated/peano.txt b/compiler/test_mono/generated/peano.txt index bee928bf10..88f73530ed 100644 --- a/compiler/test_mono/generated/peano.txt +++ b/compiler/test_mono/generated/peano.txt @@ -1,6 +1,6 @@ procedure Test.0 (): - let Test.10 : TODO = Z ; - let Test.9 : TODO = S Test.10; - let Test.8 : TODO = S Test.9; - let Test.2 : TODO = S Test.8; + let Test.10 : [, C *self] = Z ; + let Test.9 : [, C *self] = S Test.10; + let Test.8 : [, C *self] = S Test.9; + let Test.2 : [, C *self] = S Test.8; ret Test.2; diff --git a/compiler/test_mono/generated/peano1.txt b/compiler/test_mono/generated/peano1.txt index 8d6b38bd55..2c0ab5219e 100644 --- a/compiler/test_mono/generated/peano1.txt +++ b/compiler/test_mono/generated/peano1.txt @@ -1,8 +1,8 @@ procedure Test.0 (): - let Test.14 : TODO = Z ; - let Test.13 : TODO = S Test.14; - let Test.12 : TODO = S Test.13; - let Test.2 : TODO = S Test.12; + let Test.14 : [, C *self] = Z ; + let Test.13 : [, C *self] = S Test.14; + let Test.12 : [, C *self] = S Test.13; + let Test.2 : [, C *self] = S Test.12; let Test.9 : Int1 = 1i64; let Test.10 : Int1 = GetTagId Test.2; dec Test.2; diff --git a/compiler/test_mono/generated/peano2.txt b/compiler/test_mono/generated/peano2.txt index a708c54baf..466d098896 100644 --- a/compiler/test_mono/generated/peano2.txt +++ b/compiler/test_mono/generated/peano2.txt @@ -1,13 +1,13 @@ procedure Test.0 (): - let Test.20 : TODO = Z ; - let Test.19 : TODO = S Test.20; - let Test.18 : TODO = S Test.19; - let Test.2 : TODO = S Test.18; + let Test.20 : [, C *self] = Z ; + let Test.19 : [, C *self] = S Test.20; + let Test.18 : [, C *self] = S Test.19; + let Test.2 : [, C *self] = S Test.18; let Test.15 : Int1 = 0i64; let Test.16 : Int1 = GetTagId Test.2; let Test.17 : Int1 = lowlevel Eq Test.15 Test.16; if Test.17 then - let Test.11 : TODO = UnionAtIndex (Id 0) (Index 0) Test.2; + let Test.11 : [, C *self] = UnionAtIndex (Id 0) (Index 0) Test.2; inc Test.11; dec Test.2; let Test.12 : Int1 = 0i64; diff --git a/compiler/test_mono/generated/quicksort_help.txt b/compiler/test_mono/generated/quicksort_help.txt index c9bad93164..1c286140e4 100644 --- a/compiler/test_mono/generated/quicksort_help.txt +++ b/compiler/test_mono/generated/quicksort_help.txt @@ -1,37 +1,37 @@ +procedure Num.19 (#Attr.2, #Attr.3): + let Num.228 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.228; + +procedure Num.20 (#Attr.2, #Attr.3): + let Num.229 : I64 = lowlevel NumSub #Attr.2 #Attr.3; + ret Num.229; + procedure Num.22 (#Attr.2, #Attr.3): - let Test.19 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.19; + let Num.230 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.230; -procedure Num.23 (#Attr.2, #Attr.3): - let Test.22 : I64 = lowlevel NumSub #Attr.2 #Attr.3; - ret Test.22; - -procedure Num.25 (#Attr.2, #Attr.3): - let Test.26 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Test.26; - -procedure Test.1 (Test.27, Test.28, Test.29): +procedure Test.1 (Test.24, Test.25, Test.26): joinpoint Test.12 Test.2 Test.3 Test.4: - let Test.14 : Int1 = CallByName Num.25 Test.3 Test.4; + let Test.14 : Int1 = CallByName Num.22 Test.3 Test.4; if Test.14 then dec Test.2; - let Test.25 : List [] = Array []; - let Test.24 : I64 = 0i64; - let Test.23 : {I64, List []} = Struct {Test.24, Test.25}; - let Test.5 : I64 = StructAtIndex 0 Test.23; - let Test.6 : List [] = StructAtIndex 1 Test.23; + let Test.23 : List [] = Array []; + let Test.22 : I64 = 0i64; + let Test.21 : {I64, List []} = Struct {Test.22, Test.23}; + let Test.5 : I64 = StructAtIndex 0 Test.21; + let Test.6 : List [] = StructAtIndex 1 Test.21; inc Test.6; - dec Test.23; - let Test.21 : I64 = 1i64; - let Test.20 : I64 = CallByName Num.23 Test.5 Test.21; - let Test.16 : List I64 = CallByName Test.1 Test.6 Test.3 Test.20; + dec Test.21; + let Test.20 : I64 = 1i64; + let Test.19 : I64 = CallByName Num.20 Test.5 Test.20; + let Test.16 : List I64 = CallByName Test.1 Test.6 Test.3 Test.19; let Test.18 : I64 = 1i64; - let Test.17 : I64 = CallByName Num.22 Test.5 Test.18; + let Test.17 : I64 = CallByName Num.19 Test.5 Test.18; jump Test.12 Test.16 Test.17 Test.4; else ret Test.2; in - jump Test.12 Test.27 Test.28 Test.29; + jump Test.12 Test.24 Test.25 Test.26; procedure Test.0 (): let Test.9 : List I64 = Array []; diff --git a/compiler/test_mono/generated/quicksort_swap.txt b/compiler/test_mono/generated/quicksort_swap.txt index 467ec71cf3..ba42385f06 100644 --- a/compiler/test_mono/generated/quicksort_swap.txt +++ b/compiler/test_mono/generated/quicksort_swap.txt @@ -1,72 +1,67 @@ -procedure List.3 (#Attr.2, #Attr.3): - 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; +procedure List.2 (#Attr.2, #Attr.3): + let List.154 : U64 = lowlevel ListLen #Attr.2; + let List.151 : Int1 = lowlevel NumLt #Attr.3 List.154; + if List.151 then + let List.153 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + let List.152 : [C {}, C I64] = Ok List.153; + ret List.152; else - let Test.38 : {} = Struct {}; - let Test.37 : [C {}, C I64] = Err Test.38; - ret Test.37; + let List.150 : {} = Struct {}; + let List.149 : [C {}, C I64] = Err List.150; + ret List.149; -procedure List.4 (#Attr.2, #Attr.3, #Attr.4): - let Test.20 : U64 = lowlevel ListLen #Attr.2; - let Test.13 : Int1 = lowlevel NumLt #Attr.3 Test.20; - if Test.13 then - 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.3 (List.63, List.64, List.65): + let List.142 : {List I64, I64} = CallByName List.57 List.63 List.64 List.65; + let List.141 : List I64 = StructAtIndex 0 List.142; + inc List.141; + dec List.142; + ret List.141; -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; +procedure List.57 (#Attr.2, #Attr.3, #Attr.4): + let List.158 : U64 = lowlevel ListLen #Attr.2; + let List.156 : Int1 = lowlevel NumLt #Attr.3 List.158; + if List.156 then + let List.157 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; + ret List.157; else - let Test.16 : {List I64, I64} = Struct {#Attr.2, #Attr.4}; - ret Test.16; + let List.155 : {List I64, I64} = Struct {#Attr.2, #Attr.4}; + ret List.155; procedure Test.1 (Test.2): - 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; + let Test.28 : U64 = 0i64; + let Test.26 : [C {}, C I64] = CallByName List.2 Test.2 Test.28; + let Test.27 : U64 = 0i64; + let Test.25 : [C {}, C I64] = CallByName List.2 Test.2 Test.27; + let Test.8 : {[C {}, C I64], [C {}, C I64]} = Struct {Test.25, Test.26}; + joinpoint Test.22: + let Test.13 : List I64 = Array []; + ret Test.13; in - 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.19 : [C {}, C I64] = StructAtIndex 1 Test.8; + let Test.20 : U8 = 1i64; + let Test.21 : U8 = GetTagId Test.19; + let Test.24 : Int1 = lowlevel Eq Test.20 Test.21; + if Test.24 then + let Test.16 : [C {}, C I64] = StructAtIndex 0 Test.8; + let Test.17 : U8 = 1i64; + let Test.18 : U8 = GetTagId Test.16; + let Test.23 : Int1 = lowlevel Eq Test.17 Test.18; + if Test.23 then + let Test.15 : [C {}, C I64] = StructAtIndex 0 Test.8; + let Test.4 : I64 = UnionAtIndex (Id 1) (Index 0) Test.15; + let Test.14 : [C {}, C I64] = StructAtIndex 1 Test.8; + let Test.5 : I64 = UnionAtIndex (Id 1) (Index 0) Test.14; + let Test.12 : U64 = 0i64; + let Test.10 : List I64 = CallByName List.3 Test.2 Test.12 Test.5; let Test.11 : U64 = 0i64; - let Test.9 : List I64 = CallByName List.4 Test.10 Test.11 Test.4; + let Test.9 : List I64 = CallByName List.3 Test.10 Test.11 Test.4; ret Test.9; else dec Test.2; - jump Test.31; + jump Test.22; else dec Test.2; - jump Test.31; + jump Test.22; procedure Test.0 (): let Test.7 : List I64 = Array [1i64, 2i64]; diff --git a/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt b/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt index efbc0ca6e2..355268a33e 100644 --- a/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt @@ -1,16 +1,16 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.8 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.8; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.228 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.228; procedure Test.1 (Test.4): let Test.2 : I64 = StructAtIndex 0 Test.4; let Test.3 : I64 = StructAtIndex 1 Test.4; - let Test.7 : I64 = CallByName Num.22 Test.2 Test.3; + let Test.7 : I64 = CallByName Num.19 Test.2 Test.3; ret Test.7; procedure Test.0 (): - let Test.9 : I64 = 4i64; - let Test.10 : I64 = 9i64; - let Test.6 : {I64, I64} = Struct {Test.9, Test.10}; + let Test.8 : I64 = 4i64; + let Test.9 : I64 = 9i64; + let Test.6 : {I64, I64} = Struct {Test.8, Test.9}; let Test.5 : I64 = CallByName Test.1 Test.6; ret Test.5; diff --git a/compiler/test_mono/generated/record_optional_field_function_use_default.txt b/compiler/test_mono/generated/record_optional_field_function_use_default.txt index b3a418d3f3..fce1b7a406 100644 --- a/compiler/test_mono/generated/record_optional_field_function_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_function_use_default.txt @@ -1,13 +1,13 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.9 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.9; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.228 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.228; procedure Test.1 (Test.4): let Test.8 : I64 = 10i64; - let Test.7 : I64 = CallByName Num.22 Test.8 Test.4; + let Test.7 : I64 = CallByName Num.19 Test.8 Test.4; ret Test.7; procedure Test.0 (): - let Test.10 : I64 = 9i64; - let Test.5 : I64 = CallByName Test.1 Test.10; + let Test.9 : I64 = 9i64; + let Test.5 : I64 = CallByName Test.1 Test.9; ret Test.5; diff --git a/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt b/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt index 4c10690d37..864862eaef 100644 --- a/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt @@ -1,16 +1,16 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.8 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.8; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.228 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.228; procedure Test.1 (Test.2): let Test.3 : I64 = StructAtIndex 0 Test.2; let Test.4 : I64 = StructAtIndex 1 Test.2; - let Test.7 : I64 = CallByName Num.22 Test.3 Test.4; + let Test.7 : I64 = CallByName Num.19 Test.3 Test.4; ret Test.7; procedure Test.0 (): - let Test.9 : I64 = 4i64; - let Test.10 : I64 = 9i64; - let Test.6 : {I64, I64} = Struct {Test.9, Test.10}; + let Test.8 : I64 = 4i64; + let Test.9 : I64 = 9i64; + let Test.6 : {I64, I64} = Struct {Test.8, Test.9}; let Test.5 : I64 = CallByName Test.1 Test.6; ret Test.5; diff --git a/compiler/test_mono/generated/record_optional_field_let_use_default.txt b/compiler/test_mono/generated/record_optional_field_let_use_default.txt index 14a00e3638..e8bf95b3e4 100644 --- a/compiler/test_mono/generated/record_optional_field_let_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_let_use_default.txt @@ -1,13 +1,13 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.8 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.8; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.228 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.228; procedure Test.1 (Test.2): let Test.3 : I64 = 10i64; - let Test.7 : I64 = CallByName Num.22 Test.3 Test.2; + let Test.7 : I64 = CallByName Num.19 Test.3 Test.2; ret Test.7; procedure Test.0 (): - let Test.9 : I64 = 9i64; - let Test.5 : I64 = CallByName Test.1 Test.9; + let Test.8 : I64 = 9i64; + let Test.5 : I64 = CallByName Test.1 Test.8; ret Test.5; diff --git a/compiler/test_mono/generated/rigids.txt b/compiler/test_mono/generated/rigids.txt index bb5a06de40..fb83256d5a 100644 --- a/compiler/test_mono/generated/rigids.txt +++ b/compiler/test_mono/generated/rigids.txt @@ -1,68 +1,63 @@ -procedure List.3 (#Attr.2, #Attr.3): - 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; +procedure List.2 (#Attr.2, #Attr.3): + let List.154 : U64 = lowlevel ListLen #Attr.2; + let List.151 : Int1 = lowlevel NumLt #Attr.3 List.154; + if List.151 then + let List.153 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + let List.152 : [C {}, C I64] = Ok List.153; + ret List.152; else - let Test.40 : {} = Struct {}; - let Test.39 : [C {}, C I64] = Err Test.40; - ret Test.39; + let List.150 : {} = Struct {}; + let List.149 : [C {}, C I64] = Err List.150; + ret List.149; -procedure List.4 (#Attr.2, #Attr.3, #Attr.4): - let Test.24 : U64 = lowlevel ListLen #Attr.2; - let Test.17 : Int1 = lowlevel NumLt #Attr.3 Test.24; - if Test.17 then - 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.3 (List.63, List.64, List.65): + let List.142 : {List I64, I64} = CallByName List.57 List.63 List.64 List.65; + let List.141 : List I64 = StructAtIndex 0 List.142; + inc List.141; + dec List.142; + ret List.141; -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; +procedure List.57 (#Attr.2, #Attr.3, #Attr.4): + let List.158 : U64 = lowlevel ListLen #Attr.2; + let List.156 : Int1 = lowlevel NumLt #Attr.3 List.158; + if List.156 then + let List.157 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; + ret List.157; else - let Test.20 : {List I64, I64} = Struct {#Attr.2, #Attr.4}; - ret Test.20; + let List.155 : {List I64, I64} = Struct {#Attr.2, #Attr.4}; + ret List.155; 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; + let Test.29 : [C {}, C I64] = CallByName List.2 Test.4 Test.3; + let Test.28 : [C {}, C I64] = CallByName List.2 Test.4 Test.2; + let Test.13 : {[C {}, C I64], [C {}, C I64]} = Struct {Test.28, Test.29}; + joinpoint Test.25: + let Test.16 : List I64 = Array []; + ret Test.16; in - 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; + let Test.22 : [C {}, C I64] = StructAtIndex 1 Test.13; + let Test.23 : U8 = 1i64; + let Test.24 : U8 = GetTagId Test.22; + let Test.27 : Int1 = lowlevel Eq Test.23 Test.24; + if Test.27 then + let Test.19 : [C {}, C I64] = StructAtIndex 0 Test.13; + let Test.20 : U8 = 1i64; + let Test.21 : U8 = GetTagId Test.19; + let Test.26 : Int1 = lowlevel Eq Test.20 Test.21; + if Test.26 then + let Test.18 : [C {}, C I64] = StructAtIndex 0 Test.13; + let Test.6 : I64 = UnionAtIndex (Id 1) (Index 0) Test.18; + let Test.17 : [C {}, C I64] = StructAtIndex 1 Test.13; + let Test.7 : I64 = UnionAtIndex (Id 1) (Index 0) Test.17; + let Test.15 : List I64 = CallByName List.3 Test.4 Test.2 Test.7; + let Test.14 : List I64 = CallByName List.3 Test.15 Test.3 Test.6; ret Test.14; else dec Test.4; - jump Test.34; + jump Test.25; else dec Test.4; - jump Test.34; + jump Test.25; procedure Test.0 (): let Test.10 : U64 = 0i64; diff --git a/compiler/test_mono/generated/somehow_drops_definitions.txt b/compiler/test_mono/generated/somehow_drops_definitions.txt index 067e533b9c..8e5dc13493 100644 --- a/compiler/test_mono/generated/somehow_drops_definitions.txt +++ b/compiler/test_mono/generated/somehow_drops_definitions.txt @@ -1,27 +1,27 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.27 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.27; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.229 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.229; -procedure Num.24 (#Attr.2, #Attr.3): - let Test.22 : I64 = lowlevel NumMul #Attr.2 #Attr.3; - ret Test.22; +procedure Num.21 (#Attr.2, #Attr.3): + let Num.228 : I64 = lowlevel NumMul #Attr.2 #Attr.3; + ret Num.228; procedure Test.1 (): - let Test.28 : I64 = 1i64; - ret Test.28; + let Test.26 : I64 = 1i64; + ret Test.26; procedure Test.2 (): - let Test.23 : I64 = 2i64; - ret Test.23; + let Test.22 : I64 = 2i64; + ret Test.22; procedure Test.3 (Test.6): - let Test.26 : I64 = CallByName Test.1; - let Test.25 : I64 = CallByName Num.22 Test.6 Test.26; - ret Test.25; + let Test.25 : I64 = CallByName Test.1; + let Test.24 : I64 = CallByName Num.19 Test.6 Test.25; + ret Test.24; procedure Test.4 (Test.7): let Test.21 : I64 = CallByName Test.2; - let Test.20 : I64 = CallByName Num.24 Test.7 Test.21; + let Test.20 : I64 = CallByName Num.21 Test.7 Test.21; ret Test.20; procedure Test.5 (Test.8, Test.9): @@ -44,8 +44,8 @@ procedure Test.0 (): let Test.11 : I64 = CallByName Test.5 Test.12 Test.13; ret Test.11; in - let Test.24 : Int1 = true; - if Test.24 then + let Test.23 : Int1 = true; + if Test.23 then let Test.3 : Int1 = false; jump Test.19 Test.3; else diff --git a/compiler/test_mono/generated/specialize_ability_call.txt b/compiler/test_mono/generated/specialize_ability_call.txt new file mode 100644 index 0000000000..7f4932ff35 --- /dev/null +++ b/compiler/test_mono/generated/specialize_ability_call.txt @@ -0,0 +1,7 @@ +procedure Test.5 (Test.7): + ret Test.7; + +procedure Test.0 (): + let Test.9 : U64 = 1234i64; + let Test.8 : U64 = CallByName Test.5 Test.9; + ret Test.8; diff --git a/compiler/test_mono/generated/specialize_closures.txt b/compiler/test_mono/generated/specialize_closures.txt index 1d6423ffb7..65fbdf1102 100644 --- a/compiler/test_mono/generated/specialize_closures.txt +++ b/compiler/test_mono/generated/specialize_closures.txt @@ -1,10 +1,10 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.28 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.28; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.229 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.229; -procedure Num.24 (#Attr.2, #Attr.3): - let Test.25 : I64 = lowlevel NumMul #Attr.2 #Attr.3; - ret Test.25; +procedure Num.21 (#Attr.2, #Attr.3): + let Num.228 : I64 = lowlevel NumMul #Attr.2 #Attr.3; + ret Num.228; procedure Test.1 (Test.2, Test.3): let Test.17 : U8 = GetTagId Test.2; @@ -23,29 +23,29 @@ procedure Test.1 (Test.2, Test.3): procedure Test.7 (Test.10, #Attr.12): let Test.4 : I64 = UnionAtIndex (Id 0) (Index 0) #Attr.12; - let Test.27 : I64 = CallByName Num.22 Test.10 Test.4; - ret Test.27; + let Test.26 : I64 = CallByName Num.19 Test.10 Test.4; + ret Test.26; procedure Test.8 (Test.11, #Attr.12): let Test.6 : Int1 = UnionAtIndex (Id 1) (Index 1) #Attr.12; let Test.5 : I64 = UnionAtIndex (Id 1) (Index 0) #Attr.12; if Test.6 then - let Test.24 : I64 = CallByName Num.24 Test.11 Test.5; + let Test.24 : I64 = CallByName Num.21 Test.11 Test.5; ret Test.24; else ret Test.11; procedure Test.0 (): + let Test.4 : I64 = 1i64; let Test.5 : I64 = 2i64; let Test.6 : Int1 = true; - let Test.4 : I64 = 1i64; joinpoint Test.22 Test.14: let Test.15 : I64 = 42i64; let Test.13 : I64 = CallByName Test.1 Test.14 Test.15; ret Test.13; in - let Test.26 : Int1 = true; - if Test.26 then + let Test.25 : Int1 = true; + if Test.25 then let Test.7 : [C I64, C I64 Int1] = ClosureTag(Test.7) Test.4; jump Test.22 Test.7; else diff --git a/compiler/test_mono/generated/specialize_lowlevel.txt b/compiler/test_mono/generated/specialize_lowlevel.txt index 8a15d461e3..b6ede4db71 100644 --- a/compiler/test_mono/generated/specialize_lowlevel.txt +++ b/compiler/test_mono/generated/specialize_lowlevel.txt @@ -1,24 +1,24 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.24 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.24; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.229 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.229; -procedure Num.24 (#Attr.2, #Attr.3): - let Test.21 : I64 = lowlevel NumMul #Attr.2 #Attr.3; - ret Test.21; +procedure Num.21 (#Attr.2, #Attr.3): + let Num.228 : I64 = lowlevel NumMul #Attr.2 #Attr.3; + ret Num.228; procedure Test.6 (Test.8, #Attr.12): let Test.4 : I64 = UnionAtIndex (Id 0) (Index 0) #Attr.12; - let Test.23 : I64 = CallByName Num.22 Test.8 Test.4; - ret Test.23; + let Test.22 : I64 = CallByName Num.19 Test.8 Test.4; + ret Test.22; procedure Test.7 (Test.9, #Attr.12): let Test.5 : I64 = UnionAtIndex (Id 1) (Index 0) #Attr.12; - let Test.20 : I64 = CallByName Num.24 Test.9 Test.5; + let Test.20 : I64 = CallByName Num.21 Test.9 Test.5; ret Test.20; procedure Test.0 (): - let Test.5 : I64 = 2i64; let Test.4 : I64 = 1i64; + let Test.5 : I64 = 2i64; let Test.12 : I64 = 42i64; joinpoint Test.19 Test.13: let Test.14 : U8 = GetTagId Test.13; @@ -35,8 +35,8 @@ procedure Test.0 (): jump Test.15 Test.17; in - let Test.22 : Int1 = true; - if Test.22 then + let Test.21 : Int1 = true; + if Test.21 then let Test.6 : [C I64, C I64] = ClosureTag(Test.6) Test.4; jump Test.19 Test.6; else diff --git a/compiler/test_mono/generated/when_nested_maybe.txt b/compiler/test_mono/generated/when_nested_maybe.txt index 6bb9eebf35..061f72e7c9 100644 --- a/compiler/test_mono/generated/when_nested_maybe.txt +++ b/compiler/test_mono/generated/when_nested_maybe.txt @@ -1,30 +1,30 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.8 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.8; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.228 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.228; procedure Test.0 (): - let Test.20 : I64 = 41i64; - let Test.19 : [C I64, C ] = Just Test.20; - let Test.2 : [C [C I64, C ], C ] = Just Test.19; - joinpoint Test.16: - let Test.9 : I64 = 1i64; - ret Test.9; + let Test.19 : I64 = 41i64; + let Test.18 : [C I64, C ] = Just Test.19; + let Test.2 : [C [C I64, C ], C ] = Just Test.18; + joinpoint Test.15: + let Test.8 : I64 = 1i64; + ret Test.8; in - let Test.14 : U8 = 0i64; - let Test.15 : U8 = GetTagId Test.2; - let Test.18 : Int1 = lowlevel Eq Test.14 Test.15; - if Test.18 then - let Test.11 : [C I64, C ] = UnionAtIndex (Id 0) (Index 0) Test.2; - let Test.12 : U8 = 0i64; - let Test.13 : U8 = GetTagId Test.11; - let Test.17 : Int1 = lowlevel Eq Test.12 Test.13; - if Test.17 then - let Test.10 : [C I64, C ] = UnionAtIndex (Id 0) (Index 0) Test.2; - let Test.5 : I64 = UnionAtIndex (Id 0) (Index 0) Test.10; + let Test.13 : U8 = 0i64; + let Test.14 : U8 = GetTagId Test.2; + let Test.17 : Int1 = lowlevel Eq Test.13 Test.14; + if Test.17 then + let Test.10 : [C I64, C ] = UnionAtIndex (Id 0) (Index 0) Test.2; + let Test.11 : U8 = 0i64; + let Test.12 : U8 = GetTagId Test.10; + let Test.16 : Int1 = lowlevel Eq Test.11 Test.12; + if Test.16 then + let Test.9 : [C I64, C ] = UnionAtIndex (Id 0) (Index 0) Test.2; + let Test.5 : I64 = UnionAtIndex (Id 0) (Index 0) Test.9; let Test.7 : I64 = 1i64; - let Test.6 : I64 = CallByName Num.22 Test.5 Test.7; + let Test.6 : I64 = CallByName Num.19 Test.5 Test.7; ret Test.6; else - jump Test.16; + jump Test.15; else - jump Test.16; + jump Test.15; diff --git a/compiler/test_mono/generated/when_on_record.txt b/compiler/test_mono/generated/when_on_record.txt index b313695135..9dcef9aa67 100644 --- a/compiler/test_mono/generated/when_on_record.txt +++ b/compiler/test_mono/generated/when_on_record.txt @@ -1,9 +1,9 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.5 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.5; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.228 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.228; procedure Test.0 (): - let Test.6 : I64 = 2i64; + let Test.5 : I64 = 2i64; let Test.4 : I64 = 3i64; - let Test.3 : I64 = CallByName Num.22 Test.6 Test.4; + let Test.3 : I64 = CallByName Num.19 Test.5 Test.4; ret Test.3; diff --git a/compiler/test_mono/generated/when_on_two_values.txt b/compiler/test_mono/generated/when_on_two_values.txt index 2085f6c7df..c9866b76f4 100644 --- a/compiler/test_mono/generated/when_on_two_values.txt +++ b/compiler/test_mono/generated/when_on_two_values.txt @@ -1,28 +1,28 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.7 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.7; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.228 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.228; procedure Test.0 (): - let Test.16 : I64 = 3i64; - let Test.15 : I64 = 2i64; - let Test.4 : {I64, I64} = Struct {Test.15, Test.16}; - joinpoint Test.12: + let Test.15 : I64 = 3i64; + let Test.14 : I64 = 2i64; + let Test.4 : {I64, I64} = Struct {Test.14, Test.15}; + joinpoint Test.11: let Test.2 : I64 = StructAtIndex 0 Test.4; let Test.3 : I64 = StructAtIndex 1 Test.4; - let Test.6 : I64 = CallByName Num.22 Test.2 Test.3; + let Test.6 : I64 = CallByName Num.19 Test.2 Test.3; ret Test.6; in - let Test.10 : I64 = StructAtIndex 1 Test.4; - let Test.11 : I64 = 3i64; - let Test.14 : Int1 = lowlevel Eq Test.11 Test.10; - if Test.14 then - let Test.8 : I64 = StructAtIndex 0 Test.4; - let Test.9 : I64 = 4i64; - let Test.13 : Int1 = lowlevel Eq Test.9 Test.8; - if Test.13 then + let Test.9 : I64 = StructAtIndex 1 Test.4; + let Test.10 : I64 = 3i64; + let Test.13 : Int1 = lowlevel Eq Test.10 Test.9; + if Test.13 then + let Test.7 : I64 = StructAtIndex 0 Test.4; + let Test.8 : I64 = 4i64; + let Test.12 : Int1 = lowlevel Eq Test.8 Test.7; + if Test.12 then let Test.5 : I64 = 9i64; ret Test.5; else - jump Test.12; + jump Test.11; else - jump Test.12; + jump Test.11; diff --git a/compiler/test_mono/src/tests.rs b/compiler/test_mono/src/tests.rs index 74e17f9a15..69eb86b8c0 100644 --- a/compiler/test_mono/src/tests.rs +++ b/compiler/test_mono/src/tests.rs @@ -5,9 +5,6 @@ // we actually want to compare against the literal float bits #![allow(clippy::float_cmp)] -#[macro_use] -extern crate pretty_assertions; - #[macro_use] extern crate indoc; @@ -123,14 +120,12 @@ fn compiles_to_ir(test_name: &str, src: &str) { let can_problems = loaded.can_problems.remove(&home).unwrap_or_default(); let type_problems = loaded.type_problems.remove(&home).unwrap_or_default(); - let mono_problems = loaded.mono_problems.remove(&home).unwrap_or_default(); if !can_problems.is_empty() { println!("Ignoring {} canonicalization problems", can_problems.len()); } - assert_eq!(type_problems, Vec::new()); - assert_eq!(mono_problems, Vec::new()); + assert!(type_problems.is_empty()); debug_assert_eq!(exposed_to_host.values.len(), 1); @@ -275,7 +270,7 @@ fn ir_round() { #[mono_test] fn ir_when_idiv() { r#" - when Num.divFloorChecked 1000 10 is + when Num.divTruncChecked 1000 10 is Ok val -> val Err _ -> -1 "# @@ -1283,6 +1278,23 @@ fn issue_2583_specialize_errors_behind_unified_branches() { ) } +#[mono_test] +fn issue_2810() { + indoc!( + r#" + Command : [ Command Tool ] + + Job : [ Job Command ] + + Tool : [ SystemTool, FromJob Job ] + + a : Job + a = Job (Command (FromJob (Job (Command SystemTool)))) + a + "# + ) +} + #[mono_test] fn issue_2811() { indoc!( @@ -1294,6 +1306,42 @@ fn issue_2811() { ) } +#[mono_test] +fn specialize_ability_call() { + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + Id := U64 + + hash : Id -> U64 + hash = \@Id n -> n + + main = hash (@Id 1234) + "# + ) +} + +#[mono_test] +fn opaque_assign_to_symbol() { + indoc!( + r#" + app "test" provides [ out ] to "./platform" + + Variable := U8 + + fromUtf8 : U8 -> Result Variable [ InvalidVariableUtf8 ] + fromUtf8 = \char -> + Ok (@Variable char) + + out = fromUtf8 98 + "# + ) +} + // #[ignore] // #[mono_test] // fn static_str_closure() { diff --git a/compiler/types/src/builtin_aliases.rs b/compiler/types/src/builtin_aliases.rs index 7db708764c..6be002465c 100644 --- a/compiler/types/src/builtin_aliases.rs +++ b/compiler/types/src/builtin_aliases.rs @@ -11,7 +11,6 @@ const NUM_BUILTIN_IMPORTS: usize = 8; /// These can be shared between definitions, they will get instantiated when converted to Type const TVAR1: VarId = VarId::from_u32(1); -const TVAR2: VarId = VarId::from_u32(2); pub fn aliases() -> MutMap { let mut aliases = HashMap::with_capacity_and_hasher(NUM_BUILTIN_IMPORTS, default_hasher()); @@ -29,53 +28,58 @@ pub fn aliases() -> MutMap { aliases.insert(symbol, alias); }; - // Int range : [ @Int range ] + // Int range : Num (Integer range) add_alias( Symbol::NUM_INT, BuiltinAlias { region: Region::zero(), vars: vec![Loc::at(Region::zero(), "range".into())], typ: int_alias_content(flex(TVAR1)), + kind: AliasKind::Structural, }, ); - // Float range : [ @Float range ] + // Float range : Num (FloatingPoint range) add_alias( Symbol::NUM_FLOAT, BuiltinAlias { region: Region::zero(), vars: vec![Loc::at(Region::zero(), "range".into())], typ: float_alias_content(flex(TVAR1)), + kind: AliasKind::Structural, }, ); - // Num range : [ @Num range ] + // Num range := range add_alias( Symbol::NUM_NUM, BuiltinAlias { region: Region::zero(), vars: vec![Loc::at(Region::zero(), "range".into())], typ: num_alias_content(flex(TVAR1)), + kind: AliasKind::Opaque, }, ); - // Integer range : [ @Integer range ] + // Integer range := range add_alias( Symbol::NUM_INTEGER, BuiltinAlias { region: Region::zero(), vars: vec![Loc::at(Region::zero(), "range".into())], typ: integer_alias_content(flex(TVAR1)), + kind: AliasKind::Opaque, }, ); - // Natural : [ @Natural ] + // Natural := [] add_alias( Symbol::NUM_NATURAL, BuiltinAlias { region: Region::zero(), vars: vec![], typ: natural_alias_content(), + kind: AliasKind::Opaque, }, ); @@ -86,16 +90,18 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: nat_alias_content(), + kind: AliasKind::Structural, }, ); - // Signed128 : [ @Signed128 ] + // Signed128 := [] add_alias( Symbol::NUM_SIGNED128, BuiltinAlias { region: Region::zero(), vars: vec![], typ: signed128_alias_content(), + kind: AliasKind::Opaque, }, ); @@ -106,6 +112,7 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: i128_alias_content(), + kind: AliasKind::Structural, }, ); @@ -116,16 +123,18 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: u128_alias_content(), + kind: AliasKind::Structural, }, ); - // Signed64 : [ @Signed64 ] + // Signed64 := [] add_alias( Symbol::NUM_SIGNED64, BuiltinAlias { region: Region::zero(), vars: vec![], typ: signed64_alias_content(), + kind: AliasKind::Opaque, }, ); @@ -136,6 +145,7 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: i64_alias_content(), + kind: AliasKind::Structural, }, ); @@ -146,16 +156,18 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: u64_alias_content(), + kind: AliasKind::Structural, }, ); - // Signed32 : [ @Signed32 ] + // Signed32 := [] add_alias( Symbol::NUM_SIGNED32, BuiltinAlias { region: Region::zero(), vars: vec![], typ: signed32_alias_content(), + kind: AliasKind::Opaque, }, ); @@ -166,6 +178,7 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: i32_alias_content(), + kind: AliasKind::Structural, }, ); @@ -176,16 +189,18 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: u32_alias_content(), + kind: AliasKind::Structural, }, ); - // Signed16 : [ @Signed16 ] + // Signed16 := [] add_alias( Symbol::NUM_SIGNED16, BuiltinAlias { region: Region::zero(), vars: vec![], typ: signed16_alias_content(), + kind: AliasKind::Opaque, }, ); @@ -196,6 +211,7 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: i16_alias_content(), + kind: AliasKind::Structural, }, ); @@ -206,16 +222,18 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: u16_alias_content(), + kind: AliasKind::Structural, }, ); - // Signed8 : [ @Signed8 ] + // Signed8 := [] add_alias( Symbol::NUM_SIGNED8, BuiltinAlias { region: Region::zero(), vars: vec![], typ: signed8_alias_content(), + kind: AliasKind::Opaque, }, ); @@ -226,6 +244,7 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: i8_alias_content(), + kind: AliasKind::Structural, }, ); @@ -236,46 +255,51 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: u8_alias_content(), + kind: AliasKind::Structural, }, ); - // Decimal : [ @Decimal ] + // Decimal := [] add_alias( Symbol::NUM_DECIMAL, BuiltinAlias { region: Region::zero(), vars: vec![], typ: decimal_alias_content(), + kind: AliasKind::Opaque, }, ); - // Binary64 : [ @Binary64 ] + // Binary64 := [] add_alias( Symbol::NUM_BINARY64, BuiltinAlias { region: Region::zero(), vars: vec![], typ: binary64_alias_content(), + kind: AliasKind::Opaque, }, ); - // Binary32 : [ @Binary32 ] + // Binary32 := [] add_alias( Symbol::NUM_BINARY32, BuiltinAlias { region: Region::zero(), vars: vec![], typ: binary32_alias_content(), + kind: AliasKind::Opaque, }, ); - // FloatingPoint range : [ @FloatingPoint range ] + // FloatingPoint range := range add_alias( Symbol::NUM_FLOATINGPOINT, BuiltinAlias { region: Region::zero(), vars: vec![Loc::at(Region::zero(), "range".into())], typ: floatingpoint_alias_content(flex(TVAR1)), + kind: AliasKind::Opaque, }, ); @@ -286,6 +310,7 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: dec_alias_content(), + kind: AliasKind::Structural, }, ); @@ -296,6 +321,7 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: f64_alias_content(), + kind: AliasKind::Structural, }, ); @@ -306,6 +332,7 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: f32_alias_content(), + kind: AliasKind::Structural, }, ); @@ -316,19 +343,7 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: bool_alias_content(), - }, - ); - - // Result ok err : [ Ok ok, Err err ] - add_alias( - Symbol::RESULT_RESULT, - BuiltinAlias { - region: Region::zero(), - vars: vec![ - Loc::at(Region::zero(), "ok".into()), - Loc::at(Region::zero(), "err".into()), - ], - typ: result_alias_content(flex(TVAR1), flex(TVAR2)), + kind: AliasKind::Structural, }, ); @@ -339,6 +354,7 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: str_utf8_byte_problem_alias_content(), + kind: AliasKind::Structural, }, ); @@ -349,6 +365,7 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: str_utf8_byte_problem_alias_content(), + kind: AliasKind::Structural, }, ); @@ -367,13 +384,13 @@ pub fn num_type(range: SolvedType) -> SolvedType { vec![("range".into(), range.clone())], vec![], Box::new(num_alias_content(range)), - AliasKind::Structural, + AliasKind::Opaque, ) } #[inline(always)] fn num_alias_content(range: SolvedType) -> SolvedType { - single_private_tag(Symbol::NUM_AT_NUM, vec![range]) + range } // FLOATING POINT @@ -385,13 +402,13 @@ pub fn floatingpoint_type(range: SolvedType) -> SolvedType { vec![("range".into(), range.clone())], vec![], Box::new(floatingpoint_alias_content(range)), - AliasKind::Structural, + AliasKind::Opaque, ) } #[inline(always)] fn floatingpoint_alias_content(range: SolvedType) -> SolvedType { - single_private_tag(Symbol::NUM_AT_FLOATINGPOINT, vec![range]) + range } // FLOAT @@ -673,13 +690,13 @@ pub fn integer_type(range: SolvedType) -> SolvedType { vec![("range".into(), range.clone())], vec![], Box::new(integer_alias_content(range)), - AliasKind::Structural, + AliasKind::Opaque, ) } #[inline(always)] fn integer_alias_content(range: SolvedType) -> SolvedType { - single_private_tag(Symbol::NUM_AT_INTEGER, vec![range]) + range } #[inline(always)] @@ -695,7 +712,7 @@ pub fn binary64_type() -> SolvedType { #[inline(always)] pub fn binary64_alias_content() -> SolvedType { - single_private_tag(Symbol::NUM_AT_BINARY64, vec![]) + SolvedType::EmptyTagUnion } #[inline(always)] @@ -711,7 +728,7 @@ pub fn binary32_type() -> SolvedType { #[inline(always)] fn binary32_alias_content() -> SolvedType { - single_private_tag(Symbol::NUM_AT_BINARY32, vec![]) + SolvedType::EmptyTagUnion } #[inline(always)] @@ -727,7 +744,7 @@ pub fn natural_type() -> SolvedType { #[inline(always)] fn natural_alias_content() -> SolvedType { - single_private_tag(Symbol::NUM_AT_NATURAL, vec![]) + SolvedType::EmptyTagUnion } #[inline(always)] @@ -743,7 +760,7 @@ pub fn signed128_type() -> SolvedType { #[inline(always)] fn signed128_alias_content() -> SolvedType { - single_private_tag(Symbol::NUM_AT_SIGNED128, vec![]) + SolvedType::EmptyTagUnion } #[inline(always)] @@ -759,7 +776,7 @@ pub fn signed64_type() -> SolvedType { #[inline(always)] fn signed64_alias_content() -> SolvedType { - single_private_tag(Symbol::NUM_AT_SIGNED64, vec![]) + SolvedType::EmptyTagUnion } #[inline(always)] @@ -775,7 +792,7 @@ pub fn signed32_type() -> SolvedType { #[inline(always)] fn signed32_alias_content() -> SolvedType { - single_private_tag(Symbol::NUM_AT_SIGNED32, vec![]) + SolvedType::EmptyTagUnion } #[inline(always)] @@ -791,7 +808,7 @@ pub fn signed16_type() -> SolvedType { #[inline(always)] fn signed16_alias_content() -> SolvedType { - single_private_tag(Symbol::NUM_AT_SIGNED16, vec![]) + SolvedType::EmptyTagUnion } #[inline(always)] @@ -807,7 +824,7 @@ pub fn signed8_type() -> SolvedType { #[inline(always)] fn signed8_alias_content() -> SolvedType { - single_private_tag(Symbol::NUM_AT_SIGNED8, vec![]) + SolvedType::EmptyTagUnion } #[inline(always)] @@ -823,7 +840,7 @@ pub fn unsigned128_type() -> SolvedType { #[inline(always)] fn unsigned128_alias_content() -> SolvedType { - single_private_tag(Symbol::NUM_AT_UNSIGNED128, vec![]) + SolvedType::EmptyTagUnion } #[inline(always)] @@ -839,7 +856,7 @@ pub fn unsigned64_type() -> SolvedType { #[inline(always)] fn unsigned64_alias_content() -> SolvedType { - single_private_tag(Symbol::NUM_AT_UNSIGNED64, vec![]) + SolvedType::EmptyTagUnion } #[inline(always)] @@ -855,7 +872,7 @@ pub fn unsigned32_type() -> SolvedType { #[inline(always)] fn unsigned32_alias_content() -> SolvedType { - single_private_tag(Symbol::NUM_AT_UNSIGNED32, vec![]) + SolvedType::EmptyTagUnion } #[inline(always)] @@ -871,7 +888,7 @@ pub fn unsigned16_type() -> SolvedType { #[inline(always)] fn unsigned16_alias_content() -> SolvedType { - single_private_tag(Symbol::NUM_AT_UNSIGNED16, vec![]) + SolvedType::EmptyTagUnion } #[inline(always)] @@ -887,12 +904,12 @@ pub fn unsigned8_type() -> SolvedType { #[inline(always)] fn unsigned8_alias_content() -> SolvedType { - single_private_tag(Symbol::NUM_AT_UNSIGNED8, vec![]) + SolvedType::EmptyTagUnion } #[inline(always)] fn decimal_alias_content() -> SolvedType { - single_private_tag(Symbol::NUM_AT_DECIMAL, vec![]) + SolvedType::EmptyTagUnion } // Dec @@ -938,8 +955,8 @@ pub fn bool_type() -> SolvedType { fn bool_alias_content() -> SolvedType { SolvedType::TagUnion( vec![ - (TagName::Global("False".into()), vec![]), - (TagName::Global("True".into()), vec![]), + (TagName::Tag("False".into()), vec![]), + (TagName::Tag("True".into()), vec![]), ], Box::new(SolvedType::EmptyTagUnion), ) @@ -950,9 +967,9 @@ pub fn ordering_type() -> SolvedType { // [ LT, EQ, GT ] SolvedType::TagUnion( vec![ - (TagName::Global("EQ".into()), vec![]), - (TagName::Global("GT".into()), vec![]), - (TagName::Global("LT".into()), vec![]), + (TagName::Tag("EQ".into()), vec![]), + (TagName::Tag("GT".into()), vec![]), + (TagName::Tag("LT".into()), vec![]), ], Box::new(SolvedType::EmptyTagUnion), ) @@ -978,8 +995,8 @@ pub fn box_type(a: SolvedType) -> SolvedType { fn result_alias_content(a: SolvedType, e: SolvedType) -> SolvedType { SolvedType::TagUnion( vec![ - (TagName::Global("Err".into()), vec![e]), - (TagName::Global("Ok".into()), vec![a]), + (TagName::Tag("Err".into()), vec![e]), + (TagName::Tag("Ok".into()), vec![a]), ], Box::new(SolvedType::EmptyTagUnion), ) @@ -1039,12 +1056,12 @@ pub fn str_utf8_byte_problem_alias_content() -> SolvedType { // [ CodepointTooLarge, EncodesSurrogateHalf, OverlongEncoding, InvalidStartByte, UnexpectedEndOfSequence, ExpectedContinuation ] SolvedType::TagUnion( vec![ - (TagName::Global("CodepointTooLarge".into()), vec![]), - (TagName::Global("EncodesSurrogateHalf".into()), vec![]), - (TagName::Global("ExpectedContinuation".into()), vec![]), - (TagName::Global("InvalidStartByte".into()), vec![]), - (TagName::Global("OverlongEncoding".into()), vec![]), - (TagName::Global("UnexpectedEndOfSequence".into()), vec![]), + (TagName::Tag("CodepointTooLarge".into()), vec![]), + (TagName::Tag("EncodesSurrogateHalf".into()), vec![]), + (TagName::Tag("ExpectedContinuation".into()), vec![]), + (TagName::Tag("InvalidStartByte".into()), vec![]), + (TagName::Tag("OverlongEncoding".into()), vec![]), + (TagName::Tag("UnexpectedEndOfSequence".into()), vec![]), ], Box::new(SolvedType::EmptyTagUnion), ) @@ -1059,10 +1076,3 @@ pub fn set_type(a: SolvedType) -> SolvedType { pub fn dict_type(key: SolvedType, value: SolvedType) -> SolvedType { SolvedType::Apply(Symbol::DICT_DICT, vec![key, value]) } - -pub fn single_private_tag(symbol: Symbol, type_arguments: Vec) -> SolvedType { - SolvedType::TagUnion( - vec![(TagName::Private(symbol), type_arguments)], - Box::new(SolvedType::EmptyTagUnion), - ) -} diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index 998684d777..917e4a33e0 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -248,7 +248,12 @@ fn name_root( subs: &mut Subs, taken: &mut MutSet, ) -> u32 { - let (generated_name, new_letters_used) = name_type_var(letters_used, taken); + let (generated_name, new_letters_used) = + name_type_var(letters_used, &mut taken.iter(), |var, str| { + var.as_str() == str + }); + + taken.insert(generated_name.clone()); set_root_name(root, generated_name, subs); @@ -307,6 +312,8 @@ pub fn content_to_string( write_content(&env, &mut ctx, content, subs, &mut buf, Parens::Unnecessary); + ctx.able_variables.sort(); + ctx.able_variables.dedup(); for (i, (var, ability)) in ctx.able_variables.into_iter().enumerate() { buf.push_str(if i == 0 { " | " } else { ", " }); buf.push_str(var); @@ -443,7 +450,9 @@ fn write_content<'a>( } // useful for debugging - if false { + if cfg!(debug_assertions) + && std::env::var("ROC_PRETTY_PRINT_ALIAS_CONTENTS").is_ok() + { buf.push_str("[[ but really "); let content = subs.get_content_without_compacting(*_actual); write_content(env, ctx, content, subs, buf, parens); @@ -985,7 +994,7 @@ fn write_fn<'a>( fn write_symbol(env: &Env, symbol: Symbol, buf: &mut String) { let interns = &env.interns; - let ident = symbol.ident_str(interns); + let ident_str = symbol.as_str(interns); let module_id = symbol.module_id(); // Don't qualify the symbol if it's in our home module, @@ -995,5 +1004,5 @@ fn write_symbol(env: &Env, symbol: Symbol, buf: &mut String) { buf.push('.'); } - buf.push_str(ident.as_str()); + buf.push_str(ident_str); } diff --git a/compiler/types/src/solved_types.rs b/compiler/types/src/solved_types.rs index a9acc39cfa..76fd39ecab 100644 --- a/compiler/types/src/solved_types.rs +++ b/compiler/types/src/solved_types.rs @@ -78,6 +78,7 @@ pub struct BuiltinAlias { pub region: Region, pub vars: Vec>, pub typ: SolvedType, + pub kind: AliasKind, } #[derive(Debug, Clone, Default)] diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 1aac8b45bd..97fd484cd2 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -114,7 +114,6 @@ fn round_to_multiple_of(value: usize, base: usize) -> usize { enum SerializedTagName { Global(SubsSlice), - Private(Symbol), Closure(Symbol), } @@ -204,14 +203,13 @@ impl Subs { for tag_name in tag_names { let serialized = match tag_name { - TagName::Global(uppercase) => { + TagName::Tag(uppercase) => { let slice = SubsSlice::extend_new( &mut buf, uppercase.as_str().as_bytes().iter().copied(), ); SerializedTagName::Global(slice) } - TagName::Private(symbol) => SerializedTagName::Private(*symbol), TagName::Closure(symbol) => SerializedTagName::Closure(*symbol), }; @@ -352,9 +350,8 @@ impl Subs { offset += bytes.len(); let string = unsafe { std::str::from_utf8_unchecked(bytes) }; - TagName::Global(string.into()) + TagName::Tag(string.into()) } - SerializedTagName::Private(symbol) => TagName::Private(*symbol), SerializedTagName::Closure(symbol) => TagName::Closure(*symbol), }; @@ -395,7 +392,7 @@ pub struct Subs { pub struct TagNameCache { globals: Vec, globals_slices: Vec>, - /// Currently private tags and closure tags; in the future just closure tags + /// Just closure tags symbols: Vec, symbols_slices: Vec>, } @@ -403,29 +400,27 @@ pub struct TagNameCache { impl TagNameCache { pub fn get_mut(&mut self, tag_name: &TagName) -> Option<&mut SubsSlice> { match tag_name { - TagName::Global(uppercase) => { + TagName::Tag(uppercase) => { // force into block match self.globals.iter().position(|u| u == uppercase) { Some(index) => Some(&mut self.globals_slices[index]), None => None, } } - TagName::Private(symbol) | TagName::Closure(symbol) => { - match self.symbols.iter().position(|s| s == symbol) { - Some(index) => Some(&mut self.symbols_slices[index]), - None => None, - } - } + TagName::Closure(symbol) => match self.symbols.iter().position(|s| s == symbol) { + Some(index) => Some(&mut self.symbols_slices[index]), + None => None, + }, } } pub fn push(&mut self, tag_name: &TagName, slice: SubsSlice) { match tag_name { - TagName::Global(uppercase) => { + TagName::Tag(uppercase) => { self.globals.push(uppercase.clone()); self.globals_slices.push(slice); } - TagName::Private(symbol) | TagName::Closure(symbol) => { + TagName::Closure(symbol) => { self.symbols.push(*symbol); self.symbols_slices.push(slice); } @@ -647,7 +642,7 @@ impl SubsSlice { let start = subs.tag_names.len() as u32; subs.tag_names - .extend(std::iter::repeat(TagName::Global(Uppercase::default())).take(length)); + .extend(std::iter::repeat(TagName::Tag(Uppercase::default())).take(length)); Self::new(start, length as u16) } @@ -772,7 +767,15 @@ fn subs_fmt_content(this: &Content, subs: &Subs, f: &mut fmt::Formatter) -> fmt: AliasKind::Opaque => "Opaque", }; - write!(f, "{}({:?}, {:?}, {:?})", wrap, name, slice, actual) + write!( + f, + "{}({:?}, {:?}, <{:?}>{:?})", + wrap, + name, + slice, + actual, + SubsFmtContent(subs.get_content_without_compacting(*actual), subs) + ) } Content::RangedNumber(typ, range) => { let slice = subs.get_subs_slice(*range); @@ -833,7 +836,16 @@ fn subs_fmt_flat_type(this: &FlatType, subs: &Subs, f: &mut fmt::Formatter) -> f let (it, new_ext) = tags.sorted_iterator_and_ext(subs, *ext); for (name, slice) in it { - write!(f, "{:?} {:?}, ", name, slice)?; + write!(f, "{:?} ", name)?; + for var in slice { + write!( + f, + "<{:?}>{:?} ", + var, + SubsFmtContent(subs.get_content_without_compacting(*var), subs) + )?; + } + write!(f, ", ")?; } write!(f, "]<{:?}>", new_ext) @@ -953,6 +965,77 @@ impl From for Option { } } +/// Marks whether a when expression is exhaustive using a variable. +#[derive(Clone, Copy, Debug)] +pub struct ExhaustiveMark(Variable); + +impl ExhaustiveMark { + pub fn new(var_store: &mut VarStore) -> Self { + Self(var_store.fresh()) + } + + // NOTE: only ever use this if you *know* a pattern match is surely exhaustive! + // Otherwise you will get unpleasant unification errors. + pub fn known_exhaustive() -> Self { + Self(Variable::EMPTY_TAG_UNION) + } + + pub fn variable_for_introduction(&self) -> Variable { + debug_assert!( + self.0 != Variable::EMPTY_TAG_UNION, + "Attempting to introduce known mark" + ); + self.0 + } + + pub fn set_non_exhaustive(&self, subs: &mut Subs) { + subs.set_content(self.0, Content::Error); + } + + pub fn is_non_exhaustive(&self, subs: &Subs) -> bool { + matches!(subs.get_content_without_compacting(self.0), Content::Error) + } +} + +/// Marks whether a when branch is redundant using a variable. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct RedundantMark(Variable); + +impl RedundantMark { + pub fn new(var_store: &mut VarStore) -> Self { + Self(var_store.fresh()) + } + + // NOTE: only ever use this if you *know* a pattern match is surely exhaustive! + // Otherwise you will get unpleasant unification errors. + pub fn known_non_redundant() -> Self { + Self(Variable::EMPTY_TAG_UNION) + } + + pub fn variable_for_introduction(&self) -> Variable { + debug_assert!( + self.0 != Variable::EMPTY_TAG_UNION, + "Attempting to introduce known mark" + ); + self.0 + } + + pub fn set_redundant(&self, subs: &mut Subs) { + subs.set_content(self.0, Content::Error); + } + + pub fn is_redundant(&self, subs: &Subs) -> bool { + matches!(subs.get_content_without_compacting(self.0), Content::Error) + } +} + +pub fn new_marks(var_store: &mut VarStore) -> (RedundantMark, ExhaustiveMark) { + ( + RedundantMark::new(var_store), + ExhaustiveMark::new(var_store), + ) +} + #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Variable(u32); @@ -992,22 +1075,7 @@ define_const_var! { ORDER_ENUM, :pub ORDER, - // [ @Signed8 ] - AT_SIGNED8, - AT_SIGNED16, - AT_SIGNED32, - AT_SIGNED64, - AT_SIGNED128, - - AT_UNSIGNED8, - AT_UNSIGNED16, - AT_UNSIGNED32, - AT_UNSIGNED64, - AT_UNSIGNED128, - - AT_NATURAL, - - // Signed8 : [ @Signed8 ] + // Signed8 := [] :pub SIGNED8, :pub SIGNED16, :pub SIGNED32, @@ -1022,22 +1090,7 @@ define_const_var! { :pub NATURAL, - // [ @Integer Signed8 ] - AT_INTEGER_SIGNED8, - AT_INTEGER_SIGNED16, - AT_INTEGER_SIGNED32, - AT_INTEGER_SIGNED64, - AT_INTEGER_SIGNED128, - - AT_INTEGER_UNSIGNED8, - AT_INTEGER_UNSIGNED16, - AT_INTEGER_UNSIGNED32, - AT_INTEGER_UNSIGNED64, - AT_INTEGER_UNSIGNED128, - - AT_INTEGER_NATURAL, - - // Integer Signed8 : [ @Integer Signed8 ] + // Integer Signed8 := Signed8 INTEGER_SIGNED8, INTEGER_SIGNED16, INTEGER_SIGNED32, @@ -1052,22 +1105,7 @@ define_const_var! { INTEGER_NATURAL, - // [ @Num (Integer Signed8) ] - AT_NUM_INTEGER_SIGNED8, - AT_NUM_INTEGER_SIGNED16, - AT_NUM_INTEGER_SIGNED32, - AT_NUM_INTEGER_SIGNED64, - AT_NUM_INTEGER_SIGNED128, - - AT_NUM_INTEGER_UNSIGNED8, - AT_NUM_INTEGER_UNSIGNED16, - AT_NUM_INTEGER_UNSIGNED32, - AT_NUM_INTEGER_UNSIGNED64, - AT_NUM_INTEGER_UNSIGNED128, - - AT_NUM_INTEGER_NATURAL, - - // Num (Integer Signed8) + // Num (Integer Signed8) := Integer Signed8 NUM_INTEGER_SIGNED8, NUM_INTEGER_SIGNED16, NUM_INTEGER_SIGNED32, @@ -1097,32 +1135,17 @@ define_const_var! { :pub NAT, - // [ @Binary32 ] - AT_BINARY32, - AT_BINARY64, - AT_DECIMAL, - - // Binary32 : [ @Binary32 ] + // Binary32 : [] BINARY32, BINARY64, DECIMAL, - // [ @Float Binary32 ] - AT_FLOAT_BINARY32, - AT_FLOAT_BINARY64, - AT_FLOAT_DECIMAL, - - // Float Binary32 : [ @Float Binary32 ] + // Float Binary32 := Binary32 FLOAT_BINARY32, FLOAT_BINARY64, FLOAT_DECIMAL, - // [ @Num (Float Binary32) ] - AT_NUM_FLOAT_BINARY32, - AT_NUM_FLOAT_BINARY64, - AT_NUM_FLOAT_DECIMAL, - - // Num (Float Binary32) + // Num (Float Binary32) := Float Binary32 NUM_FLOAT_BINARY32, NUM_FLOAT_BINARY64, NUM_FLOAT_DECIMAL, @@ -1256,80 +1279,47 @@ impl fmt::Debug for VarId { fn integer_type( subs: &mut Subs, - num_at_signed64: Symbol, num_signed64: Symbol, num_i64: Symbol, - at_signed64: Variable, signed64: Variable, - at_integer_signed64: Variable, integer_signed64: Variable, - at_num_integer_signed64: Variable, num_integer_signed64: Variable, var_i64: Variable, ) { - // define the type Signed64 (which is an alias for [ @Signed64 ]) + // define the type Signed64 := [] { - let tags = UnionTags::insert_into_subs(subs, [(TagName::Private(num_at_signed64), [])]); - - subs.set_content(at_signed64, { - Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)) - }); - subs.set_content(signed64, { Content::Alias( num_signed64, AliasVariables::default(), - at_signed64, - AliasKind::Structural, + Variable::EMPTY_TAG_UNION, + AliasKind::Opaque, ) }); } - // define the type `Num.Integer Num.Signed64` + // define the type `Num.Integer Num.Signed64 := Num.Signed64` { - let tags = UnionTags::insert_into_subs( - subs, - [(TagName::Private(Symbol::NUM_AT_INTEGER), [signed64])], - ); - subs.set_content(at_integer_signed64, { - Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)) - }); - let vars = AliasVariables::insert_into_subs(subs, [signed64], []); subs.set_content(integer_signed64, { - Content::Alias( - Symbol::NUM_INTEGER, - vars, - at_signed64, - AliasKind::Structural, - ) + Content::Alias(Symbol::NUM_INTEGER, vars, signed64, AliasKind::Opaque) }); } - // define the type `Num.Num (Num.Integer Num.Signed64)` + // define the type `Num.Num (Num.Integer Num.Signed64) := Num.Integer Num.Signed64` { - let tags = UnionTags::insert_into_subs( - subs, - [(TagName::Private(Symbol::NUM_AT_NUM), [integer_signed64])], - ); - subs.set_content(at_num_integer_signed64, { - Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)) - }); - let vars = AliasVariables::insert_into_subs(subs, [integer_signed64], []); subs.set_content(num_integer_signed64, { - Content::Alias( - Symbol::NUM_NUM, - vars, - at_num_integer_signed64, - AliasKind::Structural, - ) + Content::Alias(Symbol::NUM_NUM, vars, integer_signed64, AliasKind::Opaque) }); + } + // define the type `Num.I64 : Num.Num (Num.Integer Num.Signed64)` + { subs.set_content(var_i64, { Content::Alias( num_i64, @@ -1344,154 +1334,110 @@ fn integer_type( fn define_integer_types(subs: &mut Subs) { integer_type( subs, - Symbol::NUM_AT_SIGNED128, Symbol::NUM_SIGNED128, Symbol::NUM_I128, - Variable::AT_SIGNED128, Variable::SIGNED128, - Variable::AT_INTEGER_SIGNED128, Variable::INTEGER_SIGNED128, - Variable::AT_NUM_INTEGER_SIGNED128, Variable::NUM_INTEGER_SIGNED128, Variable::I128, ); integer_type( subs, - Symbol::NUM_AT_SIGNED64, Symbol::NUM_SIGNED64, Symbol::NUM_I64, - Variable::AT_SIGNED64, Variable::SIGNED64, - Variable::AT_INTEGER_SIGNED64, Variable::INTEGER_SIGNED64, - Variable::AT_NUM_INTEGER_SIGNED64, Variable::NUM_INTEGER_SIGNED64, Variable::I64, ); integer_type( subs, - Symbol::NUM_AT_SIGNED32, Symbol::NUM_SIGNED32, Symbol::NUM_I32, - Variable::AT_SIGNED32, Variable::SIGNED32, - Variable::AT_INTEGER_SIGNED32, Variable::INTEGER_SIGNED32, - Variable::AT_NUM_INTEGER_SIGNED32, Variable::NUM_INTEGER_SIGNED32, Variable::I32, ); integer_type( subs, - Symbol::NUM_AT_SIGNED16, Symbol::NUM_SIGNED16, Symbol::NUM_I16, - Variable::AT_SIGNED16, Variable::SIGNED16, - Variable::AT_INTEGER_SIGNED16, Variable::INTEGER_SIGNED16, - Variable::AT_NUM_INTEGER_SIGNED16, Variable::NUM_INTEGER_SIGNED16, Variable::I16, ); integer_type( subs, - Symbol::NUM_AT_SIGNED8, Symbol::NUM_SIGNED8, Symbol::NUM_I8, - Variable::AT_SIGNED8, Variable::SIGNED8, - Variable::AT_INTEGER_SIGNED8, Variable::INTEGER_SIGNED8, - Variable::AT_NUM_INTEGER_SIGNED8, Variable::NUM_INTEGER_SIGNED8, Variable::I8, ); integer_type( subs, - Symbol::NUM_AT_UNSIGNED128, Symbol::NUM_UNSIGNED128, Symbol::NUM_U128, - Variable::AT_UNSIGNED128, Variable::UNSIGNED128, - Variable::AT_INTEGER_UNSIGNED128, Variable::INTEGER_UNSIGNED128, - Variable::AT_NUM_INTEGER_UNSIGNED128, Variable::NUM_INTEGER_UNSIGNED128, Variable::U128, ); integer_type( subs, - Symbol::NUM_AT_UNSIGNED64, Symbol::NUM_UNSIGNED64, Symbol::NUM_U64, - Variable::AT_UNSIGNED64, Variable::UNSIGNED64, - Variable::AT_INTEGER_UNSIGNED64, Variable::INTEGER_UNSIGNED64, - Variable::AT_NUM_INTEGER_UNSIGNED64, Variable::NUM_INTEGER_UNSIGNED64, Variable::U64, ); integer_type( subs, - Symbol::NUM_AT_UNSIGNED32, Symbol::NUM_UNSIGNED32, Symbol::NUM_U32, - Variable::AT_UNSIGNED32, Variable::UNSIGNED32, - Variable::AT_INTEGER_UNSIGNED32, Variable::INTEGER_UNSIGNED32, - Variable::AT_NUM_INTEGER_UNSIGNED32, Variable::NUM_INTEGER_UNSIGNED32, Variable::U32, ); integer_type( subs, - Symbol::NUM_AT_UNSIGNED16, Symbol::NUM_UNSIGNED16, Symbol::NUM_U16, - Variable::AT_UNSIGNED16, Variable::UNSIGNED16, - Variable::AT_INTEGER_UNSIGNED16, Variable::INTEGER_UNSIGNED16, - Variable::AT_NUM_INTEGER_UNSIGNED16, Variable::NUM_INTEGER_UNSIGNED16, Variable::U16, ); integer_type( subs, - Symbol::NUM_AT_UNSIGNED8, Symbol::NUM_UNSIGNED8, Symbol::NUM_U8, - Variable::AT_UNSIGNED8, Variable::UNSIGNED8, - Variable::AT_INTEGER_UNSIGNED8, Variable::INTEGER_UNSIGNED8, - Variable::AT_NUM_INTEGER_UNSIGNED8, Variable::NUM_INTEGER_UNSIGNED8, Variable::U8, ); integer_type( subs, - Symbol::NUM_AT_NATURAL, Symbol::NUM_NATURAL, Symbol::NUM_NAT, - Variable::AT_NATURAL, Variable::NATURAL, - Variable::AT_INTEGER_NATURAL, Variable::INTEGER_NATURAL, - Variable::AT_NUM_INTEGER_NATURAL, Variable::NUM_INTEGER_NATURAL, Variable::NAT, ); @@ -1501,80 +1447,47 @@ fn define_integer_types(subs: &mut Subs) { fn float_type( subs: &mut Subs, - num_at_binary64: Symbol, num_binary64: Symbol, num_f64: Symbol, - at_binary64: Variable, binary64: Variable, - at_float_binary64: Variable, float_binary64: Variable, - at_num_float_binary64: Variable, num_float_binary64: Variable, var_f64: Variable, ) { - // define the type Binary64 (which is an alias for [ @Binary64 ]) + // define the type Binary64 := [] { - let tags = UnionTags::insert_into_subs(subs, [(TagName::Private(num_at_binary64), [])]); - - subs.set_content(at_binary64, { - Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)) - }); - subs.set_content(binary64, { Content::Alias( num_binary64, AliasVariables::default(), - at_binary64, + Variable::EMPTY_TAG_UNION, AliasKind::Structural, ) }); } - // define the type `Num.Float Num.Binary64` + // define the type `Num.Float Num.Binary64 := Num.Binary64` { - let tags = UnionTags::insert_into_subs( - subs, - [(TagName::Private(Symbol::NUM_AT_FLOATINGPOINT), [binary64])], - ); - subs.set_content(at_float_binary64, { - Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)) - }); - let vars = AliasVariables::insert_into_subs(subs, [binary64], []); subs.set_content(float_binary64, { - Content::Alias( - Symbol::NUM_FLOATINGPOINT, - vars, - at_binary64, - AliasKind::Structural, - ) + Content::Alias(Symbol::NUM_FLOATINGPOINT, vars, binary64, AliasKind::Opaque) + }); + } + + // define the type `Num.Num (Num.Float Num.Binary64) := Num.Float Num.Binary64` + { + let vars = AliasVariables::insert_into_subs(subs, [float_binary64], []); + subs.set_content(num_float_binary64, { + Content::Alias(Symbol::NUM_NUM, vars, float_binary64, AliasKind::Opaque) }); } // define the type `F64: Num.Num (Num.Float Num.Binary64)` { - let tags = UnionTags::insert_into_subs( - subs, - [(TagName::Private(Symbol::NUM_AT_NUM), [float_binary64])], - ); - subs.set_content(at_num_float_binary64, { - Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)) - }); - - let vars = AliasVariables::insert_into_subs(subs, [float_binary64], []); - subs.set_content(num_float_binary64, { - Content::Alias( - Symbol::NUM_NUM, - vars, - at_num_float_binary64, - AliasKind::Structural, - ) - }); - subs.set_content(var_f64, { Content::Alias( num_f64, @@ -1589,42 +1502,30 @@ fn float_type( fn define_float_types(subs: &mut Subs) { float_type( subs, - Symbol::NUM_AT_BINARY32, Symbol::NUM_BINARY32, Symbol::NUM_F32, - Variable::AT_BINARY32, Variable::BINARY32, - Variable::AT_FLOAT_BINARY32, Variable::FLOAT_BINARY32, - Variable::AT_NUM_FLOAT_BINARY32, Variable::NUM_FLOAT_BINARY32, Variable::F32, ); float_type( subs, - Symbol::NUM_AT_BINARY64, Symbol::NUM_BINARY64, Symbol::NUM_F64, - Variable::AT_BINARY64, Variable::BINARY64, - Variable::AT_FLOAT_BINARY64, Variable::FLOAT_BINARY64, - Variable::AT_NUM_FLOAT_BINARY64, Variable::NUM_FLOAT_BINARY64, Variable::F64, ); float_type( subs, - Symbol::NUM_AT_DECIMAL, Symbol::NUM_DECIMAL, Symbol::NUM_DEC, - Variable::AT_DECIMAL, Variable::DECIMAL, - Variable::AT_FLOAT_DECIMAL, Variable::FLOAT_DECIMAL, - Variable::AT_NUM_FLOAT_DECIMAL, Variable::NUM_FLOAT_DECIMAL, Variable::DEC, ); @@ -1634,12 +1535,9 @@ impl Subs { pub const RESULT_TAG_NAMES: SubsSlice = SubsSlice::new(0, 2); pub const TAG_NAME_ERR: SubsIndex = SubsIndex::new(0); pub const TAG_NAME_OK: SubsIndex = SubsIndex::new(1); - 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 const TAG_NAME_INVALID_NUM_STR: SubsIndex = SubsIndex::new(5); - pub const TAG_NAME_BAD_UTF_8: SubsIndex = SubsIndex::new(6); - pub const TAG_NAME_OUT_OF_BOUNDS: SubsIndex = SubsIndex::new(7); + pub const TAG_NAME_INVALID_NUM_STR: SubsIndex = SubsIndex::new(2); + pub const TAG_NAME_BAD_UTF_8: SubsIndex = SubsIndex::new(3); + pub const TAG_NAME_OUT_OF_BOUNDS: SubsIndex = SubsIndex::new(4); pub fn new() -> Self { Self::with_capacity(0) @@ -1650,16 +1548,12 @@ impl Subs { let mut tag_names = Vec::with_capacity(32); - tag_names.push(TagName::Global("Err".into())); - tag_names.push(TagName::Global("Ok".into())); + tag_names.push(TagName::Tag("Err".into())); + tag_names.push(TagName::Tag("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)); - - tag_names.push(TagName::Global("InvalidNumStr".into())); - tag_names.push(TagName::Global("BadUtf8".into())); - tag_names.push(TagName::Global("OutOfBounds".into())); + tag_names.push(TagName::Tag("InvalidNumStr".into())); + tag_names.push(TagName::Tag("BadUtf8".into())); + tag_names.push(TagName::Tag("OutOfBounds".into())); let mut subs = Subs { utable: UnificationTable::default(), @@ -1699,8 +1593,8 @@ impl Subs { let bool_union_tags = UnionTags::insert_into_subs( &mut subs, [ - (TagName::Global("False".into()), []), - (TagName::Global("True".into()), []), + (TagName::Tag("False".into()), []), + (TagName::Tag("True".into()), []), ], ); @@ -1889,7 +1783,14 @@ impl Subs { } pub fn occurs(&self, var: Variable) -> Result<(), (Variable, Vec)> { - occurs(self, &[], var) + occurs(self, &[], var, false) + } + + pub fn occurs_including_recursion_vars( + &self, + var: Variable, + ) -> Result<(), (Variable, Vec)> { + occurs(self, &[], var, true) } pub fn mark_tag_union_recursive( @@ -2134,6 +2035,9 @@ roc_error_macros::assert_sizeof_aarch64!((Variable, Option), 4 * 8); roc_error_macros::assert_sizeof_wasm!((Variable, Option), 4 * 4); roc_error_macros::assert_sizeof_default!((Variable, Option), 4 * 8); +roc_error_macros::assert_copyable!(Content); +roc_error_macros::assert_copyable!(Descriptor); + #[derive(Clone, Copy, Debug)] pub enum Content { /// A type variable which the user did not name in an annotation, @@ -2378,10 +2282,10 @@ impl UnionTags { slice.length == 1 } - pub fn is_newtype_wrapper_of_global_tag(&self, subs: &Subs) -> bool { + pub fn is_newtype_wrapper_of_tag(&self, subs: &Subs) -> bool { self.is_newtype_wrapper(subs) && { let tags = &subs.tag_names[self.tag_names().indices()]; - matches!(tags[0], TagName::Global(_)) + matches!(tags[0], TagName::Tag(_)) } } @@ -2500,7 +2404,8 @@ impl UnionTags { pub fn iter_all( &self, - ) -> impl Iterator, SubsIndex)> { + ) -> impl Iterator, SubsIndex)> + ExactSizeIterator + { self.tag_names() .into_iter() .zip(self.variables().into_iter()) @@ -2603,7 +2508,7 @@ impl<'a> UnsortedUnionTags<'a> { } } -pub type SortedTagsIterator<'a> = Box + 'a>; +pub type SortedTagsIterator<'a> = Box + 'a>; pub type SortedTagsSlicesIterator<'a> = Box + 'a>; pub fn is_empty_tag_union(subs: &Subs, mut var: Variable) -> bool { @@ -2859,6 +2764,7 @@ fn occurs( subs: &Subs, seen: &[Variable], input_var: Variable, + include_recursion_var: bool, ) -> Result<(), (Variable, Vec)> { use self::Content::*; use self::FlatType::*; @@ -2882,47 +2788,77 @@ fn occurs( new_seen.push(root_var); match flat_type { - Apply(_, args) => { - short_circuit(subs, root_var, &new_seen, subs.get_subs_slice(*args).iter()) - } + Apply(_, args) => short_circuit( + subs, + root_var, + &new_seen, + subs.get_subs_slice(*args).iter(), + include_recursion_var, + ), Func(arg_vars, closure_var, ret_var) => { let it = once(ret_var) .chain(once(closure_var)) .chain(subs.get_subs_slice(*arg_vars).iter()); - short_circuit(subs, root_var, &new_seen, it) + short_circuit(subs, root_var, &new_seen, it, include_recursion_var) } Record(vars_by_field, ext_var) => { let slice = SubsSlice::new(vars_by_field.variables_start, vars_by_field.length); let it = once(ext_var).chain(subs.get_subs_slice(slice).iter()); - short_circuit(subs, root_var, &new_seen, it) + short_circuit(subs, root_var, &new_seen, it, include_recursion_var) } TagUnion(tags, ext_var) => { for slice_index in tags.variables() { let slice = subs[slice_index]; for var_index in slice { let var = subs[var_index]; - short_circuit_help(subs, root_var, &new_seen, var)?; + short_circuit_help( + subs, + root_var, + &new_seen, + var, + include_recursion_var, + )?; } } - short_circuit_help(subs, root_var, &new_seen, *ext_var) + short_circuit_help( + subs, + root_var, + &new_seen, + *ext_var, + include_recursion_var, + ) } FunctionOrTagUnion(_, _, ext_var) => { let it = once(ext_var); - short_circuit(subs, root_var, &new_seen, it) + short_circuit(subs, root_var, &new_seen, it, include_recursion_var) } - RecursiveTagUnion(_rec_var, tags, ext_var) => { - // TODO rec_var is excluded here, verify that this is correct + RecursiveTagUnion(rec_var, tags, ext_var) => { + if include_recursion_var { + new_seen.push(subs.get_root_key_without_compacting(*rec_var)); + } for slice_index in tags.variables() { let slice = subs[slice_index]; for var_index in slice { let var = subs[var_index]; - short_circuit_help(subs, root_var, &new_seen, var)?; + short_circuit_help( + subs, + root_var, + &new_seen, + var, + include_recursion_var, + )?; } } - short_circuit_help(subs, root_var, &new_seen, *ext_var) + short_circuit_help( + subs, + root_var, + &new_seen, + *ext_var, + include_recursion_var, + ) } EmptyRecord | EmptyTagUnion | Erroneous(_) => Ok(()), } @@ -2933,7 +2869,7 @@ fn occurs( for var_index in args.into_iter() { let var = subs[var_index]; - short_circuit_help(subs, root_var, &new_seen, var)?; + short_circuit_help(subs, root_var, &new_seen, var, include_recursion_var)?; } Ok(()) @@ -2942,7 +2878,7 @@ fn occurs( let mut new_seen = seen.to_owned(); new_seen.push(root_var); - short_circuit_help(subs, root_var, &new_seen, *typ)?; + short_circuit_help(subs, root_var, &new_seen, *typ, include_recursion_var)?; // _range_vars excluded because they are not explicitly part of the type. Ok(()) @@ -2957,12 +2893,13 @@ fn short_circuit<'a, T>( root_key: Variable, seen: &[Variable], iter: T, + include_recursion_var: bool, ) -> Result<(), (Variable, Vec)> where T: Iterator, { for var in iter { - short_circuit_help(subs, root_key, seen, *var)?; + short_circuit_help(subs, root_key, seen, *var, include_recursion_var)?; } Ok(()) @@ -2974,8 +2911,9 @@ fn short_circuit_help( root_key: Variable, seen: &[Variable], var: Variable, + include_recursion_var: bool, ) -> Result<(), (Variable, Vec)> { - if let Err((v, mut vec)) = occurs(subs, seen, var) { + if let Err((v, mut vec)) = occurs(subs, seen, var, include_recursion_var) { vec.push(root_key); return Err((v, vec)); } @@ -3660,10 +3598,14 @@ fn flat_type_to_err_type( } fn get_fresh_var_name(state: &mut ErrorTypeState) -> Lowercase { - let (name, new_index) = name_type_var(state.normals, &mut state.taken); + let (name, new_index) = name_type_var(state.normals, &mut state.taken.iter(), |var, str| { + var.as_str() == str + }); state.normals = new_index; + state.taken.insert(name.clone()); + name } diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index 8f5f68636e..3f7e26e923 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -120,13 +120,15 @@ impl RecordField { } } - pub fn instantiate_aliases( + pub fn instantiate_aliases<'a, F>( &mut self, region: Region, - aliases: &ImMap, + aliases: &'a F, var_store: &mut VarStore, introduced: &mut ImSet, - ) { + ) where + F: Fn(Symbol) -> Option<&'a Alias>, + { use RecordField::*; match self { @@ -168,13 +170,15 @@ impl LambdaSet { &mut self.0 } - fn instantiate_aliases( + fn instantiate_aliases<'a, F>( &mut self, region: Region, - aliases: &ImMap, + aliases: &'a F, var_store: &mut VarStore, introduced: &mut ImSet, - ) { + ) where + F: Fn(Symbol) -> Option<&'a Alias>, + { self.0 .instantiate_aliases(region, aliases, var_store, introduced) } @@ -1064,13 +1068,28 @@ impl Type { result } - pub fn instantiate_aliases( + pub fn shallow_structural_dealias(&self) -> &Self { + let mut result = self; + while let Type::Alias { + actual, + kind: AliasKind::Structural, + .. + } = result + { + result = actual; + } + result + } + + pub fn instantiate_aliases<'a, F>( &mut self, region: Region, - aliases: &ImMap, + aliases: &'a F, var_store: &mut VarStore, new_lambda_set_variables: &mut ImSet, - ) { + ) where + F: Fn(Symbol) -> Option<&'a Alias>, + { use Type::*; match self { @@ -1138,7 +1157,7 @@ impl Type { ); } Apply(symbol, args, _) => { - if let Some(alias) = aliases.get(symbol) { + if let Some(alias) = aliases(*symbol) { // TODO switch to this, but we still need to check for recursion with the // `else` branch if false { @@ -1670,6 +1689,7 @@ pub enum PReason { }, WhenMatch { index: HumanIndex, + sub_pattern: HumanIndex, }, TagArg { tag_name: TagName, @@ -1711,6 +1731,10 @@ pub enum Reason { name: Option, arg_index: HumanIndex, }, + TypedArg { + name: Option, + arg_index: HumanIndex, + }, FnCall { name: Option, arity: u8, @@ -1727,6 +1751,7 @@ pub enum Reason { IntLiteral, NumLiteral, StrInterpolation, + WhenBranches, WhenBranch { index: HumanIndex, }, @@ -1794,6 +1819,9 @@ pub enum Category { DefaultValue(Lowercase), // for setting optional fields AbilityMemberSpecialization(Symbol), + + Expect, + Unknown, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -1990,7 +2018,7 @@ fn write_error_type_help( if write_parens { buf.push('('); } - buf.push_str(symbol.ident_str(interns).as_str()); + buf.push_str(symbol.as_str(interns)); for arg in arguments { buf.push(' '); @@ -2328,26 +2356,33 @@ fn write_type_ext(ext: TypeExt, buf: &mut String) { static THE_LETTER_A: u32 = 'a' as u32; -pub fn name_type_var(letters_used: u32, taken: &mut MutSet) -> (Lowercase, u32) { +pub fn name_type_var bool>( + letters_used: u32, + taken: &mut impl Iterator, + mut predicate: F, +) -> (Lowercase, u32) { // TODO we should arena-allocate this String, // so all the strings in the entire pass only require ~1 allocation. - let mut generated_name = String::with_capacity((letters_used as usize) / 26 + 1); + let mut buf = String::with_capacity((letters_used as usize) / 26 + 1); - let mut remaining = letters_used as i32; - while remaining >= 0 { - generated_name.push(std::char::from_u32(THE_LETTER_A + ((remaining as u32) % 26)).unwrap()); - remaining -= 26; - } + let is_taken = { + let mut remaining = letters_used as i32; - let generated_name = generated_name.into(); + while remaining >= 0 { + buf.push(std::char::from_u32(THE_LETTER_A + ((remaining as u32) % 26)).unwrap()); + remaining -= 26; + } - if taken.contains(&generated_name) { + let generated_name: &str = buf.as_str(); + + taken.any(|item| predicate(&item, generated_name)) + }; + + if is_taken { // If the generated name is already taken, try again. - name_type_var(letters_used + 1, taken) + name_type_var(letters_used + 1, taken, predicate) } else { - taken.insert(generated_name.clone()); - - (generated_name, letters_used + 1) + (buf.into(), letters_used + 1) } } @@ -2477,7 +2512,10 @@ pub fn gather_tags_unsorted_iter( // TODO investigate this likely can happen when there is a type error RigidVar(_) => break, - other => unreachable!("something weird ended up in a tag union type: {:?}", other), + other => unreachable!( + "something weird ended up in a tag union type: {:?} at {:?}", + other, var + ), } } diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index 8de5e0245f..f5c224bc09 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -1,5 +1,5 @@ use bitflags::bitflags; -use roc_error_macros::todo_abilities; +use roc_error_macros::internal_error; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_types::subs::Content::{self, *}; @@ -119,6 +119,21 @@ impl Mode { fn as_eq(self) -> Self { (self - Mode::PRESENT) | Mode::EQ } + + #[cfg(debug_assertions)] + fn pretty_print(&self) -> &str { + if self.contains(Mode::EQ | Mode::RIGID_AS_FLEX) { + "~*" + } else if self.contains(Mode::PRESENT | Mode::RIGID_AS_FLEX) { + "+=*" + } else if self.contains(Mode::EQ) { + "~" + } else if self.contains(Mode::PRESENT) { + "+=" + } else { + unreachable!("Bad mode!") + } + } } #[derive(Debug)] @@ -134,14 +149,26 @@ pub struct Context { pub enum Unified { Success { vars: Pool, - must_implement_ability: Vec, + must_implement_ability: MustImplementConstraints, }, Failure(Pool, ErrorType, ErrorType, DoesNotImplementAbility), BadType(Pool, roc_types::types::Problem), } +impl Unified { + pub fn expect_success(self, err_msg: &'static str) -> (Pool, MustImplementConstraints) { + match self { + Unified::Success { + vars, + must_implement_ability, + } => (vars, must_implement_ability), + _ => internal_error!("{}", err_msg), + } + } +} + /// Specifies that `type` must implement the ability `ability`. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct MustImplementAbility { // This only points to opaque type names currently. // TODO(abilities) support structural types in general @@ -149,12 +176,39 @@ pub struct MustImplementAbility { pub ability: Symbol, } +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct MustImplementConstraints(Vec); + +impl MustImplementConstraints { + pub fn push(&mut self, must_implement: MustImplementAbility) { + self.0.push(must_implement) + } + + pub fn extend(&mut self, other: Self) { + self.0.extend(other.0) + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn get_unique(mut self) -> Vec { + self.0.sort(); + self.0.dedup(); + self.0 + } + + pub fn iter_for_ability(&self, ability: Symbol) -> impl Iterator { + self.0.iter().filter(move |mia| mia.ability == ability) + } +} + #[derive(Debug, Default)] pub struct Outcome { mismatches: Vec, /// We defer these checks until the end of a solving phase. /// NOTE: this vector is almost always empty! - must_implement_ability: Vec, + must_implement_ability: MustImplementConstraints, } impl Outcome { @@ -235,12 +289,30 @@ pub fn unify_pool( } } +/// Set `ROC_PRINT_UNIFICATIONS` in debug runs to print unifications as they start and complete as +/// a tree to stderr. +/// NOTE: Only run this on individual tests! Run on multiple threads, this would clobber each others' output. #[cfg(debug_assertions)] -fn debug_print_unified_types(subs: &mut Subs, ctx: &Context, before_unified: bool) { +fn debug_print_unified_types(subs: &mut Subs, ctx: &Context, opt_outcome: Option<&Outcome>) { + use roc_types::subs::SubsFmtContent; + + static mut UNIFICATION_DEPTH: usize = 0; + if std::env::var("ROC_PRINT_UNIFICATIONS").is_ok() { - let time = if before_unified { "START" } else { "END" }; - // if true, print the types that are unified. - // + let prefix = match opt_outcome { + None => "❔", + Some(outcome) if outcome.mismatches.is_empty() => "βœ…", + Some(_) => "❌", + }; + + let depth = unsafe { UNIFICATION_DEPTH }; + let indent = 2; + let (use_depth, new_depth) = if opt_outcome.is_none() { + (depth, depth + indent) + } else { + (depth - indent, depth - indent) + }; + // NOTE: names are generated here (when creating an error type) and that modifies names // generated by pretty_print.rs. So many test will fail with changes in variable names when // this block runs. @@ -253,24 +325,27 @@ fn debug_print_unified_types(subs: &mut Subs, ctx: &Context, before_unified: boo // 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 { "+=" }; + let mode = ctx.mode.pretty_print(); eprintln!( - "{}({:?}-{:?}): {:?} {:?} {} {:?} {:?}", - time, + "{}{}({:?}-{:?}): {:?} {:?} {} {:?} {:?}", + " ".repeat(use_depth), + prefix, ctx.first, ctx.second, ctx.first, - roc_types::subs::SubsFmtContent(&content_1, subs), + SubsFmtContent(&content_1, subs), mode, ctx.second, - roc_types::subs::SubsFmtContent(&content_2, subs), + SubsFmtContent(&content_2, subs), ); + + unsafe { UNIFICATION_DEPTH = new_depth }; } } fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome { #[cfg(debug_assertions)] - debug_print_unified_types(subs, &ctx, true); + debug_print_unified_types(subs, &ctx, None); let result = match &ctx.first_desc.content { FlexVar(opt_name) => unify_flex(subs, &ctx, opt_name, None, &ctx.second_desc.content), @@ -299,8 +374,11 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome { Structure(flat_type) => { unify_structure(subs, pool, &ctx, flat_type, &ctx.second_desc.content) } - Alias(symbol, args, real_var, kind) => { - unify_alias(subs, pool, &ctx, *symbol, *args, *real_var, *kind) + Alias(symbol, args, real_var, AliasKind::Structural) => { + unify_alias(subs, pool, &ctx, *symbol, *args, *real_var) + } + Alias(symbol, args, real_var, AliasKind::Opaque) => { + unify_opaque(subs, pool, &ctx, *symbol, *args, *real_var) } &RangedNumber(typ, range_vars) => unify_ranged_number(subs, pool, &ctx, typ, range_vars), Error => { @@ -310,7 +388,7 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome { }; #[cfg(debug_assertions)] - debug_print_unified_types(subs, &ctx, true); + debug_print_unified_types(subs, &ctx, Some(&result)); result } @@ -330,9 +408,12 @@ fn unify_ranged_number( // Ranged number wins merge(subs, ctx, RangedNumber(real_var, range_vars)) } - RecursionVar { .. } | RigidVar(..) | Alias(..) | Structure(..) => { - unify_pool(subs, pool, real_var, ctx.second, ctx.mode) - } + RecursionVar { .. } + | RigidVar(..) + | Alias(..) + | Structure(..) + | RigidAbleVar(..) + | FlexAbleVar(..) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode), &RangedNumber(other_real_var, other_range_vars) => { let outcome = unify_pool(subs, pool, real_var, other_real_var, ctx.mode); if outcome.mismatches.is_empty() { @@ -343,9 +424,6 @@ fn unify_ranged_number( // TODO: We should probably check that "range_vars" and "other_range_vars" intersect } Error => merge(subs, ctx, Error), - FlexAbleVar(..) | RigidAbleVar(..) => { - todo_abilities!("I don't think this can be reached yet") - } }; if !outcome.mismatches.is_empty() { @@ -389,6 +467,57 @@ fn check_valid_range( } } +#[inline(always)] +#[allow(clippy::too_many_arguments)] +fn unify_two_aliases( + subs: &mut Subs, + pool: &mut Pool, + ctx: &Context, + symbol: Symbol, + args: AliasVariables, + real_var: Variable, + other_args: AliasVariables, + other_real_var: Variable, + other_content: &Content, +) -> Outcome { + if args.len() == other_args.len() { + let mut outcome = Outcome::default(); + let it = args + .all_variables() + .into_iter() + .zip(other_args.all_variables().into_iter()); + + let args_unification_snapshot = subs.snapshot(); + + for (l, r) in it { + let l_var = subs[l]; + let r_var = subs[r]; + outcome.union(unify_pool(subs, pool, l_var, r_var, ctx.mode)); + } + + if outcome.mismatches.is_empty() { + outcome.union(merge(subs, ctx, *other_content)); + } + + let args_unification_had_changes = !subs + .vars_since_snapshot(&args_unification_snapshot) + .is_empty(); + subs.commit_snapshot(args_unification_snapshot); + + if !args.is_empty() && args_unification_had_changes && outcome.mismatches.is_empty() { + // We need to unify the real vars because unification of type variables + // may have made them larger, which then needs to be reflected in the `real_var`. + outcome.union(unify_pool(subs, pool, real_var, other_real_var, ctx.mode)); + } + + outcome + } else { + dbg!(args.len(), other_args.len()); + mismatch!("{:?}", symbol) + } +} + +// Unifies a structural alias #[inline(always)] fn unify_alias( subs: &mut Subs, @@ -397,72 +526,40 @@ fn unify_alias( symbol: Symbol, args: AliasVariables, real_var: Variable, - kind: AliasKind, ) -> Outcome { let other_content = &ctx.second_desc.content; - let either_is_opaque = - kind == AliasKind::Opaque || matches!(other_content, Alias(_, _, _, AliasKind::Opaque)); + let kind = AliasKind::Structural; match other_content { FlexVar(_) => { // Alias wins merge(subs, ctx, Alias(symbol, args, real_var, kind)) } - RecursionVar { structure, .. } if !either_is_opaque => { - unify_pool(subs, pool, real_var, *structure, ctx.mode) + RecursionVar { structure, .. } => unify_pool(subs, pool, real_var, *structure, ctx.mode), + RigidVar(_) | RigidAbleVar(..) | FlexAbleVar(..) => { + unify_pool(subs, pool, real_var, ctx.second, ctx.mode) } - RigidVar(_) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode), - RigidAbleVar (_, ability) | FlexAbleVar(_, ability) if kind == AliasKind::Opaque && args.is_empty() => { - // Opaque type wins - let mut outcome = merge(subs, ctx, Alias(symbol, args, real_var, kind)); - outcome.must_implement_ability.push(MustImplementAbility { typ: symbol, ability: *ability }); - outcome - } - Alias(other_symbol, other_args, other_real_var, _) - // Opaques types are only equal if the opaque symbols are equal! - if !either_is_opaque || symbol == *other_symbol => - { + Alias(_, _, _, AliasKind::Opaque) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode), + Alias(other_symbol, other_args, other_real_var, AliasKind::Structural) => { if symbol == *other_symbol { - if args.len() == other_args.len() { - let mut outcome = Outcome::default(); - let it = args - .all_variables() - .into_iter() - .zip(other_args.all_variables().into_iter()); - - let args_unification_snapshot = subs.snapshot(); - - for (l, r) in it { - let l_var = subs[l]; - let r_var = subs[r]; - outcome.union(unify_pool(subs, pool, l_var, r_var, ctx.mode)); - } - - if outcome.mismatches.is_empty() { - outcome.union(merge(subs, ctx, *other_content)); - } - - let args_unification_had_changes = !subs.vars_since_snapshot(&args_unification_snapshot).is_empty(); - subs.commit_snapshot(args_unification_snapshot); - - if !args.is_empty() && args_unification_had_changes && outcome.mismatches.is_empty() { - // We need to unify the real vars because unification of type variables - // may have made them larger, which then needs to be reflected in the `real_var`. - outcome.union(unify_pool(subs, pool, real_var, *other_real_var, ctx.mode)); - } - - outcome - } else { - dbg!(args.len(), other_args.len()); - mismatch!("{:?}", symbol) - } + unify_two_aliases( + subs, + pool, + ctx, + symbol, + args, + real_var, + *other_args, + *other_real_var, + other_content, + ) } else { unify_pool(subs, pool, real_var, *other_real_var, ctx.mode) } } - Structure(_) if !either_is_opaque => unify_pool(subs, pool, real_var, ctx.second, ctx.mode), - RangedNumber(other_real_var, other_range_vars) if !either_is_opaque => { + Structure(_) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode), + RangedNumber(other_real_var, other_range_vars) => { let outcome = unify_pool(subs, pool, real_var, *other_real_var, ctx.mode); if outcome.mismatches.is_empty() { check_valid_range(subs, pool, real_var, *other_range_vars, ctx.mode) @@ -471,9 +568,69 @@ fn unify_alias( } } Error => merge(subs, ctx, Error), + } +} + +#[inline(always)] +fn unify_opaque( + subs: &mut Subs, + pool: &mut Pool, + ctx: &Context, + symbol: Symbol, + args: AliasVariables, + real_var: Variable, +) -> Outcome { + let other_content = &ctx.second_desc.content; + + let kind = AliasKind::Opaque; + + match other_content { + FlexVar(_) => { + // Alias wins + merge(subs, ctx, Alias(symbol, args, real_var, kind)) + } + // RigidVar(_) | RigidAbleVar(..) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode), + FlexAbleVar(_, ability) if args.is_empty() => { + // Opaque type wins + let mut outcome = merge(subs, ctx, Alias(symbol, args, real_var, kind)); + outcome.must_implement_ability.push(MustImplementAbility { + typ: symbol, + ability: *ability, + }); + outcome + } + Alias(_, _, other_real_var, AliasKind::Structural) => { + unify_pool(subs, pool, ctx.first, *other_real_var, ctx.mode) + } + Alias(other_symbol, other_args, other_real_var, AliasKind::Opaque) => { + // Opaques types are only equal if the opaque symbols are equal! + if symbol == *other_symbol { + unify_two_aliases( + subs, + pool, + ctx, + symbol, + args, + real_var, + *other_args, + *other_real_var, + other_content, + ) + } else { + mismatch!("{:?}", symbol) + } + } + RangedNumber(other_real_var, other_range_vars) => { + // This opaque might be a number, check if it unifies with the target ranged number var. + let outcome = unify_pool(subs, pool, ctx.first, *other_real_var, ctx.mode); + if outcome.mismatches.is_empty() { + check_valid_range(subs, pool, ctx.first, *other_range_vars, ctx.mode) + } else { + outcome + } + } other => { - // The type on the left is an alias, but the one on the right is not! - debug_assert!(either_is_opaque); + // The type on the left is an opaque, but the one on the right is not! mismatch!("Cannot unify opaque {:?} with {:?}", symbol, other) } } @@ -518,7 +675,15 @@ fn unify_structure( RecursionVar { structure, .. } => match flat_type { FlatType::TagUnion(_, _) => { // unify the structure with this unrecursive tag union - unify_pool(subs, pool, ctx.first, *structure, ctx.mode) + let mut outcome = unify_pool(subs, pool, ctx.first, *structure, ctx.mode); + + if outcome.mismatches.is_empty() { + outcome.union(fix_tag_union_recursion_variable( + subs, ctx, ctx.first, other, + )); + } + + outcome } FlatType::RecursiveTagUnion(rec, _, _) => { debug_assert!(is_recursion_var(subs, *rec)); @@ -527,7 +692,15 @@ fn unify_structure( } FlatType::FunctionOrTagUnion(_, _, _) => { // unify the structure with this unrecursive tag union - unify_pool(subs, pool, ctx.first, *structure, ctx.mode) + let mut outcome = unify_pool(subs, pool, ctx.first, *structure, ctx.mode); + + if outcome.mismatches.is_empty() { + outcome.union(fix_tag_union_recursion_variable( + subs, ctx, ctx.first, other, + )); + } + + outcome } // Only tag unions can be recursive; everything else is an error. _ => mismatch!( @@ -585,6 +758,57 @@ fn unify_structure( } } +/// Ensures that a non-recursive tag union, when unified with a recursion var to become a recursive +/// tag union, properly contains a recursion variable that recurses on itself. +// +// When might this not be the case? For example, in the code +// +// Indirect : [ Indirect ConsList ] +// +// ConsList : [ Nil, Cons Indirect ] +// +// l : ConsList +// l = Cons (Indirect (Cons (Indirect Nil))) +// # ^^^^^^^^^^^^^^^~~~~~~~~~~~~~~~~~~~~~^ region-a +// # ~~~~~~~~~~~~~~~~~~~~~ region-b +// l +// +// Suppose `ConsList` has the expanded type `[ Nil, Cons [ Indirect ] ] as `. +// After unifying the tag application annotated "region-b" with the recursion variable ``, +// the tentative total-type of the application annotated "region-a" would be +// ` = [ Nil, Cons [ Indirect ] ] as `. That is, the type of the recursive tag union +// would be inlined at the site "v", rather than passing through the correct recursion variable +// "rec" first. +// +// This is not incorrect from a type perspective, but causes problems later on for e.g. layout +// determination, which expects recursion variables to be placed correctly. Attempting to detect +// this during layout generation does not work so well because it may be that there *are* recursive +// tag unions that should be inlined, and not pass through recursion variables. So instead, try to +// resolve these cases here. +// +// See tests labeled "issue_2810" for more examples. +fn fix_tag_union_recursion_variable( + subs: &mut Subs, + ctx: &Context, + tag_union_promoted_to_recursive: Variable, + recursion_var: &Content, +) -> Outcome { + debug_assert!(matches!( + subs.get_content_without_compacting(tag_union_promoted_to_recursive), + Structure(FlatType::RecursiveTagUnion(..)) + )); + + let has_recursing_recursive_variable = subs + .occurs_including_recursion_vars(tag_union_promoted_to_recursive) + .is_err(); + + if !has_recursing_recursive_variable { + merge(subs, ctx, *recursion_var) + } else { + Outcome::default() + } +} + fn unify_record( subs: &mut Subs, pool: &mut Pool, diff --git a/docs/src/lib.rs b/docs/src/lib.rs index d09134d98d..49e2a76dd3 100644 --- a/docs/src/lib.rs +++ b/docs/src/lib.rs @@ -6,13 +6,12 @@ use html::mark_node_to_html; 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::{LoadedModule, LoadingProblem}; -use roc_module::symbol::{IdentIds, Interns, ModuleId}; +use roc_module::symbol::{IdentIdsByModule, Interns, ModuleId}; use roc_parse::ident::{parse_ident, Ident}; use roc_parse::state::State; use roc_region::all::Region; @@ -71,7 +70,7 @@ pub fn generate_docs_html(filenames: Vec, build_dir: &Path) { let exposed_values = loaded_module .exposed_values .iter() - .map(|symbol| symbol.ident_str(&loaded_module.interns).to_string()) + .map(|symbol| symbol.as_str(&loaded_module.interns).to_string()) .collect::>(); (exposed_values, d) @@ -711,7 +710,7 @@ struct DocUrl { fn doc_url<'a>( home: ModuleId, exposed_values: &[&str], - dep_idents: &MutMap, + dep_idents: &IdentIdsByModule, scope: &Scope, interns: &'a Interns, mut module_name: &'a str, @@ -844,8 +843,8 @@ fn markdown_to_html( } } } - Ok((_, Ident::GlobalTag(type_name), _)) => { - // This looks like a global tag name, but it could + Ok((_, Ident::Tag(type_name), _)) => { + // This looks like a tag name, but it could // be a type alias that's in scope, e.g. [I64] let DocUrl { url, title } = doc_url( loaded_module.module_id, diff --git a/editor/src/editor/main.rs b/editor/src/editor/main.rs index 5dbfac12f7..cac5843445 100644 --- a/editor/src/editor/main.rs +++ b/editor/src/editor/main.rs @@ -74,7 +74,7 @@ fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box) -> Result<(), Box) -> Result<(), Box) -> Result<(), Box Result<(wgpu::Device, wgpu::Queue), wgpu::RequestDeviceError> { +) -> Result<(wgpu::Device, wgpu::Queue, wgpu::TextureFormat), wgpu::RequestDeviceError> { if force_fallback_adapter { log::error!("Falling back to software renderer. GPU acceleration has been disabled."); } @@ -418,7 +416,13 @@ async fn create_device( If you're running this from inside nix, follow the instructions here to resolve this: https://github.com/rtfeldman/roc/blob/trunk/BUILDING_FROM_SOURCE.md#editor "#); - adapter + let color_format = surface.get_preferred_format(&adapter).unwrap(); + + if color_format != wgpu::TextureFormat::Bgra8UnormSrgb { + log::warn!("Your preferred TextureFormat {:?} is different than expected. Colors may look different, please report this issue on github and tag @Anton-4.", color_format); + } + + let request_res = adapter .request_device( &wgpu::DeviceDescriptor { label: None, @@ -427,7 +431,12 @@ async fn create_device( }, None, ) - .await + .await; + + match request_res { + Ok((device, queue)) => Ok((device, queue, color_format)), + Err(err) => Err(err), + } } fn draw_rects( diff --git a/editor/src/editor/mvc/ed_update.rs b/editor/src/editor/mvc/ed_update.rs index ba546e85d1..bbe05560c6 100644 --- a/editor/src/editor/mvc/ed_update.rs +++ b/editor/src/editor/mvc/ed_update.rs @@ -3169,7 +3169,7 @@ pub mod test_ed_update { assert_type_tooltips_clean( ovec!["val = [ [ 0, 1, \"2\" ], [ 3, 4, 5 ┃] ]"], - ovec!["List (Num *)", "List (List )"], + ovec!["List (Num *)", "List "], )?; Ok(()) diff --git a/editor/src/editor/mvc/let_update.rs b/editor/src/editor/mvc/let_update.rs index de59fbf901..b9a734b8f4 100644 --- a/editor/src/editor/mvc/let_update.rs +++ b/editor/src/editor/mvc/let_update.rs @@ -1,6 +1,7 @@ use roc_ast::lang::core::expr::expr2::Expr2; use roc_ast::lang::core::pattern::Pattern2; use roc_ast::lang::core::val_def::ValueDef; +use roc_module::ident::Ident; use roc_module::symbol::Symbol; use crate::editor::ed_error::EdResult; @@ -25,7 +26,8 @@ pub fn start_new_let_value(ed_model: &mut EdModel, new_char: &char) -> EdResult< let val_expr2_node = Expr2::Blank; let val_expr_id = ed_model.module.env.pool.add(val_expr2_node); - let ident_id = ed_model.module.env.ident_ids.add(val_name_string.into()); + let ident = Ident::from(val_name_string); + let ident_id = ed_model.module.env.ident_ids.add_ident(&ident); let var_symbol = Symbol::new(ed_model.module.env.home, ident_id); let body = Expr2::Var(var_symbol); let body_id = ed_model.module.env.pool.add(body); diff --git a/editor/src/editor/mvc/tld_value_update.rs b/editor/src/editor/mvc/tld_value_update.rs index e548dd2e25..3afaafdcf8 100644 --- a/editor/src/editor/mvc/tld_value_update.rs +++ b/editor/src/editor/mvc/tld_value_update.rs @@ -1,5 +1,6 @@ use roc_ast::lang::core::{def::def2::Def2, expr::expr2::Expr2}; use roc_code_markup::slow_pool::MarkNodeId; +use roc_module::ident::Ident; use crate::{ editor::ed_error::{EdResult, FailedToUpdateIdentIdName, KeyNotFound}, @@ -26,13 +27,8 @@ pub fn start_new_tld_value(ed_model: &mut EdModel, new_char: &char) -> EdResult< let val_expr_node = Expr2::Blank; let val_expr_id = ed_model.module.env.pool.add(val_expr_node); - let val_name_string = new_char.to_string(); - - let ident_id = ed_model - .module - .env - .ident_ids - .add(val_name_string.clone().into()); + let ident = Ident::from(new_char.to_string().as_str()); + let ident_id = ed_model.module.env.ident_ids.add_ident(&ident); let module_ident_ids_opt = ed_model .loaded_module @@ -42,7 +38,7 @@ pub fn start_new_tld_value(ed_model: &mut EdModel, new_char: &char) -> EdResult< if let Some(module_ident_ids_ref) = module_ident_ids_opt { // this might create different IdentId for interns and env.ident_ids which may be a problem - module_ident_ids_ref.add(val_name_string.into()); + module_ident_ids_ref.add_ident(&ident); } else { KeyNotFound { key_str: format!("{:?}", ed_model.module.env.home), diff --git a/editor/src/graphics/colors.rs b/editor/src/graphics/colors.rs index 924c9ab301..67dc2801a8 100644 --- a/editor/src/graphics/colors.rs +++ b/editor/src/graphics/colors.rs @@ -1,4 +1,4 @@ -use palette::{FromColor, Hsv, Srgb}; +use palette::{FromColor, Hsv, LinSrgb, Srgb}; pub type RgbaTup = (f32, f32, f32, f32); pub const WHITE: RgbaTup = (1.0, 1.0, 1.0, 1.0); @@ -21,11 +21,11 @@ pub fn from_hsb(hue: usize, saturation: usize, brightness: usize) -> RgbaTup { } pub fn from_hsba(hue: usize, saturation: usize, brightness: usize, alpha: f32) -> RgbaTup { - let rgb = Srgb::from_color(Hsv::new( + let rgb = LinSrgb::from(Srgb::from_color(Hsv::new( hue as f32, (saturation as f32) / 100.0, (brightness as f32) / 100.0, - )); + ))); (rgb.red, rgb.green, rgb.blue, alpha) } diff --git a/error_macros/src/lib.rs b/error_macros/src/lib.rs index 2cd1eeb732..41ec7b6172 100644 --- a/error_macros/src/lib.rs +++ b/error_macros/src/lib.rs @@ -66,6 +66,14 @@ macro_rules! assert_sizeof_all { }; } +/// Assert that a type has `Copy` +#[macro_export] +macro_rules! assert_copyable { + ($t: ty) => { + static_assertions::assert_impl_all!($t: Copy); + }; +} + // LARGE SCALE PROJECTS // // This section is for "todo!"-style macros enabled in sections where large-scale changes to the diff --git a/examples/benchmarks/AStar.roc b/examples/benchmarks/AStar.roc index 4f5a8e0fa1..d6a6ec4c1e 100644 --- a/examples/benchmarks/AStar.roc +++ b/examples/benchmarks/AStar.roc @@ -12,13 +12,12 @@ Model position : } initialModel : position -> Model position -initialModel = \start -> - { - evaluated: Set.empty, - openSet: Set.single start, - costs: Dict.single start 0, - cameFrom: Dict.empty, - } +initialModel = \start -> { + evaluated: Set.empty, + openSet: Set.single start, + costs: Dict.single start 0, + cameFrom: Dict.empty, +} cheapestOpen : (position -> F64), Model position -> Result position {} cheapestOpen = \costFn, model -> diff --git a/examples/benchmarks/Base64/Decode.roc b/examples/benchmarks/Base64/Decode.roc index e78585ad5b..ad5c92ef17 100644 --- a/examples/benchmarks/Base64/Decode.roc +++ b/examples/benchmarks/Base64/Decode.roc @@ -20,11 +20,10 @@ loopHelp = \{ remaining, string } -> c = Num.intCast z combined = Num.bitwiseOr (Num.bitwiseOr (Num.shiftLeftBy 16 a) (Num.shiftLeftBy 8 b)) c - Loop - { - remaining: remaining - 3, - string: Str.concat string (bitsToChars combined 0), - } + Loop { + remaining: remaining - 3, + string: Str.concat string (bitsToChars combined 0), + } else if remaining == 0 then Bytes.Decode.succeed (Done string) else if remaining == 2 then diff --git a/examples/benchmarks/Bytes/Decode.roc b/examples/benchmarks/Bytes/Decode.roc index d5e22e8f78..6a5df2c8f3 100644 --- a/examples/benchmarks/Bytes/Decode.roc +++ b/examples/benchmarks/Bytes/Decode.roc @@ -4,7 +4,7 @@ State : { bytes : List U8, cursor : Nat } DecodeProblem : [ OutOfBytes ] -Decoder a : [ @Decoder (State -> [ Good State a, Bad DecodeProblem ]) ] +Decoder a := State -> [ Good State a, Bad DecodeProblem ] decode : List U8, Decoder a -> Result a DecodeProblem decode = \bytes, @Decoder decoder -> diff --git a/examples/benchmarks/Bytes/Encode.roc b/examples/benchmarks/Bytes/Encode.roc index 37321e8506..195ccd2407 100644 --- a/examples/benchmarks/Bytes/Encode.roc +++ b/examples/benchmarks/Bytes/Encode.roc @@ -132,11 +132,10 @@ encodeHelp = \encoder, offset, output -> List.walk bs { output, offset } - \accum, byte -> - { - offset: accum.offset + 1, - output: List.set accum.output offset byte, - } + \accum, byte -> { + offset: accum.offset + 1, + output: List.set accum.output offset byte, + } Sequence _ encoders -> List.walk diff --git a/examples/benchmarks/Closure.roc b/examples/benchmarks/Closure.roc index d6bf6b3ff8..1a78d99867 100644 --- a/examples/benchmarks/Closure.roc +++ b/examples/benchmarks/Closure.roc @@ -20,32 +20,32 @@ toUnitBorrowed = \x -> Str.countGraphemes x foo = \f, x -> f x # --- -closure2 : {} -> Task.Task {} [] -closure2 = \_ -> - x : Str - x = "a long string such that it's malloced" - - Task.succeed {} - |> Task.map (\_ -> x) - |> Task.map toUnit - -toUnit = \_ -> {} - -# --- -closure3 : {} -> Task.Task {} [] -closure3 = \_ -> - x : Str - x = "a long string such that it's malloced" - - Task.succeed {} - |> Task.after (\_ -> Task.succeed x |> Task.map (\_ -> {})) - -# --- -closure4 : {} -> Task.Task {} [] -closure4 = \_ -> - x : Str - x = "a long string such that it's malloced" - - Task.succeed {} - |> Task.after (\_ -> Task.succeed x) - |> Task.map (\_ -> {}) +# closure2 : {} -> Task.Task {} [] +# closure2 = \_ -> +# x : Str +# x = "a long string such that it's malloced" +# +# Task.succeed {} +# |> Task.map (\_ -> x) +# |> Task.map toUnit +# +# toUnit = \_ -> {} +# +# # --- +# closure3 : {} -> Task.Task {} [] +# closure3 = \_ -> +# x : Str +# x = "a long string such that it's malloced" +# +# Task.succeed {} +# |> Task.after (\_ -> Task.succeed x |> Task.map (\_ -> {})) +# +# # --- +# closure4 : {} -> Task.Task {} [] +# closure4 = \_ -> +# x : Str +# x = "a long string such that it's malloced" +# +# Task.succeed {} +# |> Task.after (\_ -> Task.succeed x) +# |> Task.map (\_ -> {}) diff --git a/examples/benchmarks/Deriv.roc b/examples/benchmarks/Deriv.roc index caf08906a6..8407d641c2 100644 --- a/examples/benchmarks/Deriv.roc +++ b/examples/benchmarks/Deriv.roc @@ -41,8 +41,8 @@ Expr : [ Val I64, Var Str, Add Expr Expr, Mul Expr Expr, Pow Expr Expr, Ln Expr divmod : I64, I64 -> Result { div : I64, mod : I64 } [ DivByZero ]* divmod = \l, r -> - when Pair (l // r) (l % r) is - Pair div (Ok mod) -> + when Pair (Num.divTruncChecked l r) (Num.remChecked l r) is + Pair (Ok div) (Ok mod) -> Ok { div, mod } _ -> diff --git a/examples/benchmarks/RBTreeCk.roc b/examples/benchmarks/RBTreeCk.roc index 225df33161..90c6fb3d4f 100644 --- a/examples/benchmarks/RBTreeCk.roc +++ b/examples/benchmarks/RBTreeCk.roc @@ -23,12 +23,12 @@ makeMapHelp = \freq, n, m, acc -> _ -> powerOf10 = - (n % 10 |> resultWithDefault 0) == 0 + n % 10 == 0 m1 = insert m n powerOf10 isFrequency = - (n % freq |> resultWithDefault 0) == 0 + n % freq == 0 x = (if isFrequency then Cons m1 acc else acc) @@ -43,15 +43,6 @@ fold = \f, tree, b -> Node _ l k v r -> fold f r (f k v (fold f l b)) -resultWithDefault : Result a e, a -> a -resultWithDefault = \res, default -> - when res is - Ok v -> - v - - Err _ -> - default - main : Task.Task {} [] main = Task.after diff --git a/examples/breakout/.gitignore b/examples/breakout/.gitignore new file mode 100644 index 0000000000..593993d712 --- /dev/null +++ b/examples/breakout/.gitignore @@ -0,0 +1 @@ +breakout diff --git a/examples/breakout/breakout.roc b/examples/breakout/breakout.roc new file mode 100644 index 0000000000..1625f9b411 --- /dev/null +++ b/examples/breakout/breakout.roc @@ -0,0 +1,165 @@ +app "breakout" + packages { pf: "platform" } + imports [ pf.Game.{ Bounds, Elem, Event } ] + provides [ program ] { Model } to pf + +paddleWidth = 0.2# width of the paddle, as a % of screen width +paddleHeight = 50# height of the paddle, in pixels +paddleSpeed = 65# how many pixels the paddle moves per keypress +blockHeight = 80# height of a block, in pixels +blockBorder = 0.025# border of a block, as a % of its width +ballSize = 55 +numRows = 4 +numCols = 8 +numBlocks = numRows * numCols + +Model : { + # Screen height and width + height : F32, + width : F32, + # Paddle X-coordinate + paddleX : F32, + # Ball coordinates + ballX : F32, + ballY : F32, + dBallX : F32, + # delta x - how much it moves per tick + dBallY : F32, + # delta y - how much it moves per tick + } + +init : Bounds -> Model +init = \{ width, height } -> { + # Screen height and width + width, + height, + # Paddle X-coordinate + paddleX: (width * 0.5) - (paddleWidth * width * 0.5), + # Ball coordinates + ballX: width * 0.5, + ballY: height * 0.4, + # Delta - how much ball moves in each tick + dBallX: 4, + dBallY: 4, +} + +update : Model, Event -> Model +update = \model, event -> + when event is + Resize size -> + { model & width: size.width, height: size.height } + + KeyDown Left -> + { model & paddleX: model.paddleX - paddleSpeed } + + KeyDown Right -> + { model & paddleX: model.paddleX + paddleSpeed } + + Tick _ -> + tick model + + _ -> + model + +tick : Model -> Model +tick = \model -> + model + |> moveBall + +moveBall : Model -> Model +moveBall = \model -> + ballX = model.ballX + model.dBallX + ballY = model.ballY + model.dBallY + + paddleTop = model.height - blockHeight - (paddleHeight * 2) + paddleLeft = model.paddleX + paddleRight = paddleLeft + (model.width * paddleWidth) + + # If its y used to be less than the paddle, and now it's greater than or equal, + # then this is the frame where the ball collided with it. + crossingPaddle = model.ballY < paddleTop && ballY >= paddleTop + + # If it collided with the paddle, bounce off. + directionChange = + if crossingPaddle && (ballX >= paddleLeft && ballX <= paddleRight) then + -1f32 + else + 1f32 + + dBallX = model.dBallX * directionChange + dBallY = model.dBallY * directionChange + + { model & ballX, ballY, dBallX, dBallY } + +render : Model -> List Elem +render = \model -> + + blocks = List.map + (List.range 0 numBlocks) + \index -> + col = + Num.rem index numCols + |> Num.toF32 + + row = + index + // numCols + |> Num.toF32 + + red = col / Num.toF32 numCols + green = row / Num.toF32 numRows + blue = Num.toF32 index / Num.toF32 numBlocks + + color = { r: red * 0.8, g: 0.2 + green * 0.6, b: 0.2 + blue * 0.8, a: 1 } + + { row, col, color } + + blockWidth = model.width / numCols + + rects = + List.joinMap + blocks + \{ row, col, color } -> + left = Num.toF32 col * blockWidth + top = Num.toF32 (row * blockHeight) + border = blockBorder * blockWidth + + outer = Rect { + left, + top, + width: blockWidth, + height: blockHeight, + color: { r: color.r * 0.8, g: color.g * 0.8, b: color.b * 0.8, a: 1 }, + } + + inner = Rect { + left: left + border, + top: top + border, + width: blockWidth - (border * 2), + height: blockHeight - (border * 2), + color, + } + + [ outer, inner ] + + ball = + color = { r: 0.7, g: 0.3, b: 0.9, a: 1.0 } + width = ballSize + height = ballSize + left = model.ballX + top = model.ballY + + Rect { left, top, width, height, color } + + paddle = + color = { r: 0.8, g: 0.8, b: 0.8, a: 1.0 } + width = model.width * paddleWidth + height = paddleHeight + left = model.paddleX + top = model.height - blockHeight - height + + Rect { left, top, width, height, color } + + List.concat rects [ paddle, ball ] + +program = { init, update, render } diff --git a/examples/breakout/hello.roc b/examples/breakout/hello.roc new file mode 100644 index 0000000000..f1a486f889 --- /dev/null +++ b/examples/breakout/hello.roc @@ -0,0 +1,17 @@ +app "breakout" + packages { pf: "platform" } + imports [ pf.Game.{ Bounds, Elem, Event } ] + provides [ program ] { Model } to pf + +Model : { text : Str } + +init : Bounds -> Model +init = \_ -> { text: "Hello, World!" } + +update : Model, Event -> Model +update = \model, _ -> model + +render : Model -> List Elem +render = \model -> [ Text model.text ] + +program = { init, update, render } diff --git a/examples/breakout/platform/.gitignore b/examples/breakout/platform/.gitignore new file mode 100644 index 0000000000..eb5a316cbd --- /dev/null +++ b/examples/breakout/platform/.gitignore @@ -0,0 +1 @@ +target diff --git a/examples/breakout/platform/Action.roc b/examples/breakout/platform/Action.roc new file mode 100644 index 0000000000..ad15ee728b --- /dev/null +++ b/examples/breakout/platform/Action.roc @@ -0,0 +1,20 @@ +interface Action + exposes [ Action, none, update, map ] + imports [] + +Action state : [ None, Update state ] + +none : Action * +none = None + +update : state -> Action state +update = Update + +map : Action a, (a -> b) -> Action b +map = \action, transform -> + when action is + None -> + None + + Update state -> + Update (transform state) diff --git a/examples/breakout/platform/Cargo.lock b/examples/breakout/platform/Cargo.lock new file mode 100644 index 0000000000..8f8005c26d --- /dev/null +++ b/examples/breakout/platform/Cargo.lock @@ -0,0 +1,2835 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ab_glyph" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61caed9aec6daeee1ea38ccf5fb225e4f96c1eeead1b4a5c267324a63cf02326" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13739d7177fbd22bb0ed28badfff9f372f8bef46c863db4e1c6248f6b223b6e" + +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "alsa" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75c4da790adcb2ce5e758c064b4f3ec17a30349f9961d3e5e6c9688b052a9e18" +dependencies = [ + "alsa-sys", + "bitflags", + "libc", + "nix 0.20.0", +] + +[[package]] +name = "alsa-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "approx" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f2a05fd1bd10b2527e20a2cd32d8873d115b8b39fe219ee25f42a8aca6ba278" +dependencies = [ + "num-traits", +] + +[[package]] +name = "approx" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "072df7202e63b127ab55acfe16ce97013d5b97bf160489336d3f1840fd78e99e" +dependencies = [ + "num-traits", +] + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "ash" +version = "0.35.1+1.2.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7fd04def1c9101b5fb488c131022d2d6f87753ef4b1b11b279e2af404fae6b9" +dependencies = [ + "libloading", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "backtrace" +version = "0.3.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "321629d8ba6513061f26707241fa9bc89524ff1cd7a915a97ef0c62c666ce1b6" +dependencies = [ + "addr2line", + "cc", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bindgen" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da379dbebc0b76ef63ca68d8fc6e71c0f13e59432e0987e508c1820e6ab5239" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", +] + +[[package]] +name = "bit-set" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + +[[package]] +name = "bumpalo" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "bytemuck" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439989e6b8c38d1b6570a384ef1e49c8848128f5a97f3914baef02920842712f" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e215f8c2f9f79cb53c8335e687ffd07d5bfcb6fe5fc80723762d0be46e7cc54" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "calloop" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf2eec61efe56aa1e813f5126959296933cf0700030e4314786c48779a66ab82" +dependencies = [ + "log", + "nix 0.22.0", +] + +[[package]] +name = "cc" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cexpr" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" +dependencies = [ + "nom 5.1.2", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "cgmath" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a98d30140e3296250832bbaaff83b27dcd6fa3cc70fb6f1f3e5c9c0023b5317" +dependencies = [ + "approx 0.4.0", + "num-traits", +] + +[[package]] +name = "clang-sys" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa66045b9cb23c2e9c1520732030608b02ee07e5cfaa5a521ec15ded7fa24c90" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "claxon" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bfbf56724aa9eca8afa4fcfadeb479e722935bb2a0900c2d37e0cc477af0688" + +[[package]] +name = "clipboard-win" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fdf5e01086b6be750428ba4a40619f847eb2e95756eee84b18e06e5f0b50342" +dependencies = [ + "lazy-bytes-cast", + "winapi", +] + +[[package]] +name = "cocoa" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832" +dependencies = [ + "bitflags", + "block", + "cocoa-foundation", + "core-foundation 0.9.2", + "core-graphics 0.22.3", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" +dependencies = [ + "bitflags", + "block", + "core-foundation 0.9.2", + "core-graphics-types", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "colored" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +dependencies = [ + "atty", + "lazy_static", + "winapi", +] + +[[package]] +name = "combine" +version = "4.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b727aacc797f9fc28e355d21f34709ac4fc9adecfe470ad07b8f4464f53062" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "confy" +version = "0.4.0" +source = "git+https://github.com/rust-cli/confy#664992aecd97b4af0eda8d9d2825885662e1c6b4" +dependencies = [ + "directories-next", + "serde", + "serde_yaml", +] + +[[package]] +name = "copyless" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536" + +[[package]] +name = "copypasta" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4423d79fed83ebd9ab81ec21fa97144300a961782158287dc9bf7eddac37ff0b" +dependencies = [ + "clipboard-win", + "objc", + "objc-foundation", + "objc_id", + "smithay-clipboard", + "x11-clipboard", +] + +[[package]] +name = "core-foundation" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" +dependencies = [ + "core-foundation-sys 0.7.0", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3" +dependencies = [ + "core-foundation-sys 0.8.3", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "core-graphics" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3889374e6ea6ab25dba90bb5d96202f61108058361f6dc72e8b03e6f8bbe923" +dependencies = [ + "bitflags", + "core-foundation 0.7.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags", + "core-foundation 0.9.2", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +dependencies = [ + "bitflags", + "core-foundation 0.9.2", + "foreign-types", + "libc", +] + +[[package]] +name = "core-video-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ecad23610ad9757664d644e369246edde1803fcb43ed72876565098a5d3828" +dependencies = [ + "cfg-if 0.1.10", + "core-foundation-sys 0.7.0", + "core-graphics 0.19.2", + "libc", + "objc", +] + +[[package]] +name = "coreaudio-rs" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11894b20ebfe1ff903cbdc52259693389eea03b94918a2def2c30c3bf227ad88" +dependencies = [ + "bitflags", + "coreaudio-sys", +] + +[[package]] +name = "coreaudio-sys" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b7e3347be6a09b46aba228d6608386739fb70beff4f61e07422da87b0bb31fa" +dependencies = [ + "bindgen", +] + +[[package]] +name = "cpal" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98f45f0a21f617cd2c788889ef710b63f075c949259593ea09c826f1e47a2418" +dependencies = [ + "alsa", + "core-foundation-sys 0.8.3", + "coreaudio-rs", + "jni", + "js-sys", + "lazy_static", + "libc", + "mach", + "ndk 0.3.0", + "ndk-glue 0.3.0", + "nix 0.20.0", + "oboe", + "parking_lot", + "stdweb", + "thiserror", + "web-sys", + "winapi", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97242a70df9b89a65d0b6df3c4bf5b9ce03c5b7309019777fbde37e7537f8762" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcae03edb34f947e64acdb1c33ec169824e20657e9ecb61cef6c8c74dcb8120" +dependencies = [ + "cfg-if 1.0.0", + "lazy_static", +] + +[[package]] +name = "cty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" + +[[package]] +name = "d3d12" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2daefd788d1e96e0a9d66dee4b828b883509bc3ea9ce30665f04c3246372690c" +dependencies = [ + "bitflags", + "libloading", + "winapi", +] + +[[package]] +name = "darling" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" +dependencies = [ + "darling_core 0.10.2", + "darling_macro 0.10.2", +] + +[[package]] +name = "darling" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d720b8683f8dd83c65155f0530560cba68cd2bf395f6513a483caee57ff7f4" +dependencies = [ + "darling_core 0.13.1", + "darling_macro 0.13.1", +] + +[[package]] +name = "darling_core" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.9.3", + "syn", +] + +[[package]] +name = "darling_core" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a340f241d2ceed1deb47ae36c4144b2707ec7dd0b649f894cb39bb595986324" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" +dependencies = [ + "darling_core 0.10.2", + "quote", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c41b3b7352feb3211a0d743dc5700a4e3b60f51bd2b368892d1e0f9a95f44b" +dependencies = [ + "darling_core 0.13.1", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if 1.0.0", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dlib" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +dependencies = [ + "libloading", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "env_logger" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "find-crate" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2" +dependencies = [ + "toml", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "fs_extra" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" + +[[package]] +name = "futures" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28560757fe2bb34e79f907794bb6b22ae8b0e5c669b638a1132f2592b19035b4" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7" + +[[package]] +name = "futures-executor" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29d6d2ff5bb10fb95c85b8ce46538a2e5f5e7fdc755623a7d4529ab8a4ed9d2a" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2" + +[[package]] +name = "futures-macro" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbd947adfffb0efc70599b3ddcf7b5597bb5fa9e245eb99f62b3a5f7bb8bd3c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3055baccb68d74ff6480350f8d6eb8fcfa3aa11bdc1a1ae3afdd0514617d508" + +[[package]] +name = "futures-task" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72" + +[[package]] +name = "futures-util" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + +[[package]] +name = "getrandom" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "glow" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bd5877156a19b8ac83a29b2306fe20537429d318f3ff0a1a2119f8d9c61919" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glyph_brush" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21932fbf719272848eec4583740d978203c6e7da4c4e203358f5b95946c97409" +dependencies = [ + "glyph_brush_draw_cache", + "glyph_brush_layout", + "log", + "ordered-float", + "rustc-hash", + "twox-hash", +] + +[[package]] +name = "glyph_brush_draw_cache" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6010675390f6889e09a21e2c8b575b3ee25667ea8237a8d59423f73cb8c28610" +dependencies = [ + "ab_glyph", + "crossbeam-channel", + "crossbeam-deque", + "linked-hash-map", + "rayon", + "rustc-hash", +] + +[[package]] +name = "glyph_brush_layout" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc32c2334f00ca5ac3695c5009ae35da21da8c62d255b5b96d56e2597a637a38" +dependencies = [ + "ab_glyph", + "approx 0.5.0", + "xi-unicode", +] + +[[package]] +name = "gpu-alloc" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc59e5f710e310e76e6707f86c561dd646f69a8876da9131703b2f717de818d" +dependencies = [ + "bitflags", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54804d0d6bc9d7f26db4eaec1ad10def69b599315f487d32c334a80d1efe67a5" +dependencies = [ + "bitflags", +] + +[[package]] +name = "gpu-descriptor" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a538f217be4d405ff4719a283ca68323cc2384003eca5baaa87501e821c81dda" +dependencies = [ + "bitflags", + "gpu-descriptor-types", + "hashbrown", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "363e3677e55ad168fef68cf9de3a4a310b53124c5e784c53a1d70e92d23f2126" +dependencies = [ + "bitflags", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "host" +version = "0.1.0" +dependencies = [ + "arrayvec", + "bytemuck", + "cgmath", + "colored", + "confy", + "copypasta", + "env_logger", + "fs_extra", + "futures", + "glyph_brush", + "libc", + "log", + "nonempty", + "page_size", + "palette", + "pest", + "pest_derive", + "roc_std", + "rodio", + "serde", + "snafu", + "threadpool", + "wgpu", + "wgpu_glyph", + "winit", +] + +[[package]] +name = "hound" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a164bb2ceaeff4f42542bdb847c41517c78a60f5649671b2a07312b6e117549" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "inplace_it" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90953f308a79fe6d62a4643e51f848fbfddcd05975a38e69fdf4ab86a7baf7ca" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "jni" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "khronos-egl" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3" +dependencies = [ + "libc", + "libloading", + "pkg-config", +] + +[[package]] +name = "lazy-bytes-cast" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10257499f089cd156ad82d0a9cd57d9501fa2c989068992a97eb3c27836f206b" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "lewton" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777b48df9aaab155475a83a7df3070395ea1ac6902f5cd062b8f2b028075c030" +dependencies = [ + "byteorder", + "ogg", + "tinyvec", +] + +[[package]] +name = "libc" +version = "0.2.113" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef78b64d87775463c549fbd80e19249ef436ea3bf1de2a1eb7e717ec7fab1e9" + +[[package]] +name = "libloading" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +dependencies = [ + "cfg-if 1.0.0", + "winapi", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" + +[[package]] +name = "lock_api" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "memmap2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b6c2ebff6180198788f5db08d7ce3bc1d0b617176678831a7510825973e357" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metal" +version = "0.23.1" +source = "git+https://github.com/gfx-rs/metal-rs?rev=44af5cc#44af5cca340617d42d701264f9bf71d1f3e68096" +dependencies = [ + "bitflags", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "minimp3" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985438f75febf74c392071a975a29641b420dd84431135a6e6db721de4b74372" +dependencies = [ + "minimp3-sys", + "slice-deque", + "thiserror", +] + +[[package]] +name = "minimp3-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e21c73734c69dc95696c9ed8926a2b393171d98b3f5f5935686a26a487ab9b90" +dependencies = [ + "cc", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "mio" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi", +] + +[[package]] +name = "naga" +version = "0.8.0" +source = "git+https://github.com/gfx-rs/naga?rev=8e2e39e#8e2e39e4d8fa5bbb657c3b170b4f6607d703e284" +dependencies = [ + "bit-set", + "bitflags", + "codespan-reporting", + "hexf-parse", + "indexmap", + "log", + "num-traits", + "rustc-hash", + "spirv", + "thiserror", +] + +[[package]] +name = "ndk" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8794322172319b972f528bf90c6b467be0079f1fa82780ffb431088e741a73ab" +dependencies = [ + "jni-sys", + "ndk-sys 0.2.2", + "num_enum", + "thiserror", +] + +[[package]] +name = "ndk" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d868f654c72e75f8687572699cdabe755f03effbb62542768e995d5b8d699d" +dependencies = [ + "bitflags", + "jni-sys", + "ndk-sys 0.2.2", + "num_enum", + "thiserror", +] + +[[package]] +name = "ndk" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" +dependencies = [ + "bitflags", + "jni-sys", + "ndk-sys 0.3.0", + "num_enum", + "thiserror", +] + +[[package]] +name = "ndk-glue" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5caf0c24d51ac1c905c27d4eda4fa0635bbe0de596b8f79235e0b17a4d29385" +dependencies = [ + "lazy_static", + "libc", + "log", + "ndk 0.3.0", + "ndk-macro 0.2.0", + "ndk-sys 0.2.2", +] + +[[package]] +name = "ndk-glue" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc291b8de2095cba8dab7cf381bf582ff4c17a09acf854c32e46545b08085d28" +dependencies = [ + "lazy_static", + "libc", + "log", + "ndk 0.5.0", + "ndk-macro 0.3.0", + "ndk-sys 0.2.2", +] + +[[package]] +name = "ndk-glue" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04c0d14b0858eb9962a5dac30b809b19f19da7e4547d64af2b0bb051d2e55d79" +dependencies = [ + "lazy_static", + "libc", + "log", + "ndk 0.6.0", + "ndk-macro 0.3.0", + "ndk-sys 0.3.0", +] + +[[package]] +name = "ndk-macro" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d1c6307dc424d0f65b9b06e94f88248e6305726b14729fd67a5e47b2dc481d" +dependencies = [ + "darling 0.10.2", + "proc-macro-crate 0.1.5", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ndk-macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c" +dependencies = [ + "darling 0.13.1", + "proc-macro-crate 1.1.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ndk-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121" + +[[package]] +name = "ndk-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "nix" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", +] + +[[package]] +name = "nix" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1e25ee6b412c2a1e3fcb6a4499a5c1bfe7f43e014bdce9a6b6666e5aa2d187" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", + "memoffset", +] + +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "memchr", + "version_check", +] + +[[package]] +name = "nom" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" +dependencies = [ + "memchr", + "minimal-lexical", + "version_check", +] + +[[package]] +name = "nonempty" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" + +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "720d3ea1055e4e4574c0c0b0f8c3fd4f24c4cdaf465948206dea090b57b526ad" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d992b768490d7fe0d8586d9b5745f6c49f557da6d81dc982b1d167ad4edbb21" +dependencies = [ + "proc-macro-crate 1.1.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "object" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" +dependencies = [ + "memchr", +] + +[[package]] +name = "oboe" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2463c8f2e19b4e0d0710a21f8e4011501ff28db1c95d7a5482a553b2100502d2" +dependencies = [ + "jni", + "ndk 0.6.0", + "ndk-glue 0.6.0", + "num-derive", + "num-traits", + "oboe-sys", +] + +[[package]] +name = "oboe-sys" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3370abb7372ed744232c12954d920d1a40f1c4686de9e79e800021ef492294bd" +dependencies = [ + "cc", +] + +[[package]] +name = "ogg" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6951b4e8bf21c8193da321bcce9c9dd2e13c858fe078bf9054a288b419ae5d6e" +dependencies = [ + "byteorder", +] + +[[package]] +name = "once_cell" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "ordered-float" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" +dependencies = [ + "num-traits", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef05f2882a8b3e7acc10c153ade2631f7bfc8ce00d2bf3fb8f4e9d2ae6ea5c3" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "page_size" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eebde548fbbf1ea81a99b128872779c437752fb99f217c45245e1a61dcd9edcd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "palette" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9735f7e1e51a3f740bacd5dc2724b61a7806f23597a8736e679f38ee3435d18" +dependencies = [ + "approx 0.5.0", + "num-traits", + "palette_derive", + "phf", +] + +[[package]] +name = "palette_derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7799c3053ea8a6d8a1193c7ba42f534e7863cf52e378a7f90406f4a645d33bad" +dependencies = [ + "find-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +dependencies = [ + "maplit", + "pest", + "sha-1", +] + +[[package]] +name = "phf" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ac8b67553a7ca9457ce0e526948cad581819238f4a9d1ea74545851fa24f37" +dependencies = [ + "phf_macros", + "phf_shared", + "proc-macro-hack", +] + +[[package]] +name = "phf_generator" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43f3220d96e0080cc9ea234978ccd80d904eafb17be31bb0f76daaea6493082" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b706f5936eb50ed880ae3009395b43ed19db5bff2ebd459c95e7bf013a89ab86" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68318426de33640f02be62b4ae8eb1261be2efbc337b60c54d845bf4484e0d9" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro-crate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83" +dependencies = [ + "thiserror", + "toml", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "profiling" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9145ac0af1d93c638c98c40cf7d25665f427b2a44ad0a99b1dccf3e2f25bb987" + +[[package]] +name = "quick-xml" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8533f14c8382aaad0d592c812ac3b826162128b65662331e1127b45c3d18536b" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core", +] + +[[package]] +name = "range-alloc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e935c45e09cc6dcf00d2f0b2d630a58f4095320223d47fc68918722f0538b6" + +[[package]] +name = "raw-window-handle" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba75eee94a9d5273a68c9e1e105d9cffe1ef700532325788389e5a83e2522b7" +dependencies = [ + "cty", +] + +[[package]] +name = "rayon" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom", + "redox_syscall", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "renderdoc-sys" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157" + +[[package]] +name = "roc_std" +version = "0.1.0" +dependencies = [ + "static_assertions 0.1.1", +] + +[[package]] +name = "rodio" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d98f5e557b61525057e2bc142c8cd7f0e70d75dc32852309bec440e6e046bf9" +dependencies = [ + "claxon", + "cpal", + "hound", + "lewton", + "minimp3", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "ryu" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.135" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cf9235533494ea2ddcdb794665461814781c53f19d87b76e571a1c35acbad2b" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.135" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dcde03d87d4c973c04be249e7d8f0b35db1c848c487bd43032808e59dd8328d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_yaml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a521f2940385c165a24ee286aa8599633d162077a54bdcae2a6fd5a7bfa7a0" +dependencies = [ + "indexmap", + "ryu", + "serde", + "yaml-rust", +] + +[[package]] +name = "sha-1" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +dependencies = [ + "block-buffer", + "digest", + "fake-simd", + "opaque-debug", +] + +[[package]] +name = "shlex" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" + +[[package]] +name = "siphasher" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a86232ab60fa71287d7f2ddae4a7073f6b7aac33631c3015abb556f08c6d0a3e" + +[[package]] +name = "slab" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" + +[[package]] +name = "slice-deque" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31ef6ee280cdefba6d2d0b4b78a84a1c1a3f3a4cec98c2d4231c8bc225de0f25" +dependencies = [ + "libc", + "mach", + "winapi", +] + +[[package]] +name = "slotmap" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + +[[package]] +name = "smithay-client-toolkit" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1325f292209cee78d5035530932422a30aa4c8fda1a16593ac083c1de211e68a" +dependencies = [ + "bitflags", + "calloop", + "dlib", + "lazy_static", + "log", + "memmap2", + "nix 0.22.0", + "pkg-config", + "wayland-client", + "wayland-cursor", + "wayland-protocols", +] + +[[package]] +name = "smithay-clipboard" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610b551bd25378bfd2b8e7a0fcbd83d427e8f2f6a40c47ae0f70688e9949dd55" +dependencies = [ + "smithay-client-toolkit", + "wayland-client", +] + +[[package]] +name = "snafu" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab12d3c261b2308b0d80c26fffb58d17eba81a4be97890101f416b478c79ca7" +dependencies = [ + "backtrace", + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "spirv" +version = "0.2.0+1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830" +dependencies = [ + "bitflags", + "num-traits", +] + +[[package]] +name = "static_assertions" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f406d6ee68db6796e11ffd7b4d171864c58b7451e79ef9460ea33c287a1f89a7" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stdweb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef5430c8e36b713e13b48a9f709cc21e046723fe44ce34587b73a830203b533e" + +[[package]] +name = "strsim" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "tinyvec" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + +[[package]] +name = "ttf-parser" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ccbe8381883510b6a2d8f1e32905bddd178c11caef8083086d0c0c9ab0ac281" + +[[package]] +name = "twox-hash" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee73e6e4924fe940354b8d4d98cad5231175d615cd855b758adc658c0aac6a0" +dependencies = [ + "cfg-if 1.0.0", + "rand", + "static_assertions 1.1.0", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "wasm-bindgen" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" + +[[package]] +name = "wayland-client" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91223460e73257f697d9e23d401279123d36039a3f7a449e983f123292d4458f" +dependencies = [ + "bitflags", + "downcast-rs", + "libc", + "nix 0.22.0", + "scoped-tls", + "wayland-commons", + "wayland-scanner", + "wayland-sys", +] + +[[package]] +name = "wayland-commons" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f6e5e340d7c13490eca867898c4cec5af56c27a5ffe5c80c6fc4708e22d33e" +dependencies = [ + "nix 0.22.0", + "once_cell", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-cursor" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c52758f13d5e7861fc83d942d3d99bf270c83269575e52ac29e5b73cb956a6bd" +dependencies = [ + "nix 0.22.0", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60147ae23303402e41fe034f74fb2c35ad0780ee88a1c40ac09a3be1e7465741" +dependencies = [ + "bitflags", + "wayland-client", + "wayland-commons", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39a1ed3143f7a143187156a2ab52742e89dac33245ba505c17224df48939f9e0" +dependencies = [ + "proc-macro2", + "quote", + "xml-rs", +] + +[[package]] +name = "wayland-sys" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9341df79a8975679188e37dab3889bfa57c44ac2cb6da166f519a81cbe452d4" +dependencies = [ + "dlib", + "lazy_static", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wgpu" +version = "0.12.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=0545e36#0545e36aa82709cca78c14eb3813f10eea7a9275" +dependencies = [ + "arrayvec", + "js-sys", + "log", + "naga", + "parking_lot", + "raw-window-handle", + "smallvec", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "0.12.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=0545e36#0545e36aa82709cca78c14eb3813f10eea7a9275" +dependencies = [ + "arrayvec", + "bitflags", + "cfg_aliases", + "codespan-reporting", + "copyless", + "fxhash", + "log", + "naga", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "thiserror", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "0.12.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=0545e36#0545e36aa82709cca78c14eb3813f10eea7a9275" +dependencies = [ + "arrayvec", + "ash", + "bit-set", + "bitflags", + "block", + "core-graphics-types", + "d3d12", + "foreign-types", + "fxhash", + "glow", + "gpu-alloc", + "gpu-descriptor", + "inplace_it", + "js-sys", + "khronos-egl", + "libloading", + "log", + "metal", + "naga", + "objc", + "parking_lot", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "thiserror", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "winapi", +] + +[[package]] +name = "wgpu-types" +version = "0.12.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=0545e36#0545e36aa82709cca78c14eb3813f10eea7a9275" +dependencies = [ + "bitflags", +] + +[[package]] +name = "wgpu_glyph" +version = "0.16.0" +source = "git+https://github.com/Anton-4/wgpu_glyph?rev=257d109#257d1098cbafa3c8a0a2465937b06fc730fc6ffb" +dependencies = [ + "bytemuck", + "glyph_brush", + "log", + "wgpu", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winit" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b43cc931d58b99461188607efd7acb2a093e65fc621f54cad78517a6063e73a" +dependencies = [ + "bitflags", + "cocoa", + "core-foundation 0.9.2", + "core-graphics 0.22.3", + "core-video-sys", + "dispatch", + "instant", + "lazy_static", + "libc", + "log", + "mio", + "ndk 0.5.0", + "ndk-glue 0.5.0", + "ndk-sys 0.2.2", + "objc", + "parking_lot", + "percent-encoding", + "raw-window-handle", + "smithay-client-toolkit", + "wasm-bindgen", + "wayland-client", + "wayland-protocols", + "web-sys", + "winapi", + "x11-dl", +] + +[[package]] +name = "x11-clipboard" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473068b7b80ac86a18328824f1054e5e007898c47b5bbc281bd7abe32bc3653c" +dependencies = [ + "xcb", +] + +[[package]] +name = "x11-dl" +version = "2.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea26926b4ce81a6f5d9d0f3a0bc401e5a37c6ae14a1bfaa8ff6099ca80038c59" +dependencies = [ + "lazy_static", + "libc", + "pkg-config", +] + +[[package]] +name = "xcb" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771e2b996df720cd1c6dd9ff90f62d91698fd3610cc078388d0564bdd6622a9c" +dependencies = [ + "libc", + "log", + "quick-xml", +] + +[[package]] +name = "xcursor" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" +dependencies = [ + "nom 7.1.0", +] + +[[package]] +name = "xi-unicode" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a" + +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] diff --git a/examples/breakout/platform/Cargo.toml b/examples/breakout/platform/Cargo.toml new file mode 100644 index 0000000000..47281be89d --- /dev/null +++ b/examples/breakout/platform/Cargo.toml @@ -0,0 +1,75 @@ +[package] +name = "host" +version = "0.1.0" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +edition = "2018" + +# Needed to be able to run on non-Windows systems for some reason. Without this, cargo panics with: +# +# error: DX12 API enabled on non-Windows OS. If your project is not using resolver="2" in Cargo.toml, it should. +resolver = "2" + +[lib] +name = "host" +path = "src/lib.rs" +crate-type = ["staticlib", "rlib"] + +[[bin]] +name = "host" +path = "src/main.rs" + +[dependencies] +roc_std = { path = "../../../roc_std" } +libc = "0.2" +arrayvec = "0.7.2" +page_size = "0.4.2" +# when changing winit version, check if copypasta can be updated simultaneously so they use the same versions for their dependencies. This will save build time. +winit = "0.26.1" +wgpu = { git = "https://github.com/gfx-rs/wgpu", rev = "0545e36" } +wgpu_glyph = { git = "https://github.com/Anton-4/wgpu_glyph", rev = "257d109" } +glyph_brush = "0.7.2" +log = "0.4.14" +env_logger = "0.9.0" +futures = "0.3.17" +cgmath = "0.18.0" +snafu = { version = "0.6.10", features = ["backtraces"] } +colored = "2.0.0" +pest = "2.1.3" +pest_derive = "2.1.0" +copypasta = "0.7.1" +palette = "0.6.0" +confy = { git = 'https://github.com/rust-cli/confy', features = [ + "yaml_conf" +], default-features = false } +serde = { version = "1.0.130", features = ["derive"] } +nonempty = "0.7.0" +fs_extra = "1.2.0" +rodio = { version = "0.14.0", optional = true } # to play sounds +threadpool = "1.8.1" + +[package.metadata.cargo-udeps.ignore] +# confy is currently unused but should not be removed +normal = ["confy"] +#development = [] +#build = [] + +[features] +default = [] +with_sound = ["rodio"] + +[dependencies.bytemuck] +version = "1.7.2" +features = ["derive"] + +[workspace] + +# Optimizations based on https://deterministic.space/high-performance-rust.html +[profile.release] +lto = "fat" +codegen-units = 1 + +# debug = true # enable when profiling +[profile.bench] +lto = "thin" +codegen-units = 1 diff --git a/examples/breakout/platform/Elem.roc b/examples/breakout/platform/Elem.roc new file mode 100644 index 0000000000..519d007b99 --- /dev/null +++ b/examples/breakout/platform/Elem.roc @@ -0,0 +1,193 @@ +interface Elem + exposes [ Elem, PressEvent, row, col, text, button, none, translate, list ] + imports [ Action.{ Action } ] + +Elem state : + # PERFORMANCE NOTE: + # If there are 8 or fewer tags here, then on a 64-bit system, the tag can be stored + # in the pointer - for massive memory savings. Try extremely hard to always limit the number + # of tags in this union to 8 or fewer! + [ + Button (ButtonConfig state) (Elem state), + Text Str, + Col (List (Elem state)), + Row (List (Elem state)), + Lazy (Result { state, elem : Elem state } [ NotCached ] -> { state, elem : Elem state }), + # TODO FIXME: using this definition of Lazy causes a stack overflow in the compiler! + # Lazy (Result (Cached state) [ NotCached ] -> Cached state), + None, + ] + +## Used internally in the type definition of Lazy +Cached state : { state, elem : Elem state } + +ButtonConfig state : { onPress : state, PressEvent -> Action state } + +PressEvent : { button : [ Touch, Mouse [ Left, Right, Middle ] ] } + +text : Str -> Elem * +text = \str -> + Text str + +button : { onPress : state, PressEvent -> Action state }, Elem state -> Elem state +button = \config, label -> + Button config label + +row : List (Elem state) -> Elem state +row = \children -> + Row children + +col : List (Elem state) -> Elem state +col = \children -> + Col children + +lazy : state, (state -> Elem state) -> Elem state +lazy = \state, render -> + # This function gets called by the host during rendering. It will + # receive the cached state and element (wrapped in Ok) if we've + # ever rendered this before, and Err otherwise. + Lazy + \result -> + when result is + Ok cached if cached.state == state -> + # If we have a cached value, and the new state is the + # same as the cached one, then we can return exactly + # what we had cached. + cached + + _ -> + # Either the state changed or else we didn't have a + # cached value to use. Either way, we need to render + # with the new state and store that for future use. + { state, elem: render state } + +none : Elem * +none = None# I've often wanted this in elm/html. Usually end up resorting to (Html.text "") - this seems nicer. +## Change an element's state type. +## +## TODO: indent the following once https://github.com/rtfeldman/roc/issues/2585 is fixed. +## State : { photo : Photo } +## +## render : State -> Elem State +## render = \state -> +## child : Elem State +## child = +## Photo.render state.photo +## |> Elem.translate .photo &photo +## +## col {} [ child, otherElems ] +## +translate = \child, toChild, toParent -> + when child is + Text str -> + Text str + + Col elems -> + Col (List.map elems \elem -> translate elem toChild toParent) + + Row elems -> + Row (List.map elems \elem -> translate elem toChild toParent) + + Button config label -> + onPress = \parentState, event -> + toChild parentState + |> config.onPress event + |> Action.map \c -> toParent parentState c + + Button { onPress } (translate label toChild toParent) + + Lazy renderChild -> + Lazy + \parentState -> + { elem, state } = renderChild (toChild parentState) + + { + elem: translate toChild toParent newChild, + state: toParent parentState state, + } + + None -> + None + +## Render a list of elements, using [Elem.translate] on each of them. +## +## Convenient when you have a [List] in your state and want to make +## a [List] of child elements out of it. +## +## TODO: indent the following once https://github.com/rtfeldman/roc/issues/2585 is fixed. +## State : { photos : List Photo } +## +## render : State -> Elem State +## render = \state -> +## children : List (Elem State) +## children = +## Elem.list Photo.render state .photos &photos +## +## col {} children +## TODO: format as multiline type annotation once https://github.com/rtfeldman/roc/issues/2586 is fixed +list : (child -> Elem child), parent, (parent -> List child), (parent, List child -> parent) -> List (Elem parent) +list = \renderChild, parent, toChildren, toParent -> + List.mapWithIndex + (toChildren parent) + \index, child -> + toChild = \par -> List.get (toChildren par) index + + newChild = translateOrDrop + child + toChild + \par, ch -> + toChildren par + |> List.set ch index + |> toParent + + renderChild newChild + +## Internal helper function for Elem.list +## +## Tries to translate a child to a parent, but +## if the child has been removed from the parent, +## drops it. +## +## TODO: format as multiline type annotation once https://github.com/rtfeldman/roc/issues/2586 is fixed +translateOrDrop : Elem child, (parent -> Result child *), (parent, child -> parent) -> Elem parent +translateOrDrop = \child, toChild, toParent -> + when child is + Text str -> + Text str + + Col elems -> + Col (List.map elems \elem -> translateOrDrop elem toChild toParent) + + Row elems -> + Row (List.map elems \elem -> translateOrDrop elem toChild toParent) + + Button config label -> + onPress = \parentState, event -> + when toChild parentState is + Ok newChild -> + newChild + |> config.onPress event + |> Action.map \c -> toParent parentState c + + Err _ -> + # The child was removed from the list before this onPress handler resolved. + # (For example, by a previous event handler that fired simultaneously.) + Action.none + + Button { onPress } (translateOrDrop label toChild toParent) + + Lazy childState renderChild -> + Lazy + (toParent childState) + \parentState -> + when toChild parentState is + Ok newChild -> + renderChild newChild + |> translateOrDrop toChild toParent + + Err _ -> + None + + # I don't think this should ever happen in practice. + None -> + None diff --git a/examples/breakout/platform/Game.roc b/examples/breakout/platform/Game.roc new file mode 100644 index 0000000000..bb270cb299 --- /dev/null +++ b/examples/breakout/platform/Game.roc @@ -0,0 +1,13 @@ +interface Game + exposes [ Bounds, Elem, Event ] + imports [] + +Rgba : { r : F32, g : F32, b : F32, a : F32 } + +Bounds : { height : F32, width : F32 } + +Elem : [ Rect { color : Rgba, left : F32, top : F32, width : F32, height : F32 }, Text Str ] + +KeyCode : [ Left, Right, Other ] + +Event : [ Resize { width : F32, height : F32 }, KeyDown KeyCode, KeyUp KeyCode, Tick U128 ] diff --git a/examples/breakout/platform/Package-Config.roc b/examples/breakout/platform/Package-Config.roc new file mode 100644 index 0000000000..87004e3735 --- /dev/null +++ b/examples/breakout/platform/Package-Config.roc @@ -0,0 +1,14 @@ +platform "gui" + requires { Model } { program : _ } + exposes [ Game ] + packages {} + imports [ Game.{ Bounds, Elem, Event } ] + provides [ programForHost ] + +# TODO allow changing the window title - maybe via a Task, since that shouldn't happen all the time +programForHost : { + init : (Bounds -> Model) as Init, + update : (Model, Event -> Model) as Update, + render : (Model -> List Elem) as Render, +} +programForHost = program diff --git a/examples/breakout/platform/build.rs b/examples/breakout/platform/build.rs new file mode 100644 index 0000000000..73159e387c --- /dev/null +++ b/examples/breakout/platform/build.rs @@ -0,0 +1,4 @@ +fn main() { + println!("cargo:rustc-link-lib=dylib=app"); + println!("cargo:rustc-link-search=."); +} diff --git a/examples/breakout/platform/host.c b/examples/breakout/platform/host.c new file mode 100644 index 0000000000..b9214bcf33 --- /dev/null +++ b/examples/breakout/platform/host.c @@ -0,0 +1,3 @@ +extern int rust_main(); + +int main() { return rust_main(); } \ No newline at end of file diff --git a/examples/breakout/platform/src/graphics/colors.rs b/examples/breakout/platform/src/graphics/colors.rs new file mode 100644 index 0000000000..71be9c8fa5 --- /dev/null +++ b/examples/breakout/platform/src/graphics/colors.rs @@ -0,0 +1,50 @@ +use cgmath::Vector4; +use palette::{FromColor, Hsv, Srgb}; + +/// This order is optimized for what Roc will send +#[repr(C)] +#[derive(Copy, Clone, Debug, PartialEq, Default)] +pub struct Rgba { + a: f32, + b: f32, + g: f32, + r: f32, +} + +impl Rgba { + pub const WHITE: Self = Self::new(1.0, 1.0, 1.0, 1.0); + + pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self { + Self { r, g, b, a } + } + + pub const fn to_array(self) -> [f32; 4] { + [self.r, self.g, self.b, self.a] + } + + pub fn from_hsb(hue: usize, saturation: usize, brightness: usize) -> Self { + Self::from_hsba(hue, saturation, brightness, 1.0) + } + + pub fn from_hsba(hue: usize, saturation: usize, brightness: usize, alpha: f32) -> Self { + let rgb = Srgb::from_color(Hsv::new( + hue as f32, + (saturation as f32) / 100.0, + (brightness as f32) / 100.0, + )); + + Self::new(rgb.red, rgb.green, rgb.blue, alpha) + } +} + +impl From for [f32; 4] { + fn from(rgba: Rgba) -> Self { + rgba.to_array() + } +} + +impl From for Vector4 { + fn from(rgba: Rgba) -> Self { + Vector4::new(rgba.r, rgba.b, rgba.g, rgba.a) + } +} diff --git a/examples/breakout/platform/src/graphics/lowlevel/buffer.rs b/examples/breakout/platform/src/graphics/lowlevel/buffer.rs new file mode 100644 index 0000000000..a5a7f54161 --- /dev/null +++ b/examples/breakout/platform/src/graphics/lowlevel/buffer.rs @@ -0,0 +1,96 @@ +// Contains parts of https://github.com/sotrh/learn-wgpu +// by Benjamin Hansen - license information can be found in the LEGAL_DETAILS +// file in the root directory of this distribution. +// +// Thank you, Benjamin! + +// Contains parts of https://github.com/iced-rs/iced/blob/adce9e04213803bd775538efddf6e7908d1c605e/wgpu/src/shader/quad.wgsl +// By HΓ©ctor RamΓ³n, Iced contributors Licensed under the MIT license. +// The license is included in the LEGAL_DETAILS file in the root directory of this distribution. + +// Thank you HΓ©ctor RamΓ³n and Iced contributors! + +use std::mem; + +use super::{quad::Quad, vertex::Vertex}; +use crate::graphics::primitives::rect::RectElt; +use wgpu::util::DeviceExt; + +pub struct RectBuffers { + pub vertex_buffer: wgpu::Buffer, + pub index_buffer: wgpu::Buffer, + pub quad_buffer: wgpu::Buffer, +} + +pub const QUAD_INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3]; + +const QUAD_VERTS: [Vertex; 4] = [ + Vertex { + _position: [0.0, 0.0], + }, + Vertex { + _position: [1.0, 0.0], + }, + Vertex { + _position: [1.0, 1.0], + }, + Vertex { + _position: [0.0, 1.0], + }, +]; + +pub const MAX_QUADS: usize = 1_000; + +pub fn create_rect_buffers( + gpu_device: &wgpu::Device, + cmd_encoder: &mut wgpu::CommandEncoder, + rects: &[RectElt], +) -> RectBuffers { + let vertex_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(&QUAD_VERTS), + usage: wgpu::BufferUsages::VERTEX, + }); + + let index_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(&QUAD_INDICES), + usage: wgpu::BufferUsages::INDEX, + }); + + let quad_buffer = gpu_device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: mem::size_of::() as u64 * MAX_QUADS as u64, + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + let quads: Vec = rects.iter().map(|rect| to_quad(rect)).collect(); + + let buffer_size = (quads.len() as u64) * Quad::SIZE; + + let staging_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(&quads), + usage: wgpu::BufferUsages::COPY_SRC, + }); + + cmd_encoder.copy_buffer_to_buffer(&staging_buffer, 0, &quad_buffer, 0, buffer_size); + + RectBuffers { + vertex_buffer, + index_buffer, + quad_buffer, + } +} + +pub fn to_quad(rect_elt: &RectElt) -> Quad { + Quad { + pos: rect_elt.rect.pos.into(), + width: rect_elt.rect.width, + height: rect_elt.rect.height, + color: (rect_elt.color.to_array()), + border_color: rect_elt.border_color.into(), + border_width: rect_elt.border_width, + } +} diff --git a/examples/breakout/platform/src/graphics/lowlevel/mod.rs b/examples/breakout/platform/src/graphics/lowlevel/mod.rs new file mode 100644 index 0000000000..0add45385d --- /dev/null +++ b/examples/breakout/platform/src/graphics/lowlevel/mod.rs @@ -0,0 +1,5 @@ +pub mod buffer; +pub mod ortho; +pub mod pipelines; +pub mod vertex; +pub mod quad; diff --git a/examples/breakout/platform/src/graphics/lowlevel/ortho.rs b/examples/breakout/platform/src/graphics/lowlevel/ortho.rs new file mode 100644 index 0000000000..2f4577871a --- /dev/null +++ b/examples/breakout/platform/src/graphics/lowlevel/ortho.rs @@ -0,0 +1,118 @@ +use cgmath::{Matrix4, Ortho}; +use wgpu::util::DeviceExt; +use wgpu::{ + BindGroup, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, Buffer, + ShaderStages, +}; + +// orthographic projection is used to transform pixel coords to the coordinate system used by wgpu + +#[repr(C)] +#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +struct Uniforms { + // We can't use cgmath with bytemuck directly so we'll have + // to convert the Matrix4 into a 4x4 f32 array + ortho: [[f32; 4]; 4], +} + +impl Uniforms { + fn new(w: u32, h: u32) -> Self { + let ortho: Matrix4 = Ortho:: { + left: 0.0, + right: w as f32, + bottom: h as f32, + top: 0.0, + near: -1.0, + far: 1.0, + } + .into(); + Self { + ortho: ortho.into(), + } + } +} + +// update orthographic buffer according to new window size +pub fn update_ortho_buffer( + inner_width: u32, + inner_height: u32, + gpu_device: &wgpu::Device, + ortho_buffer: &Buffer, + cmd_queue: &wgpu::Queue, +) { + let new_uniforms = Uniforms::new(inner_width, inner_height); + + let new_ortho_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Ortho uniform buffer"), + contents: bytemuck::cast_slice(&[new_uniforms]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_SRC, + }); + + // get a command encoder for the current frame + let mut encoder = gpu_device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Resize"), + }); + + // overwrite the new buffer over the old one + encoder.copy_buffer_to_buffer( + &new_ortho_buffer, + 0, + ortho_buffer, + 0, + (std::mem::size_of::() * vec![new_uniforms].as_slice().len()) + as wgpu::BufferAddress, + ); + + cmd_queue.submit(Some(encoder.finish())); +} + +#[derive(Debug)] +pub struct OrthoResources { + pub buffer: Buffer, + pub bind_group_layout: BindGroupLayout, + pub bind_group: BindGroup, +} + +pub fn init_ortho( + inner_width: u32, + inner_height: u32, + gpu_device: &wgpu::Device, +) -> OrthoResources { + let uniforms = Uniforms::new(inner_width, inner_height); + + let ortho_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Ortho uniform buffer"), + contents: bytemuck::cast_slice(&[uniforms]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + + // bind groups consist of extra resources that are provided to the shaders + let ortho_bind_group_layout = gpu_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::VERTEX, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + label: Some("Ortho bind group layout"), + }); + + let ortho_bind_group = gpu_device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &ortho_bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: ortho_buffer.as_entire_binding(), + }], + label: Some("Ortho bind group"), + }); + + OrthoResources { + buffer: ortho_buffer, + bind_group_layout: ortho_bind_group_layout, + bind_group: ortho_bind_group, + } +} diff --git a/examples/breakout/platform/src/graphics/lowlevel/pipelines.rs b/examples/breakout/platform/src/graphics/lowlevel/pipelines.rs new file mode 100644 index 0000000000..a0dc7908ec --- /dev/null +++ b/examples/breakout/platform/src/graphics/lowlevel/pipelines.rs @@ -0,0 +1,72 @@ +use super::ortho::{init_ortho, OrthoResources}; +use super::quad::Quad; +use super::vertex::Vertex; +use std::borrow::Cow; + +pub struct RectResources { + pub pipeline: wgpu::RenderPipeline, + pub ortho: OrthoResources, +} + +pub fn make_rect_pipeline( + gpu_device: &wgpu::Device, + surface_config: &wgpu::SurfaceConfiguration, +) -> RectResources { + let ortho = init_ortho(surface_config.width, surface_config.height, gpu_device); + + let pipeline_layout = gpu_device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[&ortho.bind_group_layout], + push_constant_ranges: &[], + }); + let pipeline = create_render_pipeline( + gpu_device, + &pipeline_layout, + surface_config.format, + &wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../shaders/quad.wgsl"))), + }, + ); + + RectResources { pipeline, ortho } +} + +pub fn create_render_pipeline( + device: &wgpu::Device, + layout: &wgpu::PipelineLayout, + color_format: wgpu::TextureFormat, + shader_module_desc: &wgpu::ShaderModuleDescriptor, +) -> wgpu::RenderPipeline { + let shader = device.create_shader_module(shader_module_desc); + + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Render pipeline"), + layout: Some(layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[Vertex::DESC, Quad::DESC], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[wgpu::ColorTargetState { + format: color_format, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + operation: wgpu::BlendOperation::Add, + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + }, + alpha: wgpu::BlendComponent::REPLACE, + }), + write_mask: wgpu::ColorWrites::ALL, + }], + }), + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + }) +} diff --git a/examples/breakout/platform/src/graphics/lowlevel/quad.rs b/examples/breakout/platform/src/graphics/lowlevel/quad.rs new file mode 100644 index 0000000000..9c1fd85ae6 --- /dev/null +++ b/examples/breakout/platform/src/graphics/lowlevel/quad.rs @@ -0,0 +1,31 @@ + + +/// A polygon with 4 corners +#[derive(Copy, Clone)] +pub struct Quad { + pub pos: [f32; 2], + pub width: f32, + pub height: f32, + pub color: [f32; 4], + pub border_color: [f32; 4], + pub border_width: f32, +} + +unsafe impl bytemuck::Pod for Quad {} +unsafe impl bytemuck::Zeroable for Quad {} + +impl Quad { + pub const SIZE: wgpu::BufferAddress = std::mem::size_of::() as wgpu::BufferAddress; + pub const DESC: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout { + array_stride: Self::SIZE, + step_mode: wgpu::VertexStepMode::Instance, + attributes: &wgpu::vertex_attr_array!( + 1 => Float32x2, + 2 => Float32, + 3 => Float32, + 4 => Float32x4, + 5 => Float32x4, + 6 => Float32, + ), + }; +} \ No newline at end of file diff --git a/examples/breakout/platform/src/graphics/lowlevel/vertex.rs b/examples/breakout/platform/src/graphics/lowlevel/vertex.rs new file mode 100644 index 0000000000..aa45bb7fb7 --- /dev/null +++ b/examples/breakout/platform/src/graphics/lowlevel/vertex.rs @@ -0,0 +1,35 @@ +// Inspired by https://github.com/sotrh/learn-wgpu +// by Benjamin Hansen - license information can be found in the LEGAL_DETAILS +// file in the root directory of this distribution. +// +// Thank you, Benjamin! + +// Inspired by https://github.com/iced-rs/iced/blob/adce9e04213803bd775538efddf6e7908d1c605e/wgpu/src/shader/quad.wgsl +// By HΓ©ctor RamΓ³n, Iced contributors Licensed under the MIT license. +// The license is included in the LEGAL_DETAILS file in the root directory of this distribution. + +// Thank you HΓ©ctor RamΓ³n and Iced contributors! +use bytemuck::{Pod, Zeroable}; + + +#[repr(C)] +#[derive(Copy, Clone, Zeroable, Pod)] +pub struct Vertex { + pub _position: [f32; 2], +} + +impl Vertex { + pub const SIZE: wgpu::BufferAddress = std::mem::size_of::() as wgpu::BufferAddress; + pub const DESC: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout { + array_stride: Self::SIZE, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[ + // position + wgpu::VertexAttribute { + offset: 0, + shader_location: 0, + format: wgpu::VertexFormat::Float32x2, + }, + ], + }; +} diff --git a/examples/breakout/platform/src/graphics/mod.rs b/examples/breakout/platform/src/graphics/mod.rs new file mode 100644 index 0000000000..0eb7fcd6da --- /dev/null +++ b/examples/breakout/platform/src/graphics/mod.rs @@ -0,0 +1,4 @@ +pub mod colors; +pub mod lowlevel; +pub mod primitives; +pub mod style; diff --git a/examples/breakout/platform/src/graphics/primitives/mod.rs b/examples/breakout/platform/src/graphics/primitives/mod.rs new file mode 100644 index 0000000000..a9adb18862 --- /dev/null +++ b/examples/breakout/platform/src/graphics/primitives/mod.rs @@ -0,0 +1,2 @@ +pub mod rect; +pub mod text; diff --git a/examples/breakout/platform/src/graphics/primitives/rect.rs b/examples/breakout/platform/src/graphics/primitives/rect.rs new file mode 100644 index 0000000000..8183fc6f7a --- /dev/null +++ b/examples/breakout/platform/src/graphics/primitives/rect.rs @@ -0,0 +1,27 @@ +use crate::graphics::colors::Rgba; +use cgmath::Vector2; + +#[derive(Debug, Copy, Clone)] +pub struct RectElt { + pub rect: Rect, + pub color: Rgba, + pub border_width: f32, + pub border_color: Rgba, +} + +/// These fields are ordered this way because in Roc, the corresponding stuct is: +/// +/// { top : F32, left : F32, width : F32, height : F32 } +/// +/// alphabetically, that's { height, left, top, width } - which works out to the same as: +/// +/// struct Rect { height: f32, pos: Vector2, width: f32 } +/// +/// ...because Vector2 is a repr(C) struct of { x: f32, y: f32 } +#[derive(Debug, Copy, Clone)] +#[repr(C)] +pub struct Rect { + pub height: f32, + pub pos: Vector2, + pub width: f32, +} diff --git a/examples/breakout/platform/src/graphics/primitives/text.rs b/examples/breakout/platform/src/graphics/primitives/text.rs new file mode 100644 index 0000000000..4ecaa28d9b --- /dev/null +++ b/examples/breakout/platform/src/graphics/primitives/text.rs @@ -0,0 +1,134 @@ +// Adapted from https://github.com/sotrh/learn-wgpu +// by Benjamin Hansen - license information can be found in the COPYRIGHT +// file in the root directory of this distribution. +// +// Thank you, Benjamin! + +use crate::graphics::colors::Rgba; +use crate::graphics::style::DEFAULT_FONT_SIZE; +use ab_glyph::{FontArc, InvalidFont}; +use cgmath::Vector2; +use wgpu_glyph::{ab_glyph, GlyphBrush, GlyphBrushBuilder}; + +#[derive(Debug)] +pub struct Text<'a> { + pub position: Vector2, + pub area_bounds: Vector2, + pub color: Rgba, + pub text: &'a str, + pub size: f32, + pub visible: bool, + pub centered: bool, +} + +impl<'a> Default for Text<'a> { + fn default() -> Self { + Self { + position: (0.0, 0.0).into(), + area_bounds: (std::f32::INFINITY, std::f32::INFINITY).into(), + color: Rgba::WHITE, + text: "", + size: DEFAULT_FONT_SIZE, + visible: true, + centered: false, + } + } +} + +// pub fn layout_from_text(text: &Text) -> wgpu_glyph::Layout { +// wgpu_glyph::Layout::default().h_align(if text.centered { +// wgpu_glyph::HorizontalAlign::Center +// } else { +// wgpu_glyph::HorizontalAlign::Left +// }) +// } + +// fn section_from_text<'a>( +// text: &'a Text, +// layout: wgpu_glyph::Layout, +// ) -> wgpu_glyph::Section<'a> { +// Section { +// screen_position: text.position.into(), +// bounds: text.area_bounds.into(), +// layout, +// ..Section::default() +// } +// .add_text( +// wgpu_glyph::Text::new(text.text) +// .with_color(text.color) +// .with_scale(text.size), +// ) +// } + +// pub fn owned_section_from_text(text: &Text) -> OwnedSection { +// let layout = layout_from_text(text); + +// OwnedSection { +// screen_position: text.position.into(), +// bounds: text.area_bounds.into(), +// layout, +// ..OwnedSection::default() +// } +// .add_text( +// glyph_brush::OwnedText::new(text.text) +// .with_color(Vector4::from(text.color)) +// .with_scale(text.size), +// ) +// } + +// pub fn owned_section_from_glyph_texts( +// text: Vec, +// screen_position: (f32, f32), +// area_bounds: (f32, f32), +// layout: wgpu_glyph::Layout, +// ) -> glyph_brush::OwnedSection { +// glyph_brush::OwnedSection { +// screen_position, +// bounds: area_bounds, +// layout, +// text, +// } +// } + +// pub fn queue_text_draw(text: &Text, glyph_brush: &mut GlyphBrush<()>) { +// let layout = layout_from_text(text); + +// let section = section_from_text(text, layout); + +// glyph_brush.queue(section.clone()); +// } + +// fn glyph_to_rect(glyph: &wgpu_glyph::SectionGlyph) -> Rect { +// let position = glyph.glyph.position; +// let px_scale = glyph.glyph.scale; +// let width = glyph_width(&glyph.glyph); +// let height = px_scale.y; +// let top_y = glyph_top_y(&glyph.glyph); + +// Rect { +// pos: [position.x, top_y].into(), +// width, +// height, +// } +// } + +// pub fn glyph_top_y(glyph: &Glyph) -> f32 { +// let height = glyph.scale.y; + +// glyph.position.y - height * 0.75 +// } + +// pub fn glyph_width(glyph: &Glyph) -> f32 { +// glyph.scale.x * 0.4765 +// } + +pub fn build_glyph_brush( + gpu_device: &wgpu::Device, + render_format: wgpu::TextureFormat, +) -> Result, InvalidFont> { + let inconsolata = FontArc::try_from_slice(include_bytes!( + "../../../../../../editor/Inconsolata-Regular.ttf" + ))?; + + Ok(GlyphBrushBuilder::using_font(inconsolata).build(gpu_device, render_format)) +} diff --git a/examples/breakout/platform/src/graphics/shaders/quad.wgsl b/examples/breakout/platform/src/graphics/shaders/quad.wgsl new file mode 100644 index 0000000000..a561e2fc24 --- /dev/null +++ b/examples/breakout/platform/src/graphics/shaders/quad.wgsl @@ -0,0 +1,60 @@ + + +struct Globals { + ortho: mat4x4; +}; + +@group(0) +@binding(0) +var globals: Globals; + +struct VertexInput { + @location(0) position: vec2; +}; + +struct Quad { + @location(1) pos: vec2; // can't use the name "position" twice for compatibility with metal on MacOS + @location(2) width: f32; + @location(3) height: f32; + @location(4) color: vec4; + @location(5) border_color: vec4; + @location(6) border_width: f32; +}; + +struct VertexOutput { + @builtin(position) position: vec4; + @location(0) color: vec4; + @location(1) border_color: vec4; + @location(2) border_width: f32; +}; + +@stage(vertex) +fn vs_main( + input: VertexInput, + quad: Quad +) -> VertexOutput { + + var transform: mat4x4 = mat4x4( + vec4(quad.width, 0.0, 0.0, 0.0), + vec4(0.0, quad.height, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(quad.pos, 0.0, 1.0) + ); + + var out: VertexOutput; + + out.position = globals.ortho * transform * vec4(input.position, 0.0, 1.0);; + out.color = quad.color; + out.border_color = quad.border_color; + out.border_width = quad.border_width; + + return out; +} + + +@stage(fragment) +fn fs_main( + input: VertexOutput +) -> @location(0) vec4 { + return input.color; +} diff --git a/examples/breakout/platform/src/graphics/style.rs b/examples/breakout/platform/src/graphics/style.rs new file mode 100644 index 0000000000..11e609075b --- /dev/null +++ b/examples/breakout/platform/src/graphics/style.rs @@ -0,0 +1 @@ +pub const DEFAULT_FONT_SIZE: f32 = 30.0; diff --git a/examples/breakout/platform/src/gui.rs b/examples/breakout/platform/src/gui.rs new file mode 100644 index 0000000000..ef0412ea25 --- /dev/null +++ b/examples/breakout/platform/src/gui.rs @@ -0,0 +1,513 @@ +use crate::{ + graphics::{ + colors::Rgba, + lowlevel::buffer::create_rect_buffers, + lowlevel::{buffer::MAX_QUADS, ortho::update_ortho_buffer}, + lowlevel::{buffer::QUAD_INDICES, pipelines}, + primitives::{ + rect::{Rect, RectElt}, + text::build_glyph_brush, + }, + }, + roc::{self, Bounds, RocElem, RocElemTag, RocEvent}, +}; +use cgmath::{Vector2, Vector4}; +use glyph_brush::{GlyphCruncher, OwnedSection}; +use pipelines::RectResources; +use std::{ + error::Error, + time::{Duration, Instant}, +}; +use wgpu::{CommandEncoder, LoadOp, RenderPass, TextureView}; +use wgpu_glyph::GlyphBrush; +use winit::{ + dpi::PhysicalSize, + event, + event::{ElementState, Event, ModifiersState, StartCause}, + event_loop::ControlFlow, + platform::run_return::EventLoopExtRunReturn, +}; + +// Inspired by: +// https://github.com/sotrh/learn-wgpu by Benjamin Hansen, which is licensed under the MIT license +// https://github.com/cloudhead/rgx by Alexis Sellier, which is licensed under the MIT license +// +// See this link to learn wgpu: https://sotrh.github.io/learn-wgpu/ + +const TIME_BETWEEN_TICKS: Duration = Duration::new(0, 1000 / 60); + +pub fn run_event_loop(title: &str, window_bounds: Bounds) -> Result<(), Box> { + let (mut model, mut elems) = roc::init_and_render(window_bounds); + + // Open window and create a surface + let mut event_loop = winit::event_loop::EventLoop::new(); + + let window = winit::window::WindowBuilder::new() + .with_inner_size(PhysicalSize::new(window_bounds.width, window_bounds.height)) + .with_title(title) + .build(&event_loop) + .unwrap(); + + macro_rules! update_and_rerender { + ($event:expr) => { + // TODO use (model, elems) = ... once we've upgraded rust versions + let pair = roc::update_and_render(model, $event); + + model = pair.0; + elems = pair.1; + + window.request_redraw(); + }; + } + + let instance = wgpu::Instance::new(wgpu::Backends::all()); + let surface = unsafe { instance.create_surface(&window) }; + + // Initialize GPU + let (gpu_device, cmd_queue) = futures::executor::block_on(async { + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::HighPerformance, + compatible_surface: Some(&surface), + force_fallback_adapter: false, + }) + .await + .expect(r#"Request adapter + If you're running this from inside nix, follow the instructions here to resolve this: https://github.com/rtfeldman/roc/blob/trunk/BUILDING_FROM_SOURCE.md#editor + "#); + + adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + features: wgpu::Features::empty(), + limits: wgpu::Limits::default(), + }, + None, + ) + .await + .expect("Request device") + }); + + // Create staging belt and a local pool + let mut staging_belt = wgpu::util::StagingBelt::new(1024); + let mut local_pool = futures::executor::LocalPool::new(); + let local_spawner = local_pool.spawner(); + + // Prepare swap chain + let render_format = wgpu::TextureFormat::Bgra8Unorm; + let mut size = window.inner_size(); + + let surface_config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: render_format, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::Mailbox, + }; + + surface.configure(&gpu_device, &surface_config); + + let rect_resources = pipelines::make_rect_pipeline(&gpu_device, &surface_config); + + let mut glyph_brush = build_glyph_brush(&gpu_device, render_format)?; + let mut keyboard_modifiers = ModifiersState::empty(); + + // Render loop + let app_start_time = Instant::now(); + let mut next_tick = app_start_time + TIME_BETWEEN_TICKS; + + window.request_redraw(); + + event_loop.run_return(|event, _, control_flow| { + match event { + // Close + Event::WindowEvent { + event: event::WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + // Resize + Event::WindowEvent { + event: event::WindowEvent::Resized(new_size), + .. + } => { + size = new_size; + + surface.configure( + &gpu_device, + &wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: render_format, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::Mailbox, + }, + ); + + update_ortho_buffer( + size.width, + size.height, + &gpu_device, + &rect_resources.ortho.buffer, + &cmd_queue, + ); + + update_and_rerender!(RocEvent::Resize(Bounds { + height: size.height as f32, + width: size.width as f32, + })); + } + // Keyboard input + Event::WindowEvent { + event: + event::WindowEvent::KeyboardInput { + input: + event::KeyboardInput { + virtual_keycode: Some(keycode), + state: input_state, + .. + }, + .. + }, + .. + } => { + let roc_event = match input_state { + ElementState::Pressed => RocEvent::KeyDown(keycode.into()), + ElementState::Released => RocEvent::KeyUp(keycode.into()), + }; + + model = roc::update(model, roc_event); + } + // Modifiers Changed + Event::WindowEvent { + event: event::WindowEvent::ModifiersChanged(modifiers), + .. + } => { + keyboard_modifiers = modifiers; + } + Event::RedrawRequested { .. } => { + // Get a command cmd_encoder for the current frame + let mut cmd_encoder = + gpu_device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Redraw"), + }); + + let surface_texture = surface + .get_current_texture() + .expect("Failed to acquire next SwapChainTexture"); + + let view = surface_texture + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + for elem in elems.iter() { + let (_bounds, drawable) = to_drawable( + elem, + Bounds { + width: size.width as f32, + height: size.height as f32, + }, + &mut glyph_brush, + ); + + process_drawable( + drawable, + &mut staging_belt, + &mut glyph_brush, + &mut cmd_encoder, + &view, + &gpu_device, + &rect_resources, + wgpu::LoadOp::Load, + Bounds { + width: size.width as f32, + height: size.height as f32, + }, + ); + } + + staging_belt.finish(); + cmd_queue.submit(Some(cmd_encoder.finish())); + surface_texture.present(); + + // Recall unused staging buffers + use futures::task::SpawnExt; + + local_spawner + .spawn(staging_belt.recall()) + .expect("Recall staging belt"); + + local_pool.run_until_stalled(); + } + Event::NewEvents(StartCause::ResumeTimeReached { + requested_resume, .. + }) => { + // Only run this logic if this is the tick we originally requested. + if requested_resume == next_tick { + let now = Instant::now(); + + // Set a new next_tick *before* running update and rerender, + // so their runtime isn't factored into when we want to render next. + next_tick = now + TIME_BETWEEN_TICKS; + + let tick = now.saturating_duration_since(app_start_time); + + update_and_rerender!(RocEvent::Tick(tick)); + + *control_flow = winit::event_loop::ControlFlow::WaitUntil(next_tick); + } + } + _ => { + // Keep waiting until the next tick. + *control_flow = winit::event_loop::ControlFlow::WaitUntil(next_tick); + } + } + }); + + Ok(()) +} + +fn draw_rects( + all_rects: &[RectElt], + cmd_encoder: &mut CommandEncoder, + texture_view: &TextureView, + gpu_device: &wgpu::Device, + rect_resources: &RectResources, + load_op: LoadOp, +) { + let rect_buffers = create_rect_buffers(gpu_device, cmd_encoder, all_rects); + + let mut render_pass = begin_render_pass(cmd_encoder, texture_view, load_op); + + render_pass.set_pipeline(&rect_resources.pipeline); + render_pass.set_bind_group(0, &rect_resources.ortho.bind_group, &[]); + + render_pass.set_vertex_buffer(0, rect_buffers.vertex_buffer.slice(..)); + render_pass.set_vertex_buffer(1, rect_buffers.quad_buffer.slice(..)); + + render_pass.set_index_buffer( + rect_buffers.index_buffer.slice(..), + wgpu::IndexFormat::Uint16, + ); + + render_pass.draw_indexed(0..QUAD_INDICES.len() as u32, 0, 0..MAX_QUADS as u32); +} + +fn begin_render_pass<'a>( + cmd_encoder: &'a mut CommandEncoder, + texture_view: &'a TextureView, + load_op: LoadOp, +) -> RenderPass<'a> { + cmd_encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[wgpu::RenderPassColorAttachment { + view: texture_view, + resolve_target: None, + ops: wgpu::Operations { + load: load_op, + store: true, + }, + }], + depth_stencil_attachment: None, + label: None, + }) +} + +#[derive(Clone, Debug)] +struct Drawable { + pos: Vector2, + bounds: Bounds, + content: DrawableContent, +} + +#[derive(Clone, Debug)] +enum DrawableContent { + /// This stores an actual Section because an earlier step needs to know the bounds of + /// the text, and making a Section is a convenient way to compute those bounds. + Text(OwnedSection, Vector2), + FillRect { + color: Rgba, + border_width: f32, + border_color: Rgba, + }, +} + +fn process_drawable( + drawable: Drawable, + staging_belt: &mut wgpu::util::StagingBelt, + glyph_brush: &mut GlyphBrush<()>, + cmd_encoder: &mut CommandEncoder, + texture_view: &TextureView, + gpu_device: &wgpu::Device, + rect_resources: &RectResources, + load_op: LoadOp, + texture_size: Bounds, +) { + draw( + drawable.bounds, + drawable.content, + drawable.pos, + staging_belt, + glyph_brush, + cmd_encoder, + texture_view, + gpu_device, + rect_resources, + load_op, + texture_size, + ); +} + +fn draw( + bounds: Bounds, + content: DrawableContent, + pos: Vector2, + staging_belt: &mut wgpu::util::StagingBelt, + glyph_brush: &mut GlyphBrush<()>, + cmd_encoder: &mut CommandEncoder, + texture_view: &TextureView, + gpu_device: &wgpu::Device, + rect_resources: &RectResources, + load_op: LoadOp, + texture_size: Bounds, +) { + use DrawableContent::*; + + match content { + Text(section, offset) => { + glyph_brush.queue(section.with_screen_position(pos + offset).to_borrowed()); + + glyph_brush + .draw_queued( + gpu_device, + staging_belt, + cmd_encoder, + texture_view, + texture_size.width as u32, // TODO why do we make these be u32 and then cast to f32 in orthorgraphic_projection? + texture_size.height as u32, + ) + .expect("Failed to draw text element"); + } + FillRect { + color, + border_width, + border_color, + } => { + // TODO store all these colors and things in FillRect + let rect_elt = RectElt { + rect: Rect { + pos, + width: bounds.width, + height: bounds.height, + }, + color, + border_width, + border_color, + }; + + // TODO inline draw_rects into here! + draw_rects( + &[rect_elt], + cmd_encoder, + texture_view, + gpu_device, + rect_resources, + load_op, + ); + } + } +} + +/// focused_elem is the currently-focused element (or NULL if nothing has the focus) +fn to_drawable( + elem: &RocElem, + bounds: Bounds, + glyph_brush: &mut GlyphBrush<()>, +) -> (Bounds, Drawable) { + use RocElemTag::*; + + match elem.tag() { + Rect => { + let rect = unsafe { &elem.entry().rect }; + + let bounds = Bounds { + width: rect.width, + height: rect.height, + }; + + let drawable = Drawable { + pos: (rect.left, rect.top).into(), + bounds, + content: DrawableContent::FillRect { + color: rect.color, + border_width: 1.0, + border_color: rect.color, + }, + }; + + (bounds, drawable) + } + Text => { + let text = unsafe { &elem.entry().text }; + let is_centered = true; // TODO don't hardcode this + let layout = wgpu_glyph::Layout::default().h_align(if is_centered { + wgpu_glyph::HorizontalAlign::Center + } else { + wgpu_glyph::HorizontalAlign::Left + }); + + let section = owned_section_from_str(text.as_str(), bounds, layout); + + // Calculate the bounds and offset by measuring glyphs + let text_bounds; + let offset; + + match glyph_brush.glyph_bounds(section.to_borrowed()) { + Some(glyph_bounds) => { + text_bounds = Bounds { + width: glyph_bounds.max.x - glyph_bounds.min.x, + height: glyph_bounds.max.y - glyph_bounds.min.y, + }; + + offset = (-glyph_bounds.min.x, -glyph_bounds.min.y).into(); + } + None => { + text_bounds = Bounds { + width: 0.0, + height: 0.0, + }; + + offset = (0.0, 0.0).into(); + } + } + + let drawable = Drawable { + pos: (0.0, 0.0).into(), // TODO store the pos in Text and read it here + bounds: text_bounds, + content: DrawableContent::Text(section, offset), + }; + + (text_bounds, drawable) + } + } +} + +fn owned_section_from_str( + string: &str, + bounds: Bounds, + layout: wgpu_glyph::Layout, +) -> OwnedSection { + // TODO don't hardcode any of this! + let color = Rgba::WHITE; + let size: f32 = 40.0; + + OwnedSection { + bounds: (bounds.width, bounds.height), + layout, + ..OwnedSection::default() + } + .add_text( + glyph_brush::OwnedText::new(string) + .with_color(Vector4::from(color)) + .with_scale(size), + ) +} diff --git a/examples/breakout/platform/src/lib.rs b/examples/breakout/platform/src/lib.rs new file mode 100644 index 0000000000..196a65c017 --- /dev/null +++ b/examples/breakout/platform/src/lib.rs @@ -0,0 +1,16 @@ +mod graphics; +mod gui; +mod roc; + +#[no_mangle] +pub extern "C" fn rust_main() -> i32 { + let bounds = roc::Bounds { + width: 1900.0, + height: 1000.0, + }; + + gui::run_event_loop("RocOut!", bounds).expect("Error running event loop"); + + // Exit code + 0 +} diff --git a/examples/breakout/platform/src/main.rs b/examples/breakout/platform/src/main.rs new file mode 100644 index 0000000000..51175f934b --- /dev/null +++ b/examples/breakout/platform/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + std::process::exit(host::rust_main()); +} diff --git a/examples/breakout/platform/src/roc.rs b/examples/breakout/platform/src/roc.rs new file mode 100644 index 0000000000..a8aeb73d31 --- /dev/null +++ b/examples/breakout/platform/src/roc.rs @@ -0,0 +1,435 @@ +use crate::graphics::colors::Rgba; +use core::alloc::Layout; +use core::ffi::c_void; +use core::mem::ManuallyDrop; +use roc_std::{ReferenceCount, RocList, RocStr}; +use std::ffi::CStr; +use std::fmt::Debug; +use std::mem::MaybeUninit; +use std::os::raw::c_char; +use std::time::Duration; +use winit::event::VirtualKeyCode; + +extern "C" { + // program + + #[link_name = "roc__programForHost_1_exposed_generic"] + fn roc_program() -> (); + + #[link_name = "roc__programForHost_size"] + fn roc_program_size() -> i64; + + // init + + #[link_name = "roc__programForHost_1_Init_caller"] + fn call_init(size: *const Bounds, closure_data: *const u8, output: *mut Model); + + #[link_name = "roc__programForHost_1_Init_size"] + fn init_size() -> i64; + + #[link_name = "roc__programForHost_1_Init_result_size"] + fn init_result_size() -> i64; + + // update + + #[link_name = "roc__programForHost_1_Update_caller"] + fn call_update( + model: *const Model, + event: *const RocEvent, + closure_data: *const u8, + output: *mut Model, + ); + + #[link_name = "roc__programForHost_1_Update_size"] + fn update_size() -> i64; + + #[link_name = "roc__programForHost_1_Update_result_size"] + fn update_result_size() -> i64; + + // render + + #[link_name = "roc__programForHost_1_Render_caller"] + fn call_render(model: *const Model, closure_data: *const u8, output: *mut RocList); + + #[link_name = "roc__programForHost_1_Render_size"] + fn roc_render_size() -> i64; +} + +#[repr(C)] +pub union RocEventEntry { + pub key_down: RocKeyCode, + pub key_up: RocKeyCode, + pub resize: Bounds, + pub tick: [u8; 16], // u128 is unsupported in repr(C) +} + +#[repr(u8)] +#[allow(unused)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RocEventTag { + KeyDown = 0, + KeyUp, + Resize, + Tick, +} + +#[repr(C)] +#[cfg(target_pointer_width = "64")] // on a 64-bit system, the tag fits in this pointer's spare 3 bits +pub struct RocEvent { + entry: RocEventEntry, + tag: RocEventTag, +} + +impl Debug for RocEvent { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use RocEventTag::*; + + match self.tag() { + KeyDown => unsafe { self.entry().key_down }.fmt(f), + KeyUp => unsafe { self.entry().key_up }.fmt(f), + Resize => unsafe { self.entry().resize }.fmt(f), + Tick => unsafe { self.entry().tick }.fmt(f), + } + } +} + +impl RocEvent { + #[cfg(target_pointer_width = "64")] + pub fn tag(&self) -> RocEventTag { + self.tag + } + + pub fn entry(&self) -> &RocEventEntry { + &self.entry + } + + #[allow(non_snake_case)] + pub fn Resize(size: Bounds) -> Self { + Self { + tag: RocEventTag::Resize, + entry: RocEventEntry { resize: size }, + } + } + + #[allow(non_snake_case)] + pub fn KeyDown(keycode: RocKeyCode) -> Self { + Self { + tag: RocEventTag::KeyDown, + entry: RocEventEntry { key_down: keycode }, + } + } + + #[allow(non_snake_case)] + pub fn KeyUp(keycode: RocKeyCode) -> Self { + Self { + tag: RocEventTag::KeyUp, + entry: RocEventEntry { key_up: keycode }, + } + } + + #[allow(non_snake_case)] + pub fn Tick(duration: Duration) -> Self { + Self { + tag: RocEventTag::Tick, + entry: RocEventEntry { + tick: duration.as_nanos().to_ne_bytes(), + }, + } + } +} + +#[repr(u8)] +#[allow(unused)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RocKeyCode { + Left = 0, + Other, + Right, +} + +impl From for RocKeyCode { + fn from(keycode: VirtualKeyCode) -> Self { + use VirtualKeyCode::*; + + match keycode { + Left => RocKeyCode::Left, + Right => RocKeyCode::Right, + _ => RocKeyCode::Other, + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { + return libc::malloc(size); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_realloc( + c_ptr: *mut c_void, + new_size: usize, + _old_size: usize, + _alignment: u32, +) -> *mut c_void { + return libc::realloc(c_ptr, new_size); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { + return libc::free(c_ptr); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { + match tag_id { + 0 => { + let slice = CStr::from_ptr(c_ptr as *const c_char); + let string = slice.to_str().unwrap(); + eprintln!("Roc hit a panic: {}", string); + std::process::exit(1); + } + _ => todo!(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn roc_memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void { + libc::memcpy(dst, src, n) +} + +#[no_mangle] +pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void { + libc::memset(dst, c, n) +} + +#[repr(transparent)] +#[cfg(target_pointer_width = "64")] // on a 64-bit system, the tag fits in this pointer's spare 3 bits +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct ElemId(*const RocElemEntry); + +#[repr(C)] +pub union RocElemEntry { + pub rect: ManuallyDrop, + pub text: ManuallyDrop, +} + +#[repr(u8)] +#[allow(unused)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RocElemTag { + Rect = 0, + Text = 1, +} + +#[repr(C)] +#[cfg(target_pointer_width = "64")] // on a 64-bit system, the tag fits in this pointer's spare 3 bits +pub struct RocElem { + entry: RocElemEntry, + tag: RocElemTag, +} + +impl Debug for RocElem { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use RocElemTag::*; + + match self.tag() { + Rect => unsafe { &*self.entry().rect }.fmt(f), + Text => unsafe { &*self.entry().text }.fmt(f), + } + } +} + +impl RocElem { + #[cfg(target_pointer_width = "64")] + pub fn tag(&self) -> RocElemTag { + self.tag + } + + #[allow(unused)] + pub fn entry(&self) -> &RocElemEntry { + &self.entry + } + + #[allow(unused)] + pub fn rect(styles: ButtonStyles) -> RocElem { + todo!("restore rect() method") + // let rect = RocRect { styles }; + // let entry = RocElemEntry { + // rect: ManuallyDrop::new(rect), + // }; + + // Self::elem_from_tag(entry, RocElemTag::Rect) + } + + #[allow(unused)] + pub fn text>(into_roc_str: T) -> RocElem { + todo!("TODO restore text method") + // let entry = RocElemEntry { + // text: ManuallyDrop::new(into_roc_str.into()), + // }; + + // Self::elem_from_tag(entry, RocElemTag::Text) + } +} + +#[repr(C)] +#[derive(Debug)] +pub struct RocRect { + pub color: Rgba, + + // These must be in this order for alphabetization! + pub height: f32, + pub left: f32, + pub top: f32, + pub width: f32, +} + +unsafe impl ReferenceCount for RocElem { + /// Increment the reference count. + fn increment(&self) { + use RocElemTag::*; + + match self.tag() { + Rect => { /* nothing to increment! */ } + Text => unsafe { &*self.entry().text }.increment(), + } + } + + /// Decrement the reference count. + /// + /// # Safety + /// + /// The caller must ensure that `ptr` points to a value with a non-zero + /// reference count. + unsafe fn decrement(ptr: *const Self) { + use RocElemTag::*; + + let elem = &*ptr; + + match elem.tag() { + Rect => { /* nothing to decrement! */ } + Text => ReferenceCount::decrement(&*elem.entry().text), + } + } +} + +#[repr(C)] +#[derive(Copy, Clone, Debug, Default)] +pub struct ButtonStyles { + pub bg_color: Rgba, + pub border_color: Rgba, + pub border_width: f32, + pub text_color: Rgba, +} + +#[derive(Copy, Clone, Debug, Default)] +#[repr(C)] +pub struct Bounds { + pub height: f32, + pub width: f32, +} + +type Model = c_void; + +/// Call the app's init function, then render and return that result +pub fn init_and_render(bounds: Bounds) -> (*const Model, RocList) { + let closure_data_buf; + let closure_layout; + + // Call init to get the initial model + let model = unsafe { + let ret_val_layout = Layout::array::(init_result_size() as usize).unwrap(); + + // TODO allocate on the stack if it's under a certain size + let ret_val_buf = std::alloc::alloc(ret_val_layout) as *mut Model; + + closure_layout = Layout::array::(init_size() as usize).unwrap(); + + // TODO allocate on the stack if it's under a certain size + closure_data_buf = std::alloc::alloc(closure_layout); + + call_init(&bounds, closure_data_buf, ret_val_buf); + + ret_val_buf + }; + + // Call render passing the model to get the initial Elems + let elems = unsafe { + let mut ret_val: MaybeUninit> = MaybeUninit::uninit(); + + // Reuse the buffer from the previous closure if possible + let closure_data_buf = + std::alloc::realloc(closure_data_buf, closure_layout, roc_render_size() as usize); + + call_render(model, closure_data_buf, ret_val.as_mut_ptr()); + + std::alloc::dealloc(closure_data_buf, closure_layout); + + ret_val.assume_init() + }; + + (model, elems) +} + +/// Call the app's update function, then render and return that result +pub fn update(model: *const Model, event: RocEvent) -> *const Model { + let closure_data_buf; + let closure_layout; + + // Call update to get the new model + unsafe { + let ret_val_layout = Layout::array::(update_result_size() as usize).unwrap(); + + // TODO allocate on the stack if it's under a certain size + let ret_val_buf = std::alloc::alloc(ret_val_layout) as *mut Model; + + closure_layout = Layout::array::(update_size() as usize).unwrap(); + + // TODO allocate on the stack if it's under a certain size + closure_data_buf = std::alloc::alloc(closure_layout); + + call_update(model, &event, closure_data_buf, ret_val_buf); + + ret_val_buf + } +} + +/// Call the app's update function, then render and return that result +pub fn update_and_render(model: *const Model, event: RocEvent) -> (*const Model, RocList) { + let closure_data_buf; + let closure_layout; + + // Call update to get the new model + let model = unsafe { + let ret_val_layout = Layout::array::(update_result_size() as usize).unwrap(); + + // TODO allocate on the stack if it's under a certain size + let ret_val_buf = std::alloc::alloc(ret_val_layout) as *mut Model; + + closure_layout = Layout::array::(update_size() as usize).unwrap(); + + // TODO allocate on the stack if it's under a certain size + closure_data_buf = std::alloc::alloc(closure_layout); + + call_update(model, &event, closure_data_buf, ret_val_buf); + + ret_val_buf + }; + + // Call render passing the model to get the initial Elems + let elems = unsafe { + let mut ret_val: MaybeUninit> = MaybeUninit::uninit(); + + // Reuse the buffer from the previous closure if possible + let closure_data_buf = + std::alloc::realloc(closure_data_buf, closure_layout, roc_render_size() as usize); + + call_render(model, closure_data_buf, ret_val.as_mut_ptr()); + + std::alloc::dealloc(closure_data_buf, closure_layout); + + ret_val.assume_init() + }; + + (model, elems) +} diff --git a/examples/false-interpreter/False.roc b/examples/false-interpreter/False.roc index f8306e7d8d..537d794a18 100644 --- a/examples/false-interpreter/False.roc +++ b/examples/false-interpreter/False.roc @@ -434,7 +434,7 @@ stepExecCtx = \ctx, char -> ( (T popCtx1 numR) <- Result.after (popNumber ctx) (T popCtx2 numL) <- Result.after (popNumber popCtx1) - res <- Result.after (Num.divFloorChecked numL numR) + res <- Result.after (Num.divTruncChecked numL numR) Ok (Context.pushStack popCtx2 (Number res)) ) diff --git a/examples/false-interpreter/Variable.roc b/examples/false-interpreter/Variable.roc index c4675c06c9..866ec789b1 100644 --- a/examples/false-interpreter/Variable.roc +++ b/examples/false-interpreter/Variable.roc @@ -4,7 +4,7 @@ interface Variable # Variables in False can only be single letters. Thus, the valid variables are "a" to "z". # This opaque type deals with ensure we always have valid variables. -Variable : [ @Variable U8 ] +Variable := U8 totalCount : Nat totalCount = diff --git a/examples/false-interpreter/platform/File.roc b/examples/false-interpreter/platform/File.roc index 939f31b7e8..e64efe749a 100644 --- a/examples/false-interpreter/platform/File.roc +++ b/examples/false-interpreter/platform/File.roc @@ -2,7 +2,7 @@ interface File exposes [ line, Handle, withOpen, chunk ] imports [ pf.Effect, Task.{ Task } ] -Handle : [ @Handle U64 ] +Handle := U64 line : Handle -> Task.Task Str * line = \@Handle handle -> Effect.after (Effect.getFileLine handle) Task.succeed diff --git a/examples/gui/Hello.roc b/examples/gui/Hello.roc index 653ccbd8e1..4738b97b11 100644 --- a/examples/gui/Hello.roc +++ b/examples/gui/Hello.roc @@ -8,14 +8,12 @@ render = styles = { bgColor: rgba 100 50 50 1, borderColor: rgba 10 20 30 1, borderWidth: 10, textColor: rgba 220 220 250 1 } - Col - [ - Row - [ - Button (Text "Corner ") styles, - Button (Text "Top Mid ") { styles & bgColor: rgba 100 100 50 1 }, - Button (Text "Top Right ") { styles & bgColor: rgba 50 50 150 1 }, - ], - Button (Text "Mid Left ") { styles & bgColor: rgba 150 100 100 1 }, - Button (Text "Bottom Left") { styles & bgColor: rgba 150 50 50 1 }, - ] + Col [ + Row [ + Button (Text "Corner ") styles, + Button (Text "Top Mid ") { styles & bgColor: rgba 100 100 50 1 }, + Button (Text "Top Right ") { styles & bgColor: rgba 50 50 150 1 }, + ], + Button (Text "Mid Left ") { styles & bgColor: rgba 150 100 100 1 }, + Button (Text "Bottom Left") { styles & bgColor: rgba 150 50 50 1 }, + ] diff --git a/examples/gui/platform/.gitignore b/examples/gui/platform/.gitignore new file mode 100644 index 0000000000..eb5a316cbd --- /dev/null +++ b/examples/gui/platform/.gitignore @@ -0,0 +1 @@ +target diff --git a/examples/gui/platform/src/focus.rs b/examples/gui/platform/src/focus.rs new file mode 100644 index 0000000000..71b46b6b1c --- /dev/null +++ b/examples/gui/platform/src/focus.rs @@ -0,0 +1,172 @@ +use crate::roc::{ElemId, RocElem, RocElemTag}; + +#[derive(Debug, PartialEq, Eq)] +pub struct Focus { + focused: Option, + focused_ancestors: Vec<(ElemId, usize)>, +} + +impl Default for Focus { + fn default() -> Self { + Self { + focused: None, + focused_ancestors: Vec::new(), + } + } +} + +impl Focus { + pub fn focused_elem(&self) -> Option { + self.focused + } + + /// e.g. the user pressed Tab. + /// + /// This is in contrast to next_local, which advances within a button group. + /// For example, if I have three radio buttons in a group, pressing the + /// arrow keys will cycle through them over and over without exiting the group - + /// whereas pressing Tab will cycle through them once and then exit the group. + pub fn next_global(&mut self, root: &RocElem) { + match self.focused { + Some(focused) => { + // while let Some((ancestor_id, index)) = self.focused_ancestors.pop() { + // let ancestor = ancestor_id.elem(); + + // // TODO FIXME - right now this will re-traverse a lot of ground! To prevent this, + // // we should remember past indices searched, and tell the ancestors "hey stop searching when" + // // you reach these indices, because they were already covered previously. + // // One potentially easy way to do this: pass a min_index and max_index, and only look between those! + // // + // // Related idea: instead of doing .pop() here, iterate normally so we can `break;` after storing + // // `new_ancestors = Some(next_ancestors);` - this way, we still have access to the full ancestry, and + // // can maybe even pass it in to make it clear what work has already been done! + // if let Some((new_id, new_ancestors)) = + // Self::next_focusable_sibling(focused, Some(ancestor), Some(index)) + // { + // // We found the next element to focus, so record that. + // self.focused = Some(new_id); + + // // We got a path to the new focusable's ancestor(s), so add them to the path. + // // (This may restore some of the ancestors we've been .pop()-ing as we iterated.) + // self.focused_ancestors.extend(new_ancestors); + + // return; + // } + + // // Need to write a bunch of tests for this, especially tests of focus wrapping around - e.g. + // // what happens if it wraps around to a sibling? What happens if it wraps around to something + // // higher up the tree? Lower down the tree? What if nothing is focusable? + // // A separate question: what if we should have a separate text-to-speech concept separate from focus? + // } + } + None => { + // Nothing was focused in the first place, so try to focus the root. + if root.is_focusable() { + self.focused = Some(root.id()); + self.focused_ancestors = Vec::new(); + } else if let Some((new_id, new_ancestors)) = + Self::next_focusable_sibling(root, None, None) + { + // If the root itself is not focusable, use its next focusable sibling. + self.focused = Some(new_id); + self.focused_ancestors = new_ancestors; + } + + // Regardless of whether we found a focusable Elem, we're done. + return; + } + } + } + + /// Return the next focusable sibling element after this one. + /// If this element has no siblings, or no *next* sibling after the given index + /// (e.g. the given index refers to the last element in a Row element), return None. + fn next_focusable_sibling( + elem: &RocElem, + ancestor: Option<&RocElem>, + opt_index: Option, + ) -> Option<(ElemId, Vec<(ElemId, usize)>)> { + use RocElemTag::*; + + match elem.tag() { + Button | Text => None, + Row | Col => { + let children = unsafe { &elem.entry().row_or_col.children.as_slice() }; + let iter = match opt_index { + Some(focus_index) => children[0..focus_index].iter(), + None => children.iter(), + }; + + for child in iter { + if let Some(focused) = Self::next_focusable_sibling(child, ancestor, None) { + return Some(focused); + } + } + + None + } + } + } +} + +#[test] +fn next_global_button_root() { + use crate::roc::{ButtonStyles, RocElem}; + + let child = RocElem::text(""); + let root = RocElem::button(ButtonStyles::default(), child); + let mut focus = Focus::default(); + + // At first, nothing should be focused. + assert_eq!(focus.focused_elem(), None); + + focus.next_global(&root); + + // Buttons should be focusable, so advancing focus should give the button focus. + assert_eq!(focus.focused_elem(), Some(root.id())); + + // Since the button is at the root, advancing again should maintain focus on it. + focus.next_global(&root); + assert_eq!(focus.focused_elem(), Some(root.id())); +} + +#[test] +fn next_global_text_root() { + let root = RocElem::text(""); + let mut focus = Focus::default(); + + // At first, nothing should be focused. + assert_eq!(focus.focused_elem(), None); + + focus.next_global(&root); + + // Text should not be focusable, so advancing focus should have no effect here. + assert_eq!(focus.focused_elem(), None); + + // Just to double-check, advancing a second time should not change this. + focus.next_global(&root); + assert_eq!(focus.focused_elem(), None); +} + +#[test] +fn next_global_row() { + use crate::roc::{ButtonStyles, RocElem}; + + let child = RocElem::text(""); + let button = RocElem::button(ButtonStyles::default(), child); + let button_id = button.id(); + let root = RocElem::row(&[button] as &[_]); + let mut focus = Focus::default(); + + // At first, nothing should be focused. + assert_eq!(focus.focused_elem(), None); + + focus.next_global(&root); + + // Buttons should be focusable, so advancing focus should give the first button in the row focus. + assert_eq!(focus.focused_elem(), Some(button_id)); + + // Since the button is the only element in the row, advancing again should maintain focus on it. + focus.next_global(&root); + assert_eq!(focus.focused_elem(), Some(button_id)); +} diff --git a/examples/interactive/tui.roc b/examples/interactive/tui.roc index 03b05efc17..3f09cbfdf5 100644 --- a/examples/interactive/tui.roc +++ b/examples/interactive/tui.roc @@ -6,9 +6,8 @@ app "tui" Model : Str main : Program Model -main = - { - init: \{ } -> "Hello World", - update: \model, new -> Str.concat model new, - view: \model -> Str.concat model "!", - } +main = { + init: \{ } -> "Hello World", + update: \model, new -> Str.concat model new, + view: \model -> Str.concat model "!", +} diff --git a/getting_started/linux_x86.md b/getting_started/linux_x86.md index f499209607..bbc672377a 100644 --- a/getting_started/linux_x86.md +++ b/getting_started/linux_x86.md @@ -24,9 +24,9 @@ # Rust. If you installed rust in this terminal you'll need to open a new one first! ./roc examples/hello-world/rust-platform/helloRust.roc # Zig - ./roc examples/hello-world/zig-platform/helloZig.roc + ./roc examples/hello-world/zig-platform/helloZig.roc --linker=legacy # C - ./roc examples/hello-world/c-platform/helloC.roc + ./roc examples/hello-world/c-platform/helloC.roc --linker=legacy ``` 0. See [here](../README.md#examples) for the other examples. diff --git a/highlight/src/tokenizer.rs b/highlight/src/tokenizer.rs index 9eddb99da1..95f8a9fb12 100644 --- a/highlight/src/tokenizer.rs +++ b/highlight/src/tokenizer.rs @@ -63,7 +63,6 @@ pub enum Token { OpAnd = 0b_0110_1101, OpOr = 0b_0110_1110, OpDoubleSlash = 0b_0110_1111, - OpDoublePercent = 0b_0111_0001, OpBackpassing = 0b_0111_1010, TodoNextThing = 0b_1000_0000, @@ -71,8 +70,6 @@ pub enum Token { Malformed, MalformedOperator, - PrivateTag, - String, NumberBase, @@ -150,7 +147,6 @@ fn consume_all_tokens(state: &mut LexState, bytes: &[u8], consumer: &mut impl Co 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), @@ -395,7 +391,6 @@ fn lex_operator(bytes: &[u8]) -> (Token, usize) { b"&" => Token::Ampersand, b"||" => Token::OpOr, b"//" => Token::OpDoubleSlash, - b"%%" => Token::OpDoublePercent, b"->" => Token::Arrow, b"<-" => Token::OpBackpassing, op => { @@ -410,15 +405,6 @@ 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]) { diff --git a/highlight/tests/peg_grammar.rs b/highlight/tests/peg_grammar.rs index 7b8bde04c5..f8e052f22f 100644 --- a/highlight/tests/peg_grammar.rs +++ b/highlight/tests/peg_grammar.rs @@ -72,10 +72,7 @@ mod test_peg_grammar { rule tag() = - private_tag() - / [T::UppercaseIdent] - - rule private_tag() = [T::PrivateTag] {} + [T::UppercaseIdent] rule list() = empty_list() @@ -368,7 +365,6 @@ mod test_peg_grammar { / [T::OpSlash] / [T::OpDoubleSlash] / [T::OpPercent] - / [T::OpDoublePercent] rule mul_level_expr() = unary_expr() (mul_level_op() unary_expr())* diff --git a/linker/Cargo.toml b/linker/Cargo.toml index c95a3ee5e2..d71102aa95 100644 --- a/linker/Cargo.toml +++ b/linker/Cargo.toml @@ -28,5 +28,5 @@ memmap2 = "0.5.3" object = { version = "0.26.2", features = ["read", "write"] } serde = { version = "1.0.130", features = ["derive"] } bincode = "1.3.3" -target-lexicon = "0.12.2" +target-lexicon = "0.12.3" tempfile = "3.2.0" diff --git a/packages/parser/src/Bytes/Parser.roc b/packages/parser/src/Bytes/Parser.roc index 31a61654d6..a1dc6648c0 100644 --- a/packages/parser/src/Bytes/Parser.roc +++ b/packages/parser/src/Bytes/Parser.roc @@ -17,10 +17,7 @@ interface Parser exposes [ Parser ] imports [] -Parser a : - [ - @Parser (Bytes -> Result { answer : a, rest : Bytes } Problem) - ] +Parser a := Bytes -> Result { answer : a, rest : Bytes } Problem Problem : [ diff --git a/packages/parser/src/Str/Parser.roc b/packages/parser/src/Str/Parser.roc index 9962b83a14..7dc755f4c0 100644 --- a/packages/parser/src/Str/Parser.roc +++ b/packages/parser/src/Str/Parser.roc @@ -19,10 +19,7 @@ interface Parser exposes [ Parser ] imports [] -Parser a : - [ - @Parser (Str -> Result { answer : a, rest : Str } RawProblem), - ] +Parser a := Str -> Result { answer : a, rest : Str } RawProblem Problem : [ diff --git a/packages/unicode/src/Unicode/CodePoint/Internal.roc b/packages/unicode/src/Unicode/CodePoint/Internal.roc index 5bac8917c7..5ff40f9513 100644 --- a/packages/unicode/src/Unicode/CodePoint/Internal.roc +++ b/packages/unicode/src/Unicode/CodePoint/Internal.roc @@ -10,7 +10,7 @@ interface Unicode.CodePoint.Internal [] ## This is just here so that both Unicode.Scalar and Unicode.CodePoint can access it. -CodePoint : [ @CodePoint U32 ] +CodePoint := U32 fromU32Unchecked : U32 -> CodePoint fromU32Unchecked = \u32 -> @CodePoint u32 diff --git a/packages/unicode/src/Unicode/Scalar.roc b/packages/unicode/src/Unicode/Scalar.roc index e8a3537d9a..b6a06d3e38 100644 --- a/packages/unicode/src/Unicode/Scalar.roc +++ b/packages/unicode/src/Unicode/Scalar.roc @@ -18,7 +18,7 @@ interface Unicode.Scalar ] ## A [Unicode Scalar Value](http://www.unicode.org/glossary/#unicode_scalar_value) -Scalar : [ @Scalar U32 ] +Scalar := U32 toStr : Scalar -> Str toStr = \@Scalar u32 diff --git a/repl_eval/src/eval.rs b/repl_eval/src/eval.rs index bc718a7c6c..8189250917 100644 --- a/repl_eval/src/eval.rs +++ b/repl_eval/src/eval.rs @@ -86,16 +86,21 @@ enum NewtypeKind<'a> { /// /// The returned list of newtype containers is ordered by increasing depth. As an example, /// `A ({b : C 123})` will have the unrolled list `[Tag(A), RecordField(b), Tag(C)]`. -fn unroll_newtypes<'a>( +/// +/// If we pass through aliases, the top-level alias that should be displayed to the user is passed +/// back as an option. +/// +/// Returns (new type containers, optional alias content, real content). +fn unroll_newtypes_and_aliases<'a>( env: &Env<'a, 'a>, mut content: &'a Content, -) -> (Vec<'a, NewtypeKind<'a>>, &'a Content) { +) -> (Vec<'a, NewtypeKind<'a>>, Option<&'a Content>, &'a Content) { let mut newtype_containers = Vec::with_capacity_in(1, env.arena); - let mut force_alias_content = None; + let mut alias_content = None; loop { match content { Content::Structure(FlatType::TagUnion(tags, _)) - if tags.is_newtype_wrapper_of_global_tag(env.subs) => + if tags.is_newtype_wrapper_of_tag(env.subs) => { let (tag_name, vars): (&TagName, &[Variable]) = tags .unsorted_iterator(env.subs, Variable::EMPTY_TAG_UNION) @@ -118,18 +123,19 @@ fn unroll_newtypes<'a>( } Content::Alias(_, _, real_var, _) => { // We need to pass through aliases too, because their underlying types may have - // unrolled newtypes. In such cases return the list of unrolled newtypes, but keep - // the content as the alias for readability. For example, + // unrolled newtypes. For example, // T : { a : Str } // v : T // v = { a : "value" } // v - // Here we need the newtype container to be `[RecordField(a)]`, but the content to - // remain as the alias `T`. - force_alias_content = Some(content); + // Here we need the newtype container to be `[RecordField(a)]`. + // + // At the end of the day what we should show to the user is the alias content, not + // what's inside, so keep that around too. + alias_content = Some(content); content = env.subs.get_content_without_compacting(*real_var); } - _ => return (newtype_containers, force_alias_content.unwrap_or(content)), + _ => return (newtype_containers, alias_content, content), } } } @@ -140,8 +146,8 @@ fn apply_newtypes<'a>( mut expr: Expr<'a>, ) -> Expr<'a> { let arena = env.arena; - // Reverse order of what we receieve from `unroll_newtypes` since we want the deepest - // container applied first. + // Reverse order of what we receieve from `unroll_newtypes_and_aliases` since + // we want the deepest container applied first. for container in newtype_containers.into_iter().rev() { match container { NewtypeKind::Tag(tag_name) => { @@ -162,13 +168,6 @@ fn apply_newtypes<'a>( expr } -fn unroll_aliases<'a>(env: &Env<'a, 'a>, mut content: &'a Content) -> &'a Content { - while let Content::Alias(_, _, real, _) = content { - content = env.subs.get_content_without_compacting(*real); - } - content -} - fn unroll_recursion_var<'a>(env: &Env<'a, 'a>, mut content: &'a Content) -> &'a Content { while let Content::RecursionVar { structure, .. } = content { content = env.subs.get_content_without_compacting(*structure); @@ -278,13 +277,13 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( layout: &Layout<'a>, content: &'a Content, ) -> Result, ToAstProblem> { - let (newtype_containers, content) = unroll_newtypes(env, content); - let content = unroll_aliases(env, content); + let (newtype_containers, alias_content, raw_content) = + unroll_newtypes_and_aliases(env, content); 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) + number_literal_to_ast(env.arena, num) }) }; } @@ -292,17 +291,18 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( let result = match layout { Layout::Builtin(Builtin::Bool) => Ok(app .call_function(main_fn_name, |mem: &A::Memory, num: bool| { - bool_to_ast(env, mem, num, content) + bool_to_ast(env, mem, num, raw_content) })), Layout::Builtin(Builtin::Int(int_width)) => { + use Content::*; use IntWidth::*; - let result = match (content, int_width) { - (Content::Structure(FlatType::Apply(Symbol::NUM_NUM, _)), U8) => num_helper!(u8), + let result = match (alias_content, int_width) { + (Some(Alias(Symbol::NUM_UNSIGNED8, ..)), 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) + byte_to_ast(env, mem, num, raw_content) }) } // The rest are numbers... for now @@ -344,14 +344,14 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( Layout::Builtin(Builtin::List(elem_layout)) => Ok(app.call_function( main_fn_name, |mem: &A::Memory, (addr, len): (usize, usize)| { - list_to_ast(env, mem, addr, len, elem_layout, content) + list_to_ast(env, mem, addr, len, elem_layout, raw_content) }, )), Layout::Builtin(other) => { todo!("add support for rendering builtin {:?} to the REPL", other) } Layout::Struct { field_layouts, .. } => { - let struct_addr_to_ast = |mem: &'a A::Memory, addr: usize| match content { + let struct_addr_to_ast = |mem: &'a A::Memory, addr: usize| match raw_content { Content::Structure(FlatType::Record(fields, _)) => { Ok(struct_to_ast(env, mem, addr, *fields)) } @@ -413,7 +413,14 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( main_fn_name, size as usize, |mem: &'a A::Memory, addr: usize| { - addr_to_ast(env, mem, addr, layout, WhenRecursive::Unreachable, content) + addr_to_ast( + env, + mem, + addr, + layout, + WhenRecursive::Unreachable, + raw_content, + ) }, )) } @@ -432,7 +439,7 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( addr, layout, WhenRecursive::Loop(*layout), - content, + raw_content, ) }, )) @@ -447,7 +454,14 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( main_fn_name, size as usize, |mem: &A::Memory, addr| { - addr_to_ast(env, mem, addr, layout, WhenRecursive::Unreachable, content) + addr_to_ast( + env, + mem, + addr, + layout, + WhenRecursive::Unreachable, + raw_content, + ) }, )) } @@ -457,11 +471,7 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( fn tag_name_to_expr<'a>(env: &Env<'a, '_>, tag_name: &TagName) -> Expr<'a> { match tag_name { - TagName::Global(_) => Expr::GlobalTag( - env.arena - .alloc_str(&tag_name.as_ident_str(env.interns, env.home)), - ), - TagName::Private(_) => Expr::PrivateTag( + TagName::Tag(_) => Expr::Tag( env.arena .alloc_str(&tag_name.as_ident_str(env.interns, env.home)), ), @@ -489,13 +499,14 @@ fn addr_to_ast<'a, M: ReplAppMemory>( ($method: ident, $ty: ty) => {{ let num: $ty = mem.$method(addr); - num_to_ast(env, number_literal_to_ast(env.arena, num), content) + number_literal_to_ast(env.arena, num) }}; } - let (newtype_containers, content) = unroll_newtypes(env, content); - let content = unroll_aliases(env, content); - let expr = match (content, layout) { + let (newtype_containers, _alias_content, raw_content) = + unroll_newtypes_and_aliases(env, content); + + let expr = match (raw_content, layout) { (Content::Structure(FlatType::Func(_, _, _)), _) | (_, Layout::LambdaSet(_)) => OPAQUE_FUNCTION, (_, Layout::Builtin(Builtin::Bool)) => { @@ -503,7 +514,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( // num is always false at the moment. let num: bool = mem.deref_bool(addr); - bool_to_ast(env, mem, num, content) + bool_to_ast(env, mem, num, raw_content) } (_, Layout::Builtin(Builtin::Int(int_width))) => { use IntWidth::*; @@ -534,14 +545,14 @@ fn addr_to_ast<'a, M: ReplAppMemory>( let elem_addr = mem.deref_usize(addr); let len = mem.deref_usize(addr + env.target_info.ptr_width() as usize); - list_to_ast(env, mem, elem_addr, len, elem_layout, content) + list_to_ast(env, mem, elem_addr, len, elem_layout, raw_content) } (_, Layout::Builtin(Builtin::Str)) => { let string = mem.deref_str(addr); let arena_str = env.arena.alloc_str(string); Expr::Str(StrLiteral::PlainLine(arena_str)) } - (_, Layout::Struct{field_layouts, ..}) => match content { + (_, Layout::Struct{field_layouts, ..}) => match raw_content { Content::Structure(FlatType::Record(fields, _)) => { struct_to_ast(env, mem, addr, *fields) } @@ -566,7 +577,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( } }, (_, Layout::RecursivePointer) => { - match (content, when_recursive) { + match (raw_content, when_recursive) { (Content::RecursionVar { structure, opt_name: _, @@ -580,7 +591,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( (_, Layout::Union(UnionLayout::NonRecursive(union_layouts))) => { let union_layout = UnionLayout::NonRecursive(union_layouts); - let tags = match content { + let tags = match raw_content { Content::Structure(FlatType::TagUnion(tags, _)) => tags, other => unreachable!("Weird content for nonrecursive Union layout: {:?}", other), }; @@ -614,7 +625,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( ) } (_, Layout::Union(union_layout @ UnionLayout::Recursive(union_layouts))) => { - let (rec_var, tags) = match content { + let (rec_var, tags) = match raw_content { Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), _ => unreachable!("any other content would have a different layout"), }; @@ -644,7 +655,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( ) } (_, Layout::Union(UnionLayout::NonNullableUnwrapped(_))) => { - let (rec_var, tags) = match unroll_recursion_var(env, content) { + let (rec_var, tags) = match unroll_recursion_var(env, raw_content) { Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other), }; @@ -672,7 +683,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( ) } (_, Layout::Union(UnionLayout::NullableUnwrapped { .. })) => { - let (rec_var, tags) = match unroll_recursion_var(env, content) { + let (rec_var, tags) = match unroll_recursion_var(env, raw_content) { Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other), }; @@ -706,7 +717,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( } } (_, Layout::Union(union_layout @ UnionLayout::NullableWrapped { .. })) => { - let (rec_var, tags) = match unroll_recursion_var(env, content) { + let (rec_var, tags) = match unroll_recursion_var(env, raw_content) { Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other), }; @@ -803,7 +814,8 @@ fn list_to_ast<'a, M: ReplAppMemory>( for index in 0..len { let offset_bytes = index * elem_size; let elem_addr = addr + offset_bytes; - let (newtype_containers, elem_content) = unroll_newtypes(env, elem_content); + let (newtype_containers, _alias_content, elem_content) = + unroll_newtypes_and_aliases(env, elem_content); let expr = addr_to_ast( env, mem, @@ -1036,11 +1048,7 @@ fn bool_to_ast<'a, M: ReplAppMemory>( let loc_tag_expr = { let tag_name = &tag_name.as_ident_str(env.interns, env.home); - let tag_expr = if tag_name.starts_with('@') { - Expr::PrivateTag(arena.alloc_str(tag_name)) - } else { - Expr::GlobalTag(arena.alloc_str(tag_name)) - }; + let tag_expr = Expr::Tag(arena.alloc_str(tag_name)); &*arena.alloc(Loc { value: tag_expr, @@ -1117,19 +1125,9 @@ fn byte_to_ast<'a, M: ReplAppMemory>( FlatType::TagUnion(tags, _) if tags.len() == 1 => { let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); - // If this tag union represents a number, skip right to - // returning it as an Expr::Num - if let TagName::Private(Symbol::NUM_AT_NUM) = &tag_name { - return Expr::Num(env.arena.alloc_str(&value.to_string())); - } - let loc_tag_expr = { let tag_name = &tag_name.as_ident_str(env.interns, env.home); - let tag_expr = if tag_name.starts_with('@') { - Expr::PrivateTag(arena.alloc_str(tag_name)) - } else { - Expr::GlobalTag(arena.alloc_str(tag_name)) - }; + let tag_expr = Expr::Tag(arena.alloc_str(tag_name)); &*arena.alloc(Loc { value: tag_expr, @@ -1183,7 +1181,7 @@ fn byte_to_ast<'a, M: ReplAppMemory>( } } other => { - unreachable!("Unexpected FlatType {:?} in bool_to_ast", other); + unreachable!("Unexpected FlatType {:?} in byte_to_ast", other); } } } @@ -1193,79 +1191,7 @@ fn byte_to_ast<'a, M: ReplAppMemory>( byte_to_ast(env, mem, value, content) } other => { - unreachable!("Unexpected FlatType {:?} in bool_to_ast", other); - } - } -} - -fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> Expr<'a> { - use Content::*; - - let arena = env.arena; - - match content { - Structure(flat_type) => { - match flat_type { - FlatType::Apply(Symbol::NUM_NUM, _) => num_expr, - FlatType::TagUnion(tags, _) => { - // This was a single-tag union that got unwrapped at runtime. - debug_assert_eq!(tags.len(), 1); - - let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); - - // If this tag union represents a number, skip right to - // returning it as an Expr::Num - if let TagName::Private(Symbol::NUM_AT_NUM) = &tag_name { - return num_expr; - } - - let loc_tag_expr = { - let tag_name = &tag_name.as_ident_str(env.interns, env.home); - let tag_expr = if tag_name.starts_with('@') { - Expr::PrivateTag(arena.alloc_str(tag_name)) - } else { - Expr::GlobalTag(arena.alloc_str(tag_name)) - }; - - &*arena.alloc(Loc { - value: tag_expr, - region: Region::zero(), - }) - }; - - let payload = { - // Since this has the layout of a number, there should be - // exactly one payload in this tag. - debug_assert_eq!(payload_vars.len(), 1); - - let var = *payload_vars.iter().next().unwrap(); - let content = env.subs.get_content_without_compacting(var); - - let loc_payload = &*arena.alloc(Loc { - value: num_to_ast(env, num_expr, content), - region: Region::zero(), - }); - - arena.alloc([loc_payload]) - }; - - Expr::Apply(loc_tag_expr, payload, CalledVia::Space) - } - other => { - panic!("Unexpected FlatType {:?} in num_to_ast", other); - } - } - } - Alias(_, _, var, _) => { - let content = env.subs.get_content_without_compacting(*var); - - num_to_ast(env, num_expr, content) - } - RangedNumber(typ, _) => { - num_to_ast(env, num_expr, env.subs.get_content_without_compacting(*typ)) - } - other => { - panic!("Unexpected FlatType {:?} in num_to_ast", other); + unreachable!("Unexpected FlatType {:?} in byte_to_ast", other); } } } diff --git a/repl_eval/src/gen.rs b/repl_eval/src/gen.rs index 4e8f698c29..3c50ffa14d 100644 --- a/repl_eval/src/gen.rs +++ b/repl_eval/src/gen.rs @@ -7,7 +7,7 @@ use roc_fmt::annotation::{Newlines, Parens}; use roc_load::{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_reporting::report::{can_problem, type_problem, RocDocAllocator}; use roc_target::TargetInfo; use crate::eval::ToAstProblem; @@ -47,7 +47,7 @@ pub fn compile_to_mono<'a>( target_info: TargetInfo, palette: Palette, ) -> Result, Vec> { - let filename = PathBuf::from("REPL.roc"); + let filename = PathBuf::from(""); let src_dir = Path::new("fake/test/path"); let module_src = arena.alloc(promote_expr_to_module(src)); @@ -78,7 +78,6 @@ pub fn compile_to_mono<'a>( sources, can_problems, type_problems, - mono_problems, .. } = &mut loaded; @@ -87,9 +86,8 @@ pub fn compile_to_mono<'a>( for (home, (module_path, src)) in sources.iter() { let can_probs = can_problems.remove(home).unwrap_or_default(); let type_probs = type_problems.remove(home).unwrap_or_default(); - let mono_probs = mono_problems.remove(home).unwrap_or_default(); - let error_count = can_probs.len() + type_probs.len() + mono_probs.len(); + let error_count = can_probs.len() + type_probs.len(); if error_count == 0 { continue; @@ -119,15 +117,6 @@ pub fn compile_to_mono<'a>( lines.push(buf); } } - - for problem in mono_probs { - let report = mono_problem(&alloc, &line_info, module_path.clone(), problem); - let mut buf = String::new(); - - report.render_color_terminal(&mut buf, &alloc, &palette); - - lines.push(buf); - } } if !lines.is_empty() { diff --git a/repl_test/src/tests.rs b/repl_test/src/tests.rs index d0360ac569..7e3f2aa4c2 100644 --- a/repl_test/src/tests.rs +++ b/repl_test/src/tests.rs @@ -56,20 +56,20 @@ fn float_addition() { #[cfg(not(feature = "wasm"))] #[test] fn num_rem() { - expect_success("299 % 10", "Ok 9 : Result (Int *) [ DivByZero ]*"); + expect_success("299 % 10", "9 : Int *"); } #[cfg(not(feature = "wasm"))] #[test] fn num_floor_division() { - expect_success("Num.divFloor 4 3", "1 : Int *"); + expect_success("Num.divTrunc 4 3", "1 : Int *"); } #[cfg(not(feature = "wasm"))] #[test] fn num_floor_checked_division_success() { expect_success( - "Num.divFloorChecked 4 3", + "Num.divTruncChecked 4 3", "Ok 1 : Result (Int *) [ DivByZero ]*", ); } @@ -78,7 +78,7 @@ fn num_floor_checked_division_success() { #[test] fn num_floor_checked_division_divby_zero() { expect_success( - "Num.divFloorChecked 4 0", + "Num.divTruncChecked 4 0", "Err DivByZero : Result (Int *) [ DivByZero ]*", ); } @@ -901,33 +901,33 @@ fn parse_problem() { #[cfg(not(feature = "wasm"))] // TODO: mismatch is due to terminal control codes! #[test] -fn mono_problem() { +fn exhaustiveness_problem() { expect_failure( - r#" + indoc!( + r#" t : [A, B, C] t = A when t is A -> "a" - "#, + "# + ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── - This when does not cover all the possibilities: - - 7β”‚> when t is - 8β”‚> A -> "a" - - Other possibilities include: - - B - C - - I would have to crash if I saw one of those! Add branches for them! - - - Enter an expression, or :help, or :exit/:q."# + This when does not cover all the possibilities: + + 7β”‚> when t is + 8β”‚> A -> "a" + + Other possibilities include: + + B + C + + I would have to crash if I saw one of those! Add branches for them! + "# ), ); } @@ -1019,7 +1019,7 @@ fn opaque_apply() { r#" Age := U32 - $Age 23 + @Age 23 "# ), "23 : Age", @@ -1033,7 +1033,7 @@ fn opaque_apply_polymorphic() { r#" F t u := [ Package t u ] - $F (Package "" { a: "" }) + @F (Package "" { a: "" }) "# ), r#"Package "" { a: "" } : F Str { a : Str }"#, @@ -1047,9 +1047,9 @@ fn opaque_pattern_and_call() { r#" F t u := [ Package t u ] - f = \$F (Package A {}) -> $F (Package {} A) + f = \@F (Package A {}) -> @F (Package {} A) - f ($F (Package A {})) + f (@F (Package A {})) "# ), r#"Package {} A : F {} [ A ]*"#, @@ -1138,3 +1138,42 @@ fn issue_2818() { r" : {} -> List Str", ) } + +#[test] +fn issue_2810_recursive_layout_inside_nonrecursive() { + expect_success( + indoc!( + r#" + Command : [ Command Tool ] + + Job : [ Job Command ] + + Tool : [ SystemTool, FromJob Job ] + + a : Job + a = Job (Command (FromJob (Job (Command SystemTool)))) + a + "# + ), + "Job (Command (FromJob (Job (Command SystemTool)))) : Job", + ) +} + +#[test] +fn render_nullable_unwrapped_passing_through_alias() { + expect_success( + indoc!( + r#" + Deep : [ L DeepList ] + + DeepList : [ Nil, Cons Deep ] + + v : DeepList + v = (Cons (L (Cons (L (Cons (L Nil)))))) + + v + "# + ), + "Cons (L (Cons (L (Cons (L Nil))))) : DeepList", + ) +} diff --git a/reporting/.gitignore b/reporting/.gitignore new file mode 100644 index 0000000000..cad2309100 --- /dev/null +++ b/reporting/.gitignore @@ -0,0 +1 @@ +/tmp \ No newline at end of file diff --git a/reporting/Cargo.toml b/reporting/Cargo.toml index 63e16ad7c3..1c107cf580 100644 --- a/reporting/Cargo.toml +++ b/reporting/Cargo.toml @@ -15,7 +15,6 @@ roc_problem = { path = "../compiler/problem" } roc_types = { path = "../compiler/types" } roc_can = { path = "../compiler/can" } roc_solve = { path = "../compiler/solve" } -roc_mono = { path = "../compiler/mono" } ven_pretty = { path = "../vendor/pretty" } distance = "0.4.0" bumpalo = { version = "3.8.0", features = ["collections"] } diff --git a/reporting/src/error/canonicalize.rs b/reporting/src/error/canonicalize.rs index d366baf6f2..73fd7d7ec2 100644 --- a/reporting/src/error/canonicalize.rs +++ b/reporting/src/error/canonicalize.rs @@ -45,6 +45,8 @@ const ILLEGAL_HAS_CLAUSE: &str = "ILLEGAL HAS CLAUSE"; const ABILITY_MEMBER_MISSING_HAS_CLAUSE: &str = "ABILITY MEMBER MISSING HAS CLAUSE"; const ABILITY_MEMBER_HAS_EXTRANEOUS_HAS_CLAUSE: &str = "ABILITY MEMBER HAS EXTRANEOUS HAS CLAUSE"; const ABILITY_MEMBER_BINDS_MULTIPLE_VARIABLES: &str = "ABILITY MEMBER BINDS MULTIPLE VARIABLES"; +const ABILITY_NOT_ON_TOPLEVEL: &str = "ABILITY NOT ON TOP-LEVEL"; +const ABILITY_USED_AS_TYPE: &str = "ABILITY USED AS TYPE"; pub fn can_problem<'b>( alloc: &'b RocDocAllocator<'b>, @@ -61,7 +63,7 @@ pub fn can_problem<'b>( let line = r#" then remove it so future readers of your code don't wonder why it is there."#; - doc = alloc.stack(vec![ + doc = alloc.stack([ alloc .symbol_unqualified(symbol) .append(alloc.reflow(" is not used anywhere in your code.")), @@ -76,14 +78,14 @@ pub fn can_problem<'b>( severity = Severity::Warning; } Problem::UnusedImport(module_id, region) => { - doc = alloc.stack(vec![ - alloc.concat(vec![ + doc = alloc.stack([ + alloc.concat([ alloc.reflow("Nothing from "), alloc.module(module_id), alloc.reflow(" is used in this module."), ]), alloc.region(lines.convert_region(region)), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("Since "), alloc.module(module_id), alloc.reflow(" isn't used, you don't need to import it."), @@ -94,7 +96,7 @@ pub fn can_problem<'b>( severity = Severity::Warning; } Problem::ExposedButNotDefined(symbol) => { - doc = alloc.stack(vec![ + doc = alloc.stack([ alloc.symbol_unqualified(symbol).append( alloc.reflow(" is listed as exposed, but it isn't defined in this module."), ), @@ -110,7 +112,7 @@ pub fn can_problem<'b>( severity = Severity::RuntimeError; } Problem::UnknownGeneratesWith(loc_ident) => { - doc = alloc.stack(vec![ + doc = alloc.stack([ alloc .reflow("I don't know how to generate the ") .append(alloc.ident(loc_ident.value)) @@ -127,15 +129,15 @@ pub fn can_problem<'b>( Problem::UnusedArgument(closure_symbol, argument_symbol, region) => { let line = "\". Adding an underscore at the start of a variable name is a way of saying that the variable is not used."; - doc = alloc.stack(vec![ - alloc.concat(vec![ + doc = alloc.stack([ + alloc.concat([ alloc.symbol_unqualified(closure_symbol), alloc.reflow(" doesn't use "), alloc.symbol_unqualified(argument_symbol), alloc.text("."), ]), alloc.region(lines.convert_region(region)), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("If you don't need "), alloc.symbol_unqualified(argument_symbol), alloc.reflow(", then you can just remove it. However, if you really do need "), @@ -152,9 +154,9 @@ pub fn can_problem<'b>( severity = Severity::Warning; } Problem::PrecedenceProblem(BothNonAssociative(region, left_bin_op, right_bin_op)) => { - doc = alloc.stack(vec![ + doc = alloc.stack([ if left_bin_op.value == right_bin_op.value { - alloc.concat(vec![ + alloc.concat([ alloc.reflow("Using more than one "), alloc.binop(left_bin_op.value), alloc.reflow(concat!( @@ -163,7 +165,7 @@ pub fn can_problem<'b>( )), ]) } else { - alloc.concat(vec![ + alloc.concat([ alloc.reflow("Using "), alloc.binop(left_bin_op.value), alloc.reflow(" and "), @@ -181,7 +183,7 @@ pub fn can_problem<'b>( severity = Severity::RuntimeError; } Problem::UnsupportedPattern(BadPattern::UnderscoreInDef, region) => { - doc = alloc.stack(vec![ + doc = alloc.stack([ alloc.reflow("Underscore patterns are not allowed in definitions"), alloc.region(lines.convert_region(region)), ]); @@ -209,7 +211,7 @@ pub fn can_problem<'b>( alloc.reflow(" instead."), ]; - doc = alloc.stack(vec![ + doc = alloc.stack([ alloc .reflow("This pattern is not allowed in ") .append(alloc.reflow(this_thing)), @@ -242,8 +244,8 @@ pub fn can_problem<'b>( variable_region, variable_name, } => { - doc = alloc.stack(vec![ - alloc.concat(vec![ + doc = alloc.stack([ + alloc.concat([ alloc.reflow("The "), alloc.type_variable(variable_name), alloc.reflow(" type parameter is not used in the "), @@ -270,13 +272,13 @@ pub fn can_problem<'b>( } => { let mut stack = Vec::with_capacity(4); if num_unbound == 1 { - stack.push(alloc.concat(vec![ + stack.push(alloc.concat([ alloc.reflow("The definition of "), alloc.symbol_unqualified(alias), alloc.reflow(" has an unbound type variable:"), ])); } else { - stack.push(alloc.concat(vec![ + stack.push(alloc.concat([ alloc.reflow("The definition of "), alloc.symbol_unqualified(alias), alloc.reflow(" has "), @@ -286,7 +288,7 @@ pub fn can_problem<'b>( 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![ + stack.push(alloc.tip().append(alloc.concat([ alloc.reflow("Type variables must be bound before the "), alloc.keyword(match kind { AliasKind::Structural => ":", @@ -310,8 +312,8 @@ pub fn can_problem<'b>( record_region, replaced_region, } => { - doc = alloc.stack(vec![ - alloc.concat(vec![ + doc = alloc.stack([ + alloc.concat([ alloc.reflow("This record defines the "), alloc.record_field(field_name.clone()), alloc.reflow(" field twice!"), @@ -329,7 +331,7 @@ pub fn can_problem<'b>( lines.convert_region(field_region), Annotation::TypoSuggestion, ), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("For clarity, remove the previous "), alloc.record_field(field_name), alloc.reflow(" definitions from this record."), @@ -359,8 +361,8 @@ pub fn can_problem<'b>( record_region, replaced_region, } => { - doc = alloc.stack(vec![ - alloc.concat(vec![ + doc = alloc.stack([ + alloc.concat([ alloc.reflow("This record type defines the "), alloc.record_field(field_name.clone()), alloc.reflow(" field twice!"), @@ -378,7 +380,7 @@ pub fn can_problem<'b>( lines.convert_region(field_region), Annotation::TypoSuggestion, ), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("For clarity, remove the previous "), alloc.record_field(field_name), alloc.reflow(" definitions from this record type."), @@ -394,8 +396,8 @@ pub fn can_problem<'b>( tag_region, replaced_region, } => { - doc = alloc.stack(vec![ - alloc.concat(vec![ + doc = alloc.stack([ + alloc.concat([ alloc.reflow("This tag union type defines the "), alloc.tag_name(tag_name.clone()), alloc.reflow(" tag twice!"), @@ -413,7 +415,7 @@ pub fn can_problem<'b>( lines.convert_region(tag_region), Annotation::TypoSuggestion, ), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("For clarity, remove the previous "), alloc.tag_name(tag_name), alloc.reflow(" definitions from this tag union type."), @@ -427,7 +429,7 @@ pub fn can_problem<'b>( ref annotation_pattern, ref def_pattern, } => { - doc = alloc.stack(vec![ + doc = alloc.stack([ alloc.reflow( "This annotation does not match the definition immediately following it:", ), @@ -444,14 +446,14 @@ pub fn can_problem<'b>( alias_name: type_name, region, } => { - doc = alloc.stack(vec![ - alloc.concat(vec![ + doc = alloc.stack([ + alloc.concat([ alloc.reflow("This pattern in the definition of "), alloc.symbol_unqualified(type_name), alloc.reflow(" is not what I expect:"), ]), alloc.region(lines.convert_region(region)), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("Only type variables like "), alloc.type_variable("a".into()), alloc.reflow(" or "), @@ -464,10 +466,10 @@ pub fn can_problem<'b>( severity = Severity::RuntimeError; } Problem::InvalidHexadecimal(region) => { - doc = alloc.stack(vec![ + doc = alloc.stack([ alloc.reflow("This unicode code point is invalid:"), alloc.region(lines.convert_region(region)), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"I was expecting a hexadecimal number, like "), alloc.parser_suggestion("\\u(1100)"), alloc.reflow(" or "), @@ -481,7 +483,7 @@ pub fn can_problem<'b>( severity = Severity::RuntimeError; } Problem::InvalidUnicodeCodePt(region) => { - doc = alloc.stack(vec![ + doc = alloc.stack([ alloc.reflow("This unicode code point is invalid:"), alloc.region(lines.convert_region(region)), alloc.reflow("Learn more about working with unicode in roc at TODO"), @@ -491,10 +493,10 @@ pub fn can_problem<'b>( severity = Severity::RuntimeError; } Problem::InvalidInterpolation(region) => { - doc = alloc.stack(vec![ + doc = alloc.stack([ alloc.reflow("This string interpolation is invalid:"), alloc.region(lines.convert_region(region)), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"I was expecting an identifier, like "), alloc.parser_suggestion("\\u(message)"), alloc.reflow(" or "), @@ -519,20 +521,20 @@ pub fn can_problem<'b>( def_region, differing_recursion_region, } => { - doc = alloc.stack(vec![ - alloc.concat(vec![ + doc = alloc.stack([ + alloc.concat([ alloc.symbol_unqualified(alias), alloc.reflow(" is a nested datatype. Here is one recursive usage of it:"), ]), alloc.region(lines.convert_region(differing_recursion_region)), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("But recursive usages of "), alloc.symbol_unqualified(alias), alloc.reflow(" must match its definition:"), ]), alloc.region(lines.convert_region(def_region)), alloc.reflow("Nested datatypes are not supported in Roc."), - alloc.concat(vec![ + alloc.concat([ alloc.hint("Consider rewriting the definition of "), alloc.symbol_unqualified(alias), alloc.text(" to use the recursive type with the same arguments."), @@ -551,14 +553,14 @@ pub fn can_problem<'b>( } }; - doc = alloc.stack(vec![ - alloc.concat(vec![ + doc = alloc.stack([ + alloc.concat([ alloc.reflow("This "), alloc.text(kind_str), alloc.reflow(" extension type is invalid:"), ]), alloc.region(lines.convert_region(region)), - alloc.concat(vec![ + alloc.concat([ alloc.note("A "), alloc.reflow(kind_str), alloc.reflow(" extension variable can only contain "), @@ -575,8 +577,8 @@ pub fn can_problem<'b>( name, variables_region, } => { - doc = alloc.stack(vec![ - alloc.concat(vec![ + doc = alloc.stack([ + alloc.concat([ alloc.reflow("The definition of the "), alloc.symbol_unqualified(name), alloc.reflow(" ability includes type variables:"), @@ -593,7 +595,7 @@ pub fn can_problem<'b>( Problem::HasClauseIsNotAbility { region: clause_region, } => { - doc = alloc.stack(vec![ + doc = alloc.stack([ alloc.reflow(r#"The type referenced in this "has" clause is not an ability:"#), alloc.region(lines.convert_region(clause_region)), ]); @@ -608,8 +610,8 @@ pub fn can_problem<'b>( }, ability, } => { - doc = alloc.stack(vec![ - alloc.concat(vec![ + doc = alloc.stack([ + alloc.concat([ alloc.reflow("The definition of the "), alloc.symbol_unqualified(name), alloc.reflow(" aliases references the ability "), @@ -617,12 +619,12 @@ pub fn can_problem<'b>( alloc.reflow(":"), ]), alloc.region(lines.convert_region(region)), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("Abilities are not types, but you can add an ability constraint to a type variable "), alloc.type_variable("a".into()), alloc.reflow(" by writing"), ]), - alloc.type_block(alloc.concat(vec![ + alloc.type_block(alloc.concat([ alloc.reflow("| a has "), alloc.symbol_unqualified(ability), ])), @@ -633,16 +635,18 @@ pub fn can_problem<'b>( } Problem::IllegalHasClause { region } => { - doc = alloc.stack(vec![ - alloc.concat(vec![ + doc = alloc.stack([ + alloc.concat([ alloc.reflow("A "), alloc.keyword("has"), alloc.reflow(" clause is not allowed here:"), ]), alloc.region(lines.convert_region(region)), - alloc.concat(vec![ + alloc.concat([ alloc.keyword("has"), - alloc.reflow(" clauses can only be specified on the top-level type annotation of an ability member."), + alloc.reflow( + " clauses can only be specified on the top-level type annotations.", + ), ]), ]); title = ILLEGAL_HAS_CLAUSE.to_string(); @@ -654,8 +658,8 @@ pub fn can_problem<'b>( ability, region, } => { - doc = alloc.stack(vec![ - alloc.concat(vec![ + doc = alloc.stack([ + alloc.concat([ alloc.reflow("The definition of the ability member "), alloc.symbol_unqualified(member), alloc.reflow(" does not include a "), @@ -665,21 +669,20 @@ pub fn can_problem<'b>( alloc.reflow(":"), ]), alloc.region(lines.convert_region(region)), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("Ability members must include a "), alloc.keyword("has"), alloc.reflow(" clause binding a type variable to an ability, like"), ]), - alloc.type_block(alloc.concat(vec![ + alloc.type_block(alloc.concat([ alloc.type_variable("a".into()), alloc.space(), alloc.keyword("has"), alloc.space(), alloc.symbol_unqualified(ability), ])), - alloc.concat(vec![alloc.reflow( - "Otherwise, the function does not need to be part of the ability!", - )]), + alloc.concat([alloc + .reflow("Otherwise, the function does not need to be part of the ability!")]), ]); title = ABILITY_MEMBER_MISSING_HAS_CLAUSE.to_string(); severity = Severity::RuntimeError; @@ -691,8 +694,8 @@ pub fn can_problem<'b>( span_has_clauses, mut bound_var_names, } => { - doc = alloc.stack(vec![ - alloc.concat(vec![ + doc = alloc.stack([ + alloc.concat([ alloc.reflow("The definition of the ability member "), alloc.symbol_unqualified(member), alloc.reflow(" includes multiple variables bound to the "), @@ -701,7 +704,7 @@ pub fn can_problem<'b>( ]), alloc.region(lines.convert_region(span_has_clauses)), alloc.reflow("Ability members can only bind one type variable to their parent ability. Otherwise, I wouldn't know what type implements an ability by looking at specializations!"), - alloc.concat(vec![ + alloc.concat([ alloc.hint("Did you mean to only bind "), alloc.type_variable(bound_var_names.swap_remove(0)), alloc.reflow(" to "), @@ -718,15 +721,15 @@ pub fn can_problem<'b>( ability, region, } => { - doc = alloc.stack(vec![ - alloc.concat(vec![ + doc = alloc.stack([ + alloc.concat([ alloc.reflow("The definition of the ability member "), alloc.symbol_unqualified(member), alloc.reflow(" includes a has clause binding an ability it is not a part of:"), ]), alloc.region(lines.convert_region(region)), alloc.reflow("Currently, ability members can only bind variables to the ability they are a part of."), - alloc.concat(vec![ + alloc.concat([ alloc.hint(""), alloc.reflow("Did you mean to bind the "), alloc.symbol_unqualified(ability), @@ -736,6 +739,46 @@ pub fn can_problem<'b>( title = ABILITY_MEMBER_HAS_EXTRANEOUS_HAS_CLAUSE.to_string(); severity = Severity::RuntimeError; } + + Problem::AbilityNotOnToplevel { region } => { + doc = alloc.stack(vec![ + alloc.concat(vec![alloc.reflow( + "This ability definition is not on the top-level of a module:", + )]), + alloc.region(lines.convert_region(region)), + alloc.reflow("Abilities can only be defined on the top-level of a Roc module."), + ]); + title = ABILITY_NOT_ON_TOPLEVEL.to_string(); + severity = Severity::RuntimeError; + } + + Problem::AbilityUsedAsType(suggested_var_name, ability, region) => { + doc = alloc.stack(vec![ + alloc.concat(vec![ + alloc.reflow("You are attempting to use the ability "), + alloc.symbol_unqualified(ability), + alloc.reflow(" as a type directly:"), + ]), + alloc.region(lines.convert_region(region)), + alloc.reflow( + "Abilities can only be used in type annotations to constrain type variables.", + ), + alloc + .hint("") + .append(alloc.reflow("Perhaps you meant to include a ")) + .append(alloc.keyword("has")) + .append(alloc.reflow(" annotation, like")), + alloc.type_block(alloc.concat(vec![ + alloc.type_variable(suggested_var_name), + alloc.space(), + alloc.keyword("has"), + alloc.space(), + alloc.symbol_unqualified(ability), + ])), + ]); + title = ABILITY_USED_AS_TYPE.to_string(); + severity = Severity::RuntimeError; + } }; Report { @@ -777,8 +820,8 @@ fn to_invalid_optional_value_report_help<'b>( field_region: Region, record_region: Region, ) -> RocDocBuilder<'b> { - alloc.stack(vec![ - alloc.concat(vec![ + alloc.stack([ + alloc.concat([ alloc.reflow("This record uses an optional value for the "), alloc.record_field(field_name), alloc.reflow(" field in an incorrect context!"), @@ -809,10 +852,10 @@ fn to_bad_ident_expr_report<'b>( WeirdDotAccess(pos) | StrayDot(pos) => { let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - alloc.stack(vec![ + alloc.stack([ alloc.reflow(r"I trying to parse a record field access here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("So I expect to see a lowercase letter next, like "), alloc.parser_suggestion(".name"), alloc.reflow(" or "), @@ -822,10 +865,10 @@ fn to_bad_ident_expr_report<'b>( ]) } - WeirdAccessor(_pos) => alloc.stack(vec![ + WeirdAccessor(_pos) => alloc.stack([ alloc.reflow("I am very confused by this field access"), alloc.region(lines.convert_region(surroundings)), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("It looks like a field access on an accessor. I parse"), alloc.parser_suggestion(".client.name"), alloc.reflow(" as "), @@ -840,10 +883,10 @@ fn to_bad_ident_expr_report<'b>( WeirdDotQualified(pos) => { let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - alloc.stack(vec![ + alloc.stack([ alloc.reflow("I am trying to parse a qualified name here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I was expecting to see an identifier next, like "), alloc.parser_suggestion("height"), alloc.reflow(". A complete qualified name looks something like "), @@ -855,10 +898,10 @@ fn to_bad_ident_expr_report<'b>( QualifiedTag(pos) => { let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - alloc.stack(vec![ + alloc.stack([ alloc.reflow("I am trying to parse a qualified name here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"This looks like a qualified tag name to me, "), alloc.reflow(r"but tags cannot be qualified! "), alloc.reflow(r"Maybe you wanted a qualified name, something like "), @@ -870,35 +913,32 @@ fn to_bad_ident_expr_report<'b>( Underscore(pos) => { let region = Region::new(surroundings.start(), pos); - alloc.stack(vec![ + alloc.stack([ alloc.reflow("Underscores are not allowed in identifier names:"), alloc.region_with_subregion( lines.convert_region(surroundings), lines.convert_region(region), ), - alloc.concat(vec![alloc.reflow( + alloc.concat([alloc.reflow( r"I recommend using camelCase, it is the standard in the Roc ecosystem.", )]), ]) } - BadPrivateTag(pos) | BadOpaqueRef(pos) => { + BadOpaqueRef(pos) => { use BadIdentNext::*; - let kind = if matches!(bad_ident, BadPrivateTag(..)) { - "a private tag" - } else { - "an opaque reference" - }; + let kind = "an opaque reference"; + match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { LowercaseAccess(width) => { let region = Region::new(pos, pos.bump_column(width)); - alloc.stack(vec![ + alloc.stack([ alloc.reflow("I am very confused by this field access:"), alloc.region_with_subregion( lines.convert_region(surroundings), lines.convert_region(region), ), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"It looks like a record field access on "), alloc.reflow(kind), alloc.text("."), @@ -907,13 +947,13 @@ fn to_bad_ident_expr_report<'b>( } UppercaseAccess(width) => { let region = Region::new(pos, pos.bump_column(width)); - alloc.stack(vec![ + alloc.stack([ alloc.reflow("I am very confused by this expression:"), alloc.region_with_subregion( lines.convert_region(surroundings), lines.convert_region(region), ), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"Looks like "), alloc.reflow(kind), alloc.reflow(" is treated like a module name. "), @@ -926,8 +966,8 @@ fn to_bad_ident_expr_report<'b>( Other(Some(c)) if c.is_lowercase() => { let region = Region::new(surroundings.start().bump_column(1), pos.bump_column(1)); - alloc.stack(vec![ - alloc.concat(vec![ + alloc.stack([ + alloc.concat([ alloc.reflow("I am trying to parse "), alloc.reflow(kind), alloc.reflow(" here:"), @@ -936,11 +976,11 @@ fn to_bad_ident_expr_report<'b>( lines.convert_region(surroundings), lines.convert_region(region), ), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"But after the "), alloc.keyword("@"), alloc.reflow(r" symbol I found a lowercase letter. "), - alloc.reflow(r"All tag names (global and private)"), + alloc.reflow(r"All opaque references "), alloc.reflow(r" must start with an uppercase letter, like "), alloc.parser_suggestion("@UUID"), alloc.reflow(" or "), @@ -968,10 +1008,10 @@ fn to_bad_ident_pattern_report<'b>( WeirdDotAccess(pos) | StrayDot(pos) => { let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - alloc.stack(vec![ + alloc.stack([ alloc.reflow(r"I trying to parse a record field accessor here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("Something like "), alloc.parser_suggestion(".name"), alloc.reflow(" or "), @@ -981,10 +1021,10 @@ fn to_bad_ident_pattern_report<'b>( ]) } - WeirdAccessor(_pos) => alloc.stack(vec![ + WeirdAccessor(_pos) => alloc.stack([ alloc.reflow("I am very confused by this field access"), alloc.region(lines.convert_region(surroundings)), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("It looks like a field access on an accessor. I parse"), alloc.parser_suggestion(".client.name"), alloc.reflow(" as "), @@ -999,10 +1039,10 @@ fn to_bad_ident_pattern_report<'b>( WeirdDotQualified(pos) => { let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - alloc.stack(vec![ + alloc.stack([ alloc.reflow("I am trying to parse a qualified name here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I was expecting to see an identifier next, like "), alloc.parser_suggestion("height"), alloc.reflow(". A complete qualified name looks something like "), @@ -1014,10 +1054,10 @@ fn to_bad_ident_pattern_report<'b>( QualifiedTag(pos) => { let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - alloc.stack(vec![ + alloc.stack([ alloc.reflow("I am trying to parse a qualified name here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"This looks like a qualified tag name to me, "), alloc.reflow(r"but tags cannot be qualified! "), alloc.reflow(r"Maybe you wanted a qualified name, something like "), @@ -1030,13 +1070,13 @@ fn to_bad_ident_pattern_report<'b>( Underscore(pos) => { let region = Region::from_pos(pos.sub(1)); - alloc.stack(vec![ + alloc.stack([ alloc.reflow("I am trying to parse an identifier here:"), alloc.region_with_subregion( lines.convert_region(surroundings), lines.convert_region(region), ), - alloc.concat(vec![alloc.reflow( + alloc.concat([alloc.reflow( r"Underscores are not allowed in identifiers. Use camelCase instead!", )]), ]) @@ -1123,7 +1163,7 @@ fn report_shadowing<'b>( ShadowKind::Ability => "abilities", }; - alloc.stack(vec![ + alloc.stack([ alloc .text("The ") .append(alloc.ident(shadow.value)) @@ -1131,7 +1171,7 @@ fn report_shadowing<'b>( alloc.region(lines.convert_region(original_region)), alloc.reflow("But then it's defined a second time here:"), alloc.region(lines.convert_region(shadow.region)), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("Since these "), alloc.reflow(what), alloc.reflow(" have the same name, it's easy to use the wrong one on accident. Give one of them a new name."), @@ -1213,13 +1253,13 @@ fn pretty_runtime_error<'b>( EmptySingleQuote | MultipleCharsInSingleQuote | Unknown | BadIdent(_) => { alloc.nil() } - QualifiedIdentifier => alloc.tip().append( - alloc.reflow("In patterns, only private and global tags can be qualified"), - ), + QualifiedIdentifier => alloc + .tip() + .append(alloc.reflow("In patterns, only tags can be qualified")), }; - doc = alloc.stack(vec![ - alloc.concat(vec![ + doc = alloc.stack([ + alloc.concat([ alloc.reflow("This"), alloc.text(name), alloc.reflow("pattern is malformed:"), @@ -1243,7 +1283,7 @@ fn pretty_runtime_error<'b>( suggestions.truncate(4); let did_you_mean = if suggestions.is_empty() { - alloc.concat(vec![ + alloc.concat([ alloc.reflow("In fact, it looks like "), alloc.module_name(module_name.clone()), alloc.reflow(" doesn't expose any values!"), @@ -1252,13 +1292,13 @@ fn pretty_runtime_error<'b>( let qualified_suggestions = suggestions .into_iter() .map(|v| alloc.string(module_name.to_string() + "." + v.as_str())); - alloc.stack(vec![ + alloc.stack([ alloc.reflow("Did you mean one of these?"), alloc.vcat(qualified_suggestions).indent(4), ]) }; - doc = alloc.stack(vec![ - alloc.concat(vec![ + doc = alloc.stack([ + alloc.concat([ alloc.reflow("The "), alloc.module_name(module_name), alloc.reflow(" module does not expose `"), @@ -1299,10 +1339,10 @@ fn pretty_runtime_error<'b>( title = SYNTAX_PROBLEM; } RuntimeError::MalformedTypeName(_box_str, surroundings) => { - doc = alloc.stack(vec![ + doc = alloc.stack([ alloc.reflow(r"I am confused by this type name:"), alloc.region(lines.convert_region(surroundings)), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("Type names start with an uppercase letter, "), alloc.reflow("and can optionally be qualified by a module name, like "), alloc.parser_suggestion("Bool"), @@ -1329,14 +1369,14 @@ fn pretty_runtime_error<'b>( "small" }; - doc = alloc.stack(vec![ - alloc.concat(vec![ + doc = alloc.stack([ + alloc.concat([ alloc.reflow("This float literal is too "), alloc.text(big_or_small), alloc.reflow(":"), ]), alloc.region(lines.convert_region(region)), - alloc.concat(vec![ + alloc.concat([ alloc .reflow("Roc uses signed 64-bit floating points, allowing values between "), alloc.text(format!("{:e}", f64::MIN)), @@ -1353,12 +1393,12 @@ fn pretty_runtime_error<'b>( .tip() .append(alloc.reflow("Learn more about number literals at TODO")); - doc = alloc.stack(vec![ - alloc.concat(vec![ + doc = alloc.stack([ + alloc.concat([ alloc.reflow("This float literal contains an invalid digit:"), ]), alloc.region(lines.convert_region(region)), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("Floating point literals can only contain the digits 0-9, or use scientific notation 10e4, or have a float suffix."), ]), tip, @@ -1367,10 +1407,10 @@ fn pretty_runtime_error<'b>( title = SYNTAX_PROBLEM; } RuntimeError::InvalidFloat(FloatErrorKind::IntSuffix, region, _raw_str) => { - doc = alloc.stack(vec![ - alloc.concat(vec![alloc.reflow( - "This number literal is a float, but it has an integer suffix:", - )]), + doc = alloc.stack([ + alloc + .concat([alloc + .reflow("This number literal is a float, but it has an integer suffix:")]), alloc.region(lines.convert_region(region)), ]); @@ -1417,8 +1457,8 @@ fn pretty_runtime_error<'b>( .tip() .append(alloc.reflow("Learn more about number literals at TODO")); - doc = alloc.stack(vec![ - alloc.concat(vec![ + doc = alloc.stack([ + alloc.concat([ alloc.reflow("This "), alloc.text(name), alloc.reflow(" literal contains "), @@ -1426,7 +1466,7 @@ fn pretty_runtime_error<'b>( alloc.text(":"), ]), alloc.region(lines.convert_region(region)), - alloc.concat(vec![ + alloc.concat([ alloc.text(plurals), contains, alloc.text(charset), @@ -1442,7 +1482,7 @@ fn pretty_runtime_error<'b>( let (big_or_small, info) = if let IntErrorKind::Underflow = error_kind { ( "small", - alloc.concat(vec![ + alloc.concat([ alloc.reflow( "The smallest number representable in Roc is the minimum I128 value, ", ), @@ -1453,7 +1493,7 @@ fn pretty_runtime_error<'b>( } else { ( "big", - alloc.concat(vec![ + alloc.concat([ alloc.reflow( "The largest number representable in Roc is the maximum U128 value, ", ), @@ -1467,8 +1507,8 @@ fn pretty_runtime_error<'b>( .tip() .append(alloc.reflow("Learn more about number literals at TODO")); - doc = alloc.stack(vec![ - alloc.concat(vec![ + doc = alloc.stack([ + alloc.concat([ alloc.reflow("This integer literal is too "), alloc.text(big_or_small), alloc.reflow(":"), @@ -1481,10 +1521,10 @@ fn pretty_runtime_error<'b>( title = SYNTAX_PROBLEM; } RuntimeError::InvalidInt(IntErrorKind::FloatSuffix, _base, region, _raw_str) => { - doc = alloc.stack(vec![ - alloc.concat(vec![alloc.reflow( - "This number literal is an integer, but it has a float suffix:", - )]), + doc = alloc.stack([ + alloc + .concat([alloc + .reflow("This number literal is an integer, but it has a float suffix:")]), alloc.region(lines.convert_region(region)), ]); @@ -1499,12 +1539,11 @@ fn pretty_runtime_error<'b>( region, _raw_str, ) => { - doc = alloc.stack(vec![ - alloc.concat(vec![alloc.reflow( - "This integer literal overflows the type indicated by its suffix:", - )]), + doc = alloc.stack([ + alloc.concat([alloc + .reflow("This integer literal overflows the type indicated by its suffix:")]), alloc.region(lines.convert_region(region)), - alloc.tip().append(alloc.concat(vec![ + alloc.tip().append(alloc.concat([ alloc.reflow("The suffix indicates this integer is a "), alloc.type_str(suffix_type), alloc.reflow(", whose maximum value is "), @@ -1524,12 +1563,11 @@ fn pretty_runtime_error<'b>( region, _raw_str, ) => { - doc = alloc.stack(vec![ - alloc.concat(vec![alloc.reflow( - "This integer literal underflows the type indicated by its suffix:", - )]), + doc = alloc.stack([ + alloc.concat([alloc + .reflow("This integer literal underflows the type indicated by its suffix:")]), alloc.region(lines.convert_region(region)), - alloc.tip().append(alloc.concat(vec![ + alloc.tip().append(alloc.concat([ alloc.reflow("The suffix indicates this integer is a "), alloc.type_str(suffix_type), alloc.reflow(", whose minimum value is "), @@ -1556,8 +1594,8 @@ fn pretty_runtime_error<'b>( title = SYNTAX_PROBLEM; } RuntimeError::InvalidRecordUpdate { region } => { - doc = alloc.stack(vec![ - alloc.concat(vec![ + doc = alloc.stack([ + alloc.concat([ alloc.reflow("This expression cannot be updated"), alloc.reflow(":"), ]), @@ -1592,13 +1630,11 @@ fn pretty_runtime_error<'b>( unreachable!("not currently reported (but can blow up at runtime)") } RuntimeError::ExposedButNotDefined(symbol) => { - doc = alloc.stack(vec![alloc + doc = alloc.stack([alloc .symbol_unqualified(symbol) .append(alloc.reflow(" was listed as exposed in ")) .append(alloc.module(symbol.module_id())) - .append( - alloc.reflow(", but it was not defined anywhere in that module."), - )]); + .append(alloc.reflow(", but it was not defined anywhere in that module."))]); title = MISSING_DEFINITION; } @@ -1607,8 +1643,8 @@ fn pretty_runtime_error<'b>( .tip() .append(alloc.reflow("Learn more about character literals at TODO")); - doc = alloc.stack(vec![ - alloc.concat(vec![alloc.reflow("This character literal is empty.")]), + doc = alloc.stack([ + alloc.concat([alloc.reflow("This character literal is empty.")]), alloc.region(lines.convert_region(region)), tip, ]); @@ -1620,14 +1656,12 @@ fn pretty_runtime_error<'b>( .tip() .append(alloc.reflow("Learn more about character literals at TODO")); - doc = alloc.stack(vec![ - alloc.concat(vec![ + doc = alloc.stack([ + alloc.concat([ alloc.reflow("This character literal contains more than one code point.") ]), alloc.region(lines.convert_region(region)), - alloc.concat(vec![ - alloc.reflow("Character literals can only contain one code point.") - ]), + alloc.concat([alloc.reflow("Character literals can only contain one code point.")]), tip, ]); @@ -1653,7 +1687,7 @@ fn pretty_runtime_error<'b>( } else { let qualified_suggestions = suggestions.into_iter().map(|v| alloc.string(v.to_string())); - alloc.stack(vec![ + alloc.stack([ alloc .tip() .append(alloc.reflow("Did you mean one of these opaque types?")), @@ -1662,7 +1696,7 @@ fn pretty_runtime_error<'b>( }; let mut stack = vec![ - alloc.concat(vec![ + alloc.concat([ alloc.reflow("The opaque type "), alloc.type_str(opaque.as_inline_str().as_str()), alloc.reflow(" referenced here is not defined:"), @@ -1671,7 +1705,7 @@ fn pretty_runtime_error<'b>( ]; if let Some(defined_alias_region) = opt_defined_alias { - stack.push(alloc.stack(vec![ + stack.push(alloc.stack([ alloc.note("There is an alias of the same name:"), alloc.region(lines.convert_region(defined_alias_region)), ])); @@ -1688,8 +1722,8 @@ fn pretty_runtime_error<'b>( referenced_region, imported_region, } => { - doc = alloc.stack(vec![ - alloc.concat(vec![ + doc = alloc.stack([ + alloc.concat([ alloc.reflow("The unwrapped opaque type "), alloc.type_str(opaque.as_inline_str().as_str()), alloc.reflow(" referenced here:"), @@ -1705,7 +1739,7 @@ fn pretty_runtime_error<'b>( title = OPAQUE_DECLARED_OUTSIDE_SCOPE; } RuntimeError::OpaqueNotApplied(loc_ident) => { - doc = alloc.stack(vec![ + doc = alloc.stack([ alloc.reflow("This opaque type is not applied to an argument:"), alloc.region(lines.convert_region(loc_ident.region)), alloc.note("Opaque types always wrap exactly one argument!"), @@ -1714,7 +1748,7 @@ fn pretty_runtime_error<'b>( title = OPAQUE_NOT_APPLIED; } RuntimeError::OpaqueAppliedToMultipleArgs(region) => { - doc = alloc.stack(vec![ + doc = alloc.stack([ alloc.reflow("This opaque type is applied to multiple arguments:"), alloc.region(lines.convert_region(region)), alloc.note("Opaque types always wrap exactly one argument!"), @@ -1743,7 +1777,7 @@ fn to_circular_def_doc<'b>( " value is defined directly in terms of itself, causing an infinite loop.", )), [first, others @ ..] => { - alloc.stack(vec![ + alloc.stack([ alloc .reflow("The ") .append(alloc.symbol_unqualified(first.symbol)) @@ -1784,7 +1818,7 @@ fn not_found<'b>( ); suggestions.truncate(4); - let default_no = alloc.concat(vec![ + let default_no = alloc.concat([ alloc.reflow("Is there an "), alloc.keyword("import"), alloc.reflow(" or "), @@ -1798,7 +1832,7 @@ fn not_found<'b>( if suggestions.is_empty() { no_suggestion_details } else { - alloc.stack(vec![ + alloc.stack([ yes_suggestion_details, alloc .vcat(suggestions.into_iter().map(|v| alloc.string(v.to_string()))) @@ -1807,8 +1841,8 @@ fn not_found<'b>( } }; - alloc.stack(vec![ - alloc.concat(vec![ + alloc.stack([ + alloc.concat([ alloc.reflow("I cannot find a `"), alloc.string(name.to_string()), alloc.reflow("` "), @@ -1842,7 +1876,7 @@ fn module_not_found<'b>( if suggestions.is_empty() { // We don't have any recommended spelling corrections - alloc.concat(vec![ + alloc.concat([ alloc.reflow("Is there an "), alloc.keyword("import"), alloc.reflow(" or "), @@ -1850,7 +1884,7 @@ fn module_not_found<'b>( alloc.reflow(" missing up-top"), ]) } else { - alloc.stack(vec![ + alloc.stack([ alloc.reflow("Is there an import missing? Perhaps there is a typo. Did you mean one of these?"), alloc .vcat(suggestions.into_iter().map(|v| alloc.string(v.to_string()))) @@ -1859,8 +1893,8 @@ fn module_not_found<'b>( } }; - alloc.stack(vec![ - alloc.concat(vec![ + alloc.stack([ + alloc.concat([ alloc.reflow("The `"), alloc.string(name.to_string()), alloc.reflow("` module is not imported:"), diff --git a/reporting/src/error/mod.rs b/reporting/src/error/mod.rs index 2bf7e77288..88379cf382 100644 --- a/reporting/src/error/mod.rs +++ b/reporting/src/error/mod.rs @@ -1,4 +1,3 @@ pub mod canonicalize; -pub mod mono; pub mod parse; pub mod r#type; diff --git a/reporting/src/error/mono.rs b/reporting/src/error/mono.rs deleted file mode 100644 index 270f037a7d..0000000000 --- a/reporting/src/error/mono.rs +++ /dev/null @@ -1,237 +0,0 @@ -use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity}; -use roc_module::ident::TagName; -use roc_region::all::LineInfo; -use std::path::PathBuf; -use ven_pretty::DocAllocator; - -pub fn mono_problem<'b>( - alloc: &'b RocDocAllocator<'b>, - lines: &LineInfo, - filename: PathBuf, - problem: roc_mono::ir::MonoProblem, -) -> Report<'b> { - use roc_exhaustive::Context::*; - use roc_exhaustive::Error::*; - use roc_mono::ir::MonoProblem::*; - - match problem { - PatternProblem(Incomplete(region, context, missing)) => match context { - BadArg => { - let doc = alloc.stack(vec![ - alloc.reflow("This pattern does not cover all the possibilities:"), - alloc.region(lines.convert_region(region)), - alloc.reflow("Other possibilities include:"), - unhandled_patterns_to_doc_block(alloc, missing), - alloc.concat(vec![ - alloc.reflow( - "I would have to crash if I saw one of those! \ - So rather than pattern matching in function arguments, put a ", - ), - alloc.keyword("when"), - alloc.reflow(" in the function body to account for all possibilities."), - ]), - ]); - - Report { - filename, - title: "UNSAFE PATTERN".to_string(), - doc, - severity: Severity::RuntimeError, - } - } - BadDestruct => { - let doc = alloc.stack(vec![ - alloc.reflow("This pattern does not cover all the possibilities:"), - alloc.region(lines.convert_region(region)), - alloc.reflow("Other possibilities include:"), - unhandled_patterns_to_doc_block(alloc, missing), - alloc.concat(vec![ - alloc.reflow( - "I would have to crash if I saw one of those! \ - You can use a binding to deconstruct a value if there is only ONE possibility. \ - Use a " - ), - alloc.keyword("when"), - alloc.reflow(" to account for all possibilities."), - ]), - ]); - - Report { - filename, - title: "UNSAFE PATTERN".to_string(), - doc, - severity: Severity::RuntimeError, - } - } - BadCase => { - let doc = alloc.stack(vec![ - alloc.concat(vec![ - alloc.reflow("This "), - alloc.keyword("when"), - alloc.reflow(" does not cover all the possibilities:"), - ]), - alloc.region(lines.convert_region(region)), - alloc.reflow("Other possibilities include:"), - unhandled_patterns_to_doc_block(alloc, missing), - alloc.reflow( - "I would have to crash if I saw one of those! \ - Add branches for them!", - ), - // alloc.hint().append(alloc.reflow("or use a hole.")), - ]); - - Report { - filename, - title: "UNSAFE PATTERN".to_string(), - doc, - severity: Severity::RuntimeError, - } - } - }, - PatternProblem(Redundant { - overall_region, - branch_region, - index, - }) => { - let doc = alloc.stack(vec![ - alloc.concat(vec![ - alloc.reflow("The "), - alloc.string(index.ordinal()), - alloc.reflow(" pattern is redundant:"), - ]), - alloc.region_with_subregion( - lines.convert_region(overall_region), - lines.convert_region(branch_region), - ), - alloc.reflow( - "Any value of this shape will be handled by \ - a previous pattern, so this one should be removed.", - ), - ]); - - Report { - filename, - title: "REDUNDANT PATTERN".to_string(), - doc, - severity: Severity::Warning, - } - } - } -} - -pub fn unhandled_patterns_to_doc_block<'b>( - alloc: &'b RocDocAllocator<'b>, - patterns: Vec, -) -> RocDocBuilder<'b> { - alloc - .vcat(patterns.into_iter().map(|v| pattern_to_doc(alloc, v))) - .indent(4) - .annotate(Annotation::TypeBlock) -} - -fn pattern_to_doc<'b>( - alloc: &'b RocDocAllocator<'b>, - pattern: roc_exhaustive::Pattern, -) -> RocDocBuilder<'b> { - pattern_to_doc_help(alloc, pattern, false) -} - -const AFTER_TAG_INDENT: &str = " "; - -fn pattern_to_doc_help<'b>( - alloc: &'b RocDocAllocator<'b>, - pattern: roc_exhaustive::Pattern, - in_type_param: bool, -) -> RocDocBuilder<'b> { - use roc_exhaustive::Literal::*; - use roc_exhaustive::Pattern::*; - use roc_exhaustive::RenderAs; - - match pattern { - Anything => alloc.text("_"), - Literal(l) => match l { - Int(i) => alloc.text(i.to_string()), - U128(i) => alloc.text(i.to_string()), - Bit(true) => alloc.text("True"), - Bit(false) => alloc.text("False"), - Byte(b) => alloc.text(b.to_string()), - Float(f) => alloc.text(f.to_string()), - Decimal(d) => alloc.text(d.to_string()), - Str(s) => alloc.string(s.into()), - }, - Ctor(union, tag_id, args) => { - match union.render_as { - RenderAs::Guard => { - // #Guard - debug_assert_eq!( - union.alternatives[tag_id.0 as usize].name, - TagName::Global("#Guard".into()) - ); - debug_assert!(args.len() == 2); - let tag = pattern_to_doc_help(alloc, args[1].clone(), in_type_param); - alloc.concat(vec![ - tag, - alloc.text(AFTER_TAG_INDENT), - alloc.text("(note the lack of an "), - alloc.keyword("if"), - alloc.text(" clause)"), - ]) - } - RenderAs::Record(field_names) => { - let mut arg_docs = Vec::with_capacity(args.len()); - - for (label, v) in field_names.into_iter().zip(args.into_iter()) { - match &v { - Anything => { - arg_docs.push(alloc.text(label.to_string())); - } - Literal(_) | Ctor(_, _, _) => { - arg_docs.push( - alloc - .text(label.to_string()) - .append(alloc.reflow(": ")) - .append(pattern_to_doc_help(alloc, v, false)), - ); - } - } - } - - alloc - .text("{ ") - .append(alloc.intersperse(arg_docs, alloc.reflow(", "))) - .append(" }") - } - RenderAs::Tag | RenderAs::Opaque => { - let has_args = !args.is_empty(); - let arg_docs = args - .into_iter() - .map(|v| pattern_to_doc_help(alloc, v, true)); - - let tag = &union.alternatives[tag_id.0 as usize]; - let tag_name = match union.render_as { - RenderAs::Tag => alloc.tag_name(tag.name.clone()), - RenderAs::Opaque => match tag.name { - TagName::Private(opaque) => alloc.wrapped_opaque_name(opaque), - _ => unreachable!(), - }, - _ => unreachable!(), - }; - - // We assume the alternatives are sorted. If not, this assert will trigger - debug_assert!(tag_id == tag.tag_id); - - let docs = std::iter::once(tag_name).chain(arg_docs); - - if in_type_param && has_args { - alloc - .text("(") - .append(alloc.intersperse(docs, alloc.space())) - .append(")") - } else { - alloc.intersperse(docs, alloc.space()) - } - } - } - } - } -} diff --git a/reporting/src/error/parse.rs b/reporting/src/error/parse.rs index 83b878a6c0..12e0ea1d9c 100644 --- a/reporting/src/error/parse.rs +++ b/reporting/src/error/parse.rs @@ -28,7 +28,7 @@ fn note_for_tag_union_type_indent<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocB } fn hint_for_tag_name<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuilder<'a> { - alloc.concat(vec![ + alloc.concat([ alloc.hint("Tag names "), alloc.reflow("start with an uppercase letter, like "), alloc.parser_suggestion("Err"), @@ -38,19 +38,8 @@ fn hint_for_tag_name<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuilder<'a> { ]) } -fn hint_for_private_tag_name<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuilder<'a> { - alloc.concat(vec![ - alloc.hint("Private tag names "), - alloc.reflow("start with an `@` symbol followed by an uppercase letter, like "), - alloc.parser_suggestion("@UID"), - alloc.text(" or "), - alloc.parser_suggestion("@SecretKey"), - alloc.text("."), - ]) -} - fn record_patterns_look_like<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuilder<'a> { - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"Record pattern look like "), alloc.parser_suggestion("{ name, age: currentAge },"), alloc.reflow(" so I was expecting to see a field name next."), @@ -74,7 +63,7 @@ fn to_syntax_report<'a>( match parse_problem { SyntaxError::ArgumentsBeforeEquals(region) => { - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow("Unexpected tokens in front of the `=` symbol:"), alloc.region(lines.convert_region(*region)), ]); @@ -92,8 +81,8 @@ fn to_syntax_report<'a>( region = LineColumnRegion::new(region.start(), region.end().bump_column(1)); } - let doc = alloc.stack(vec![ - alloc.concat(vec![ + let doc = alloc.stack([ + alloc.concat([ alloc.reflow("Unexpected token "), // context(alloc, &parse_problem.context_stack, "here"), alloc.text(":"), @@ -106,7 +95,7 @@ fn to_syntax_report<'a>( NotEndOfFile(pos) => { let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I expected to reach the end of the file, but got stuck here:"), alloc.region(region), ]); @@ -119,7 +108,7 @@ fn to_syntax_report<'a>( } } SyntaxError::Eof(region) => { - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow("End of Field"), alloc.region(lines.convert_region(*region)), ]); @@ -132,7 +121,7 @@ fn to_syntax_report<'a>( } } SyntaxError::OutdentedTooFar => { - let doc = alloc.stack(vec![alloc.reflow("OutdentedTooFar")]); + let doc = alloc.stack([alloc.reflow("OutdentedTooFar")]); Report { filename, @@ -202,10 +191,10 @@ fn to_expr_report<'a>( let surroundings = Region::new(start, *pos); let region = lines.convert_region(*region); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am partway through parsing a definition, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("Looks like you are trying to define a function. "), alloc.reflow("In roc, functions are always written as a lambda, like "), alloc.parser_suggestion("increment = \\n -> n + 1"), @@ -240,8 +229,8 @@ fn to_expr_report<'a>( alloc.parser_suggestion("Str.concat"), alloc.reflow(" instead."), ], - ":" => vec![alloc.stack(vec![ - alloc.concat(vec![ + ":" => vec![alloc.stack([ + alloc.concat([ alloc.reflow("The has-type operator "), alloc.parser_suggestion(":"), alloc.reflow(" can only occur in a definition's type signature, like"), @@ -258,8 +247,8 @@ fn to_expr_report<'a>( return to_unexpected_arrow_report(alloc, lines, filename, *pos, start); } _ => { - vec![alloc.stack(vec![ - alloc.concat(vec![ + vec![alloc.stack([ + alloc.concat([ alloc.reflow("The arrow "), alloc.parser_suggestion("->"), alloc.reflow(" is only used to define cases in a "), @@ -291,7 +280,7 @@ fn to_expr_report<'a>( ], }; - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"This looks like an operator, but it's not one I recognize!"), alloc.region_with_subregion( lines.convert_region(surroundings), @@ -314,10 +303,10 @@ fn to_expr_report<'a>( let surroundings = Region::new(start, *pos); let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am very confused by this identifier:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("Are you trying to qualify a name? I am execting something like "), alloc.parser_suggestion("Json.Decode.string"), alloc.reflow(". Maybe you are trying to qualify a tag? Tags like "), @@ -338,7 +327,7 @@ fn to_expr_report<'a>( let (title, expecting) = match &context { Context::InNode { .. } | Context::InDef { .. } => ( "MISSING EXPRESSION", - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I was expecting to see an expression like "), alloc.parser_suggestion("42"), alloc.reflow(" or "), @@ -348,8 +337,8 @@ fn to_expr_report<'a>( ), Context::InDefFinalExpr { .. } => ( "MISSING FINAL EXPRESSION", - alloc.stack(vec![ - alloc.concat(vec![ + alloc.stack([ + alloc.concat([ alloc.reflow("This definition is missing a final expression."), alloc.reflow(" A nested definition must be followed by"), alloc.reflow(" either another definition, or an expression"), @@ -368,7 +357,7 @@ fn to_expr_report<'a>( Context::InNode(node, pos, _) => match node { Node::WhenCondition | Node::WhenBranch | Node::WhenIfGuard => ( pos, - alloc.concat(vec![ + alloc.concat([ alloc.text("an "), alloc.keyword("when"), alloc.text(" expression"), @@ -376,7 +365,7 @@ fn to_expr_report<'a>( ), Node::IfCondition | Node::IfThenBranch | Node::IfElseBranch => ( pos, - alloc.concat(vec![ + alloc.concat([ alloc.text("an "), alloc.keyword("if"), alloc.text(" expression"), @@ -396,8 +385,8 @@ fn to_expr_report<'a>( let surroundings = Region::new(context_pos, *pos); let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); - let doc = alloc.stack(vec![ - alloc.concat(vec![ + let doc = alloc.stack([ + alloc.concat([ alloc.reflow(r"I am partway through parsing "), a_thing, alloc.reflow(", but I got stuck here:"), @@ -418,10 +407,10 @@ fn to_expr_report<'a>( let surroundings = Region::new(start, *pos); let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am partway through parsing a definition, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("This definition is missing a final expression."), alloc.reflow(" A nested definition must be followed by"), alloc.reflow(" either another definition, or an expression"), @@ -455,10 +444,10 @@ fn to_expr_report<'a>( let surroundings = Region::new(start, *pos); let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("Whatever I am running into is confusing me a lot! "), alloc.reflow("Normally I can give fairly specific hints, "), alloc.reflow("but something is really tripping me up this time."), @@ -477,10 +466,10 @@ fn to_expr_report<'a>( let surroundings = Region::new(start, *pos); let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am partway through parsing a definition, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("Looks like you are trying to define a function. "), alloc.reflow("In roc, functions are always written as a lambda, like "), alloc.parser_suggestion("increment = \\n -> n + 1"), @@ -500,12 +489,10 @@ fn to_expr_report<'a>( let surroundings = Region::new(start, *pos); let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am partway through parsing an expression, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ - alloc.reflow("Looks like you are trying to define a function. ") - ]), + alloc.concat([alloc.reflow("Looks like you are trying to define a function. ")]), ]); Report { @@ -520,10 +507,10 @@ fn to_expr_report<'a>( let surroundings = Region::new(start, *pos); let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am partway through parsing an record, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![alloc.reflow("TODO provide more context.")]), + alloc.concat([alloc.reflow("TODO provide more context.")]), ]); Report { @@ -562,11 +549,11 @@ fn to_lambda_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc .reflow(r"I am partway through parsing a function argument list, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I was expecting a "), alloc.parser_suggestion("->"), alloc.reflow(" next."), @@ -584,11 +571,11 @@ fn to_lambda_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc .reflow(r"I am partway through parsing a function argument list, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I was expecting a "), alloc.parser_suggestion("->"), alloc.reflow(" next."), @@ -609,11 +596,11 @@ fn to_lambda_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc .reflow(r"I am partway through parsing a function argument list, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I was expecting a "), alloc.parser_suggestion("->"), alloc.reflow(" next."), @@ -631,11 +618,11 @@ fn to_lambda_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc .reflow(r"I am partway through parsing a function argument list, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I was expecting a "), alloc.parser_suggestion("->"), alloc.reflow(" next."), @@ -656,11 +643,11 @@ fn to_lambda_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc .reflow(r"I am partway through parsing a function argument list, but I got stuck at this comma:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I was expecting an argument pattern before this, "), alloc.reflow("so try adding an argument before the comma and see if that helps?"), ]), @@ -677,11 +664,11 @@ fn to_lambda_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc .reflow(r"I am partway through parsing a function argument list, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I was expecting an argument pattern before this, "), alloc.reflow("so try adding an argument and see if that helps?"), ]), @@ -712,7 +699,7 @@ fn to_lambda_report<'a>( filename, pos, start, - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"I just saw a pattern, so I was expecting to see a "), alloc.parser_suggestion("->"), alloc.reflow(" next."), @@ -725,7 +712,7 @@ fn to_lambda_report<'a>( filename, pos, start, - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"I just saw a pattern, so I was expecting to see a "), alloc.parser_suggestion("->"), alloc.reflow(" next."), @@ -738,7 +725,7 @@ fn to_lambda_report<'a>( filename, pos, start, - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"I just saw a pattern, so I was expecting to see a "), alloc.parser_suggestion("->"), alloc.reflow(" next."), @@ -759,8 +746,8 @@ fn to_unfinished_lambda_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ - alloc.concat(vec![ + let doc = alloc.stack([ + alloc.concat([ alloc.reflow(r"I was partway through parsing a "), alloc.reflow(r" function, but I got stuck here:"), ]), @@ -808,8 +795,8 @@ fn to_str_report<'a>( .append(alloc.parser_suggestion(sugg)) }; - let doc = alloc.stack(vec![ - alloc.concat(vec![ + let doc = alloc.stack([ + alloc.concat([ alloc.reflow(r"I was partway through parsing a "), alloc.reflow(r" string literal, but I got stuck here:"), ]), @@ -817,7 +804,7 @@ fn to_str_report<'a>( lines.convert_region(surroundings), lines.convert_region(region), ), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"This is not an escape sequence I recognize."), alloc.reflow(r" After a backslash, I am looking for one of these:"), ]), @@ -845,12 +832,12 @@ fn to_str_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow( r"I am partway through parsing a unicode code point, but I got stuck here:", ), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"I was expecting a hexadecimal number, like "), alloc.parser_suggestion("\\u(1100)"), alloc.reflow(" or "), @@ -871,10 +858,10 @@ fn to_str_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I cannot find the end of this format expression:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"You could change it to something like "), alloc.parser_suggestion("\"The count is \\(count\\)\""), alloc.reflow("."), @@ -892,10 +879,10 @@ fn to_str_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I cannot find the end of this string:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"You could change it to something like "), alloc.parser_suggestion("\"to be or not to be\""), alloc.reflow(" or even just "), @@ -915,10 +902,10 @@ fn to_str_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I cannot find the end of this block string:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"You could change it to something like "), alloc.parser_suggestion("\"\"\"to be or not to be\"\"\""), alloc.reflow(" or even just "), @@ -960,11 +947,11 @@ fn to_expr_in_parens_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc .reflow("I am partway through parsing a record pattern, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow( r"I was expecting to see a closing parenthesis next, so try adding a ", ), @@ -984,12 +971,12 @@ fn to_expr_in_parens_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow( r"I just started parsing an expression in parentheses, but I got stuck here:", ), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"An expression in parentheses looks like "), alloc.parser_suggestion("(32)"), alloc.reflow(r" or "), @@ -1036,12 +1023,12 @@ fn to_list_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow( r"I am partway through started parsing a list, but I got stuck here:", ), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc .reflow(r"I was expecting to see a list entry before this comma, "), alloc.reflow(r"so try adding a list entry"), @@ -1059,12 +1046,12 @@ fn to_list_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow( r"I am partway through started parsing a list, but I got stuck here:", ), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow( r"I was expecting to see a closing square bracket before this, ", ), @@ -1072,7 +1059,7 @@ fn to_list_report<'a>( alloc.parser_suggestion("]"), alloc.reflow(r" and see if that helps?"), ]), - alloc.concat(vec![ + alloc.concat([ alloc.note("When "), alloc.reflow(r"I get stuck like this, "), alloc.reflow(r"it usually means that there is a missing parenthesis "), @@ -1095,10 +1082,10 @@ fn to_list_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I cannot find the end of this list:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"You could change it to something like "), alloc.parser_suggestion("[ 1, 2, 3 ]"), alloc.reflow(" or even just "), @@ -1169,7 +1156,7 @@ fn to_if_report<'a>( filename, pos, start, - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"I was expecting to see the "), alloc.keyword("then"), alloc.reflow(r" keyword next."), @@ -1184,7 +1171,7 @@ fn to_if_report<'a>( filename, pos, start, - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"I was expecting to see the "), alloc.keyword("else"), alloc.reflow(r" keyword next."), @@ -1198,9 +1185,7 @@ fn to_if_report<'a>( filename, pos, start, - alloc.concat(vec![ - alloc.reflow(r"I was expecting to see a expression next") - ]), + alloc.concat([alloc.reflow(r"I was expecting to see a expression next")]), ), } } @@ -1216,8 +1201,8 @@ fn to_unfinished_if_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ - alloc.concat(vec![ + let doc = alloc.stack([ + alloc.concat([ alloc.reflow(r"I was partway through parsing an "), alloc.keyword("if"), alloc.reflow(r" expression, but I got stuck here:"), @@ -1251,14 +1236,12 @@ fn to_when_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow( r"I just started parsing an if guard, but there is no guard condition:", ), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ - alloc.reflow("Try adding an expression before the arrow!") - ]), + alloc.concat([alloc.reflow("Try adding an expression before the arrow!")]), ]); Report { @@ -1282,14 +1265,14 @@ fn to_when_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ - alloc.concat(vec![ + let doc = alloc.stack([ + alloc.concat([ alloc.reflow(r"I am partway through parsing a "), alloc.keyword("when"), alloc.reflow(r" expression, but got stuck here:"), ]), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![alloc.reflow("I was expecting to see an arrow next.")]), + alloc.concat([alloc.reflow("I was expecting to see an arrow next.")]), note_for_when_indent_error(alloc), ]); @@ -1327,7 +1310,7 @@ fn to_when_report<'a>( filename, pos, start, - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"I just saw a "), alloc.parser_suggestion(r"|"), alloc.reflow(r" so I was expecting to see a pattern next."), @@ -1343,7 +1326,7 @@ fn to_when_report<'a>( filename, pos, start, - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"I was expecting to see the "), alloc.keyword("is"), alloc.reflow(r" keyword next."), @@ -1356,9 +1339,7 @@ fn to_when_report<'a>( filename, pos, start, - alloc.concat(vec![ - alloc.reflow(r"I was expecting to see a expression next") - ]), + alloc.concat([alloc.reflow(r"I was expecting to see a expression next")]), ), EWhen::IndentPattern(pos) => to_unfinished_when_report( @@ -1367,7 +1348,7 @@ fn to_when_report<'a>( filename, pos, start, - alloc.concat(vec![alloc.reflow(r"I was expecting to see a pattern next")]), + alloc.concat([alloc.reflow(r"I was expecting to see a pattern next")]), ), EWhen::IndentArrow(pos) => to_unfinished_when_report( @@ -1376,7 +1357,7 @@ fn to_when_report<'a>( filename, pos, start, - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"I just saw a pattern, so I was expecting to see a "), alloc.parser_suggestion("->"), alloc.reflow(" next."), @@ -1389,7 +1370,7 @@ fn to_when_report<'a>( filename, pos, start, - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"I just saw the "), alloc.keyword("if"), alloc.reflow(" keyword, so I was expecting to see an expression next."), @@ -1402,7 +1383,7 @@ fn to_when_report<'a>( filename, pos, start, - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"I was expecting to see an expression next. "), alloc.reflow("What should I do when I run into this particular pattern?"), ]), @@ -1414,7 +1395,7 @@ fn to_when_report<'a>( filename, pos, start, - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"I suspect this is a pattern that is not indented enough? (by "), alloc.text(indent.to_string()), alloc.reflow(" spaces)"), @@ -1439,8 +1420,8 @@ fn to_unfinished_when_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ - alloc.concat(vec![ + let doc = alloc.stack([ + alloc.concat([ alloc.reflow(r"I was partway through parsing a "), alloc.keyword("when"), alloc.reflow(r" expression, but I got stuck here:"), @@ -1470,8 +1451,8 @@ fn to_unexpected_arrow_report<'a>( let surroundings = Region::new(start, pos); let region = Region::new(pos, pos.bump_column(2)); - let doc = alloc.stack(vec![ - alloc.concat(vec![ + let doc = alloc.stack([ + alloc.concat([ alloc.reflow(r"I am parsing a "), alloc.keyword("when"), alloc.reflow(r" expression right now, but this arrow is confusing me:"), @@ -1480,7 +1461,7 @@ fn to_unexpected_arrow_report<'a>( lines.convert_region(surroundings), lines.convert_region(region), ), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"It makes sense to see arrows around here, "), alloc.reflow(r"so I suspect it is something earlier."), alloc.reflow( @@ -1499,8 +1480,8 @@ fn to_unexpected_arrow_report<'a>( } fn note_for_when_error<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuilder<'a> { - alloc.stack(vec![ - alloc.concat(vec![ + alloc.stack([ + alloc.concat([ alloc.note("Here is an example of a valid "), alloc.keyword("when"), alloc.reflow(r" expression for reference."), @@ -1513,7 +1494,7 @@ fn note_for_when_error<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuilder<'a> alloc.text("Err _ ->").indent(6), alloc.text("200").indent(8), ]), - alloc.concat(vec![ + alloc.concat([ alloc.reflow( "Notice the indentation. All patterns are aligned, and each branch is indented", ), @@ -1523,8 +1504,8 @@ fn note_for_when_error<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuilder<'a> } fn note_for_when_indent_error<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuilder<'a> { - alloc.stack(vec![ - alloc.concat(vec![ + alloc.stack([ + alloc.concat([ alloc.note("Sometimes I get confused by indentation, so try to make your "), alloc.keyword("when"), alloc.reflow(r" look something like this:"), @@ -1537,7 +1518,7 @@ fn note_for_when_indent_error<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuild alloc.text("Err _ ->").indent(6), alloc.text("200").indent(8), ]), - alloc.concat(vec![ + alloc.concat([ alloc.reflow( "Notice the indentation. All patterns are aligned, and each branch is indented", ), @@ -1560,7 +1541,7 @@ fn to_pattern_report<'a>( let surroundings = Region::new(start, *pos); let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I just started parsing a pattern, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.note("I may be confused by indentation"), @@ -1599,10 +1580,10 @@ fn to_precord_report<'a>( let surroundings = Region::new(start, pos); let region = to_keyword_region(lines.convert_pos(pos), keyword); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I just started parsing a record pattern, but I got stuck on this field name:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"Looks like you are trying to use "), alloc.keyword(keyword), alloc.reflow(" as a field name, but that is a reserved word. Try using a different name!"), @@ -1620,7 +1601,7 @@ fn to_precord_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I just started parsing a record pattern, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), record_patterns_look_like(alloc), @@ -1641,10 +1622,10 @@ fn to_precord_report<'a>( match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Other(Some(c)) if c.is_alphabetic() => { - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am partway through parsing a record pattern, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow( r"I was expecting to see a colon, question mark, comma or closing curly brace.", ), @@ -1659,10 +1640,10 @@ fn to_precord_report<'a>( } } _ => { - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow("I am partway through parsing a record pattern, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow( r"I was expecting to see a closing curly brace before this, so try adding a ", ), @@ -1686,10 +1667,10 @@ fn to_precord_report<'a>( let surroundings = Region::new(start, pos); let region = to_keyword_region(lines.convert_pos(pos), keyword); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I just started parsing a record pattern, but I got stuck on this field name:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"Looks like you are trying to use "), alloc.keyword(keyword), alloc.reflow(" as a field name, but that is a reserved word. Try using a different name!"), @@ -1709,10 +1690,10 @@ fn to_precord_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am partway through parsing a record pattern, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"I was expecting to see another record field defined next, so I am looking for a name like "), alloc.parser_suggestion("userName"), alloc.reflow(" or "), @@ -1756,7 +1737,7 @@ fn to_precord_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I just started parsing a record pattern, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), record_patterns_look_like(alloc), @@ -1777,12 +1758,12 @@ fn to_precord_report<'a>( let surroundings = LineColumnRegion::new(lines.convert_pos(start), curly_pos); let region = LineColumnRegion::from_pos(curly_pos); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow( "I am partway through parsing a record pattern, but I got stuck here:", ), alloc.region_with_subregion(surroundings, region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I need this curly brace to be indented more. Try adding more spaces before it!"), ]), ]); @@ -1798,12 +1779,12 @@ fn to_precord_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow( r"I am partway through parsing a record pattern, but I got stuck here:", ), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I was expecting to see a closing curly "), alloc.reflow("brace before this, so try adding a "), alloc.parser_suggestion("}"), @@ -1849,12 +1830,12 @@ fn to_pattern_in_parens_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow( r"I just started parsing a pattern in parentheses, but I got stuck here:", ), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"A pattern in parentheses looks like "), alloc.parser_suggestion("(Ok 32)"), alloc.reflow(r" or "), @@ -1875,10 +1856,10 @@ fn to_pattern_in_parens_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow("I am partway through parsing a pattern in parentheses, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow( r"I was expecting to see a closing parenthesis before this, so try adding a ", ), @@ -1901,7 +1882,7 @@ fn to_pattern_in_parens_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow( r"I just started parsing a pattern in parentheses, but I got stuck here:", ), @@ -1924,12 +1905,12 @@ fn to_pattern_in_parens_report<'a>( let surroundings = LineColumnRegion::new(lines.convert_pos(start), curly_pos); let region = LineColumnRegion::from_pos(curly_pos); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow( "I am partway through parsing a pattern in parentheses, but I got stuck here:", ), alloc.region_with_subregion(surroundings, region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I need this parenthesis to be indented more. Try adding more spaces before it!"), ]), ]); @@ -1945,12 +1926,12 @@ fn to_pattern_in_parens_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow( r"I am partway through parsing a pattern in parentheses, but I got stuck here:", ), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I was expecting to see a closing parenthesis "), alloc.reflow("before this, so try adding a "), alloc.parser_suggestion(")"), @@ -1982,7 +1963,7 @@ fn to_malformed_number_literal_report<'a>( let surroundings = Region::new(start, start); let region = LineColumnRegion::from_pos(lines.convert_pos(start)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"This number literal is malformed:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), ]); @@ -2021,10 +2002,10 @@ fn to_type_report<'a>( let surroundings = Region::new(start, *pos); let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I just started parsing a function argument type, but I encountered two commas in a row:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![alloc.reflow("Try removing one of them.")]), + alloc.concat([alloc.reflow("Try removing one of them.")]), ]); Report { @@ -2042,10 +2023,10 @@ fn to_type_report<'a>( let surroundings = Region::new(start, *pos); let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I just started parsing a type, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"I am expecting a type next, like "), alloc.parser_suggestion("Bool"), alloc.reflow(r" or "), @@ -2066,7 +2047,7 @@ fn to_type_report<'a>( let surroundings = Region::new(start, *pos); let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I just started parsing a type, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.note("I may be confused by indentation"), @@ -2084,7 +2065,7 @@ fn to_type_report<'a>( let surroundings = Region::new(start, *pos); let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am partway through parsing a type, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.note("I may be confused by indentation"), @@ -2102,7 +2083,7 @@ fn to_type_report<'a>( let surroundings = Region::new(start, *pos); let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I just started parsing an inline type alias, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.note("I may be confused by indentation"), @@ -2120,7 +2101,7 @@ fn to_type_report<'a>( let surroundings = Region::new(start, *pos); let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am expecting a type variable, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), ]); @@ -2152,10 +2133,10 @@ fn to_trecord_report<'a>( let surroundings = Region::new(start, pos); let region = to_keyword_region(lines.convert_pos(pos), keyword); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I just started parsing a record type, but I got stuck on this field name:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"Looks like you are trying to use "), alloc.keyword(keyword), alloc.reflow(" as a field name, but that is a reserved word. Try using a different name!"), @@ -2173,10 +2154,10 @@ fn to_trecord_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I just started parsing a record type, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"Record types look like "), alloc.parser_suggestion("{ name : String, age : Int },"), alloc.reflow(" so I was expecting to see a field name next."), @@ -2198,10 +2179,10 @@ fn to_trecord_report<'a>( match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { Next::Other(Some(c)) if c.is_alphabetic() => { - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am partway through parsing a record type, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow( r"I was expecting to see a colon, question mark, comma or closing curly brace.", ), @@ -2216,10 +2197,10 @@ fn to_trecord_report<'a>( } } _ => { - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow("I am partway through parsing a record type, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow( r"I was expecting to see a closing curly brace before this, so try adding a ", ), @@ -2243,10 +2224,10 @@ fn to_trecord_report<'a>( let surroundings = Region::new(start, pos); let region = to_keyword_region(lines.convert_pos(pos), keyword); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I just started parsing a record type, but I got stuck on this field name:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"Looks like you are trying to use "), alloc.keyword(keyword), alloc.reflow(" as a field name, but that is a reserved word. Try using a different name!"), @@ -2266,10 +2247,10 @@ fn to_trecord_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am partway through parsing a record type, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"I was expecting to see another record field defined next, so I am looking for a name like "), alloc.parser_suggestion("userName"), alloc.reflow(" or "), @@ -2300,10 +2281,10 @@ fn to_trecord_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I just started parsing a record type, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"Record types look like "), alloc.parser_suggestion("{ name : String, age : Int },"), alloc.reflow(" so I was expecting to see a field name next."), @@ -2325,12 +2306,12 @@ fn to_trecord_report<'a>( let surroundings = LineColumnRegion::new(lines.convert_pos(start), curly_pos); let region = LineColumnRegion::from_pos(curly_pos); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow( "I am partway through parsing a record type, but I got stuck here:", ), alloc.region_with_subregion(surroundings, region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I need this curly brace to be indented more. Try adding more spaces before it!"), ]), ]); @@ -2346,12 +2327,12 @@ fn to_trecord_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow( r"I am partway through parsing a record type, but I got stuck here:", ), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I was expecting to see a closing curly "), alloc.reflow("brace before this, so try adding a "), alloc.parser_suggestion("}"), @@ -2397,10 +2378,10 @@ fn to_ttag_union_report<'a>( let surroundings = Region::new(start, pos); let region = to_keyword_region(lines.convert_pos(pos), keyword); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I just started parsing a tag union, but I got stuck on this field name:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"Looks like you are trying to use "), alloc.keyword(keyword), alloc.reflow(" as a tag name, but that is a reserved word. Tag names must start with a uppercase letter."), @@ -2420,7 +2401,7 @@ fn to_ttag_union_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow( r"I am partway through parsing a tag union type, but I got stuck here:", ), @@ -2440,10 +2421,10 @@ fn to_ttag_union_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I just started parsing a tag union type, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"Tag unions look like "), alloc.parser_suggestion("[ Many I64, None ],"), alloc.reflow(" so I was expecting to see a tag name next."), @@ -2467,7 +2448,7 @@ fn to_ttag_union_report<'a>( Next::Other(Some(c)) if c.is_alphabetic() => { debug_assert!(c.is_lowercase()); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow( r"I am partway through parsing a tag union type, but I got stuck here:", ), @@ -2483,28 +2464,11 @@ fn to_ttag_union_report<'a>( severity: Severity::RuntimeError, } } - Next::Other(Some('@')) => { - let doc = alloc.stack(vec![ - alloc.reflow( - r"I am partway through parsing a tag union type, but I got stuck here:", - ), - alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.reflow(r"I was expecting to see a private tag name."), - hint_for_private_tag_name(alloc), - ]); - - Report { - filename, - doc, - title: "WEIRD TAG NAME".to_string(), - severity: Severity::RuntimeError, - } - } _ => { - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am partway through parsing a tag union type, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow( r"I was expecting to see a closing square bracket before this, so try adding a ", ), @@ -2529,10 +2493,10 @@ fn to_ttag_union_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I just started parsing a tag union type, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"Tag unions look like "), alloc.parser_suggestion("[ Many I64, None ],"), alloc.reflow(" so I was expecting to see a tag name next."), @@ -2557,12 +2521,12 @@ fn to_ttag_union_report<'a>( let surroundings = LineColumnRegion::new(lines.convert_pos(start), curly_pos); let region = LineColumnRegion::from_pos(curly_pos); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow( "I am partway through parsing a tag union type, but I got stuck here:", ), alloc.region_with_subregion(surroundings, region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I need this square bracket to be indented more. Try adding more spaces before it!"), ]), ]); @@ -2578,12 +2542,12 @@ fn to_ttag_union_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow( r"I am partway through parsing a tag union type, but I got stuck here:", ), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I was expecting to see a closing square "), alloc.reflow("bracket before this, so try adding a "), alloc.parser_suggestion("]"), @@ -2622,10 +2586,10 @@ fn to_tinparens_report<'a>( let surroundings = Region::new(start, pos); let region = to_keyword_region(lines.convert_pos(pos), keyword); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I just saw an open parenthesis, so I was expecting to see a type next."), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"Something like "), alloc.parser_suggestion("(List Person)"), alloc.text(" or "), @@ -2646,7 +2610,7 @@ fn to_tinparens_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow( r"I am partway through parsing a type in parentheses, but I got stuck here:", ), @@ -2666,12 +2630,12 @@ fn to_tinparens_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow( r"I just started parsing a type in parentheses, but I got stuck here:", ), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"Tag unions look like "), alloc.parser_suggestion("[ Many I64, None ],"), alloc.reflow(" so I was expecting to see a tag name next."), @@ -2697,7 +2661,7 @@ fn to_tinparens_report<'a>( debug_assert!(c.is_lowercase()); // TODO hint for tuples? - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow( r"I am partway through parsing a type in parentheses, but I got stuck here:", ), @@ -2714,10 +2678,10 @@ fn to_tinparens_report<'a>( } } _ => { - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am partway through parsing a type in parentheses, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow( r"I was expecting to see a closing parenthesis before this, so try adding a ", ), @@ -2742,11 +2706,11 @@ fn to_tinparens_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc .reflow(r"I just started parsing a type in parentheses, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow(r"Tag unions look like "), alloc.parser_suggestion("[ Many I64, None ],"), alloc.reflow(" so I was expecting to see a tag name next."), @@ -2768,12 +2732,12 @@ fn to_tinparens_report<'a>( let surroundings = LineColumnRegion::new(lines.convert_pos(start), curly_pos); let region = LineColumnRegion::from_pos(curly_pos); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow( "I am partway through parsing a type in parentheses, but I got stuck here:", ), alloc.region_with_subregion(surroundings, region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I need this parenthesis to be indented more. Try adding more spaces before it!"), ]), ]); @@ -2789,12 +2753,12 @@ fn to_tinparens_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow( r"I am partway through parsing a type in parentheses, but I got stuck here:", ), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I was expecting to see a parenthesis "), alloc.reflow("before this, so try adding a "), alloc.parser_suggestion(")"), @@ -2830,10 +2794,10 @@ fn to_tapply_report<'a>( ETypeApply::DoubleDot(pos) => { let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I encountered two dots in a row:"), alloc.region(region), - alloc.concat(vec![alloc.reflow("Try removing one of them.")]), + alloc.concat([alloc.reflow("Try removing one of them.")]), ]); Report { @@ -2846,10 +2810,10 @@ fn to_tapply_report<'a>( ETypeApply::TrailingDot(pos) => { let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I encountered a dot with nothing after it:"), alloc.region(region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("Dots are used to refer to a type in a qualified way, like "), alloc.parser_suggestion("Num.I64"), alloc.text(" or "), @@ -2868,10 +2832,10 @@ fn to_tapply_report<'a>( ETypeApply::StartIsNumber(pos) => { let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I encountered a number at the start of a qualified name segment:"), alloc.region(region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("All parts of a qualified type name must start with an uppercase letter, like "), alloc.parser_suggestion("Num.I64"), alloc.text(" or "), @@ -2890,10 +2854,10 @@ fn to_tapply_report<'a>( ETypeApply::StartNotUppercase(pos) => { let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I encountered a lowercase letter at the start of a qualified name segment:"), alloc.region(region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("All parts of a qualified type name must start with an uppercase letter, like "), alloc.parser_suggestion("Num.I64"), alloc.text(" or "), @@ -2913,7 +2877,7 @@ fn to_tapply_report<'a>( ETypeApply::End(pos) => { let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow( r"I reached the end of the input file while parsing a qualified type name", ), @@ -2944,14 +2908,14 @@ fn to_talias_report<'a>( ETypeInlineAlias::NotAnAlias(pos) => { let region = Region::from_pos(pos); - let doc = alloc.stack(vec![ - alloc.concat(vec![ + let doc = alloc.stack([ + alloc.concat([ alloc.reflow("The inline type after this "), alloc.keyword("as"), alloc.reflow(" is not a type alias:"), ]), alloc.region(lines.convert_region(region)), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("Inline alias types must start with an uppercase identifier and be followed by zero or more type arguments, like "), alloc.type_str("Point"), alloc.reflow(" or "), @@ -2970,7 +2934,7 @@ fn to_talias_report<'a>( ETypeInlineAlias::Qualified(pos) => { let region = Region::from_pos(pos); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"This type alias has a qualified name:"), alloc.region(lines.convert_region(region)), alloc.reflow("An alias introduces a new name to the current scope, so it must be unqualified."), @@ -2986,7 +2950,7 @@ fn to_talias_report<'a>( ETypeInlineAlias::ArgumentNotLowercase(pos) => { let region = Region::from_pos(pos); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"This alias type argument is not lowercase:"), alloc.region(lines.convert_region(region)), alloc.reflow("All type arguments must be lowercase."), @@ -3032,10 +2996,10 @@ fn to_header_report<'a>( let surroundings = Region::new(start, *pos); let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am partway through parsing a header, but got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![alloc.reflow("I may be confused by indentation.")]), + alloc.concat([alloc.reflow("I may be confused by indentation.")]), ]); Report { @@ -3050,10 +3014,10 @@ fn to_header_report<'a>( let surroundings = Region::new(start, *pos); let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am expecting a header, but got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I am expecting a module keyword next, one of "), alloc.keyword("interface"), alloc.reflow(", "), @@ -3076,10 +3040,10 @@ fn to_header_report<'a>( let surroundings = Region::new(start, *pos); let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am partway through parsing a header, but got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I am expecting a module name next, like "), alloc.parser_suggestion("BigNum"), alloc.reflow(" or "), @@ -3100,10 +3064,10 @@ fn to_header_report<'a>( let surroundings = Region::new(start, *pos); let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am partway through parsing a header, but got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I am expecting an application name next, like "), alloc.parser_suggestion("app \"main\""), alloc.reflow(" or "), @@ -3124,10 +3088,10 @@ fn to_header_report<'a>( let surroundings = Region::new(start, *pos); let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am partway through parsing a header, but got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I am expecting a platform name next, like "), alloc.parser_suggestion("\"roc/core\""), alloc.reflow(". Platform names must be quoted."), @@ -3147,10 +3111,10 @@ fn to_header_report<'a>( let surroundings = Region::new(start, *pos); let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am partway through parsing a header, but got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I am expecting a type name next, like "), alloc.parser_suggestion("Effect"), alloc.reflow(". Type names must start with an uppercase letter."), @@ -3185,11 +3149,11 @@ fn to_generates_with_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc .reflow(r"I am partway through parsing a provides list, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![alloc.reflow( + alloc.concat([alloc.reflow( "I was expecting a type name, value name or function name next, like", )]), alloc @@ -3209,13 +3173,13 @@ fn to_generates_with_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I am expecting the "), alloc.keyword("with"), - alloc.reflow(" keyword next, like "), + alloc.reflow(" keyword next, like"), ]), alloc .parser_suggestion("with [ after, map ]") @@ -3251,11 +3215,11 @@ fn to_provides_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc .reflow(r"I am partway through parsing a provides list, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![alloc.reflow( + alloc.concat([alloc.reflow( "I was expecting a type name, value name or function name next, like", )]), alloc @@ -3275,13 +3239,13 @@ fn to_provides_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I am expecting the "), alloc.keyword("provides"), - alloc.reflow(" keyword next, like "), + alloc.reflow(" keyword next, like"), ]), alloc .parser_suggestion("provides [ Animal, default, tame ]") @@ -3317,10 +3281,10 @@ fn to_exposes_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am partway through parsing an `exposes` list, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![alloc.reflow( + alloc.concat([alloc.reflow( "I was expecting a type name, value name or function name next, like", )]), alloc @@ -3340,13 +3304,13 @@ fn to_exposes_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I am expecting the "), alloc.keyword("exposes"), - alloc.reflow(" keyword next, like "), + alloc.reflow(" keyword next, like"), ]), alloc .parser_suggestion("exposes [ Animal, default, tame ]") @@ -3381,10 +3345,10 @@ fn to_imports_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am partway through parsing a imports list, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![alloc.reflow( + alloc.concat([alloc.reflow( "I was expecting a type name, value name or function name next, like ", )]), alloc @@ -3404,13 +3368,13 @@ fn to_imports_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I am expecting the "), alloc.keyword("imports"), - alloc.reflow(" keyword next, like "), + alloc.reflow(" keyword next, like"), ]), alloc .parser_suggestion("imports [ Animal, default, tame ]") @@ -3431,10 +3395,10 @@ fn to_imports_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am partway through parsing a header, but got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I am expecting a module name next, like "), alloc.parser_suggestion("BigNum"), alloc.reflow(" or "), @@ -3451,6 +3415,25 @@ fn to_imports_report<'a>( } } + EImports::ListEnd(pos) => { + let surroundings = Region::new(start, pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); + + let doc = alloc.stack([ + alloc.reflow(r"I am partway through parsing a imports list, but I got stuck here:"), + alloc.region_with_subregion(lines.convert_region(surroundings), region), + alloc.concat([alloc.reflow("I am expecting a comma or end of list, like")]), + alloc.parser_suggestion("imports [ Math, Util ]").indent(4), + ]); + + Report { + filename, + doc, + title: "WEIRD IMPORTS".to_string(), + severity: Severity::RuntimeError, + } + } + _ => todo!("unhandled parse error {:?}", parse_problem), } } @@ -3469,13 +3452,13 @@ fn to_requires_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I am expecting the "), alloc.keyword("requires"), - alloc.reflow(" keyword next, like "), + alloc.reflow(" keyword next, like"), ]), alloc .parser_suggestion("requires { main : Task I64 Str }") @@ -3496,13 +3479,13 @@ fn to_requires_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I am expecting the "), alloc.keyword("requires"), - alloc.reflow(" keyword next, like "), + alloc.reflow(" keyword next, like"), ]), alloc .parser_suggestion("requires { main : Task I64 Str }") @@ -3521,10 +3504,10 @@ fn to_requires_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I am expecting a list of rigids like "), alloc.keyword("{}"), alloc.reflow(" or "), @@ -3550,10 +3533,10 @@ fn to_requires_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I am expecting a list of type names like "), alloc.keyword("{}"), alloc.reflow(" or "), @@ -3593,13 +3576,13 @@ fn to_packages_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I am expecting the "), alloc.keyword("packages"), - alloc.reflow(" keyword next, like "), + alloc.reflow(" keyword next, like"), ]), alloc.parser_suggestion("packages {}").indent(4), ]); @@ -3631,10 +3614,10 @@ fn to_space_report<'a>( BadInputError::HasTab => { let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.reflow(r"I encountered a tab character"), alloc.region(region), - alloc.concat(vec![alloc.reflow("Tab characters are not allowed.")]), + alloc.concat([alloc.reflow("Tab characters are not allowed.")]), ]); Report { @@ -3668,7 +3651,7 @@ fn to_ability_def_report<'a>( alloc.reflow("not indented enough") }; - let msg = alloc.concat(vec![ + let msg = alloc.concat([ alloc.reflow("I suspect this line is "), over_under_msg, alloc.reflow(" (by "), @@ -3692,7 +3675,7 @@ fn to_ability_def_report<'a>( filename, *pos, start, - alloc.concat(vec![ + alloc.concat([ alloc.reflow("I was expecting to see a "), alloc.parser_suggestion(":"), alloc.reflow(" annotating the signature of this value next."), @@ -3712,7 +3695,7 @@ fn to_unfinished_ability_report<'a>( let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - let doc = alloc.stack(vec![ + let doc = alloc.stack([ 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, diff --git a/reporting/src/error/type.rs b/reporting/src/error/type.rs index e4723a0de2..9fbdd6eb2e 100644 --- a/reporting/src/error/type.rs +++ b/reporting/src/error/type.rs @@ -1,5 +1,7 @@ +use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity}; use roc_can::expected::{Expected, PExpected}; use roc_collections::all::{HumanIndex, MutSet, SendMap}; +use roc_exhaustive::CtorName; use roc_module::called_via::{BinOp, CalledVia}; use roc_module::ident::{Ident, IdentStr, Lowercase, TagName}; use roc_module::symbol::Symbol; @@ -10,13 +12,17 @@ use roc_types::types::{ AliasKind, Category, ErrorType, PatternCategory, Reason, RecordField, TypeExt, }; use std::path::PathBuf; - -use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity}; use ven_pretty::DocAllocator; const DUPLICATE_NAME: &str = "DUPLICATE NAME"; const ADD_ANNOTATIONS: &str = r#"Can more type annotations be added? Type annotations always help me give more specific messages, and I think they could help a lot in this case"#; +const OPAQUE_NUM_SYMBOLS: &[Symbol] = &[ + Symbol::NUM_NUM, + Symbol::NUM_INTEGER, + Symbol::NUM_FLOATINGPOINT, +]; + pub fn type_problem<'b>( alloc: &'b RocDocAllocator<'b>, lines: &LineInfo, @@ -80,8 +86,8 @@ pub fn type_problem<'b>( let found_arguments = alloc.text(type_got.to_string()); - let doc = alloc.stack(vec![ - alloc.concat(vec![ + let doc = alloc.stack([ + alloc.concat([ alloc.reflow("The "), alloc.symbol_unqualified(symbol), alloc.reflow(" alias expects "), @@ -126,7 +132,7 @@ pub fn type_problem<'b>( report(title, doc, filename) } BadExprMissingAbility(region, category, found, incomplete) => { - let note = alloc.stack(vec![ + let note = alloc.stack([ alloc.reflow("The ways this expression is used requires that the following types implement the following abilities, which they do not:"), alloc.type_block(alloc.stack(incomplete.iter().map(|incomplete| { symbol_does_not_implement(alloc, incomplete.typ, incomplete.ability) @@ -160,7 +166,7 @@ pub fn type_problem<'b>( Some(report) } BadPatternMissingAbility(region, category, found, incomplete) => { - let note = alloc.stack(vec![ + let note = alloc.stack([ alloc.reflow("The ways this expression is used requires that the following types implement the following abilities, which they do not:"), alloc.type_block(alloc.stack(incomplete.iter().map(|incomplete| { symbol_does_not_implement(alloc, incomplete.typ, incomplete.ability) @@ -193,6 +199,7 @@ pub fn type_problem<'b>( }; Some(report) } + Exhaustive(problem) => Some(exhaustive_problem(alloc, lines, filename, problem)), } } @@ -210,7 +217,7 @@ fn report_incomplete_ability<'a>( debug_assert!(!missing_members.is_empty()); - let mut stack = vec![alloc.concat(vec![ + let mut stack = vec![alloc.concat([ alloc.reflow("The type "), alloc.symbol_unqualified(typ), alloc.reflow(" does not fully implement the ability "), @@ -219,7 +226,7 @@ fn report_incomplete_ability<'a>( ])]; for member in missing_members.into_iter() { - stack.push(alloc.concat(vec![ + stack.push(alloc.concat([ alloc.reflow("A specialization for "), alloc.symbol_unqualified(member.value), alloc.reflow(", which is defined here:"), @@ -228,7 +235,7 @@ fn report_incomplete_ability<'a>( } if !specialized_members.is_empty() { - stack.push(alloc.concat(vec![ + stack.push(alloc.concat([ alloc.note(""), alloc.symbol_unqualified(typ), alloc.reflow(" specializes the following members of "), @@ -237,7 +244,7 @@ fn report_incomplete_ability<'a>( ])); for spec in specialized_members { - stack.push(alloc.concat(vec![ + stack.push(alloc.concat([ alloc.symbol_unqualified(spec.value), alloc.reflow(", specialized here:"), ])); @@ -256,7 +263,7 @@ fn report_shadowing<'b>( ) -> RocDocBuilder<'b> { let line = r#"Since these types have the same name, it's easy to use the wrong one on accident. Give one of them a new name."#; - alloc.stack(vec![ + alloc.stack([ alloc .text("The ") .append(alloc.ident(shadow.value)) @@ -279,7 +286,7 @@ pub fn cyclic_alias<'b>( alloc.reflow("Recursion in aliases is only allowed if recursion happens behind a tagged union, at least one variant of which is not recursive."); let doc = if others.is_empty() { - alloc.stack(vec![ + alloc.stack([ alloc .reflow("The ") .append(alloc.symbol_unqualified(symbol)) @@ -288,7 +295,7 @@ pub fn cyclic_alias<'b>( when_is_recursion_legal, ]) } else { - alloc.stack(vec![ + alloc.stack([ alloc .reflow("The ") .append(alloc.symbol_unqualified(symbol)) @@ -435,6 +442,38 @@ fn to_expr_report<'b>( ) -> Report<'b> { match expected { Expected::NoExpectation(expected_type) => { + // If it looks like a record field typo, early return with a special report for that. + if let ErrorType::Record(expected_fields, _) = + expected_type.clone().unwrap_structural_alias() + { + if let ErrorType::Record(found_fields, found_ext) = + found.clone().unwrap_structural_alias() + { + let expected_set: MutSet<_> = expected_fields.keys().cloned().collect(); + let found_set: MutSet<_> = found_fields.keys().cloned().collect(); + let mut diff = expected_set.difference(&found_set); + + if let Some(field) = diff.next() { + let opt_sym = match category { + Category::Lookup(name) => Some(name), + _ => None, + }; + return report_record_field_typo( + alloc, + lines, + filename, + opt_sym, + ".", + field, + "", + expr_region, + found_fields, + found_ext, + ); + } + } + }; + let comparison = type_comparison( alloc, found, @@ -448,7 +487,7 @@ fn to_expr_report<'b>( Report { filename, title: "TYPE MISMATCH".to_string(), - doc: alloc.stack(vec![ + doc: alloc.stack([ alloc.text("This expression is used in an unexpected way:"), alloc.region(lines.convert_region(expr_region)), comparison, @@ -461,8 +500,8 @@ fn to_expr_report<'b>( let (the_name_text, on_name_text) = match pattern_to_doc(alloc, &name.value) { Some(doc) => ( - alloc.concat(vec![alloc.reflow("the "), doc.clone()]), - alloc.concat(vec![alloc.reflow(" on "), doc]), + alloc.concat([alloc.reflow("the "), doc.clone()]), + alloc.concat([alloc.reflow(" on "), doc]), ), None => (alloc.text("this"), alloc.nil()), }; @@ -474,7 +513,7 @@ fn to_expr_report<'b>( index, num_branches, .. - } if num_branches == 2 => alloc.concat(vec![ + } if num_branches == 2 => alloc.concat([ alloc.keyword(if index == HumanIndex::FIRST { "then" } else { @@ -484,19 +523,19 @@ fn to_expr_report<'b>( alloc.keyword("if"), alloc.text(" expression:"), ]), - TypedIfBranch { index, .. } => alloc.concat(vec![ + TypedIfBranch { index, .. } => alloc.concat([ alloc.string(index.ordinal()), alloc.reflow(" branch of this "), alloc.keyword("if"), alloc.text(" expression:"), ]), - TypedWhenBranch { index, .. } => alloc.concat(vec![ + TypedWhenBranch { index, .. } => alloc.concat([ alloc.string(index.ordinal()), alloc.reflow(" branch of this "), alloc.keyword("when"), alloc.text(" expression:"), ]), - TypedBody { .. } => alloc.concat(vec![ + TypedBody { .. } => alloc.concat([ alloc.text("body of "), the_name_text, alloc.text(" definition:"), @@ -525,14 +564,14 @@ fn to_expr_report<'b>( found, expected_type, expectation_context, - alloc.concat(vec![ + alloc.concat([ alloc.reflow("The type annotation"), on_name_text, alloc.reflow(" says "), it.clone(), alloc.reflow(" should have the type:"), ]), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("However, the type of "), it, alloc.reflow(" is connected to another type in a way that isn't reflected in this annotation.") @@ -545,7 +584,7 @@ fn to_expr_report<'b>( expected_type, expectation_context, add_category(alloc, alloc.text(it_is), &category), - alloc.concat(vec![ + alloc.concat([ alloc.text("But the type annotation"), on_name_text, alloc.text(" says it should be:"), @@ -557,7 +596,7 @@ fn to_expr_report<'b>( Report { title: "TYPE MISMATCH".to_string(), filename, - doc: alloc.stack(vec![ + doc: alloc.stack([ alloc.text("Something is off with the ").append(thing), { // for typed bodies, include the line(s) with the signature @@ -575,7 +614,7 @@ fn to_expr_report<'b>( } Expected::ForReason(reason, expected_type, region) => match reason { Reason::ExpectCondition => { - let problem = alloc.concat(vec![ + let problem = alloc.concat([ alloc.text("This "), alloc.keyword("expect"), alloc.text(" condition needs to be a "), @@ -594,15 +633,15 @@ fn to_expr_report<'b>( Some(expr_region), problem, alloc.text("Right now it’s"), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("But I need every "), alloc.keyword("expect"), alloc.reflow(" condition to evaluate to a "), alloc.type_str("Bool"), alloc.reflow("β€”either "), - alloc.global_tag_name("True".into()), + alloc.tag("True".into()), alloc.reflow(" or "), - alloc.global_tag_name("False".into()), + alloc.tag("False".into()), alloc.reflow("."), ]), // Note: Elm has a hint here about truthiness. I think that @@ -614,7 +653,7 @@ fn to_expr_report<'b>( ) } Reason::IfCondition => { - let problem = alloc.concat(vec![ + let problem = alloc.concat([ alloc.text("This "), alloc.keyword("if"), alloc.text(" condition needs to be a "), @@ -633,15 +672,15 @@ fn to_expr_report<'b>( Some(expr_region), problem, alloc.text("Right now it’s"), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("But I need every "), alloc.keyword("if"), alloc.reflow(" condition to evaluate to a "), alloc.type_str("Bool"), alloc.reflow("β€”either "), - alloc.global_tag_name("True".into()), + alloc.tag("True".into()), alloc.reflow(" or "), - alloc.global_tag_name("False".into()), + alloc.tag("False".into()), alloc.reflow("."), ]), // Note: Elm has a hint here about truthiness. I think that @@ -653,7 +692,7 @@ fn to_expr_report<'b>( ) } Reason::WhenGuard => { - let problem = alloc.concat(vec![ + let problem = alloc.concat([ alloc.text("This "), alloc.keyword("if"), alloc.text(" guard condition needs to be a "), @@ -671,15 +710,15 @@ fn to_expr_report<'b>( Some(expr_region), problem, alloc.text("Right now it’s"), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("But I need every "), alloc.keyword("if"), alloc.reflow(" guard condition to evaluate to a "), alloc.type_str("Bool"), alloc.reflow("β€”either "), - alloc.global_tag_name("True".into()), + alloc.tag("True".into()), alloc.reflow(" or "), - alloc.global_tag_name("False".into()), + alloc.tag("False".into()), alloc.reflow("."), ]), ) @@ -697,7 +736,7 @@ fn to_expr_report<'b>( expected_type, region, Some(expr_region), - alloc.concat(vec![ + alloc.concat([ alloc.text("This "), alloc.keyword("if"), alloc.text(" has an "), @@ -706,17 +745,17 @@ fn to_expr_report<'b>( alloc.keyword("then"), alloc.text(" branch:"), ]), - alloc.concat(vec![ + alloc.concat([ alloc.text("The "), alloc.keyword("else"), alloc.text(" branch is"), ]), - alloc.concat(vec![ + alloc.concat([ alloc.text("but the "), alloc.keyword("then"), alloc.text(" branch has the type:"), ]), - Some(alloc.concat(vec![ + Some(alloc.concat([ alloc.text("I need all branches in an "), alloc.keyword("if"), alloc.text(" to have the same type!"), @@ -731,7 +770,7 @@ fn to_expr_report<'b>( expected_type, region, Some(expr_region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("The "), alloc.string(index.ordinal()), alloc.reflow(" branch of this "), @@ -740,7 +779,7 @@ fn to_expr_report<'b>( ]), alloc.string(format!("The {} branch is", index.ordinal())), alloc.reflow("But all the previous branches have type:"), - Some(alloc.concat(vec![ + Some(alloc.concat([ alloc.reflow("I need all branches in an "), alloc.keyword("if"), alloc.reflow(" to have the same type!"), @@ -756,20 +795,20 @@ fn to_expr_report<'b>( expected_type, region, Some(expr_region), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("The "), alloc.string(index.ordinal()), alloc.reflow(" branch of this "), alloc.keyword("when"), alloc.reflow(" does not match all the previous branches:"), ]), - alloc.concat(vec![ + alloc.concat([ alloc.reflow("The "), alloc.string(index.ordinal()), alloc.reflow(" branch is"), ]), alloc.reflow("But all the previous branches have type:"), - Some(alloc.concat(vec![ + Some(alloc.concat([ alloc.reflow("I need all branches of a "), alloc.keyword("when"), alloc.reflow(" to have the same type!"), @@ -810,12 +849,12 @@ fn to_expr_report<'b>( expected_type, region, Some(expr_region), - alloc.concat(vec![ + alloc.concat([ alloc.text("I cannot update the "), alloc.record_field(field.to_owned()), alloc.text(" field like this:"), ]), - alloc.concat(vec![ + alloc.concat([ alloc.text("You are trying to update "), alloc.record_field(field), alloc.text(" to be"), @@ -827,116 +866,67 @@ fn to_expr_report<'b>( You can achieve that with record literal syntax.", )), ), - Reason::RecordUpdateKeys(symbol, expected_fields) => match found - .clone() - .unwrap_structural_alias() - { - ErrorType::Record(actual_fields, ext) => { - let expected_set: MutSet<_> = expected_fields.keys().cloned().collect(); - let actual_set: MutSet<_> = actual_fields.keys().cloned().collect(); + Reason::RecordUpdateKeys(symbol, expected_fields) => { + match found.clone().unwrap_structural_alias() { + ErrorType::Record(actual_fields, ext) => { + let expected_set: MutSet<_> = expected_fields.keys().cloned().collect(); + let actual_set: MutSet<_> = actual_fields.keys().cloned().collect(); - let mut diff = expected_set.difference(&actual_set); + let mut diff = expected_set.difference(&actual_set); - match diff.next().and_then(|k| Some((k, expected_fields.get(k)?))) { - None => report_mismatch( - alloc, - lines, - filename, - &category, - found, - expected_type, - region, - Some(expr_region), - alloc.reflow("Something is off with this record update:"), - alloc.concat(vec![ - alloc.reflow("The"), - alloc.symbol_unqualified(symbol), - alloc.reflow(" record is"), - ]), - alloc.reflow("But this update needs it to be compatible with:"), - None, - ), - Some((field, field_region)) => { - let r_doc = alloc.symbol_unqualified(symbol); - let f_doc = alloc.record_field(field.clone()); - - let header = alloc.concat(vec![ - alloc.reflow("The "), - r_doc.clone(), - alloc.reflow(" record does not have a "), - f_doc.clone(), - alloc.reflow(" field:"), - ]); - - let mut suggestions = suggest::sort( - field.as_str(), - actual_fields.into_iter().collect::>(), - ); - - let doc = alloc.stack(vec![ - header, - alloc.region(lines.convert_region(*field_region)), - if suggestions.is_empty() { - alloc.concat(vec![ - alloc.reflow("In fact, "), - r_doc, - alloc.reflow(" is a record with NO fields!"), - ]) - } else { - let f = suggestions.remove(0); - let fs = suggestions; - - alloc.stack(vec![ - alloc.concat(vec![ - alloc.reflow("This is usually a typo. Here are the "), - r_doc, - alloc.reflow(" fields that are most similar:"), - ]), - report_text::to_suggestion_record( - alloc, - f.clone(), - fs, - ext, - ), - alloc.concat(vec![ - alloc.reflow("So maybe "), - f_doc, - alloc.reflow(" should be "), - alloc.record_field(f.0), - alloc.reflow("?"), - ]), - ]) - }, - ]); - - Report { + match diff.next().and_then(|k| Some((k, expected_fields.get(k)?))) { + None => report_mismatch( + alloc, + lines, filename, - title: "TYPE MISMATCH".to_string(), - doc, - severity: Severity::RuntimeError, - } + &category, + found, + expected_type, + region, + Some(expr_region), + alloc.reflow("Something is off with this record update:"), + alloc.concat([ + alloc.reflow("The"), + alloc.symbol_unqualified(symbol), + alloc.reflow(" record is"), + ]), + alloc.reflow("But this update needs it to be compatible with:"), + None, + ), + Some((field, field_region)) => report_record_field_typo( + alloc, + lines, + filename, + Some(symbol), + "", + field, + ":", + *field_region, + actual_fields, + ext, + ), } } + _ => report_bad_type( + alloc, + lines, + filename, + &category, + found, + expected_type, + region, + Some(expr_region), + alloc.reflow("This is not a record, so it has no fields to update!"), + alloc.reflow("It is"), + alloc.reflow("But I need a record!"), + ), } - _ => report_bad_type( - alloc, - lines, - filename, - &category, - found, - expected_type, - region, - Some(expr_region), - alloc.reflow("This is not a record, so it has no fields to update!"), - alloc.reflow("It is"), - alloc.reflow("But I need a record!"), - ), - }, + } Reason::FnCall { name, arity } => match count_arguments(&found) { 0 => { let this_value = match name { None => alloc.text("This value"), - Some(symbol) => alloc.concat(vec![ + Some(symbol) => alloc.concat([ alloc.text("The "), alloc.symbol_unqualified(symbol), alloc.text(" value"), @@ -944,7 +934,7 @@ fn to_expr_report<'b>( }; let lines = vec![ - alloc.concat(vec![ + alloc.concat([ this_value, alloc.string(format!( " is not a function, but it was given {}:", @@ -969,7 +959,7 @@ fn to_expr_report<'b>( n => { let this_function = match name { None => alloc.text("This function"), - Some(symbol) => alloc.concat(vec![ + Some(symbol) => alloc.concat([ alloc.text("The "), alloc.symbol_unqualified(symbol), alloc.text(" function"), @@ -978,7 +968,7 @@ fn to_expr_report<'b>( if n < arity as usize { let lines = vec![ - alloc.concat(vec![ + alloc.concat([ this_function, alloc.string(format!( " expects {}, but it got {} instead:", @@ -1002,7 +992,7 @@ fn to_expr_report<'b>( } } else { let lines = vec![ - alloc.concat(vec![ + alloc.concat([ this_function, alloc.string(format!( " expects {}, but it got only {}:", @@ -1047,13 +1037,13 @@ fn to_expr_report<'b>( expected_type, region, Some(expr_region), - alloc.concat(vec![ + alloc.concat([ alloc.string(format!("The {} argument to ", ith)), this_function.clone(), alloc.text(" is not what I expect:"), ]), alloc.text("This argument is"), - alloc.concat(vec![ + alloc.concat([ alloc.text("But "), this_function, alloc.string(format!(" needs the {} argument to be:", ith)), @@ -1082,13 +1072,13 @@ fn to_expr_report<'b>( def_region: _, unimplemented_abilities, } => { - let problem = alloc.concat(vec![ + let problem = alloc.concat([ alloc.reflow("Something is off with this specialization of "), alloc.symbol_unqualified(member_name), alloc.reflow(":"), ]); let this_is = alloc.reflow("This value is"); - let instead_of = alloc.concat(vec![ + let instead_of = alloc.concat([ alloc.reflow("But the type annotation on "), alloc.symbol_unqualified(member_name), alloc.reflow(" says it must match:"), @@ -1102,8 +1092,8 @@ fn to_expr_report<'b>( stack.push(does_not_implement(alloc, err_type, ability)); } - let hint = alloc.stack(vec![ - alloc.concat(vec![ + let hint = alloc.stack([ + alloc.concat([ alloc.note(""), alloc.reflow("Some types in this specialization don't implement the abilities they are expected to. I found the following missing implementations:"), ]), @@ -1133,20 +1123,20 @@ fn to_expr_report<'b>( member_name, def_region: _, } => { - let problem = alloc.concat(vec![ + let problem = alloc.concat([ alloc.reflow("This specialization of "), alloc.symbol_unqualified(member_name), alloc.reflow(" is overly general:"), ]); let this_is = alloc.reflow("This value is"); - let instead_of = alloc.concat(vec![ + let instead_of = alloc.concat([ alloc.reflow("But the type annotation on "), alloc.symbol_unqualified(member_name), alloc.reflow(" says it must match:"), ]); - let note = alloc.stack(vec![ - alloc.concat(vec![ + let note = alloc.stack([ + alloc.concat([ alloc.note(""), alloc.reflow("The specialized type is too general, and does not provide a concrete type where a type variable is bound to an ability."), ]), @@ -1169,6 +1159,88 @@ fn to_expr_report<'b>( ) } + Reason::WhenBranches => { + let snippet = alloc.region_with_subregion( + lines.convert_region(region), + lines.convert_region(expr_region), + ); + + let this_is = alloc.concat([ + alloc.reflow("The "), + alloc.keyword("when"), + alloc.reflow(" condition is"), + ]); + + let wanted = alloc.reflow("But the branch patterns have type:"); + let details = Some(alloc.concat([ + alloc.reflow("The branches must be cases of the "), + alloc.keyword("when"), + alloc.reflow(" condition's type!"), + ])); + + let lines = [ + alloc.concat([ + alloc.reflow("The branches of this "), + alloc.keyword("when"), + alloc.reflow(" expression don't match the condition:"), + ]), + snippet, + type_comparison( + alloc, + found, + expected_type, + ExpectationContext::WhenCondition, + add_category(alloc, this_is, &category), + wanted, + details, + ), + ]; + + Report { + title: "TYPE MISMATCH".to_string(), + filename, + doc: alloc.stack(lines), + severity: Severity::RuntimeError, + } + } + + Reason::TypedArg { name, arg_index } => { + let name = match name { + Some(n) => alloc.symbol_unqualified(n), + None => alloc.text(" this definition "), + }; + let doc = alloc.stack([ + alloc + .text("The ") + .append(alloc.text(arg_index.ordinal())) + .append(alloc.text(" argument to ")) + .append(name.clone()) + .append(alloc.text(" is weird:")), + alloc.region(lines.convert_region(region)), + pattern_type_comparison( + alloc, + expected_type, + found, + add_category(alloc, alloc.text("The argument matches"), &category), + alloc.concat([ + alloc.text("But the annotation on "), + name, + alloc.text(" says the "), + alloc.text(arg_index.ordinal()), + alloc.text(" argument should be:"), + ]), + vec![], + ), + ]); + + Report { + filename, + title: "TYPE MISMATCH".to_string(), + doc, + severity: Severity::RuntimeError, + } + } + Reason::LowLevelOpArg { op, arg_index } => { panic!( "Compiler bug: argument #{} to low-level operation {:?} was the wrong type!", @@ -1176,6 +1248,7 @@ fn to_expr_report<'b>( op ); } + Reason::ForeignCallArg { foreign_symbol, arg_index, @@ -1207,7 +1280,7 @@ fn does_not_implement<'a>( err_type: ErrorType, ability: Symbol, ) -> RocDocBuilder<'a> { - alloc.concat(vec![ + alloc.concat([ to_doc(alloc, Parens::Unnecessary, err_type).0, alloc.reflow(" does not implement "), alloc.symbol_unqualified(ability), @@ -1219,7 +1292,7 @@ fn symbol_does_not_implement<'a>( symbol: Symbol, ability: Symbol, ) -> RocDocBuilder<'a> { - alloc.concat(vec![ + alloc.concat([ alloc.symbol_unqualified(symbol), alloc.reflow(" does not implement "), alloc.symbol_unqualified(ability), @@ -1241,7 +1314,10 @@ fn count_arguments(tipe: &ErrorType) -> usize { enum ExpectationContext<'a> { /// An expected type was discovered from a type annotation. Corresponds to /// [`Expected::FromAnnotation`](Expected::FromAnnotation). - Annotation { on: RocDocBuilder<'a> }, + Annotation { + on: RocDocBuilder<'a>, + }, + WhenCondition, /// When we don't know the context, or it's not relevant. Arbitrary, } @@ -1250,6 +1326,7 @@ impl<'a> std::fmt::Debug for ExpectationContext<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ExpectationContext::Annotation { .. } => f.write_str("Annotation"), + ExpectationContext::WhenCondition => f.write_str("WhenCondition"), ExpectationContext::Arbitrary => f.write_str("Arbitrary"), } } @@ -1321,7 +1398,7 @@ fn format_category<'b>( match category { Lookup(name) => ( - alloc.concat(vec![ + alloc.concat([ alloc.text(format!("{}his ", t)), alloc.symbol_foreign_qualified(*name), alloc.text(" value"), @@ -1330,7 +1407,7 @@ fn format_category<'b>( ), If => ( - alloc.concat(vec![ + alloc.concat([ alloc.text(format!("{}his ", t)), alloc.keyword("if"), alloc.text(" expression"), @@ -1338,7 +1415,7 @@ fn format_category<'b>( alloc.text(" produces:"), ), When => ( - alloc.concat(vec![ + alloc.concat([ alloc.text(format!("{}his ", t)), alloc.keyword("when"), alloc.text(" expression"), @@ -1346,47 +1423,44 @@ fn format_category<'b>( alloc.text(" produces:"), ), List => ( - alloc.concat(vec![this_is, alloc.text(" a list")]), + alloc.concat([this_is, alloc.text(" a list")]), alloc.text(" of type:"), ), Num => ( - alloc.concat(vec![this_is, alloc.text(" a number")]), + alloc.concat([this_is, alloc.text(" a number")]), alloc.text(" of type:"), ), Int => ( - alloc.concat(vec![this_is, alloc.text(" an integer")]), + alloc.concat([this_is, alloc.text(" an integer")]), alloc.text(" of type:"), ), Float => ( - alloc.concat(vec![this_is, alloc.text(" a float")]), + alloc.concat([this_is, alloc.text(" a float")]), alloc.text(" of type:"), ), Str => ( - alloc.concat(vec![this_is, alloc.text(" a string")]), + alloc.concat([this_is, alloc.text(" a string")]), alloc.text(" of type:"), ), StrInterpolation => ( - alloc.concat(vec![ - this_is, - alloc.text(" a value in a string interpolation,"), - ]), + alloc.concat([this_is, alloc.text(" a value in a string interpolation,")]), alloc.text(" which was of type:"), ), Character => ( - alloc.concat(vec![this_is, alloc.text(" a character")]), + alloc.concat([this_is, alloc.text(" a character")]), alloc.text(" of type:"), ), Lambda => ( - alloc.concat(vec![this_is, alloc.text(" an anonymous function")]), + alloc.concat([this_is, alloc.text(" an anonymous function")]), alloc.text(" of type:"), ), ClosureSize => ( - alloc.concat(vec![this_is, alloc.text(" the closure size of a function")]), + alloc.concat([this_is, alloc.text(" the closure size of a function")]), alloc.text(" of type:"), ), OpaqueWrap(opaque) => ( - alloc.concat(vec![ + alloc.concat([ alloc.text(format!("{}his ", t)), alloc.opaque_name(*opaque), alloc.text(" opaque wrapping"), @@ -1395,58 +1469,34 @@ fn format_category<'b>( ), OpaqueArg => ( - alloc.concat(vec![ - alloc.text(format!("{}his argument to an opaque type", t)) - ]), + alloc.concat([alloc.text(format!("{}his argument to an opaque type", t))]), alloc.text(" has type:"), ), TagApply { - tag_name: TagName::Global(name), + tag_name: TagName::Tag(name), args_count: 0, } => ( - alloc.concat(vec![ + alloc.concat([ alloc.text(format!("{}his ", t)), - alloc.global_tag_name(name.to_owned()), + alloc.tag(name.to_owned()), if name.as_str() == "True" || name.as_str() == "False" { alloc.text(" boolean") } else { - alloc.text(" global tag") + alloc.text(" tag") }, ]), alloc.text(" has the type:"), ), - TagApply { - tag_name: TagName::Private(name), - args_count: 0, - } => ( - alloc.concat(vec![ - alloc.text(format!("{}his ", t)), - alloc.private_tag_name(*name), - alloc.text(" private tag"), - ]), - alloc.text(" has the type:"), - ), TagApply { - tag_name: TagName::Global(name), + tag_name: TagName::Tag(name), args_count: _, } => ( - alloc.concat(vec![ + alloc.concat([ alloc.text(format!("{}his ", t)), - alloc.global_tag_name(name.to_owned()), - alloc.text(" global tag application"), - ]), - alloc.text(" has the type:"), - ), - TagApply { - tag_name: TagName::Private(name), - args_count: _, - } => ( - alloc.concat(vec![ - alloc.text("This "), - alloc.private_tag_name(*name), - alloc.text(" private tag application"), + alloc.tag(name.to_owned()), + alloc.text(" tag application"), ]), alloc.text(" has the type:"), ), @@ -1456,12 +1506,12 @@ fn format_category<'b>( } => unreachable!("closure tags are for internal use only"), Record => ( - alloc.concat(vec![this_is, alloc.text(" a record")]), + alloc.concat([this_is, alloc.text(" a record")]), alloc.text(" of type:"), ), Accessor(field) => ( - alloc.concat(vec![ + alloc.concat([ alloc.text(format!("{}his ", t)), alloc.record_field(field.to_owned()), alloc.text(" value"), @@ -1469,7 +1519,7 @@ fn format_category<'b>( alloc.text(" is a:"), ), Access(field) => ( - alloc.concat(vec![ + alloc.concat([ alloc.text(format!("{}he value at ", t)), alloc.record_field(field.to_owned()), ]), @@ -1490,11 +1540,11 @@ fn format_category<'b>( alloc.text(" produces:"), ), CallResult(Some(_), CalledVia::StringInterpolation) => ( - alloc.concat(vec![this_is, alloc.text(" a string")]), + alloc.concat([this_is, alloc.text(" a string")]), alloc.text(" of type:"), ), CallResult(Some(symbol), _) => ( - alloc.concat(vec![ + alloc.concat([ alloc.text(format!("{}his ", t)), alloc.symbol_foreign_qualified(*symbol), alloc.text(" call"), @@ -1513,19 +1563,23 @@ fn format_category<'b>( } Uniqueness => ( - alloc.concat(vec![this_is, alloc.text(" an uniqueness attribute")]), + alloc.concat([this_is, alloc.text(" an uniqueness attribute")]), alloc.text(" of type:"), ), - Storage(_file, _line) => ( - alloc.concat(vec![this_is, alloc.text(" a value")]), + Storage(..) | Unknown => ( + alloc.concat([this_is, alloc.text(" a value")]), alloc.text(" of type:"), ), DefaultValue(_) => ( - alloc.concat(vec![this_is, alloc.text(" a default field")]), + alloc.concat([this_is, alloc.text(" a default field")]), alloc.text(" of type:"), ), AbilityMemberSpecialization(_ability_member) => ( - alloc.concat(vec![this_is, alloc.text(" a declared specialization")]), + alloc.concat([this_is, alloc.text(" a declared specialization")]), + alloc.text(" of type:"), + ), + Expect => ( + alloc.concat([this_is, alloc.text(" an expectation")]), alloc.text(" of type:"), ), } @@ -1537,7 +1591,7 @@ fn add_category<'b>( category: &Category, ) -> RocDocBuilder<'b> { let (summary, suffix) = format_category(alloc, this_is, category, true); - alloc.concat(vec![summary, suffix]) + alloc.concat([summary, suffix]) } fn to_pattern_report<'b>( @@ -1553,7 +1607,7 @@ fn to_pattern_report<'b>( match expected { PExpected::NoExpectation(expected_type) => { - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc.text("This pattern is being used in an unexpected way:"), alloc.region(lines.convert_region(expr_region)), pattern_type_comparison( @@ -1581,7 +1635,7 @@ fn to_pattern_report<'b>( Some(n) => alloc.symbol_unqualified(n), None => alloc.text(" this definition "), }; - let doc = alloc.stack(vec![ + let doc = alloc.stack([ alloc .text("The ") .append(alloc.text(index.ordinal())) @@ -1598,7 +1652,7 @@ fn to_pattern_report<'b>( alloc.text("The argument is a pattern that matches"), &category, ), - alloc.concat(vec![ + alloc.concat([ alloc.text("But the annotation on "), name, alloc.text(" says the "), @@ -1616,14 +1670,17 @@ fn to_pattern_report<'b>( severity: Severity::RuntimeError, } } - PReason::WhenMatch { index } => { - if index == HumanIndex::FIRST { - let doc = alloc.stack(vec![ + PReason::WhenMatch { index, sub_pattern } => { + let doc = match (index, sub_pattern) { + (HumanIndex::FIRST, HumanIndex::FIRST) => alloc.stack([ alloc .text("The 1st pattern in this ") .append(alloc.keyword("when")) .append(alloc.text(" is causing a mismatch:")), - alloc.region(lines.convert_region(region)), + alloc.region_with_subregion( + lines.convert_region(region), + lines.convert_region(expr_region), + ), pattern_type_comparison( alloc, found, @@ -1633,7 +1690,7 @@ fn to_pattern_report<'b>( alloc.text("The first pattern is trying to match"), &category, ), - alloc.concat(vec![ + alloc.concat([ alloc.text("But the expression between "), alloc.keyword("when"), alloc.text(" and "), @@ -1642,44 +1699,55 @@ fn to_pattern_report<'b>( ]), vec![], ), - ]); + ]), + (index, sub_pattern) => { + let (first, index) = match sub_pattern { + HumanIndex::FIRST => { + let doc = alloc + .string(format!("The {} pattern in this ", index.ordinal())) + .append(alloc.keyword("when")) + .append(alloc.text(" does not match the previous ones:")); + (doc, index) + } - Report { - filename, - title: "TYPE MISMATCH".to_string(), - doc, - severity: Severity::RuntimeError, - } - } else { - let doc = alloc.stack(vec![ - alloc - .string(format!("The {} pattern in this ", index.ordinal())) - .append(alloc.keyword("when")) - .append(alloc.text(" does not match the previous ones:")), - alloc.region(lines.convert_region(region)), - pattern_type_comparison( - alloc, - found, - expected_type, - add_pattern_category( - alloc, - alloc.string(format!( - "The {} pattern is trying to match", - index.ordinal() - )), - &category, + _ => { + let doc = alloc.string(format!( + "The {} pattern in this branch does not match the previous ones:", + sub_pattern.ordinal() + )); + (doc, sub_pattern) + } + }; + + alloc.stack([ + first, + alloc.region_with_subregion( + lines.convert_region(region), + lines.convert_region(expr_region), ), - alloc.text("But all the previous branches match:"), - vec![], - ), - ]); - - Report { - filename, - title: "TYPE MISMATCH".to_string(), - doc, - severity: Severity::RuntimeError, + pattern_type_comparison( + alloc, + found, + expected_type, + add_pattern_category( + alloc, + alloc.string(format!( + "The {} pattern is trying to match", + index.ordinal() + )), + &category, + ), + alloc.text("But all the previous branches match:"), + vec![], + ), + ]) } + }; + Report { + filename, + title: "TYPE MISMATCH".to_string(), + doc, + severity: Severity::RuntimeError, } } PReason::TagArg { .. } | PReason::PatternGuard => { @@ -1730,12 +1798,12 @@ fn add_pattern_category<'b>( PatternDefault => alloc.reflow(" an optional field of type:"), Set => alloc.reflow(" sets of type:"), Map => alloc.reflow(" maps of type:"), - Ctor(tag_name) => alloc.concat(vec![ + Ctor(tag_name) => alloc.concat([ alloc.reflow(" a "), alloc.tag_name(tag_name.clone()), alloc.reflow(" tag of type:"), ]), - Opaque(opaque) => alloc.concat(vec![ + Opaque(opaque) => alloc.concat([ alloc.opaque_name(*opaque), alloc.reflow(" unwrappings of type:"), ]), @@ -1746,7 +1814,7 @@ fn add_pattern_category<'b>( Character => alloc.reflow(" characters:"), }; - alloc.concat(vec![i_am_trying_to_match, rest]) + alloc.concat([i_am_trying_to_match, rest]) } fn to_circular_report<'b>( @@ -1761,13 +1829,13 @@ fn to_circular_report<'b>( title: "CIRCULAR TYPE".to_string(), filename, doc: { - alloc.stack(vec![ + alloc.stack([ alloc .reflow("I'm inferring a weird self-referential type for ") .append(alloc.symbol_unqualified(symbol)) .append(alloc.text(":")), alloc.region(lines.convert_region(region)), - alloc.stack(vec![ + alloc.stack([ alloc.reflow( "Here is my best effort at writing down the type. \ You will see ∞ for parts of the type that repeat \ @@ -1789,7 +1857,7 @@ pub enum Problem { FieldsMissing(Vec), TagTypo(TagName, Vec), TagsMissing(Vec), - BadRigidVar(Lowercase, ErrorType), + BadRigidVar(Lowercase, ErrorType, Option), OptionalRequiredMismatch(Lowercase), OpaqueComparedToNonOpaque, } @@ -1894,7 +1962,7 @@ fn diff_is_wildcard_comparison<'b>( ) -> bool { let Comparison { problems, .. } = to_comparison(alloc, actual, expected); match problems.last() { - Some(Problem::BadRigidVar(v1, ErrorType::RigidVar(v2))) => { + Some(Problem::BadRigidVar(v1, ErrorType::RigidVar(v2), None)) => { v1.as_str() == WILDCARD && v2.as_str() == WILDCARD } _ => false, @@ -2165,6 +2233,32 @@ fn to_diff<'b>( same(alloc, parens, type1) } + (RigidVar(x), other) | (other, RigidVar(x)) => { + let (left, left_able) = to_doc(alloc, Parens::InFn, type1); + let (right, right_able) = to_doc(alloc, Parens::InFn, type2); + + Diff { + left, + right, + status: Status::Different(vec![Problem::BadRigidVar(x, other, None)]), + left_able, + right_able, + } + } + + (RigidAbleVar(x, ab), other) | (other, RigidAbleVar(x, ab)) => { + let (left, left_able) = to_doc(alloc, Parens::InFn, type1); + let (right, right_able) = to_doc(alloc, Parens::InFn, type2); + + Diff { + left, + right, + status: Status::Different(vec![Problem::BadRigidVar(x, other, Some(ab))]), + left_able, + right_able, + } + } + (Function(args1, _, ret1), Function(args2, _, ret2)) => { if args1.len() == args2.len() { let mut status = Status::Similar; @@ -2251,7 +2345,10 @@ fn to_diff<'b>( } } - (Alias(_, _, _, AliasKind::Opaque), _) | (_, Alias(_, _, _, AliasKind::Opaque)) => { + (Alias(sym, _, _, AliasKind::Opaque), _) | (_, Alias(sym, _, _, AliasKind::Opaque)) + // Skip the hint for numbers; it's not as useful as saying "this type is not a number" + if !OPAQUE_NUM_SYMBOLS.contains(&sym) => + { let (left, left_able) = to_doc(alloc, Parens::InFn, type1); let (right, right_able) = to_doc(alloc, Parens::InFn, type2); @@ -2347,7 +2444,6 @@ fn to_diff<'b>( }; let problems = match pair { - (RigidVar(x), other) | (other, RigidVar(x)) => vec![Problem::BadRigidVar(x, other)], (a, b) if (is_int(&a) && is_float(&b)) || (is_float(&a) && is_int(&b)) => { vec![Problem::IntFloat] } @@ -2633,22 +2729,24 @@ fn diff_tag_union<'b>( let all_fields_shared = left.peek().is_none() && right.peek().is_none(); let status = match (ext_has_fixed_fields(&ext1), ext_has_fixed_fields(&ext2)) { - (true, true) => match left.peek() { - Some((f, _, _, _)) => Status::Different(vec![Problem::TagTypo( + (true, true) => match (left.peek(), right.peek()) { + (Some((f, _, _, _)), Some(_)) => Status::Different(vec![Problem::TagTypo( f.clone(), fields2.keys().cloned().collect(), )]), - None => { - if right.peek().is_none() { - Status::Similar - } else { - let result = - Status::Different(vec![Problem::TagsMissing(right.map(|v| v.0).collect())]); - // we just used the values in `right`. in - right = right_keys.iter().map(to_unknown_docs).peekable(); - result - } + (Some(_), None) => { + let status = + Status::Different(vec![Problem::TagsMissing(left.map(|v| v.0).collect())]); + left = left_keys.iter().map(to_unknown_docs).peekable(); + status } + (None, Some(_)) => { + let status = + Status::Different(vec![Problem::TagsMissing(right.map(|v| v.0).collect())]); + right = right_keys.iter().map(to_unknown_docs).peekable(); + status + } + (None, None) => Status::Similar, }, (false, true) => match left.peek() { Some((f, _, _, _)) => Status::Different(vec![Problem::TagTypo( @@ -2773,6 +2871,7 @@ fn ext_to_status(ext1: &TypeExt, ext2: &TypeExt) -> Status { Status::Different(vec![Problem::BadRigidVar( x.clone(), ErrorType::RigidVar(y.clone()), + None, )]) } } @@ -2800,7 +2899,7 @@ mod report_text { args: Vec>, ret: RocDocBuilder<'b>, ) -> RocDocBuilder<'b> { - let function_doc = alloc.concat(vec![ + let function_doc = alloc.concat([ alloc.intersperse(args, alloc.reflow(", ")), alloc.reflow(" -> "), ret, @@ -2821,11 +2920,8 @@ mod report_text { if args.is_empty() { name } else { - let apply_doc = alloc.concat(vec![ - name, - alloc.space(), - alloc.intersperse(args, alloc.space()), - ]); + let apply_doc = + alloc.concat([name, alloc.space(), alloc.intersperse(args, alloc.space())]); match parens { Parens::Unnecessary | Parens::InFn => apply_doc, @@ -2892,22 +2988,14 @@ mod report_text { ) }; - if fs.len() <= 3 { - let mut selection = vec![f]; - selection.extend(fs); + let mut selection = vec![f]; + selection.extend(fs); - let fields = selection.into_iter().map(entry_to_doc).collect(); + let fields = selection.into_iter().map(entry_to_doc).collect(); - vertical_record(alloc, fields, ext_to_doc(alloc, ext)) - .annotate(Annotation::TypeBlock) - .indent(4) - } else { - let fields = fs.into_iter().take(3).map(entry_to_doc).collect(); - - vertical_record_snippet(alloc, entry_to_doc(f), fields) - .annotate(Annotation::TypeBlock) - .indent(4) - } + vertical_record(alloc, fields, ext_to_doc(alloc, ext)) + .annotate(Annotation::TypeBlock) + .indent(4) } fn vertical_record<'b>( @@ -2915,66 +3003,44 @@ mod report_text { entries: Vec<(RocDocBuilder<'b>, RocDocBuilder<'b>)>, opt_ext: Option>, ) -> RocDocBuilder<'b> { - let entry_to_doc = |(field_name, field_type): (RocDocBuilder<'b>, RocDocBuilder<'b>)| { - field_name - .append(alloc.text(" : ")) - .hang(4) - .append(field_type) - }; + let fields = if entries.is_empty() { + alloc.text("{}") + } else { + const MAX_ENTRIES_TO_DISPLAY: usize = 4; - match opt_ext { - None => { - if entries.is_empty() { - alloc.text("{}") - } else { - let start = std::iter::once(alloc.reflow("{ ")) - .chain(std::iter::repeat(alloc.reflow(", "))); - let entry_docs = start - .zip(entries.into_iter().map(entry_to_doc)) - .map(|(a, b)| a.append(b)); - alloc.vcat(entry_docs.chain(std::iter::once(alloc.text("}")))) - } - } - Some(ext) => { - let start = std::iter::once(alloc.reflow("{ ")) - .chain(std::iter::repeat(alloc.reflow(", "))); - let entry_docs = start - .zip(entries.into_iter().map(entry_to_doc)) - .map(|(a, b)| a.append(b)); - alloc - .vcat(entry_docs.chain(std::iter::once(alloc.text("}")))) - .append(ext) - } - } - } + let is_truncated = entries.len() > MAX_ENTRIES_TO_DISPLAY; + let entry_to_doc = + |(field_name, field_type): (RocDocBuilder<'b>, RocDocBuilder<'b>)| { + field_name + .indent(4) + .append(alloc.text(" : ")) + .append(field_type) + .append(alloc.text(",")) + }; - fn vertical_record_snippet<'b>( - alloc: &'b RocDocAllocator<'b>, - entry: (RocDocBuilder<'b>, RocDocBuilder<'b>), - entries: Vec<(RocDocBuilder<'b>, RocDocBuilder<'b>)>, - ) -> RocDocBuilder<'b> { - let entry_to_doc = |(field_name, field_type): (RocDocBuilder<'b>, RocDocBuilder<'b>)| { - field_name - .append(alloc.text(" : ")) - .hang(4) - .append(field_type) - }; - - let field = alloc.reflow("{ ").append(entry_to_doc(entry)); - let fields = std::iter::repeat(alloc.reflow(", ")) - .zip( + let closing = std::iter::once(alloc.text("}")); + let fields = std::iter::once(alloc.reflow("{")).chain( entries .into_iter() .map(entry_to_doc) - .chain(std::iter::once(alloc.text("..."))), - ) - .map(|(a, b)| a.append(b)); + .take(MAX_ENTRIES_TO_DISPLAY), + ); - alloc.vcat( - std::iter::once(field) - .chain(fields) - .chain(std::iter::once(alloc.text("}"))), - ) + if is_truncated { + alloc.vcat( + fields + .chain(std::iter::once(alloc.text("…").indent(4))) + .chain(closing), + ) + } else { + alloc.vcat(fields.chain(closing)) + } + }; + + match opt_ext { + Some(ext) => fields.append(ext), + None => fields, + } } pub fn tag_union<'b>( @@ -3183,15 +3249,25 @@ fn type_problem_to_pretty<'b>( alloc.tip().append(line) } - (BadRigidVar(x, tipe), expectation) => { + (BadRigidVar(x, tipe, opt_ability), expectation) => { use ErrorType::*; let bad_rigid_var = |name: Lowercase, a_thing| { + let kind_of_value = match opt_ability { + Some(ability) => alloc.concat([ + alloc.reflow("any value implementing the "), + alloc.symbol_unqualified(ability), + alloc.reflow(" ability"), + ]), + None => alloc.reflow("any type of value"), + }; alloc .tip() .append(alloc.reflow("The type annotation uses the type variable ")) .append(alloc.type_variable(name)) - .append(alloc.reflow(" to say that this definition can produce any type of value. But in the body I see that it will only produce ")) + .append(alloc.reflow(" to say that this definition can produce ") + .append(kind_of_value) + .append(alloc.reflow(". But in the body I see that it will only produce "))) .append(a_thing) .append(alloc.reflow(" of a single specific type. Maybe change the type annotation to be more specific? Maybe change the code to be more general?")) }; @@ -3235,7 +3311,7 @@ fn type_problem_to_pretty<'b>( Infinite | Error | FlexVar(_) => alloc.nil(), FlexAbleVar(_, ability) => bad_rigid_var( x, - alloc.concat(vec![ + alloc.concat([ alloc.reflow("an instance of the ability "), alloc.symbol_unqualified(ability), ]), @@ -3248,7 +3324,7 @@ fn type_problem_to_pretty<'b>( } Alias(symbol, _, _, _) | Type(symbol, _) => bad_rigid_var( x, - alloc.concat(vec![ + alloc.concat([ alloc.reflow("a "), alloc.symbol_unqualified(symbol), alloc.reflow(" value"), @@ -3258,7 +3334,7 @@ fn type_problem_to_pretty<'b>( } } - (IntFloat, _) => alloc.tip().append(alloc.concat(vec![ + (IntFloat, _) => alloc.tip().append(alloc.concat([ alloc.reflow("You can convert between "), alloc.type_str("Int"), alloc.reflow(" and "), @@ -3270,6 +3346,33 @@ fn type_problem_to_pretty<'b>( alloc.reflow("."), ])), + (TagsMissing(missing), ExpectationContext::WhenCondition) => match missing.split_last() { + None => alloc.nil(), + Some(split) => { + let missing_tags = match split { + (f1, []) => alloc.tag_name(f1.clone()).append(alloc.reflow(" tag.")), + (last, init) => alloc + .intersperse(init.iter().map(|v| alloc.tag_name(v.clone())), ", ") + .append(alloc.reflow(" and ")) + .append(alloc.tag_name(last.clone())) + .append(alloc.reflow(" tags.")), + }; + + let tip1 = alloc + .tip() + .append(alloc.reflow("Looks like the branches are missing coverage of the ")) + .append(missing_tags); + + let tip2 = alloc + .tip() + .append(alloc.reflow("Maybe you need to add a catch-all branch, like ")) + .append(alloc.keyword("_")) + .append(alloc.reflow("?")); + + alloc.stack([tip1, tip2]) + } + }, + (TagsMissing(missing), _) => match missing.split_last() { None => alloc.nil(), Some((f1, [])) => { @@ -3285,7 +3388,7 @@ fn type_problem_to_pretty<'b>( Can you use an open tag union?", )); - alloc.stack(vec![tip1, tip2]) + alloc.stack([tip1, tip2]) } Some((last, init)) => { @@ -3308,10 +3411,10 @@ fn type_problem_to_pretty<'b>( Can you use an open tag union?", )); - alloc.stack(vec![tip1, tip2]) + alloc.stack([tip1, tip2]) } }, - (OptionalRequiredMismatch(field), _) => alloc.tip().append(alloc.concat(vec![ + (OptionalRequiredMismatch(field), _) => alloc.tip().append(alloc.concat([ alloc.reflow("To extract the "), alloc.record_field(field), alloc.reflow( @@ -3320,7 +3423,7 @@ fn type_problem_to_pretty<'b>( alloc.reflow("Learn more about optional fields at TODO."), ])), - (OpaqueComparedToNonOpaque, _) => alloc.tip().append(alloc.concat(vec![ + (OpaqueComparedToNonOpaque, _) => alloc.tip().append(alloc.concat([ alloc.reflow( "Type comparisons between an opaque type are only ever \ equal if both types are the same opaque type. Did you mean \ @@ -3333,3 +3436,338 @@ fn type_problem_to_pretty<'b>( ])), } } + +#[allow(clippy::too_many_arguments)] +fn report_record_field_typo<'b>( + alloc: &'b RocDocAllocator<'b>, + lines: &LineInfo, + filename: PathBuf, + opt_sym: Option, + field_prefix: &str, + field: &Lowercase, + field_suffix: &str, + field_region: Region, + actual_fields: SendMap>, + ext: TypeExt, +) -> Report<'b> { + let header = { + let f_doc = alloc + .text(field.as_str().to_string()) + .annotate(Annotation::Typo); + + let r_doc = match opt_sym { + Some(symbol) => alloc.symbol_unqualified(symbol).append(" "), + None => alloc.text(""), + }; + + alloc.concat([ + alloc.reflow("This "), + r_doc, + alloc.reflow("record doesn’t have a "), + f_doc, + alloc.reflow(" field:"), + ]) + }; + + let mut suggestions = suggest::sort( + field.as_str(), + actual_fields.into_iter().collect::>(), + ); + + let doc = alloc.stack([ + header, + alloc.region(lines.convert_region(field_region)), + if suggestions.is_empty() { + let r_doc = match opt_sym { + Some(symbol) => alloc.symbol_unqualified(symbol).append(" is"), + None => alloc.text("it’s"), + }; + alloc.concat([ + alloc.reflow("In fact, "), + r_doc, + alloc.reflow(" a record with no fields at all!"), + ]) + } else { + let f = suggestions.remove(0); + let fs = suggestions; + let f_doc = alloc + .text(format!("{}{}{}", field_prefix, field, field_suffix)) + .annotate(Annotation::Typo); + + let r_doc = match opt_sym { + Some(symbol) => alloc.symbol_unqualified(symbol).append(" fields"), + None => alloc.text("fields on the record"), + }; + + alloc.stack([ + alloc.concat([ + alloc.reflow("There may be a typo. These "), + r_doc, + alloc.reflow(" are the most similar:"), + ]), + report_text::to_suggestion_record(alloc, f.clone(), fs, ext), + alloc.concat([ + alloc.reflow("Maybe "), + f_doc, + alloc.reflow(" should be "), + alloc + .text(format!("{}{}{}", field_prefix, f.0, field_suffix)) + .annotate(Annotation::TypoSuggestion), + alloc.reflow(" instead?"), + ]), + ]) + }, + ]); + + Report { + filename, + title: "TYPE MISMATCH".to_string(), + doc, + severity: Severity::RuntimeError, + } +} + +fn exhaustive_problem<'a>( + alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, + filename: PathBuf, + problem: roc_exhaustive::Error, +) -> Report<'a> { + use roc_exhaustive::Context::*; + use roc_exhaustive::Error::*; + + match problem { + Incomplete(region, context, missing) => match context { + BadArg => { + let doc = alloc.stack([ + alloc.reflow("This pattern does not cover all the possibilities:"), + alloc.region(lines.convert_region(region)), + alloc.reflow("Other possibilities include:"), + unhandled_patterns_to_doc_block(alloc, missing), + alloc.concat([ + alloc.reflow( + "I would have to crash if I saw one of those! \ + So rather than pattern matching in function arguments, put a ", + ), + alloc.keyword("when"), + alloc.reflow(" in the function body to account for all possibilities."), + ]), + ]); + + Report { + filename, + title: "UNSAFE PATTERN".to_string(), + doc, + severity: Severity::RuntimeError, + } + } + BadDestruct => { + let doc = alloc.stack([ + alloc.reflow("This pattern does not cover all the possibilities:"), + alloc.region(lines.convert_region(region)), + alloc.reflow("Other possibilities include:"), + unhandled_patterns_to_doc_block(alloc, missing), + alloc.concat([ + alloc.reflow( + "I would have to crash if I saw one of those! \ + You can use a binding to deconstruct a value if there is only ONE possibility. \ + Use a " + ), + alloc.keyword("when"), + alloc.reflow(" to account for all possibilities."), + ]), + ]); + + Report { + filename, + title: "UNSAFE PATTERN".to_string(), + doc, + severity: Severity::RuntimeError, + } + } + BadCase => { + let doc = alloc.stack([ + alloc.concat([ + alloc.reflow("This "), + alloc.keyword("when"), + alloc.reflow(" does not cover all the possibilities:"), + ]), + alloc.region(lines.convert_region(region)), + alloc.reflow("Other possibilities include:"), + unhandled_patterns_to_doc_block(alloc, missing), + alloc.reflow( + "I would have to crash if I saw one of those! \ + Add branches for them!", + ), + // alloc.hint().append(alloc.reflow("or use a hole.")), + ]); + + Report { + filename, + title: "UNSAFE PATTERN".to_string(), + doc, + severity: Severity::RuntimeError, + } + } + }, + Redundant { + overall_region, + branch_region, + index, + } => { + let doc = alloc.stack([ + alloc.concat([ + alloc.reflow("The "), + alloc.string(index.ordinal()), + alloc.reflow(" pattern is redundant:"), + ]), + alloc.region_with_subregion( + lines.convert_region(overall_region), + lines.convert_region(branch_region), + ), + alloc.reflow( + "Any value of this shape will be handled by \ + a previous pattern, so this one should be removed.", + ), + ]); + + Report { + filename, + title: "REDUNDANT PATTERN".to_string(), + doc, + severity: Severity::Warning, + } + } + } +} + +pub fn unhandled_patterns_to_doc_block<'b>( + alloc: &'b RocDocAllocator<'b>, + patterns: Vec, +) -> RocDocBuilder<'b> { + alloc + .vcat( + patterns + .into_iter() + .map(|v| exhaustive_pattern_to_doc(alloc, v)), + ) + .indent(4) + .annotate(Annotation::TypeBlock) +} + +fn exhaustive_pattern_to_doc<'b>( + alloc: &'b RocDocAllocator<'b>, + pattern: roc_exhaustive::Pattern, +) -> RocDocBuilder<'b> { + pattern_to_doc_help(alloc, pattern, false) +} + +const AFTER_TAG_INDENT: &str = " "; + +fn pattern_to_doc_help<'b>( + alloc: &'b RocDocAllocator<'b>, + pattern: roc_exhaustive::Pattern, + in_type_param: bool, +) -> RocDocBuilder<'b> { + use roc_can::exhaustive::{GUARD_CTOR, NONEXHAUSIVE_CTOR}; + use roc_exhaustive::Literal::*; + use roc_exhaustive::Pattern::*; + use roc_exhaustive::RenderAs; + + match pattern { + Anything => alloc.text("_"), + Literal(l) => match l { + Int(i) => alloc.text(i.to_string()), + U128(i) => alloc.text(i.to_string()), + Bit(true) => alloc.text("True"), + Bit(false) => alloc.text("False"), + Byte(b) => alloc.text(b.to_string()), + Float(f) => alloc.text(f.to_string()), + Decimal(d) => alloc.text(d.to_string()), + Str(s) => alloc.string(s.into()), + }, + Ctor(union, tag_id, args) => { + match union.render_as { + RenderAs::Guard => { + // #Guard + debug_assert!(union.alternatives[tag_id.0 as usize] + .name + .is_tag(&TagName::Tag(GUARD_CTOR.into()))); + debug_assert!(args.len() == 2); + let tag = pattern_to_doc_help(alloc, args[1].clone(), in_type_param); + alloc.concat([ + tag, + alloc.text(AFTER_TAG_INDENT), + alloc.text("(note the lack of an "), + alloc.keyword("if"), + alloc.text(" clause)"), + ]) + } + RenderAs::Record(field_names) => { + let mut arg_docs = Vec::with_capacity(args.len()); + + for (label, v) in field_names.into_iter().zip(args.into_iter()) { + match &v { + Anything => { + arg_docs.push(alloc.text(label.to_string())); + } + Literal(_) | Ctor(_, _, _) => { + arg_docs.push( + alloc + .text(label.to_string()) + .append(alloc.reflow(": ")) + .append(pattern_to_doc_help(alloc, v, false)), + ); + } + } + } + + alloc + .text("{ ") + .append(alloc.intersperse(arg_docs, alloc.reflow(", "))) + .append(" }") + } + RenderAs::Tag | RenderAs::Opaque => { + let ctor = &union.alternatives[tag_id.0 as usize]; + match &ctor.name { + CtorName::Tag(TagName::Tag(name)) if name.as_str() == NONEXHAUSIVE_CTOR => { + return pattern_to_doc_help( + alloc, + roc_exhaustive::Pattern::Anything, + in_type_param, + ) + } + _ => {} + } + + let tag_name = match (union.render_as, &ctor.name) { + (RenderAs::Tag, CtorName::Tag(tag)) => alloc.tag_name(tag.clone()), + (RenderAs::Opaque, CtorName::Opaque(opaque)) => { + alloc.wrapped_opaque_name(*opaque) + } + _ => unreachable!(), + }; + + let has_args = !args.is_empty(); + let arg_docs = args + .into_iter() + .map(|v| pattern_to_doc_help(alloc, v, true)); + + // We assume the alternatives are sorted. If not, this assert will trigger + debug_assert!(tag_id == ctor.tag_id); + + let docs = std::iter::once(tag_name).chain(arg_docs); + + if in_type_param && has_args { + alloc + .text("(") + .append(alloc.intersperse(docs, alloc.space())) + .append(")") + } else { + alloc.intersperse(docs, alloc.space()) + } + } + } + } + } +} diff --git a/reporting/src/report.rs b/reporting/src/report.rs index fb6de7f29b..0fe8fe4213 100644 --- a/reporting/src/report.rs +++ b/reporting/src/report.rs @@ -3,11 +3,10 @@ use roc_module::ident::{Lowercase, ModuleName, TagName, Uppercase}; use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_region::all::LineColumnRegion; use std::fmt; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder, Render, RenderAnnotated}; pub use crate::error::canonicalize::can_problem; -pub use crate::error::mono::mono_problem; pub use crate::error::parse::parse_problem; pub use crate::error::r#type::type_problem; @@ -60,6 +59,46 @@ pub fn cycle<'b>( .annotate(Annotation::TypeBlock) } +const HEADER_WIDTH: usize = 80; + +pub fn pretty_header(title: &str) -> String { + let title_width = title.len() + 4; + let header = format!("── {} {}", title, "─".repeat(HEADER_WIDTH - title_width)); + header +} + +pub fn pretty_header_with_path(title: &str, path: &Path) -> String { + let cwd = std::env::current_dir().unwrap(); + let relative_path = match path.strip_prefix(cwd) { + Ok(p) => p, + _ => path, + } + .to_str() + .unwrap(); + + let title_width = title.len() + 4; + let relative_path_width = relative_path.len() + 3; + let available_path_width = HEADER_WIDTH - title_width - 1; + + // If path is too long to fit in 80 characters with everything else then truncate it + let path_width = relative_path_width.min(available_path_width); + let path_trim = relative_path_width - path_width; + let path = if path_trim > 0 { + format!("...{}", &relative_path[(path_trim + 3)..]) + } else { + relative_path.to_string() + }; + + let header = format!( + "── {} {} {} ─", + title, + "─".repeat(HEADER_WIDTH - (title_width + path_width)), + path + ); + + header +} + #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Severity { /// This will cause a runtime error if some code get srun @@ -129,16 +168,13 @@ impl<'b> Report<'b> { if self.title.is_empty() { self.doc } else { - let header = format!( - "── {} {}", - self.title, - "─".repeat(80 - (self.title.len() + 4)) - ); + let header = if self.filename == PathBuf::from("") { + crate::report::pretty_header(&self.title) + } else { + crate::report::pretty_header_with_path(&self.title, &self.filename) + }; - alloc.stack(vec![ - alloc.text(header).annotate(Annotation::Header), - self.doc, - ]) + alloc.stack([alloc.text(header).annotate(Annotation::Header), self.doc]) } } @@ -194,7 +230,7 @@ const fn default_palette_from_style_codes(codes: StyleCodes) -> Palette { module_name: codes.green, binop: codes.green, typo: codes.yellow, - typo_suggestion: codes.green, + typo_suggestion: codes.yellow, parser_suggestion: codes.yellow, bold: codes.bold, underline: codes.underline, @@ -218,6 +254,7 @@ pub struct StyleCodes { pub bold: &'static str, pub underline: &'static str, pub reset: &'static str, + pub color_reset: &'static str, } pub const ANSI_STYLE_CODES: StyleCodes = StyleCodes { @@ -231,6 +268,7 @@ pub const ANSI_STYLE_CODES: StyleCodes = StyleCodes { bold: "\u{001b}[1m", underline: "\u{001b}[4m", reset: "\u{001b}[0m", + color_reset: "\u{1b}[39m", }; macro_rules! html_color { @@ -250,6 +288,7 @@ pub const HTML_STYLE_CODES: StyleCodes = StyleCodes { bold: "", underline: "", reset: "", + color_reset: "", }; // define custom allocator struct so we can `impl RocDocAllocator` custom helpers @@ -345,26 +384,25 @@ impl<'a> RocDocAllocator<'a> { pub fn tag_name(&'a self, tn: TagName) -> DocBuilder<'a, Self, Annotation> { match tn { - TagName::Global(uppercase) => self.global_tag_name(uppercase), - TagName::Private(symbol) => self.private_tag_name(symbol), + TagName::Tag(uppercase) => self.tag(uppercase), TagName::Closure(_symbol) => unreachable!("closure tags are internal only"), } } pub fn symbol_unqualified(&'a self, symbol: Symbol) -> DocBuilder<'a, Self, Annotation> { - self.text(format!("{}", symbol.ident_str(self.interns))) + self.text(symbol.as_str(self.interns)) .annotate(Annotation::Symbol) } pub fn symbol_foreign_qualified(&'a self, symbol: Symbol) -> DocBuilder<'a, Self, Annotation> { if symbol.module_id() == self.home || symbol.module_id().is_builtin() { // Render it unqualified if it's in the current module or a builtin - self.text(format!("{}", symbol.ident_str(self.interns))) + self.text(symbol.as_str(self.interns)) .annotate(Annotation::Symbol) } else { self.text(format!( "{}.{}", symbol.module_string(self.interns), - symbol.ident_str(self.interns), + symbol.as_str(self.interns), )) .annotate(Annotation::Symbol) } @@ -373,40 +411,25 @@ impl<'a> RocDocAllocator<'a> { self.text(format!( "{}.{}", symbol.module_string(self.interns), - symbol.ident_str(self.interns), + symbol.as_str(self.interns), )) .annotate(Annotation::Symbol) } - pub fn private_tag_name(&'a self, symbol: Symbol) -> DocBuilder<'a, Self, Annotation> { - if symbol.module_id() == self.home { - // Render it unqualified if it's in the current module. - self.text(format!("{}", symbol.ident_str(self.interns))) - .annotate(Annotation::PrivateTag) - } else { - self.text(format!( - "{}.{}", - symbol.module_string(self.interns), - symbol.ident_str(self.interns), - )) - .annotate(Annotation::PrivateTag) - } - } - - pub fn global_tag_name(&'a self, uppercase: Uppercase) -> DocBuilder<'a, Self, Annotation> { + pub fn tag(&'a self, uppercase: Uppercase) -> DocBuilder<'a, Self, Annotation> { self.text(format!("{}", uppercase)) - .annotate(Annotation::GlobalTag) + .annotate(Annotation::Tag) } pub fn opaque_name(&'a self, opaque: Symbol) -> DocBuilder<'a, Self, Annotation> { let fmt = if opaque.module_id() == self.home { // Render it unqualified if it's in the current module. - format!("{}", opaque.ident_str(self.interns)) + opaque.as_str(self.interns).to_string() } else { format!( "{}.{}", opaque.module_string(self.interns), - opaque.ident_str(self.interns), + opaque.as_str(self.interns), ) }; @@ -416,8 +439,7 @@ impl<'a> RocDocAllocator<'a> { pub fn wrapped_opaque_name(&'a self, opaque: Symbol) -> DocBuilder<'a, Self, Annotation> { debug_assert_eq!(opaque.module_id(), self.home, "Opaque wrappings can only be defined in the same module they're defined in, but this one is defined elsewhere: {:?}", opaque); - // TODO(opaques): $->@ - self.text(format!("${}", opaque.ident_str(self.interns))) + self.text(format!("@{}", opaque.as_str(self.interns))) .annotate(Annotation::Opaque) } @@ -767,8 +789,7 @@ pub enum Annotation { Emphasized, Url, Keyword, - GlobalTag, - PrivateTag, + Tag, RecordField, TypeVariable, Alias, @@ -860,8 +881,7 @@ where Url => { self.write_str("<")?; } - GlobalTag | PrivateTag | Keyword | RecordField | Symbol | Typo | TypoSuggestion - | TypeVariable + Tag | Keyword | RecordField | Symbol | Typo | TypoSuggestion | TypeVariable if !self.in_type_block && !self.in_code_block => { self.write_str("`")?; @@ -891,8 +911,7 @@ where Url => { self.write_str(">")?; } - GlobalTag | PrivateTag | Keyword | RecordField | Symbol | Typo | TypoSuggestion - | TypeVariable + Tag | Keyword | RecordField | Symbol | Typo | TypoSuggestion | TypeVariable if !self.in_type_block && !self.in_code_block => { self.write_str("`")?; @@ -984,7 +1003,7 @@ where ParserSuggestion => { self.write_str(self.palette.parser_suggestion)?; } - TypeBlock | GlobalTag | PrivateTag | RecordField => { /* nothing yet */ } + TypeBlock | Tag | RecordField => { /* nothing yet */ } } self.style_stack.push(*annotation); Ok(()) @@ -1002,7 +1021,7 @@ where self.write_str(self.palette.reset)?; } - TypeBlock | GlobalTag | PrivateTag | Opaque | RecordField => { /* nothing yet */ } + TypeBlock | Tag | Opaque | RecordField => { /* nothing yet */ } }, } Ok(()) diff --git a/reporting/tests/helpers/mod.rs b/reporting/tests/helpers/mod.rs index 34aa95bf8f..83918a5934 100644 --- a/reporting/tests/helpers/mod.rs +++ b/reporting/tests/helpers/mod.rs @@ -151,9 +151,9 @@ pub fn can_expr_with<'a>( // rules multiple times unnecessarily. let loc_expr = operator::desugar_expr(arena, &loc_expr); - let mut scope = Scope::new(home, &mut var_store); + let mut scope = Scope::new_with_aliases(home, &mut var_store, IdentIds::default()); let dep_idents = IdentIds::exposed_builtins(0); - let mut env = Env::new(home, &dep_idents, &module_ids, IdentIds::default()); + let mut env = Env::new(home, &dep_idents, &module_ids); let (loc_expr, output) = canonicalize_expr( &mut env, &mut var_store, @@ -183,15 +183,8 @@ pub fn can_expr_with<'a>( let constraint = introduce_builtin_imports(&mut constraints, imports, constraint, &mut var_store); - let mut all_ident_ids = MutMap::default(); - - // When pretty printing types, we may need the exposed builtins, - // so include them in the Interns we'll ultimately return. - for (module_id, ident_ids) in IdentIds::exposed_builtins(0) { - all_ident_ids.insert(module_id, ident_ids); - } - - all_ident_ids.insert(home, env.ident_ids); + let mut all_ident_ids = IdentIds::exposed_builtins(1); + all_ident_ids.insert(home, scope.ident_ids); let interns = Interns { module_ids: env.module_ids.clone(), diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 6b7d822835..a88ad14ecb 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -12,16 +12,12 @@ mod test_reporting { use bumpalo::Bump; use indoc::indoc; use roc_can::abilities::AbilitiesStore; - use roc_can::def::Declaration; - use roc_can::pattern::Pattern; use roc_load::{self, LoadedModule, LoadingProblem}; 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, RenderTarget, Report, Severity, - ANSI_STYLE_CODES, DEFAULT_PALETTE, + can_problem, parse_problem, type_problem, RenderTarget, Report, Severity, ANSI_STYLE_CODES, + DEFAULT_PALETTE, }; use roc_reporting::report::{RocDocAllocator, RocDocBuilder}; use roc_solve::solve; @@ -41,7 +37,7 @@ mod test_reporting { Report { title: "".to_string(), doc, - filename: filename_from_string(r"\code\proj\Main.roc"), + filename: filename_from_string(r"/code/proj/Main.roc"), severity: Severity::RuntimeError, } } @@ -61,12 +57,12 @@ mod test_reporting { } fn run_load_and_infer<'a>( + subdir: &str, arena: &'a Bump, src: &'a str, ) -> (String, Result>) { use std::fs::File; use std::io::Write; - use tempfile::tempdir; let module_src = if src.starts_with("app") { // this is already a module @@ -78,7 +74,12 @@ mod test_reporting { let exposed_types = Default::default(); let loaded = { - let dir = tempdir().unwrap(); + // Use a deterministic temporary directory. + // We can't have all tests use "tmp" because tests run in parallel, + // so append the test name to the tmp path. + let tmp = format!("tmp/{}", subdir); + let dir = roc_test_utils::TmpDir::new(&tmp); + let filename = PathBuf::from("Test.roc"); let file_path = dir.path().join(filename); let full_file_path = file_path.clone(); @@ -94,8 +95,6 @@ mod test_reporting { ); drop(file); - dir.close().unwrap(); - result }; @@ -103,6 +102,7 @@ mod test_reporting { } fn infer_expr_help_new<'a>( + subdir: &str, arena: &'a Bump, expr_src: &'a str, ) -> Result< @@ -110,13 +110,12 @@ mod test_reporting { String, Vec, Vec, - Vec, ModuleId, Interns, ), LoadingProblem<'a>, > { - let (module_src, result) = run_load_and_infer(arena, expr_src); + let (module_src, result) = run_load_and_infer(subdir, arena, expr_src); let LoadedModule { module_id: home, mut can_problems, @@ -124,7 +123,6 @@ mod test_reporting { interns, mut solved, exposed_to_host, - mut declarations_by_id, .. } = result?; @@ -137,79 +135,22 @@ mod test_reporting { name_all_type_vars(*var, subs); } - let mut mono_problems = Vec::new(); - - // MONO - - if type_problems.is_empty() && can_problems.is_empty() { - let arena = Bump::new(); - - assert!(exposed_to_host.len() == 1); - let (sym, _var) = exposed_to_host.into_iter().next().unwrap(); - - let home_decls = declarations_by_id.remove(&home).unwrap(); - let (loc_expr, var) = home_decls - .into_iter() - .find_map(|decl| match decl { - Declaration::Declare(def) => match def.loc_pattern.value { - Pattern::Identifier(s) if s == sym => Some((def.loc_expr, def.expr_var)), - _ => None, - }, - _ => None, - }) - .expect("No expression to monomorphize found!"); - - // Compile and add all the Procs before adding main - let mut procs = Procs::new_in(&arena); - let mut ident_ids = interns.all_ident_ids.get(&home).unwrap().clone(); - let mut update_mode_ids = UpdateModeIds::new(); - - // Populate Procs and Subs, and get the low-level Expr from the canonical Expr - let target_info = roc_target::TargetInfo::default_x86_64(); - let mut layout_cache = LayoutCache::new(target_info); - let mut mono_env = roc_mono::ir::Env { - arena: &arena, - subs, - problems: &mut mono_problems, - home, - ident_ids: &mut ident_ids, - update_mode_ids: &mut update_mode_ids, - target_info, - // call_specialization_counter=0 is reserved - call_specialization_counter: 1, - }; - let _mono_expr = Stmt::new( - &mut mono_env, - loc_expr.value, - var, - &mut procs, - &mut layout_cache, - ); - } - - Ok(( - module_src, - type_problems, - can_problems, - mono_problems, - home, - interns, - )) + Ok((module_src, type_problems, can_problems, home, interns)) } - fn list_reports_new(arena: &Bump, src: &str, finalize_render: F) -> String + fn list_reports_new(subdir: &str, arena: &Bump, src: &str, finalize_render: F) -> String where F: FnOnce(RocDocBuilder<'_>, &mut String), { use ven_pretty::DocAllocator; - let filename = filename_from_string(r"\code\proj\Main.roc"); + let filename = filename_from_string(r"/code/proj/Main.roc"); let mut buf = String::new(); - match infer_expr_help_new(arena, src) { + match infer_expr_help_new(subdir, arena, src) { Err(LoadingProblem::FormattedReport(fail)) => fail, - Ok((module_src, type_problems, can_problems, mono_problems, home, interns)) => { + Ok((module_src, type_problems, can_problems, home, interns)) => { let lines = LineInfo::new(&module_src); let src_lines: Vec<&str> = module_src.split('\n').collect(); let mut reports = Vec::new(); @@ -229,11 +170,6 @@ mod test_reporting { } } - for problem in mono_problems { - let report = mono_problem(&alloc, &lines, filename.clone(), problem.clone()); - reports.push(report); - } - let has_reports = !reports.is_empty(); let doc = alloc @@ -261,14 +197,13 @@ mod test_reporting { ( Vec, Vec, - Vec, ModuleId, Interns, ), ParseErrOut<'a>, > { let CanExprOut { - loc_expr, + loc_expr: _, output, var_store, var, @@ -309,42 +244,7 @@ mod test_reporting { name_all_type_vars(var, &mut subs); - let mut mono_problems = Vec::new(); - - // MONO - - if unify_problems.is_empty() && can_problems.is_empty() { - let arena = Bump::new(); - - // Compile and add all the Procs before adding main - let mut procs = Procs::new_in(&arena); - let mut ident_ids = interns.all_ident_ids.get(&home).unwrap().clone(); - let mut update_mode_ids = UpdateModeIds::new(); - - // Populate Procs and Subs, and get the low-level Expr from the canonical Expr - let target_info = roc_target::TargetInfo::default_x86_64(); - let mut layout_cache = LayoutCache::new(target_info); - let mut mono_env = roc_mono::ir::Env { - arena: &arena, - subs: &mut subs, - problems: &mut mono_problems, - home, - ident_ids: &mut ident_ids, - update_mode_ids: &mut update_mode_ids, - target_info, - // call_specialization_counter=0 is reserved - call_specialization_counter: 1, - }; - let _mono_expr = Stmt::new( - &mut mono_env, - loc_expr.value, - var, - &mut procs, - &mut layout_cache, - ); - } - - Ok((unify_problems, can_problems, mono_problems, home, interns)) + Ok((unify_problems, can_problems, home, interns)) } fn list_reports(arena: &Bump, src: &str, buf: &mut String, callback: F) @@ -356,7 +256,7 @@ mod test_reporting { let src_lines: Vec<&str> = src.split('\n').collect(); let lines = LineInfo::new(src); - let filename = filename_from_string(r"\code\proj\Main.roc"); + let filename = filename_from_string(r"/code/proj/Main.roc"); match infer_expr_help(arena, src) { Err(parse_err) => { @@ -373,7 +273,7 @@ mod test_reporting { callback(doc.pretty(&alloc).append(alloc.line()), buf) } - Ok((type_problems, can_problems, mono_problems, home, interns)) => { + Ok((type_problems, can_problems, home, interns)) => { let mut reports = Vec::new(); let alloc = RocDocAllocator::new(&src_lines, home, &interns); @@ -391,11 +291,6 @@ mod test_reporting { } } - for problem in mono_problems { - let report = mono_problem(&alloc, &lines, filename.clone(), problem.clone()); - reports.push(report); - } - let has_reports = !reports.is_empty(); let doc = alloc @@ -421,7 +316,7 @@ mod test_reporting { let state = State::new(src.as_bytes()); - let filename = filename_from_string(r"\code\proj\Main.roc"); + let filename = filename_from_string(r"/code/proj/Main.roc"); let src_lines: Vec<&str> = src.split('\n').collect(); let lines = LineInfo::new(src); @@ -511,7 +406,7 @@ mod test_reporting { assert_eq!(readable, expected_rendering); } - fn new_report_problem_as(src: &str, expected_rendering: &str) { + fn new_report_problem_as(subdir: &str, src: &str, expected_rendering: &str) { let arena = Bump::new(); let finalize_render = |doc: RocDocBuilder<'_>, buf: &mut String| { @@ -520,7 +415,7 @@ mod test_reporting { .expect("list_reports") }; - let buf = list_reports_new(&arena, src, finalize_render); + let buf = list_reports_new(subdir, &arena, src, finalize_render); // convenient to copy-paste the generated message if buf != expected_rendering { @@ -555,7 +450,7 @@ mod test_reporting { ), indoc!( r#" - ── NOT EXPOSED ───────────────────────────────────────────────────────────────── + ── NOT EXPOSED ─────────────────────────────────────────── /code/proj/Main.roc ─ The List module does not expose `isempty`: @@ -586,7 +481,7 @@ mod test_reporting { ), indoc!( r#" - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `y` is not used anywhere in your code. @@ -615,7 +510,7 @@ mod test_reporting { ), indoc!( r#" - ── DUPLICATE NAME ────────────────────────────────────────────────────────────── + ── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─ The `i` name is first defined here: @@ -643,16 +538,15 @@ mod test_reporting { Booly : [ Yes, No, Maybe ] - x = - No + x : List Booly + x = [] x "# ), - // Booly is called a "variable" indoc!( r#" - ── DUPLICATE NAME ────────────────────────────────────────────────────────────── + ── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─ The `Booly` name is first defined here: @@ -666,26 +560,6 @@ mod test_reporting { Since these aliases have the same name, it's easy to use the wrong one on accident. Give one of them a new name. - - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── - - `Booly` is not used anywhere in your code. - - 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. - - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── - - `Booly` is not used anywhere in your code. - - 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. "# ), ) @@ -772,7 +646,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ Using != and == together requires parentheses, to clarify how they should be grouped. @@ -804,7 +678,7 @@ mod test_reporting { ), indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ I cannot find a `bar` value @@ -814,9 +688,9 @@ mod test_reporting { Did you mean one of these? baz - Nat Str Err + main "# ), ) @@ -832,7 +706,7 @@ mod test_reporting { ), indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ I cannot find a `true` value @@ -843,8 +717,8 @@ mod test_reporting { True Str - Num Err + List "# ), ) @@ -868,7 +742,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ Using more than one == like this requires parentheses, to clarify how things should be grouped. @@ -898,7 +772,7 @@ mod test_reporting { ), indoc!( r#" - ── UNUSED ARGUMENT ───────────────────────────────────────────────────────────── + ── UNUSED ARGUMENT ─────────────────────────────────────── /code/proj/Main.roc ─ `box` doesn't use `htmlChildren`. @@ -911,7 +785,7 @@ mod test_reporting { at the start of a variable name is a way of saying that the variable is not used. - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `y` is not used anywhere in your code. @@ -936,7 +810,7 @@ mod test_reporting { ); let arena = Bump::new(); - let (_type_problems, _can_problems, _mono_problems, home, interns) = + let (_type_problems, _can_problems, home, interns) = infer_expr_help(&arena, src).expect("parse error"); let mut buf = String::new(); @@ -967,7 +841,7 @@ mod test_reporting { ); let arena = Bump::new(); - let (_type_problems, _can_problems, _mono_problems, home, mut interns) = + let (_type_problems, _can_problems, home, mut interns) = infer_expr_help(&arena, src).expect("parse error"); let mut buf = String::new(); @@ -997,7 +871,7 @@ mod test_reporting { ), indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ I cannot find a `theAdmin` value @@ -1006,10 +880,10 @@ mod test_reporting { Did you mean one of these? - Decimal - Dec - Result - Num + Set + List + True + Box "# ), ); @@ -1071,7 +945,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This `if` condition needs to be a Bool: @@ -1101,7 +975,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This `if` guard condition needs to be a Bool: @@ -1130,7 +1004,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This `if` has an `else` branch with a different type from its `then` branch: @@ -1161,7 +1035,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 3rd branch of this `if` does not match all the previous branches: @@ -1190,18 +1064,19 @@ mod test_reporting { when 1 is 2 -> "foo" 3 -> {} + _ -> "" "# ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd branch of this `when` does not match all the previous branches: - 1β”‚ when 1 is - 2β”‚ 2 -> "foo" - 3β”‚ 3 -> {} - ^^ + 1β”‚ when 1 is + 2β”‚ 2 -> "foo" + 3β”‚> 3 -> {} + 4β”‚ _ -> "" The 2nd branch is a record of type: @@ -1227,7 +1102,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This list contains elements with different types: @@ -1261,7 +1136,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ I cannot update the `.foo` field like this: @@ -1295,7 +1170,7 @@ mod test_reporting { ), indoc!( r#" - ── CIRCULAR TYPE ─────────────────────────────────────────────────────────────── + ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ I'm inferring a weird self-referential type for `g`: @@ -1324,7 +1199,7 @@ mod test_reporting { ), indoc!( r#" - ── CIRCULAR TYPE ─────────────────────────────────────────────────────────────── + ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ I'm inferring a weird self-referential type for `f`: @@ -1348,15 +1223,15 @@ mod test_reporting { r#" bar = { bar : 0x3 } - f : { foo : Int * } -> Bool - f = \_ -> True + f : { foo : Num.Int * } -> [ Yes, No ] + f = \_ -> Yes f bar "# ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `f` is not what I expect: @@ -1386,22 +1261,22 @@ mod test_reporting { report_problem_as( indoc!( r#" - f : [ Red, Green ] -> Bool - f = \_ -> True + f : [ Red, Green ] -> [ Yes, No ] + f = \_ -> Yes f Blue "# ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `f` is not what I expect: 4β”‚ f Blue ^^^^ - This `Blue` global tag has the type: + This `Blue` tag has the type: [ Blue ]a @@ -1424,28 +1299,28 @@ mod test_reporting { report_problem_as( indoc!( r#" - f : [ Red (Int *), Green Bool ] -> Bool - f = \_ -> True + f : [ Red (Num.Int *), Green Str ] -> Str + f = \_ -> "yes" f (Blue 3.14) "# ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `f` is not what I expect: 4β”‚ f (Blue 3.14) ^^^^^^^^^ - This `Blue` global tag application has the type: + This `Blue` tag application has the type: [ Blue (Float a) ]b But `f` needs the 1st argument to be: - [ Green Bool, Red (Int *) ] + [ Green Str, Red (Int *) ] Tip: Seems like a tag typo. Maybe `Blue` should be `Red`? @@ -1462,7 +1337,7 @@ mod test_reporting { report_problem_as( indoc!( r#" - x : Int * + x : Num.Int * x = if True then 3.14 else 4 x @@ -1470,11 +1345,11 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the `then` branch of this `if` expression: - 1β”‚ x : Int * + 1β”‚ x : Num.Int * 2β”‚ x = if True then 3.14 else 4 ^^^^ @@ -1498,7 +1373,7 @@ mod test_reporting { report_problem_as( indoc!( r#" - x : Int * + x : Num.Int * x = when True is _ -> 3.14 @@ -1508,11 +1383,11 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `x` definition: - 1β”‚ x : Int * + 1β”‚ x : Num.Int * 2β”‚ x = 3β”‚> when True is 4β”‚> _ -> 3.14 @@ -1537,7 +1412,7 @@ mod test_reporting { report_problem_as( indoc!( r#" - x : Int * -> Int * + x : Num.Int * -> Num.Int * x = \_ -> 3.14 x @@ -1545,11 +1420,11 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `x` definition: - 1β”‚ x : Int * -> Int * + 1β”‚ x : Num.Int * -> Num.Int * 2β”‚ x = \_ -> 3.14 ^^^^ @@ -1573,7 +1448,7 @@ mod test_reporting { report_problem_as( indoc!( r#" - x : I64 + x : Num.I64 x = 42 x 3 @@ -1581,7 +1456,7 @@ mod test_reporting { ), indoc!( r#" - ── TOO MANY ARGS ─────────────────────────────────────────────────────────────── + ── TOO MANY ARGS ───────────────────────────────────────── /code/proj/Main.roc ─ The `x` value is not a function, but it was given 1 argument: @@ -1599,7 +1474,7 @@ mod test_reporting { report_problem_as( indoc!( r#" - f : I64 -> I64 + f : Num.I64 -> Num.I64 f = \_ -> 42 f 1 2 @@ -1607,7 +1482,7 @@ mod test_reporting { ), indoc!( r#" - ── TOO MANY ARGS ─────────────────────────────────────────────────────────────── + ── TOO MANY ARGS ───────────────────────────────────────── /code/proj/Main.roc ─ The `f` function expects 1 argument, but it got 2 instead: @@ -1625,7 +1500,7 @@ mod test_reporting { report_problem_as( indoc!( r#" - f : I64, I64 -> I64 + f : Num.I64, Num.I64 -> Num.I64 f = \_, _ -> 42 f 1 @@ -1633,7 +1508,7 @@ mod test_reporting { ), indoc!( r#" - ── TOO FEW ARGS ──────────────────────────────────────────────────────────────── + ── TOO FEW ARGS ────────────────────────────────────────── /code/proj/Main.roc ─ The `f` function expects 2 arguments, but it got only 1: @@ -1658,20 +1533,22 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - The 1st pattern in this `when` is causing a mismatch: + The branches of this `when` expression don't match the condition: - 2β”‚ {} -> 42 - ^^ + 1β”‚> when 1 is + 2β”‚ {} -> 42 - The first pattern is trying to match record values of type: + The `when` condition is a number of type: + + Num a + + But the branch patterns have type: {}a - But the expression between `when` and `is` has the type: - - Num a + The branches must be cases of the `when` condition's type! "# ), ) @@ -1689,7 +1566,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd pattern in this `when` does not match the previous ones: @@ -1719,20 +1596,22 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - The 1st pattern in this `when` is causing a mismatch: + The branches of this `when` expression don't match the condition: - 2β”‚ { foo: True } -> 42 - ^^^^^^^^^^^^^ + 1β”‚> when { foo: 1 } is + 2β”‚ { foo: True } -> 42 - The first pattern is trying to match record values of type: + The `when` condition is a record of type: + + { foo : Num a } + + But the branch patterns have type: { foo : [ True ] } - But the expression between `when` and `is` has the type: - - { foo : Num a } + The branches must be cases of the `when` condition's type! "# ), ) @@ -1749,20 +1628,22 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - The 1st pattern in this `when` is causing a mismatch: + The branches of this `when` expression don't match the condition: - 2β”‚ { foo: True } -> 42 - ^^^^^^^^^^^^^ + 1β”‚> when { foo: "" } is + 2β”‚ { foo: True } -> 42 - The first pattern is trying to match record values of type: + The `when` condition is a record of type: + + { foo : Str } + + But the branch patterns have type: { foo : [ True ] } - But the expression between `when` and `is` has the type: - - { foo : Str } + The branches must be cases of the `when` condition's type! "# ), ) @@ -1775,24 +1656,24 @@ mod test_reporting { indoc!( r#" when { foo: 1 } is - { foo: 2 } -> foo + { foo: _ } -> foo "# ), indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ I cannot find a `foo` value - 2β”‚ { foo: 2 } -> foo + 2β”‚ { foo: _ } -> foo ^^^ Did you mean one of these? Box - Bool - U8 - F64 + Set + Str + Ok "# ), ) @@ -1845,20 +1726,20 @@ mod test_reporting { // Just putting this here. We should probably handle or-patterns better indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - The 1st pattern in this `when` is causing a mismatch: + The 2nd pattern in this branch does not match the previous ones: 2β”‚ {} | 1 -> 3 - ^^^^^^ + ^ - The first pattern is trying to match numbers: + The 2nd pattern is trying to match numbers: Num a - But the expression between `when` and `is` has the type: + But all the previous branches match: - { foo : Num a } + {}a "# ), ) @@ -1877,7 +1758,7 @@ mod test_reporting { // Maybe this should specifically say the pattern doesn't work? indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This expression is used in an unexpected way: @@ -1901,7 +1782,7 @@ mod test_reporting { report_problem_as( indoc!( r#" - { x } : { x : Int * } + { x } : { x : Num.Int * } { x } = { x: 4.0 } x @@ -1909,11 +1790,11 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of this definition: - 1β”‚ { x } : { x : Int * } + 1β”‚ { x } : { x : Num.Int * } 2β”‚ { x } = { x: 4.0 } ^^^^^^^^^^ @@ -1944,7 +1825,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This integer pattern is malformed: @@ -1969,7 +1850,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This float pattern is malformed: @@ -1994,7 +1875,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This hex integer pattern is malformed: @@ -2019,7 +1900,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This octal integer pattern is malformed: @@ -2044,7 +1925,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This binary integer pattern is malformed: @@ -2062,7 +1943,7 @@ mod test_reporting { report_problem_as( indoc!( r#" - x : { a : Int *, b : Float *, c : Bool } + x : { a : Num.Int *, b : Num.Float *, c : Str } x = { b: 4.0 } x @@ -2070,11 +1951,11 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `x` definition: - 1β”‚ x : { a : Int *, b : Float *, c : Bool } + 1β”‚ x : { a : Num.Int *, b : Num.Float *, c : Str } 2β”‚ x = { b: 4.0 } ^^^^^^^^^^ @@ -2084,7 +1965,7 @@ mod test_reporting { But the type annotation on `x` says it should be: - { a : Int *, b : Float *, c : Bool } + { a : Int *, b : Float *, c : Str } Tip: Looks like the c and a fields are missing. "# @@ -2120,7 +2001,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the `else` branch of this `if` expression: @@ -2150,7 +2031,7 @@ mod test_reporting { report_problem_as( indoc!( r#" - f : Bool -> msg + f : Str -> msg f = \_ -> Foo f @@ -2158,15 +2039,15 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `f` definition: - 1β”‚ f : Bool -> msg + 1β”‚ f : Str -> msg 2β”‚ f = \_ -> Foo ^^^ - This `Foo` global tag has the type: + This `Foo` tag has the type: [ Foo ]a @@ -2197,7 +2078,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `f` definition: @@ -2229,7 +2110,7 @@ mod test_reporting { report_problem_as( indoc!( r#" - f : Bool -> [ Ok I64, InvalidFoo ] + f : Str -> [ Ok Num.I64, InvalidFoo ] f = \_ -> ok 4 f @@ -2237,7 +2118,7 @@ mod test_reporting { ), indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ I cannot find a `ok` value @@ -2247,9 +2128,9 @@ mod test_reporting { Did you mean one of these? Ok - U8 - Box f + Box + Set "# ), ) @@ -2261,7 +2142,7 @@ mod test_reporting { report_problem_as( indoc!( r#" - f : Bool -> I64 + f : Str -> Num.I64 f = \_ -> ok = 3 @@ -2272,7 +2153,7 @@ mod test_reporting { ), indoc!( r#" - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `ok` is not used anywhere in your code. @@ -2282,18 +2163,18 @@ mod test_reporting { If you didn't intend on using `ok` then remove it so future readers of your code don't wonder why it is there. - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `f` definition: - 1β”‚ f : Bool -> I64 + 1β”‚ f : Str -> Num.I64 2β”‚ f = \_ -> 3β”‚ ok = 3 4β”‚ 5β”‚ Ok ^^ - This `Ok` global tag has the type: + This `Ok` tag has the type: [ Ok ]a @@ -2318,7 +2199,7 @@ mod test_reporting { ), indoc!( r#" - ── CIRCULAR DEFINITION ───────────────────────────────────────────────────────── + ── CIRCULAR DEFINITION ─────────────────────────────────── /code/proj/Main.roc ─ The `f` value is defined directly in terms of itself, causing an infinite loop. @@ -2342,7 +2223,7 @@ mod test_reporting { ), indoc!( r#" - ── CIRCULAR DEFINITION ───────────────────────────────────────────────────────── + ── CIRCULAR DEFINITION ─────────────────────────────────── /code/proj/Main.roc ─ The `foo` definition is causing a very tricky infinite loop: @@ -2374,14 +2255,14 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - The `x` record does not have a `.foo` field: + This `x` record doesn’t have a `foo` field: 3β”‚ { x & foo: 3 } ^^^^^^ - In fact, `x` is a record with NO fields! + In fact, `x` is a record with no fields at all! "# ), ) @@ -2400,20 +2281,21 @@ mod test_reporting { // TODO also suggest fields with the correct type indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - The `x` record does not have a `.foo` field: + This `x` record doesn’t have a `foo` field: 3β”‚ { x & foo: 3 } ^^^^^^ - This is usually a typo. Here are the `x` fields that are most similar: + There may be a typo. These `x` fields are the most similar: - { fo : Num b - , bar : Num a + { + fo : Num b, + bar : Num a, } - So maybe `.foo` should be `.fo`? + Maybe `foo:` should be `fo:` instead? "# ), ) @@ -2424,7 +2306,7 @@ mod test_reporting { report_problem_as( indoc!( r#" - f : { fo: I64 }ext -> I64 + f : { fo: Num.I64 }ext -> Num.I64 f = \r -> r2 = { r & foo: r.fo } @@ -2436,19 +2318,20 @@ mod test_reporting { // TODO also suggest fields with the correct type indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - The `r` record does not have a `.foo` field: + This `r` record doesn’t have a `foo` field: 3β”‚ r2 = { r & foo: r.fo } ^^^^^^^^^ - This is usually a typo. Here are the `r` fields that are most similar: + There may be a typo. These `r` fields are the most similar: - { fo : I64 + { + fo : I64, }ext - So maybe `.foo` should be `.fo`? + Maybe `foo:` should be `fo:` instead? "# ), ) @@ -2467,23 +2350,24 @@ mod test_reporting { // TODO also suggest fields with the correct type indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - The `x` record does not have a `.foo` field: + This `x` record doesn’t have a `foo` field: 3β”‚ { x & foo: 3 } ^^^^^^ - This is usually a typo. Here are the `x` fields that are most similar: + There may be a typo. These `x` fields are the most similar: - { fo : Num c - , foobar : Num d - , bar : Num a - , baz : Num b - , ... + { + fo : Num c, + foobar : Num d, + bar : Num a, + baz : Num b, + … } - So maybe `.foo` should be `.fo`? + Maybe `foo:` should be `fo:` instead? "# ), ) @@ -2500,7 +2384,7 @@ mod test_reporting { // TODO also suggest fields with the correct type indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd argument to `add` is not what I expect: @@ -2529,7 +2413,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd argument to `add` is not what I expect: @@ -2561,7 +2445,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd argument to `add` is not what I expect: @@ -2593,7 +2477,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `f` definition: @@ -2631,7 +2515,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `f` definition: @@ -2661,12 +2545,12 @@ mod test_reporting { report_problem_as( indoc!( r#" - Either : [ Left I64, Right Bool ] + Either : [ Left {}, Right Str ] x : Either - x = Left 42 + x = Left {} - f : Either -> I64 + f : Either -> {} f = \Left v -> v f x @@ -2674,7 +2558,7 @@ mod test_reporting { ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This pattern does not cover all the possibilities: @@ -2698,8 +2582,8 @@ mod test_reporting { report_problem_as( indoc!( r#" - x : [ Left I64, Right Bool ] - x = Left 42 + x : [ Left {}, Right Str ] + x = Left {} (Left y) = x @@ -2709,7 +2593,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This expression is used in an unexpected way: @@ -2718,17 +2602,16 @@ mod test_reporting { This `x` value is a: - [ Left I64, Right Bool ] + [ Left {}, Right Str ] But you are trying to use it as: [ Left a ] - Tip: Seems like a tag typo. Maybe `Right` should be `Left`? + Tip: Looks like a closed tag union does not have the `Right` tag. - Tip: Can more type annotations be added? Type annotations always help - me give more specific messages, and I think they could help a lot in - this case + Tip: Closed tag unions can't grow, because that might change the size + in memory. Can you use an open tag union? "# ), ) @@ -2745,7 +2628,7 @@ mod test_reporting { ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This `when` does not cover all the possibilities: @@ -2767,25 +2650,25 @@ mod test_reporting { report_problem_as( indoc!( r#" - x : Bool - x = True + x : [ Red, Green ] + x = Green when x is - False -> 3 + Red -> 3 "# ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This `when` does not cover all the possibilities: 4β”‚> when x is - 5β”‚> False -> 3 + 5β”‚> Red -> 3 Other possibilities include: - True + Green I would have to crash if I saw one of those! Add branches for them! "# @@ -2808,7 +2691,7 @@ mod test_reporting { ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This `when` does not cover all the possibilities: @@ -2833,7 +2716,7 @@ mod test_reporting { r#" RemoteData e a : [ NotAsked, Loading, Failure e, Success a ] - x : RemoteData I64 Str + x : RemoteData Num.I64 Str when x is NotAsked -> 3 @@ -2841,7 +2724,7 @@ mod test_reporting { ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This `when` does not cover all the possibilities: @@ -2874,7 +2757,7 @@ mod test_reporting { // Tip: Looks like a record field guard is not exhaustive. Learn more about record pattern matches at TODO. indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This `when` does not cover all the possibilities: @@ -2896,7 +2779,7 @@ mod test_reporting { report_problem_as( indoc!( r#" - y : [ Nothing, Just I64 ] + y : [ Nothing, Just Num.I64 ] y = Just 4 x = { a: y, b: 42} @@ -2907,7 +2790,7 @@ mod test_reporting { ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This `when` does not cover all the possibilities: @@ -2917,7 +2800,7 @@ mod test_reporting { Other possibilities include: - { a: Just _, b } + { a: Just _ } I would have to crash if I saw one of those! Add branches for them! "# @@ -2937,7 +2820,7 @@ mod test_reporting { ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This `when` does not cover all the possibilities: @@ -2968,7 +2851,7 @@ mod test_reporting { ), indoc!( r#" - ── REDUNDANT PATTERN ─────────────────────────────────────────────────────────── + ── REDUNDANT PATTERN ───────────────────────────────────── /code/proj/Main.roc ─ The 2nd pattern is redundant: @@ -2989,9 +2872,9 @@ mod test_reporting { report_problem_as( indoc!( r#" - Foo a : { x : Int a } + Foo a : { x : Num.Int a } - f : Foo a -> Int a + f : Foo a -> Num.Int a f = \r -> r.x f { y: 3.14 } @@ -3000,7 +2883,7 @@ mod test_reporting { // de-aliases the alias to give a better error message indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `f` is not what I expect: @@ -3042,7 +2925,7 @@ mod test_reporting { // should not report Bar as unused! indoc!( r#" - ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ The `Foo` alias is recursive in an invalid way: @@ -3061,7 +2944,7 @@ mod test_reporting { Recursion in aliases is only allowed if recursion happens behind a tag. - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ `Bar` is not used anywhere in your code. @@ -3091,7 +2974,7 @@ mod test_reporting { // should not report Bar as unused! indoc!( r#" - ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ The `Foo` alias is self-recursive in an invalid way: @@ -3115,7 +2998,7 @@ mod test_reporting { ), indoc!( r#" - ── DUPLICATE FIELD NAME ──────────────────────────────────────────────────────── + ── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─ This record defines the `.x` field twice! @@ -3143,7 +3026,7 @@ mod test_reporting { ), indoc!( r#" - ── DUPLICATE FIELD NAME ──────────────────────────────────────────────────────── + ── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─ This record defines the `.x` field twice! @@ -3175,7 +3058,7 @@ mod test_reporting { ), indoc!( r#" - ── DUPLICATE FIELD NAME ──────────────────────────────────────────────────────── + ── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─ This record defines the `.x` field twice! @@ -3214,7 +3097,7 @@ mod test_reporting { ), indoc!( r#" - ── DUPLICATE FIELD NAME ──────────────────────────────────────────────────────── + ── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─ This record defines the `.x` field twice! @@ -3243,25 +3126,25 @@ mod test_reporting { report_problem_as( indoc!( r#" - a : { foo : I64, bar : F64, foo : Str } - a = { bar: 3.0, foo: "foo" } + a : { foo : Num.I64, bar : {}, foo : Str } + a = { bar: {}, foo: "foo" } a "# ), indoc!( r#" - ── DUPLICATE FIELD NAME ──────────────────────────────────────────────────────── + ── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─ This record type defines the `.foo` field twice! - 1β”‚ a : { foo : I64, bar : F64, foo : Str } - ^^^^^^^^^ ^^^^^^^^^ + 1β”‚ a : { foo : Num.I64, bar : {}, foo : Str } + ^^^^^^^^^^^^^ ^^^^^^^^^ In the rest of the program, I will only use the latter definition: - 1β”‚ a : { foo : I64, bar : F64, foo : Str } - ^^^^^^^^^ + 1β”‚ a : { foo : Num.I64, bar : {}, foo : Str } + ^^^^^^^^^ For clarity, remove the previous `.foo` definitions from this record type. @@ -3275,7 +3158,7 @@ mod test_reporting { report_problem_as( indoc!( r#" - a : [ Foo I64, Bar F64, Foo Str ] + a : [ Foo Num.I64, Bar {}, Foo Str ] a = Foo "foo" a @@ -3283,17 +3166,17 @@ mod test_reporting { ), indoc!( r#" - ── DUPLICATE TAG NAME ────────────────────────────────────────────────────────── + ── DUPLICATE TAG NAME ──────────────────────────────────── /code/proj/Main.roc ─ This tag union type defines the `Foo` tag twice! - 1β”‚ a : [ Foo I64, Bar F64, Foo Str ] - ^^^^^^^ ^^^^^^^ + 1β”‚ a : [ Foo Num.I64, Bar {}, Foo Str ] + ^^^^^^^^^^^ ^^^^^^^ In the rest of the program, I will only use the latter definition: - 1β”‚ a : [ Foo I64, Bar F64, Foo Str ] - ^^^^^^^ + 1β”‚ a : [ Foo Num.I64, Bar {}, Foo Str ] + ^^^^^^^ For clarity, remove the previous `Foo` definitions from this tag union type. @@ -3307,7 +3190,7 @@ mod test_reporting { report_problem_as( indoc!( r#" - bar : I64 + bar : Num.I64 foo = \x -> x # NOTE: neither bar or foo are defined at this point @@ -3316,12 +3199,12 @@ mod test_reporting { ), indoc!( r#" - ── NAMING PROBLEM ────────────────────────────────────────────────────────────── + ── NAMING PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This annotation does not match the definition immediately following it: - 1β”‚> bar : I64 + 1β”‚> bar : Num.I64 2β”‚> foo = \x -> x Is it a typo? If not, put either a newline or comment between them. @@ -3335,7 +3218,7 @@ mod test_reporting { report_problem_as( indoc!( r#" - bar : I64 + bar : Num.I64 foo = \x -> x @@ -3351,28 +3234,28 @@ mod test_reporting { report_problem_as( indoc!( r#" - MyAlias 1 : I64 + MyAlias 1 : Num.I64 4 "# ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This pattern in the definition of `MyAlias` is not what I expect: - 1β”‚ MyAlias 1 : I64 + 1β”‚ MyAlias 1 : Num.I64 ^ Only type variables like `a` or `value` can occur in this position. - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `MyAlias` is not used anywhere in your code. - 1β”‚ MyAlias 1 : I64 - ^^^^^^^^^^^^^^^ + 1β”‚ MyAlias 1 : Num.I64 + ^^^^^^^^^^^^^^^^^^^ If you didn't intend on using `MyAlias` then remove it so future readers of your code don't wonder why it is there. @@ -3386,7 +3269,7 @@ mod test_reporting { report_problem_as( indoc!( r#" - Age 1 := I64 + Age 1 := Num.I64 a : Age a @@ -3394,11 +3277,11 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This pattern in the definition of `Age` is not what I expect: - 1β”‚ Age 1 := I64 + 1β”‚ Age 1 := Num.I64 ^ Only type variables like `a` or `value` can occur in this position. @@ -3412,7 +3295,7 @@ mod test_reporting { report_problem_as( indoc!( r#" - a : Num I64 F64 + a : Num.Num Num.I64 Num.F64 a = 3 a @@ -3420,12 +3303,12 @@ mod test_reporting { ), indoc!( r#" - ── TOO MANY TYPE ARGUMENTS ───────────────────────────────────────────────────── + ── TOO MANY TYPE ARGUMENTS ─────────────────────────────── /code/proj/Main.roc ─ The `Num` alias expects 1 type argument, but it got 2 instead: - 1β”‚ a : Num I64 F64 - ^^^^^^^^^^^ + 1β”‚ a : Num.Num Num.I64 Num.F64 + ^^^^^^^^^^^^^^^^^^^^^^^ Are there missing parentheses? "# @@ -3438,7 +3321,7 @@ mod test_reporting { report_problem_as( indoc!( r#" - f : Bool -> Num I64 F64 + f : Str -> Num.Num Num.I64 Num.F64 f = \_ -> 3 f @@ -3446,12 +3329,12 @@ mod test_reporting { ), indoc!( r#" - ── TOO MANY TYPE ARGUMENTS ───────────────────────────────────────────────────── + ── TOO MANY TYPE ARGUMENTS ─────────────────────────────── /code/proj/Main.roc ─ The `Num` alias expects 1 type argument, but it got 2 instead: - 1β”‚ f : Bool -> Num I64 F64 - ^^^^^^^^^^^ + 1β”‚ f : Str -> Num.Num Num.I64 Num.F64 + ^^^^^^^^^^^^^^^^^^^^^^^ Are there missing parentheses? "# @@ -3466,7 +3349,7 @@ mod test_reporting { r#" Pair a b : [ Pair a b ] - x : Pair I64 + x : Pair Num.I64 x = Pair 2 3 x @@ -3474,12 +3357,12 @@ mod test_reporting { ), indoc!( r#" - ── TOO FEW TYPE ARGUMENTS ────────────────────────────────────────────────────── + ── TOO FEW TYPE ARGUMENTS ──────────────────────────────── /code/proj/Main.roc ─ The `Pair` alias expects 2 type arguments, but it got 1 instead: - 3β”‚ x : Pair I64 - ^^^^^^^^ + 3β”‚ x : Pair Num.I64 + ^^^^^^^^^^^^ Are there missing parentheses? "# @@ -3494,7 +3377,7 @@ mod test_reporting { r#" Pair a b : [ Pair a b ] - x : Pair I64 I64 I64 + x : Pair Num.I64 Num.I64 Num.I64 x = 3 x @@ -3502,12 +3385,12 @@ mod test_reporting { ), indoc!( r#" - ── TOO MANY TYPE ARGUMENTS ───────────────────────────────────────────────────── + ── TOO MANY TYPE ARGUMENTS ─────────────────────────────── /code/proj/Main.roc ─ The `Pair` alias expects 2 type arguments, but it got 3 instead: - 3β”‚ x : Pair I64 I64 I64 - ^^^^^^^^^^^^^^^^ + 3β”‚ x : Pair Num.I64 Num.I64 Num.I64 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Are there missing parentheses? "# @@ -3522,14 +3405,14 @@ mod test_reporting { r#" Foo a : [ Foo ] - f : Foo I64 + f : Foo Num.I64 f "# ), indoc!( r#" - ── UNUSED TYPE ALIAS PARAMETER ───────────────────────────────────────────────── + ── UNUSED TYPE ALIAS PARAMETER ─────────────────────────── /code/proj/Main.roc ─ The `a` type parameter is not used in the `Foo` alias definition: @@ -3555,7 +3438,7 @@ mod test_reporting { ), indoc!( r#" - ── ARGUMENTS BEFORE EQUALS ───────────────────────────────────────────────────── + ── ARGUMENTS BEFORE EQUALS ─────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a definition, but I got stuck here: @@ -3584,7 +3467,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `x` definition: @@ -3592,7 +3475,7 @@ mod test_reporting { 4β”‚ x = Cons {} (Cons "foo" Nil) ^^^^^^^^^^^^^^^^^^^^^^^^ - This `Cons` global tag application has the type: + This `Cons` tag application has the type: [ Cons {} [ Cons Str [ Cons {} a, Nil ] as a, Nil ], Nil ] @@ -3612,7 +3495,7 @@ mod test_reporting { AList a b : [ ACons a (BList a b), ANil ] BList a b : [ BCons a (AList a b), BNil ] - x : AList I64 I64 + x : AList Num.I64 Num.I64 x = ACons 0 (BCons 1 (ACons "foo" BNil )) y : BList a a @@ -3625,15 +3508,15 @@ mod test_reporting { // TODO do not show recursion var if the recursion var does not render on the surface of a type indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `x` definition: - 4β”‚ x : AList I64 I64 + 4β”‚ x : AList Num.I64 Num.I64 5β”‚ x = ACons 0 (BCons 1 (ACons "foo" BNil )) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - This `ACons` global tag application has the type: + This `ACons` tag application has the type: [ ACons (Num (Integer Signed64)) [ BCons (Num (Integer Signed64)) [ ACons Str [ BCons I64 a, BNil ], @@ -3667,7 +3550,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This integer literal is too big: @@ -3679,7 +3562,7 @@ mod test_reporting { Tip: Learn more about number literals at TODO - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This integer literal is too small: @@ -3691,7 +3574,7 @@ mod test_reporting { Tip: Learn more about number literals at TODO - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This integer literal is too big: @@ -3703,7 +3586,7 @@ mod test_reporting { Tip: Learn more about number literals at TODO - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This integer literal is too small: @@ -3733,7 +3616,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This float literal is too big: @@ -3745,7 +3628,7 @@ mod test_reporting { Tip: Learn more about number literals at TODO - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This float literal is too small: @@ -3782,7 +3665,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This integer literal contains an invalid digit: @@ -3794,7 +3677,7 @@ mod test_reporting { Tip: Learn more about number literals at TODO - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This hex integer literal contains an invalid digit: @@ -3806,7 +3689,7 @@ mod test_reporting { Tip: Learn more about number literals at TODO - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This octal integer literal contains an invalid digit: @@ -3818,7 +3701,7 @@ mod test_reporting { Tip: Learn more about number literals at TODO - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This binary integer literal contains an invalid digit: @@ -3852,7 +3735,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This hex integer literal contains no digits: @@ -3864,7 +3747,7 @@ mod test_reporting { Tip: Learn more about number literals at TODO - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This octal integer literal contains no digits: @@ -3876,7 +3759,7 @@ mod test_reporting { Tip: Learn more about number literals at TODO - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This binary integer literal contains no digits: @@ -3904,7 +3787,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This float literal contains an invalid digit: @@ -3939,7 +3822,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This expression cannot be updated: @@ -3962,7 +3845,7 @@ mod test_reporting { ), indoc!( r#" - ── MODULE NOT IMPORTED ───────────────────────────────────────────────────────── + ── MODULE NOT IMPORTED ─────────────────────────────────── /code/proj/Main.roc ─ The `Foo` module is not imported: @@ -3991,7 +3874,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd argument to `add` is not what I expect: @@ -4015,7 +3898,7 @@ mod test_reporting { report_problem_as( indoc!( r#" - f : { x : I64, y ? I64 } -> I64 + f : { x : Num.I64, y ? Num.I64 } -> Num.I64 f = \{ x, y ? "foo" } -> (\g, _ -> g) x y f @@ -4023,7 +3906,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `f` is weird: @@ -4048,7 +3931,7 @@ mod test_reporting { indoc!( r#" \rec -> - { x, y } : { x : I64, y ? Bool } + { x, y } : { x : Num.I64, y ? Str } { x, y } = rec { x, y } @@ -4056,20 +3939,20 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of this definition: - 2β”‚> { x, y } : { x : I64, y ? Bool } + 2β”‚> { x, y } : { x : Num.I64, y ? Str } 3β”‚> { x, y } = rec The body is a value of type: - { x : I64, y : Bool } + { x : I64, y : Str } But the type annotation says it should be: - { x : I64, y ? Bool } + { x : I64, y ? Str } Tip: To extract the `.y` field it must be non-optional, but the type says this field is optional. Learn more about optional fields at TODO. @@ -4083,7 +3966,7 @@ mod test_reporting { report_problem_as( indoc!( r#" - f : { x : I64, y ? I64 } -> I64 + f : { x : Num.I64, y ? Num.I64 } -> Num.I64 f = \{ x, y } -> x + y f @@ -4091,7 +3974,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `f` is weird: @@ -4118,7 +4001,7 @@ mod test_reporting { report_problem_as( indoc!( r#" - f : { x : I64, y ? I64 } -> I64 + f : { x : Num.I64, y ? Num.I64 } -> Num.I64 f = \r -> when r is { x, y } -> x + y @@ -4128,20 +4011,22 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - The 1st pattern in this `when` is causing a mismatch: + The branches of this `when` expression don't match the condition: - 4β”‚ { x, y } -> x + y - ^^^^^^^^ + 3β”‚> when r is + 4β”‚ { x, y } -> x + y - The first pattern is trying to match record values of type: + This `r` value is a: + + { x : I64, y ? I64 } + + But the branch patterns have type: { x : I64, y : I64 } - But the expression between `when` and `is` has the type: - - { x : I64, y ? I64 } + The branches must be cases of the `when` condition's type! Tip: To extract the `.y` field it must be non-optional, but the type says this field is optional. Learn more about optional fields at TODO. @@ -4155,7 +4040,7 @@ mod test_reporting { report_problem_as( indoc!( r#" - f : { x : I64, y ? I64 } -> I64 + f : { x : Num.I64, y ? Num.I64 } -> Num.I64 f = \r -> r.y f @@ -4163,7 +4048,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This expression is used in an unexpected way: @@ -4190,7 +4075,7 @@ mod test_reporting { report_problem_as( indoc!( r#" - f : { x : I64, y ? I64 } -> I64 + f : { x : Num.I64, y ? Num.I64 } -> Num.I64 f = \r -> .y r f @@ -4198,7 +4083,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to this function is not what I expect: @@ -4225,7 +4110,7 @@ mod test_reporting { report_problem_as( indoc!( r#" - f : { x : I64, y : I64 } -> I64 + f : { x : Num.I64, y : Num.I64 } -> Num.I64 f = \r -> when r is { x, y : "foo" } -> x + 0 @@ -4236,20 +4121,23 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - The 1st pattern in this `when` is causing a mismatch: + The branches of this `when` expression don't match the condition: - 4β”‚ { x, y : "foo" } -> x + 0 - ^^^^^^^^^^^^^^^^ + 3β”‚> when r is + 4β”‚ { x, y : "foo" } -> x + 0 + 5β”‚ _ -> 0 - The first pattern is trying to match record values of type: + This `r` value is a: + + { x : I64, y : I64 } + + But the branch patterns have type: { x : I64, y : Str } - But the expression between `when` and `is` has the type: - - { x : I64, y : I64 } + The branches must be cases of the `when` condition's type! "# ), ) @@ -4260,7 +4148,7 @@ mod test_reporting { report_problem_as( indoc!( r#" - f : { x : I64, y ? I64 } -> I64 + f : { x : Num.I64, y ? Num.I64 } -> Num.I64 f = \r -> when r is { x, y ? "foo" } -> (\g, _ -> g) x y @@ -4271,20 +4159,23 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - The 1st pattern in this `when` is causing a mismatch: + The branches of this `when` expression don't match the condition: - 4β”‚ { x, y ? "foo" } -> (\g, _ -> g) x y - ^^^^^^^^^^^^^^^^ + 3β”‚> when r is + 4β”‚ { x, y ? "foo" } -> (\g, _ -> g) x y + 5β”‚ _ -> 0 - The first pattern is trying to match record values of type: + This `r` value is a: + + { x : I64, y ? I64 } + + But the branch patterns have type: { x : I64, y ? Str } - But the expression between `when` and `is` has the type: - - { x : I64, y ? I64 } + The branches must be cases of the `when` condition's type! "# ), ) @@ -4300,7 +4191,7 @@ mod test_reporting { ), indoc!( r#" - ── BAD OPTIONAL VALUE ────────────────────────────────────────────────────────── + ── BAD OPTIONAL VALUE ──────────────────────────────────── /code/proj/Main.roc ─ This record uses an optional value for the `.y` field in an incorrect context! @@ -4342,7 +4233,7 @@ mod test_reporting { ), indoc!( r#" - ── REDUNDANT PATTERN ─────────────────────────────────────────────────────────── + ── REDUNDANT PATTERN ───────────────────────────────────── /code/proj/Main.roc ─ The 3rd pattern is redundant: @@ -4393,7 +4284,7 @@ mod test_reporting { ), indoc!( r#" - ── UNUSED ARGUMENT ───────────────────────────────────────────────────────────── + ── UNUSED ARGUMENT ─────────────────────────────────────── /code/proj/Main.roc ─ `f` doesn't use `foo`. @@ -4410,7 +4301,7 @@ mod test_reporting { } #[test] - fn qualified_global_tag() { + fn qualified_tag() { report_problem_as( indoc!( r#" @@ -4419,7 +4310,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am trying to parse a qualified name here: @@ -4444,7 +4335,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am trying to parse a qualified name here: @@ -4468,7 +4359,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I trying to parse a record field access here: @@ -4481,30 +4372,6 @@ mod test_reporting { ) } - #[test] - fn qualified_private_tag() { - report_problem_as( - indoc!( - r#" - @Foo.Bar - "# - ), - indoc!( - r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── - - I am very confused by this expression: - - 1β”‚ @Foo.Bar - ^^^^ - - Looks like a private tag is treated like a module name. Maybe you - wanted a qualified name, like Json.Decode.string? - "# - ), - ) - } - #[test] fn type_annotation_double_colon() { report_problem_as( @@ -4518,7 +4385,7 @@ mod test_reporting { ), indoc!( r#" - ── UNKNOWN OPERATOR ──────────────────────────────────────────────────────────── + ── UNKNOWN OPERATOR ────────────────────────────────────── /code/proj/Main.roc ─ This looks like an operator, but it's not one I recognize! @@ -4551,7 +4418,7 @@ mod test_reporting { ), indoc!( r#" - ── TOO MANY ARGS ─────────────────────────────────────────────────────────────── + ── TOO MANY ARGS ───────────────────────────────────────── /code/proj/Main.roc ─ This value is not a function, but it was given 3 arguments: @@ -4574,7 +4441,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED TAG UNION TYPE ─────────────────────────────────────────────────── + ── UNFINISHED TAG UNION TYPE ───────────────────────────── /code/proj/Main.roc ─ I just started parsing a tag union type, but I got stuck here: @@ -4598,7 +4465,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED TAG UNION TYPE ─────────────────────────────────────────────────── + ── UNFINISHED TAG UNION TYPE ───────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a tag union type, but I got stuck here: @@ -4622,7 +4489,7 @@ mod test_reporting { ), indoc!( r#" - ── WEIRD TAG NAME ────────────────────────────────────────────────────────────── + ── WEIRD TAG NAME ──────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a tag union type, but I got stuck here: @@ -4647,7 +4514,7 @@ mod test_reporting { ), indoc!( r#" - ── WEIRD TAG NAME ────────────────────────────────────────────────────────────── + ── WEIRD TAG NAME ──────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a tag union type, but I got stuck here: @@ -4672,7 +4539,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED RECORD TYPE ────────────────────────────────────────────────────── + ── UNFINISHED RECORD TYPE ──────────────────────────────── /code/proj/Main.roc ─ I just started parsing a record type, but I got stuck here: @@ -4697,7 +4564,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED RECORD TYPE ────────────────────────────────────────────────────── + ── UNFINISHED RECORD TYPE ──────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a record type, but I got stuck here: @@ -4723,7 +4590,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED RECORD TYPE ────────────────────────────────────────────────────── + ── UNFINISHED RECORD TYPE ──────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a record type, but I got stuck here: @@ -4747,7 +4614,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED RECORD TYPE ────────────────────────────────────────────────────── + ── UNFINISHED RECORD TYPE ──────────────────────────────── /code/proj/Main.roc ─ I just started parsing a record type, but I got stuck on this field name: @@ -4773,7 +4640,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED RECORD TYPE ────────────────────────────────────────────────────── + ── UNFINISHED RECORD TYPE ──────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a record type, but I got stuck here: @@ -4794,7 +4661,7 @@ mod test_reporting { "f : { foo \t }", indoc!( r#" - ── TAB CHARACTER ─────────────────────────────────────────────────────────────── + ── TAB CHARACTER ───────────────────────────────────────── /code/proj/Main.roc ─ I encountered a tab character @@ -4813,7 +4680,7 @@ mod test_reporting { "# comment with a \t\n4", indoc!( " - ── TAB CHARACTER ─────────────────────────────────────────────────────────────── + ── TAB CHARACTER ───────────────────────────────────────── /code/proj/Main.roc ─ I encountered a tab character @@ -4837,7 +4704,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED TYPE ───────────────────────────────────────────────────────────── + ── UNFINISHED TYPE ─────────────────────────────────────── /code/proj/Main.roc ─ I just started parsing a type, but I got stuck here: @@ -4860,7 +4727,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED PARENTHESES ────────────────────────────────────────────────────── + ── UNFINISHED PARENTHESES ──────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a type in parentheses, but I got stuck here: @@ -4889,7 +4756,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am confused by this type name: @@ -4924,7 +4791,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am confused by this type name: @@ -4958,7 +4825,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED TYPE ───────────────────────────────────────────────────────────── + ── UNFINISHED TYPE ─────────────────────────────────────── /code/proj/Main.roc ─ I just started parsing a type, but I got stuck here: @@ -4983,7 +4850,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am confused by this type name: @@ -5019,7 +4886,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am confused by this type name: @@ -5043,7 +4910,7 @@ mod test_reporting { ), indoc!( r#" - ── MISSING FINAL EXPRESSION ──────────────────────────────────────────────────── + ── MISSING FINAL EXPRESSION ────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a definition's final expression, but I got stuck here: @@ -5076,7 +4943,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED INLINE ALIAS ───────────────────────────────────────────────────── + ── UNFINISHED INLINE ALIAS ─────────────────────────────── /code/proj/Main.roc ─ I just started parsing an inline type alias, but I got stuck here: @@ -5102,7 +4969,7 @@ mod test_reporting { ), indoc!( r#" - ── DOUBLE COMMA ──────────────────────────────────────────────────────────────── + ── DOUBLE COMMA ────────────────────────────────────────── /code/proj/Main.roc ─ I just started parsing a function argument type, but I encountered two commas in a row: @@ -5129,7 +4996,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED TYPE ───────────────────────────────────────────────────────────── + ── UNFINISHED TYPE ─────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a type, but I got stuck here: @@ -5156,7 +5023,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED TYPE ───────────────────────────────────────────────────────────── + ── UNFINISHED TYPE ─────────────────────────────────────── /code/proj/Main.roc ─ I just started parsing a type, but I got stuck here: @@ -5169,43 +5036,13 @@ mod test_reporting { ) } - #[test] - fn invalid_private_tag_name() { - // TODO could do better by pointing out we're parsing a function type - report_problem_as( - indoc!( - r#" - f : [ @Foo Bool, @100 I64 ] - f = 0 - - f - "# - ), - indoc!( - r#" - ── WEIRD TAG NAME ────────────────────────────────────────────────────────────── - - I am partway through parsing a tag union type, but I got stuck here: - - 1β”‚ f : [ @Foo Bool, @100 I64 ] - ^ - - I was expecting to see a private tag name. - - Hint: Private tag names start with an `@` symbol followed by an - uppercase letter, like @UID or @SecretKey. - "# - ), - ) - } - #[test] fn dict_type_formatting() { // TODO could do better by pointing out we're parsing a function type report_problem_as( indoc!( r#" - myDict : Dict I64 Str + myDict : Dict Num.I64 Str myDict = Dict.insert Dict.empty "foo" 42 myDict @@ -5213,11 +5050,11 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `myDict` definition: - 1β”‚ myDict : Dict I64 Str + 1β”‚ myDict : Dict Num.I64 Str 2β”‚ myDict = Dict.insert Dict.empty "foo" 42 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -5250,7 +5087,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `myDict` definition: @@ -5286,7 +5123,7 @@ mod test_reporting { ), indoc!( r#" - ── IF GUARD NO CONDITION ─────────────────────────────────────────────────────── + ── IF GUARD NO CONDITION ───────────────────────────────── /code/proj/Main.roc ─ I just started parsing an if guard, but there is no guard condition: @@ -5315,7 +5152,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED PATTERN ────────────────────────────────────────────────────────── + ── UNFINISHED PATTERN ──────────────────────────────────── /code/proj/Main.roc ─ I just started parsing a pattern, but I got stuck here: @@ -5344,7 +5181,7 @@ mod test_reporting { ), indoc!( r#" - ── MISSING EXPRESSION ────────────────────────────────────────────────────────── + ── MISSING EXPRESSION ──────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a definition, but I got stuck here: @@ -5371,7 +5208,7 @@ mod test_reporting { ), indoc!( r#" - ── MISSING ARROW ─────────────────────────────────────────────────────────────── + ── MISSING ARROW ───────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a `when` expression, but got stuck here: @@ -5408,7 +5245,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED ARGUMENT LIST ──────────────────────────────────────────────────── + ── UNFINISHED ARGUMENT LIST ────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a function argument list, but I got stuck at this comma: @@ -5433,7 +5270,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED ARGUMENT LIST ──────────────────────────────────────────────────── + ── UNFINISHED ARGUMENT LIST ────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a function argument list, but I got stuck at this comma: @@ -5461,7 +5298,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I got stuck here: @@ -5512,7 +5349,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I got stuck here: @@ -5540,7 +5377,7 @@ mod test_reporting { ), indoc!( r#" - ── UNEXPECTED ARROW ──────────────────────────────────────────────────────────── + ── UNEXPECTED ARROW ────────────────────────────────────── /code/proj/Main.roc ─ I am parsing a `when` expression right now, but this arrow is confusing me: @@ -5583,7 +5420,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED IF ─────────────────────────────────────────────────────────────── + ── UNFINISHED IF ───────────────────────────────────────── /code/proj/Main.roc ─ I was partway through parsing an `if` expression, but I got stuck here: @@ -5607,7 +5444,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED IF ─────────────────────────────────────────────────────────────── + ── UNFINISHED IF ───────────────────────────────────────── /code/proj/Main.roc ─ I was partway through parsing an `if` expression, but I got stuck here: @@ -5630,7 +5467,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED LIST ───────────────────────────────────────────────────────────── + ── UNFINISHED LIST ─────────────────────────────────────── /code/proj/Main.roc ─ I am partway through started parsing a list, but I got stuck here: @@ -5654,7 +5491,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED LIST ───────────────────────────────────────────────────────────── + ── UNFINISHED LIST ─────────────────────────────────────── /code/proj/Main.roc ─ I am partway through started parsing a list, but I got stuck here: @@ -5682,7 +5519,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This float literal contains an invalid digit: @@ -5704,7 +5541,7 @@ mod test_reporting { r#""abc\u(zzzz)def""#, indoc!( r#" - ── WEIRD CODE POINT ──────────────────────────────────────────────────────────── + ── WEIRD CODE POINT ────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a unicode code point, but I got stuck here: @@ -5726,7 +5563,7 @@ mod test_reporting { r#""abc\(32)def""#, indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This string interpolation is invalid: @@ -5748,7 +5585,7 @@ mod test_reporting { r#""abc\u(110000)def""#, indoc!( r#" - ── INVALID UNICODE ───────────────────────────────────────────────────────────── + ── INVALID UNICODE ─────────────────────────────────────── /code/proj/Main.roc ─ This unicode code point is invalid: @@ -5767,7 +5604,7 @@ mod test_reporting { r#""abc\qdef""#, indoc!( r#" - ── WEIRD ESCAPE ──────────────────────────────────────────────────────────────── + ── WEIRD ESCAPE ────────────────────────────────────────── /code/proj/Main.roc ─ I was partway through parsing a string literal, but I got stuck here: @@ -5795,7 +5632,7 @@ mod test_reporting { r#""there is no end"#, indoc!( r#" - ── ENDLESS STRING ────────────────────────────────────────────────────────────── + ── ENDLESS STRING ──────────────────────────────────────── /code/proj/Main.roc ─ I cannot find the end of this string: @@ -5815,7 +5652,7 @@ mod test_reporting { r#""""there is no end"#, indoc!( r#" - ── ENDLESS STRING ────────────────────────────────────────────────────────────── + ── ENDLESS STRING ──────────────────────────────────────── /code/proj/Main.roc ─ I cannot find the end of this block string: @@ -5842,7 +5679,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This `if` has an `else` branch with a different type from its `then` branch: @@ -5871,7 +5708,7 @@ mod test_reporting { report_problem_as( &format!(r#"if True then "abc" else 1 {} 2"#, $op), &format!( -r#"── TYPE MISMATCH ─────────────────────────────────────────────────────────────── +r#"── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This `if` has an `else` branch with a different type from its `then` branch: @@ -5917,22 +5754,14 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - This expression is used in an unexpected way: + This `foo` record doesn’t have a `if` field: 3β”‚ foo.if ^^^^^^ - This `foo` value is a: - - {} - - But you are trying to use it as: - - { if : a }b - - + In fact, `foo` is a record with no fields at all! "# ), ) @@ -5948,7 +5777,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── NOT EXPOSED ───────────────────────────────────────────────────────────────── + ── NOT EXPOSED ─────────────────────────────────────────── /code/proj/Main.roc ─ The Num module does not expose `if`: @@ -5976,7 +5805,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I trying to parse a record field access here: @@ -5990,32 +5819,7 @@ I need all branches in an `if` to have the same type! } #[test] - fn private_tag_not_uppercase() { - report_problem_as( - indoc!( - r#" - Num.add @foo 23 - "# - ), - indoc!( - r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── - - I am trying to parse a private tag here: - - 1β”‚ Num.add @foo 23 - ^ - - But after the `@` symbol I found a lowercase letter. All tag names - (global and private) must start with an uppercase letter, like @UUID - or @Secrets. - "# - ), - ) - } - - #[test] - fn private_tag_field_access() { + fn opaque_ref_field_access() { report_problem_as( indoc!( r#" @@ -6024,36 +5828,13 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am very confused by this field access: 1β”‚ @UUID.bar ^^^^ - It looks like a record field access on a private tag. - "# - ), - ) - } - - #[test] - fn opaque_ref_field_access() { - report_problem_as( - indoc!( - r#" - $UUID.bar - "# - ), - indoc!( - r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── - - I am very confused by this field access: - - 1β”‚ $UUID.bar - ^^^^ - It looks like a record field access on an opaque reference. "# ), @@ -6070,7 +5851,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am very confused by this field access @@ -6095,7 +5876,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I trying to parse a record field access here: @@ -6118,7 +5899,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── NAMING PROBLEM ────────────────────────────────────────────────────────────── + ── NAMING PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am trying to parse an identifier here: @@ -6175,7 +5956,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ I cannot find a `bar` value @@ -6184,10 +5965,10 @@ I need all branches in an `if` to have the same type! Did you mean one of these? - Nat Str Err - U8 + Box + Set "# ), ) @@ -6204,7 +5985,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNKNOWN OPERATOR ──────────────────────────────────────────────────────────── + ── UNKNOWN OPERATOR ────────────────────────────────────── /code/proj/Main.roc ─ This looks like an operator, but it's not one I recognize! @@ -6230,7 +6011,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNKNOWN OPERATOR ──────────────────────────────────────────────────────────── + ── UNKNOWN OPERATOR ────────────────────────────────────── /code/proj/Main.roc ─ This looks like an operator, but it's not one I recognize! @@ -6258,7 +6039,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNKNOWN OPERATOR ──────────────────────────────────────────────────────────── + ── UNKNOWN OPERATOR ────────────────────────────────────── /code/proj/Main.roc ─ This looks like an operator, but it's not one I recognize! @@ -6288,7 +6069,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNKNOWN OPERATOR ──────────────────────────────────────────────────────────── + ── UNKNOWN OPERATOR ────────────────────────────────────── /code/proj/Main.roc ─ This looks like an operator, but it's not one I recognize! @@ -6318,7 +6099,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── WEIRD PROVIDES ────────────────────────────────────────────────────────────── + ── WEIRD PROVIDES ──────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a provides list, but I got stuck here: @@ -6355,7 +6136,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── BAD REQUIRES ──────────────────────────────────────────────────────────────── + ── BAD REQUIRES ────────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a header, but I got stuck here: @@ -6383,14 +6164,14 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── WEIRD IMPORTS ─────────────────────────────────────────────────────────────── + ── WEIRD IMPORTS ───────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a header, but I got stuck here: 2β”‚ exposes [ main, Foo ] ^ - I am expecting the `imports` keyword next, like + I am expecting the `imports` keyword next, like imports [ Animal, default, tame ] "# @@ -6410,7 +6191,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── WEIRD EXPOSES ─────────────────────────────────────────────────────────────── + ── WEIRD EXPOSES ───────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing an `exposes` list, but I got stuck here: @@ -6438,7 +6219,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── WEIRD MODULE NAME ─────────────────────────────────────────────────────────── + ── WEIRD MODULE NAME ───────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a header, but got stuck here: @@ -6464,7 +6245,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── WEIRD APP NAME ────────────────────────────────────────────────────────────── + ── WEIRD APP NAME ──────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a header, but got stuck here: @@ -6490,7 +6271,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TOO MANY ARGS ─────────────────────────────────────────────────────────────── + ── TOO MANY ARGS ───────────────────────────────────────── /code/proj/Main.roc ─ This value is not a function, but it was given 2 arguments: @@ -6515,7 +6296,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TOO MANY ARGS ─────────────────────────────────────────────────────────────── + ── TOO MANY ARGS ───────────────────────────────────────── /code/proj/Main.roc ─ This value is not a function, but it was given 2 arguments: @@ -6541,7 +6322,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `x` definition: @@ -6571,7 +6352,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNFINISHED PARENTHESES ────────────────────────────────────────────────────── + ── UNFINISHED PARENTHESES ──────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a pattern in parentheses, but I got stuck here: @@ -6596,7 +6377,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNFINISHED PARENTHESES ────────────────────────────────────────────────────── + ── UNFINISHED PARENTHESES ──────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a pattern in parentheses, but I got stuck here: @@ -6621,7 +6402,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNFINISHED PARENTHESES ────────────────────────────────────────────────────── + ── UNFINISHED PARENTHESES ──────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a pattern in parentheses, but I got stuck here: @@ -6647,7 +6428,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── NEED MORE INDENTATION ─────────────────────────────────────────────────────── + ── NEED MORE INDENTATION ───────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a pattern in parentheses, but I got stuck here: @@ -6673,7 +6454,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNFINISHED PATTERN ────────────────────────────────────────────────────────── + ── UNFINISHED PATTERN ──────────────────────────────────── /code/proj/Main.roc ─ I just started parsing a pattern, but I got stuck here: @@ -6700,7 +6481,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── NEED MORE INDENTATION ─────────────────────────────────────────────────────── + ── NEED MORE INDENTATION ───────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a type in parentheses, but I got stuck here: @@ -6729,7 +6510,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd argument to `map` is not what I expect: @@ -6761,7 +6542,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ Underscore patterns are not allowed in definitions @@ -6784,7 +6565,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This `expect` condition needs to be a Bool: @@ -6807,7 +6588,7 @@ I need all branches in an `if` to have the same type! report_problem_as( indoc!( r#" - mult : Num *, F64 -> F64 + mult : Num.Num *, Num.F64 -> Num.F64 mult = \a, b -> a * b mult 0 0 @@ -6815,7 +6596,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd argument to `mul` is not what I expect: @@ -6830,11 +6611,11 @@ I need all branches in an `if` to have the same type! Num * - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `mult` definition: - 1β”‚ mult : Num *, F64 -> F64 + 1β”‚ mult : Num.Num *, Num.F64 -> Num.F64 2β”‚ mult = \a, b -> a * b ^^^^^ @@ -6855,7 +6636,7 @@ I need all branches in an `if` to have the same type! report_problem_as( indoc!( r#" - mult : Num a, F64 -> F64 + mult : Num.Num a, Num.F64 -> Num.F64 mult = \a, b -> a * b mult 0 0 @@ -6863,7 +6644,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd argument to `mul` is not what I expect: @@ -6878,11 +6659,11 @@ I need all branches in an `if` to have the same type! Num a - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `mult` definition: - 1β”‚ mult : Num a, F64 -> F64 + 1β”‚ mult : Num.Num a, Num.F64 -> Num.F64 2β”‚ mult = \a, b -> a * b ^^^^^ @@ -6903,6 +6684,8 @@ I need all branches in an `if` to have the same type! report_problem_as( indoc!( r#" + Result a b : [ Ok a, Err b ] + canIGo : _ -> Result _ canIGo = \color -> when color is @@ -6915,11 +6698,11 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TOO FEW TYPE ARGUMENTS ────────────────────────────────────────────────────── + ── TOO FEW TYPE ARGUMENTS ──────────────────────────────── /code/proj/Main.roc ─ The `Result` alias expects 2 type arguments, but it got 1 instead: - 1β”‚ canIGo : _ -> Result _ + 3β”‚ canIGo : _ -> Result _ ^^^^^^^^ Are there missing parentheses? @@ -6933,6 +6716,8 @@ I need all branches in an `if` to have the same type! report_problem_as( indoc!( r#" + Result a b : [ Ok a, Err b ] + canIGo : _ -> Result _ _ _ canIGo = \color -> when color is @@ -6945,11 +6730,11 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TOO MANY TYPE ARGUMENTS ───────────────────────────────────────────────────── + ── TOO MANY TYPE ARGUMENTS ─────────────────────────────── /code/proj/Main.roc ─ The `Result` alias expects 2 type arguments, but it got 3 instead: - 1β”‚ canIGo : _ -> Result _ _ _ + 3β”‚ canIGo : _ -> Result _ _ _ ^^^^^^^^^^^^ Are there missing parentheses? @@ -6971,7 +6756,7 @@ I need all branches in an `if` to have the same type! // TODO: We should tell the user that we inferred `_` as `a` indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `f` definition: @@ -7009,7 +6794,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `f` definition: @@ -7045,7 +6830,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `f` definition: @@ -7085,27 +6870,24 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - Something is off with the body of the `f` definition: + Something is off with the body of the `inner` definition: - 1β”‚ f : a, b, * -> * - 2β”‚ f = \_, _, x2 -> 3β”‚ inner : * -> * 4β”‚ inner = \y -> y - 5β”‚ inner x2 - ^^^^^^^^ + ^ - The type annotation on `f` says this `inner` call should have the type: + The type annotation on `inner` says this `y` value should have the type: * - However, the type of this `inner` call is connected to another type in a + However, the type of this `y` value is connected to another type in a way that isn't reflected in this annotation. Tip: Any connection between types must use a named type variable, not - a `*`! Maybe the annotation on `f` should have a named type variable in - place of the `*`? + a `*`! Maybe the annotation on `inner` should have a named type variable + in place of the `*`? "# ), ) @@ -7121,7 +6903,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── NOT AN INLINE ALIAS ───────────────────────────────────────────────────────── + ── NOT AN INLINE ALIAS ─────────────────────────────────── /code/proj/Main.roc ─ The inline type after this `as` is not a type alias: @@ -7145,7 +6927,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── QUALIFIED ALIAS NAME ──────────────────────────────────────────────────────── + ── QUALIFIED ALIAS NAME ────────────────────────────────── /code/proj/Main.roc ─ This type alias has a qualified name: @@ -7169,7 +6951,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE ARGUMENT NOT LOWERCASE ───────────────────────────────────────────────── + ── TYPE ARGUMENT NOT LOWERCASE ─────────────────────────── /code/proj/Main.roc ─ This alias type argument is not lowercase: @@ -7197,14 +6979,14 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `isEmpty` is not what I expect: 6β”‚ isEmpty (Name "boo") ^^^^^^^^^^ - This `Name` global tag application has the type: + This `Name` tag application has the type: [ Name Str ]a @@ -7230,14 +7012,14 @@ I need all branches in an `if` to have the same type! C a b : a -> D a b D a b : { a, b } - f : C a Nat -> D a Nat + f : C a Num.Nat -> D a Num.Nat f = \c -> c 6 f "# ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `c` is not what I expect: @@ -7274,7 +7056,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ The `F` alias is self-recursive in an invalid way: @@ -7301,7 +7083,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ The `F` alias is self-recursive in an invalid way: @@ -7327,7 +7109,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ The `F` alias is self-recursive in an invalid way: @@ -7346,17 +7128,17 @@ I need all branches in an `if` to have the same type! report_problem_as( indoc!( r#" - Job : [ @Job { inputs : List Str } ] + Job : [ Job { inputs : List Str } ] job : { inputs ? List Str } -> Job job = \{ inputs } -> - @Job { inputs } + Job { inputs } job { inputs: [ "build", "test" ] } "# ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `job` is weird: @@ -7384,33 +7166,33 @@ I need all branches in an `if` to have the same type! report_problem_as( indoc!( r#" - Job : [ @Job { inputs : List Job } ] + Job : [ Job { inputs : List Job } ] job : { inputs : List Str } -> Job job = \{ inputs } -> - @Job { inputs } + Job { inputs } job { inputs: [ "build", "test" ] } "# ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `job` definition: 3β”‚ job : { inputs : List Str } -> Job 4β”‚ job = \{ inputs } -> - 5β”‚ @Job { inputs } - ^^^^^^^^^^^^^^^ + 5β”‚ Job { inputs } + ^^^^^^^^^^^^^^ - This `@Job` private tag application has the type: + This `Job` tag application has the type: - [ @Job { inputs : List Str } ] + [ Job { inputs : List Str } ] But the type annotation on `job` says it should be: - [ @Job { inputs : List a } ] as a + [ Job { inputs : List a } ] as a "# ), ) @@ -7430,7 +7212,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── NESTED DATATYPE ───────────────────────────────────────────────────────────── + ── NESTED DATATYPE ─────────────────────────────────────── /code/proj/Main.roc ─ `Nested` is a nested datatype. Here is one recursive usage of it: @@ -7462,7 +7244,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── NESTED DATATYPE ───────────────────────────────────────────────────────────── + ── NESTED DATATYPE ─────────────────────────────────────── /code/proj/Main.roc ─ `Nested` is a nested datatype. Here is one recursive usage of it: @@ -7499,13 +7281,13 @@ I need all branches in an `if` to have the same type! report_problem_as( &format!(indoc!( r#" - use : {} -> U8 + use : Num.{} -> Num.U8 use {}{} "# ), bad_type, number, $suffix), &format!(indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `use` is not what I expect: @@ -7552,11 +7334,6 @@ I need all branches in an `if` to have the same type! typ.get_mut(0..1).unwrap().make_ascii_uppercase(); let bad_suffix = if $suffix == "u8" { "i8" } else { "u8" }; let bad_type = if $suffix == "u8" { "I8" } else { "U8" }; - let carets = "^".repeat(number.len() + $suffix.len()); - let kind = match $suffix { - "dec"|"f32"|"f64" => "floats", - _ => "integers", - }; report_problem_as( &format!(indoc!( @@ -7568,22 +7345,25 @@ I need all branches in an `if` to have the same type! ), number, bad_suffix, number, $suffix), &format!(indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - The 1st pattern in this `when` is causing a mismatch: + The branches of this `when` expression don't match the condition: - 2β”‚ {}{} -> 1 - {} + 1β”‚> when {}{} is + 2β”‚ {}{} -> 1 + 3β”‚ _ -> 1 - The first pattern is trying to match {}: + The `when` condition is an integer of type: {} - But the expression between `when` and `is` has the type: + But the branch patterns have type: {} + + The branches must be cases of the `when` condition's type! "# - ), number, $suffix, carets, kind, typ, bad_type), + ), number, bad_suffix, number, $suffix, bad_type, typ), ) } )*} @@ -7617,7 +7397,7 @@ I need all branches in an `if` to have the same type! // TODO: link to number suffixes indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This integer literal contains an invalid digit: @@ -7644,7 +7424,7 @@ I need all branches in an `if` to have the same type! // TODO: link to number suffixes indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This integer literal contains an invalid digit: @@ -7670,7 +7450,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── CONFLICTING NUMBER SUFFIX ─────────────────────────────────────────────────── + ── CONFLICTING NUMBER SUFFIX ───────────────────────────── /code/proj/Main.roc ─ This number literal is an integer, but it has a float suffix: @@ -7691,7 +7471,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── CONFLICTING NUMBER SUFFIX ─────────────────────────────────────────────────── + ── CONFLICTING NUMBER SUFFIX ───────────────────────────── /code/proj/Main.roc ─ This number literal is a float, but it has an integer suffix: @@ -7708,7 +7488,7 @@ I need all branches in an `if` to have the same type! "256u8", indoc!( r#" - ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ This integer literal overflows the type indicated by its suffix: @@ -7728,7 +7508,7 @@ I need all branches in an `if` to have the same type! "-1u8", indoc!( r#" - ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ This integer literal underflows the type indicated by its suffix: @@ -7748,7 +7528,7 @@ I need all branches in an `if` to have the same type! "65536u16", indoc!( r#" - ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ This integer literal overflows the type indicated by its suffix: @@ -7768,7 +7548,7 @@ I need all branches in an `if` to have the same type! "-1u16", indoc!( r#" - ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ This integer literal underflows the type indicated by its suffix: @@ -7788,7 +7568,7 @@ I need all branches in an `if` to have the same type! "4_294_967_296u32", indoc!( r#" - ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ This integer literal overflows the type indicated by its suffix: @@ -7808,7 +7588,7 @@ I need all branches in an `if` to have the same type! "-1u32", indoc!( r#" - ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ This integer literal underflows the type indicated by its suffix: @@ -7828,7 +7608,7 @@ I need all branches in an `if` to have the same type! "18_446_744_073_709_551_616u64", indoc!( r#" - ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ This integer literal overflows the type indicated by its suffix: @@ -7848,7 +7628,7 @@ I need all branches in an `if` to have the same type! "-1u64", indoc!( r#" - ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ This integer literal underflows the type indicated by its suffix: @@ -7868,7 +7648,7 @@ I need all branches in an `if` to have the same type! "-1u128", indoc!( r#" - ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ This integer literal underflows the type indicated by its suffix: @@ -7888,7 +7668,7 @@ I need all branches in an `if` to have the same type! "128i8", indoc!( r#" - ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ This integer literal overflows the type indicated by its suffix: @@ -7908,7 +7688,7 @@ I need all branches in an `if` to have the same type! "-129i8", indoc!( r#" - ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ This integer literal underflows the type indicated by its suffix: @@ -7928,7 +7708,7 @@ I need all branches in an `if` to have the same type! "32768i16", indoc!( r#" - ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ This integer literal overflows the type indicated by its suffix: @@ -7948,7 +7728,7 @@ I need all branches in an `if` to have the same type! "-32769i16", indoc!( r#" - ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ This integer literal underflows the type indicated by its suffix: @@ -7968,7 +7748,7 @@ I need all branches in an `if` to have the same type! "2_147_483_648i32", indoc!( r#" - ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ This integer literal overflows the type indicated by its suffix: @@ -7988,7 +7768,7 @@ I need all branches in an `if` to have the same type! "-2_147_483_649i32", indoc!( r#" - ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ This integer literal underflows the type indicated by its suffix: @@ -8008,7 +7788,7 @@ I need all branches in an `if` to have the same type! "9_223_372_036_854_775_808i64", indoc!( r#" - ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ This integer literal overflows the type indicated by its suffix: @@ -8028,7 +7808,7 @@ I need all branches in an `if` to have the same type! "-9_223_372_036_854_775_809i64", indoc!( r#" - ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ This integer literal underflows the type indicated by its suffix: @@ -8048,7 +7828,7 @@ I need all branches in an `if` to have the same type! "170_141_183_460_469_231_731_687_303_715_884_105_728i128", indoc!( r#" - ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ This integer literal overflows the type indicated by its suffix: @@ -8074,7 +7854,7 @@ I need all branches in an `if` to have the same type! // be used as ... because of its literal value" indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd argument to `get` is not what I expect: @@ -8104,7 +7884,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd argument to `get` is not what I expect: @@ -8135,7 +7915,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd argument to `get` is not what I expect: @@ -8166,20 +7946,23 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - The 1st pattern in this `when` is causing a mismatch: + The branches of this `when` expression don't match the condition: - 2β”‚ 1u8 -> 1 - ^^^ + 1β”‚> when -1 is + 2β”‚ 1u8 -> 1 + 3β”‚ _ -> 1 - The first pattern is trying to match integers: + The `when` condition is a number of type: + + I8, I16, I32, I64, I128, F32, F64, or Dec + + But the branch patterns have type: U8 - But the expression between `when` and `is` has the type: - - I8, I16, I32, I64, I128, F32, F64, or Dec + The branches must be cases of the `when` condition's type! "# ), ) @@ -8192,13 +7975,13 @@ I need all branches in an `if` to have the same type! r#" R a : [ Only (R a) ] - v : R U8 + v : R Str v "# ), indoc!( r#" - ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ The `R` alias is self-recursive in an invalid way: @@ -8219,13 +8002,13 @@ I need all branches in an `if` to have the same type! r#" R a : [ Only { very: [ Deep (R a) ] } ] - v : R U8 + v : R Str v "# ), indoc!( r#" - ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ The `R` alias is self-recursive in an invalid way: @@ -8247,28 +8030,28 @@ I need all branches in an `if` to have the same type! Foo a : [ Thing (Bar a) ] Bar a : [ Stuff (Foo a) ] - v : Bar U8 + v : Bar Str v "# ), indoc!( r#" - ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── - + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ + 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: - + β”Œβ”€β”€β”€β”€β”€β” β”‚ Foo β”‚ ↓ β”‚ Bar β””β”€β”€β”€β”€β”€β”˜ - + Recursion in aliases is only allowed if recursion happens behind a tagged union, at least one variant of which is not recursive. "# @@ -8281,10 +8064,12 @@ I need all branches in an `if` to have the same type! report_problem_as( indoc!( r#" + Result a b : [ Ok a, Err b ] + Foo a : [ Blah (Result (Bar a) []) ] Bar a : Foo a - v : Bar U8 + v : Bar Str v "# ), @@ -8297,16 +8082,16 @@ I need all branches in an `if` to have the same type! report_problem_as( indoc!( r#" - $Age 21 + @Age 21 "# ), indoc!( r#" - ── OPAQUE TYPE NOT DEFINED ───────────────────────────────────────────────────── + ── OPAQUE TYPE NOT DEFINED ─────────────────────────────── /code/proj/Main.roc ─ The opaque type Age referenced here is not defined: - 1β”‚ $Age 21 + 1β”‚ @Age 21 ^^^^ Note: It looks like there are no opaque types declared in this scope yet! @@ -8320,33 +8105,33 @@ I need all branches in an `if` to have the same type! report_problem_as( indoc!( r#" - Age : U8 + Age : Num.U8 - $Age 21 + @Age 21 "# ), indoc!( r#" - ── OPAQUE TYPE NOT DEFINED ───────────────────────────────────────────────────── + ── OPAQUE TYPE NOT DEFINED ─────────────────────────────── /code/proj/Main.roc ─ The opaque type Age referenced here is not defined: - 3β”‚ $Age 21 + 3β”‚ @Age 21 ^^^^ Note: There is an alias of the same name: - 1β”‚ Age : U8 + 1β”‚ Age : Num.U8 ^^^ Note: It looks like there are no opaque types declared in this scope yet! - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `Age` is not used anywhere in your code. - 1β”‚ Age : U8 - ^^^^^^^^ + 1β”‚ Age : Num.U8 + ^^^^^^^^^^^^ If you didn't intend on using `Age` then remove it so future readers of your code don't wonder why it is there. @@ -8360,28 +8145,28 @@ I need all branches in an `if` to have the same type! report_problem_as( indoc!( r#" - OtherModule.$Age 21 + OtherModule.@Age 21 "# ), - // TODO: get rid of the first error. Consider parsing OtherModule.$Age to completion + // TODO: get rid of the first error. Consider parsing OtherModule.@Age to completion // and checking it during can. The reason the error appears is because it is parsed as - // Apply(Error(OtherModule), [ $Age, 21 ]) + // Apply(Error(OtherModule), [ @Age, 21 ]) indoc!( r#" - ── OPAQUE TYPE NOT APPLIED ───────────────────────────────────────────────────── + ── OPAQUE TYPE NOT APPLIED ─────────────────────────────── /code/proj/Main.roc ─ This opaque type is not applied to an argument: - 1β”‚ OtherModule.$Age 21 + 1β”‚ OtherModule.@Age 21 ^^^^ Note: Opaque types always wrap exactly one argument! - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am trying to parse a qualified name here: - 1β”‚ OtherModule.$Age 21 + 1β”‚ OtherModule.@Age 21 ^ I was expecting to see an identifier next, like height. A complete @@ -8397,32 +8182,32 @@ I need all branches in an `if` to have the same type! indoc!( r#" age = - Age := U8 + Age := Num.U8 21u8 - $Age age + @Age age "# ), // TODO(opaques): there is a potential for a better error message here, if the usage of - // `$Age` can be linked to the declaration of `Age` inside `age`, and a suggestion to + // `@Age` can be linked to the declaration of `Age` inside `age`, and a suggestion to // raise that declaration to the outer scope. indoc!( r#" - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `Age` is not used anywhere in your code. - 2β”‚ Age := U8 - ^^^^^^^^^ + 2β”‚ Age := Num.U8 + ^^^^^^^^^^^^^ If you didn't intend on using `Age` then remove it so future readers of your code don't wonder why it is there. - ── OPAQUE TYPE NOT DEFINED ───────────────────────────────────────────────────── + ── OPAQUE TYPE NOT DEFINED ─────────────────────────────── /code/proj/Main.roc ─ The opaque type Age referenced here is not defined: - 5β”‚ $Age age + 5β”‚ @Age age ^^^^ Note: It looks like there are no opaque types declared in this scope yet! @@ -8443,7 +8228,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── MODULE NOT IMPORTED ───────────────────────────────────────────────────────── + ── MODULE NOT IMPORTED ─────────────────────────────────── /code/proj/Main.roc ─ The `Task` module is not imported: @@ -8467,10 +8252,10 @@ I need all branches in an `if` to have the same type! report_problem_as( indoc!( r#" - Age := U8 + Age := Num.U8 n : Age - n = $Age "" + n = @Age "" n "# @@ -8479,11 +8264,11 @@ I need all branches in an `if` to have the same type! // that the argument be a U8, and linking to the definitin! indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This expression is used in an unexpected way: - 4β”‚ n = $Age "" + 4β”‚ n = @Age "" ^^ This argument to an opaque type has type: @@ -8506,17 +8291,17 @@ I need all branches in an `if` to have the same type! F n := n if True - then $F "" - else $F {} + then @F "" + else @F {} "# ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This expression is used in an unexpected way: - 5β”‚ else $F {} + 5β”‚ else @F {} ^^ This argument to an opaque type has type: @@ -8546,7 +8331,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `v` definition: @@ -8576,9 +8361,9 @@ I need all branches in an `if` to have the same type! report_problem_as( indoc!( r#" - Age := U8 + Age := Num.U8 - f : Age -> U8 + f : Age -> Num.U8 f = \Age n -> n f @@ -8588,18 +8373,18 @@ I need all branches in an `if` to have the same type! // probably wants to change "Age" to "@Age"! indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - This pattern is being used in an unexpected way: + The 1st argument to `f` is weird: 4β”‚ f = \Age n -> n ^^^^^ - It is a `Age` tag of type: + The argument is a pattern that matches a `Age` tag of type: [ Age a ] - But it needs to match: + But the annotation on `f` says the 1st argument should be: Age @@ -8621,17 +8406,17 @@ I need all branches in an `if` to have the same type! \x -> when x is - $F A -> "" - $F {} -> "" + @F A -> "" + @F {} -> "" "# ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd pattern in this `when` does not match the previous ones: - 6β”‚ $F {} -> "" + 6β”‚ @F {} -> "" ^^^^^ The 2nd pattern is trying to matchF unwrappings of type: @@ -8656,25 +8441,33 @@ I need all branches in an `if` to have the same type! v : F [ A, B, C ] when v is - $F A -> "" - $F B -> "" + @F A -> "" + @F B -> "" "# ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - This `when` does not cover all the possibilities: + The branches of this `when` expression don't match the condition: 5β”‚> when v is - 6β”‚> $F A -> "" - 7β”‚> $F B -> "" + 6β”‚ @F A -> "" + 7β”‚ @F B -> "" - Other possibilities include: + This `v` value is a: - $F C + F [ A, B, C ] - I would have to crash if I saw one of those! Add branches for them! + But the branch patterns have type: + + F [ A, B ] + + The branches must be cases of the `when` condition's type! + + Tip: Looks like the branches are missing coverage of the `C` tag. + + Tip: Maybe you need to add a catch-all branch, like `_`? "# ), ) @@ -8687,26 +8480,26 @@ I need all branches in an `if` to have the same type! r#" F n := n - v : F U8 + v : F Num.U8 when v is - $F 1 -> "" - $F 2 -> "" + @F 1 -> "" + @F 2 -> "" "# ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This `when` does not cover all the possibilities: 5β”‚> when v is - 6β”‚> $F 1 -> "" - 7β”‚> $F 2 -> "" + 6β”‚> @F 1 -> "" + 7β”‚> @F 2 -> "" Other possibilities include: - $F _ + @F _ I would have to crash if I saw one of those! Add branches for them! "# @@ -8733,7 +8526,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `y` is not what I expect: @@ -8770,7 +8563,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This `when` does not cover all the possibilities: @@ -8792,18 +8585,18 @@ I need all branches in an `if` to have the same type! report_problem_as( indoc!( r#" - f : { x : Nat }U32 + f : { x : Num.Nat }[] f "# ), indoc!( r#" - ── INVALID_EXTENSION_TYPE ────────────────────────────────────────────────────── + ── INVALID_EXTENSION_TYPE ──────────────────────────────── /code/proj/Main.roc ─ This record extension type is invalid: - 1β”‚ f : { x : Nat }U32 - ^^^ + 1β”‚ f : { x : Num.Nat }[] + ^^ Note: A record extension variable can only contain a type variable or another record. @@ -8823,7 +8616,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── INVALID_EXTENSION_TYPE ────────────────────────────────────────────────────── + ── INVALID_EXTENSION_TYPE ──────────────────────────────── /code/proj/Main.roc ─ This tag union extension type is invalid: @@ -8843,18 +8636,18 @@ I need all branches in an `if` to have the same type! indoc!( r#" Type : [ Constructor UnknownType ] - + insertHelper : UnknownType, Type -> Type insertHelper = \h, m -> when m is - Constructor _ -> Constructor h + Constructor _ -> Constructor h insertHelper "# ), indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ I cannot find a `UnknownType` value @@ -8864,23 +8657,23 @@ I need all branches in an `if` to have the same type! Did you mean one of these? Type - Unsigned8 - Unsigned32 - Unsigned16 + True + Box + Ok - ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ I cannot find a `UnknownType` value 3β”‚ insertHelper : UnknownType, Type -> Type - ^^^^ + ^^^^^^^^^^^ Did you mean one of these? Type - Unsigned8 - Unsigned32 - Unsigned16 + True + insertHelper + Box "# ), ) @@ -8899,7 +8692,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNFINISHED ABILITY ────────────────────────────────────────────────────────── + ── UNFINISHED ABILITY ──────────────────────────────────── /code/proj/Main.roc ─ I was partway through parsing an ability definition, but I got stuck here: @@ -8917,6 +8710,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_demands_not_indented_with_first() { new_report_problem_as( + "ability_demands_not_indented_with_first", indoc!( r#" Eq has @@ -8928,7 +8722,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNFINISHED ABILITY ────────────────────────────────────────────────────────── + ── UNFINISHED ABILITY ─── tmp/ability_demands_not_indented_with_first/Test.roc ─ I was partway through parsing an ability definition, but I got stuck here: @@ -8945,6 +8739,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_demand_value_has_args() { new_report_problem_as( + "ability_demand_value_has_args", indoc!( r#" Eq has @@ -8955,7 +8750,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNFINISHED ABILITY ────────────────────────────────────────────────────────── + ── UNFINISHED ABILITY ───────────── tmp/ability_demand_value_has_args/Test.roc ─ I was partway through parsing an ability definition, but I got stuck here: @@ -8982,7 +8777,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNFINISHED ABILITY ────────────────────────────────────────────────────────── + ── UNFINISHED ABILITY ──────────────────────────────────── /code/proj/Main.roc ─ I was partway through parsing an ability definition, but I got stuck here: @@ -9002,19 +8797,19 @@ I need all branches in an `if` to have the same type! report_problem_as( indoc!( r#" - I : Int * + I : Num.Int * a : I a "# ), indoc!( r#" - ── UNBOUND TYPE VARIABLE ─────────────────────────────────────────────────────── + ── UNBOUND TYPE VARIABLE ───────────────────────────────── /code/proj/Main.roc ─ The definition of `I` has an unbound type variable: - 1β”‚ I : Int * - ^ + 1β”‚ I : Num.Int * + ^ Tip: Type variables must be bound before the `:`. Perhaps you intended to add a type parameter to this type? @@ -9028,19 +8823,19 @@ I need all branches in an `if` to have the same type! report_problem_as( indoc!( r#" - I := Int * + I := Num.Int * a : I a "# ), indoc!( r#" - ── UNBOUND TYPE VARIABLE ─────────────────────────────────────────────────────── + ── UNBOUND TYPE VARIABLE ───────────────────────────────── /code/proj/Main.roc ─ The definition of `I` has an unbound type variable: - 1β”‚ I := Int * - ^ + 1β”‚ I := Num.Int * + ^ Tip: Type variables must be bound before the `:=`. Perhaps you intended to add a type parameter to this type? @@ -9054,21 +8849,21 @@ I need all branches in an `if` to have the same type! report_problem_as( indoc!( r#" - I : [ A (Int *), B (Int *) ] + I : [ A (Num.Int *), B (Num.Int *) ] a : I a "# ), indoc!( r#" - ── UNBOUND TYPE VARIABLE ─────────────────────────────────────────────────────── + ── UNBOUND TYPE VARIABLE ───────────────────────────────── /code/proj/Main.roc ─ The definition of `I` has 2 unbound type variables. Here is one occurrence: - 1β”‚ I : [ A (Int *), B (Int *) ] - ^ + 1β”‚ I : [ A (Num.Int *), B (Num.Int *) ] + ^ Tip: Type variables must be bound before the `:`. Perhaps you intended to add a type parameter to this type? @@ -9082,19 +8877,19 @@ I need all branches in an `if` to have the same type! report_problem_as( indoc!( r#" - I : Int _ + I : Num.Int _ a : I a "# ), indoc!( r#" - ── UNBOUND TYPE VARIABLE ─────────────────────────────────────────────────────── + ── UNBOUND TYPE VARIABLE ───────────────────────────────── /code/proj/Main.roc ─ The definition of `I` has an unbound type variable: - 1β”‚ I : Int _ - ^ + 1β”‚ I : Num.Int _ + ^ Tip: Type variables must be bound before the `:`. Perhaps you intended to add a type parameter to this type? @@ -9108,19 +8903,19 @@ I need all branches in an `if` to have the same type! report_problem_as( indoc!( r#" - I : Int a + I : Num.Int a a : I a "# ), indoc!( r#" - ── UNBOUND TYPE VARIABLE ─────────────────────────────────────────────────────── + ── UNBOUND TYPE VARIABLE ───────────────────────────────── /code/proj/Main.roc ─ The definition of `I` has an unbound type variable: - 1β”‚ I : Int a - ^ + 1β”‚ I : Num.Int a + ^ Tip: Type variables must be bound before the `:`. Perhaps you intended to add a type parameter to this type? @@ -9132,32 +8927,33 @@ I need all branches in an `if` to have the same type! #[test] fn ability_bad_type_parameter() { new_report_problem_as( + "ability_bad_type_parameter", indoc!( r#" + app "test" provides [] to "./platform" + Hash a b c has hash : a -> U64 | a has Hash - - 1 "# ), indoc!( r#" - ── ABILITY HAS TYPE VARIABLES ────────────────────────────────────────────────── + ── ABILITY HAS TYPE VARIABLES ──────────────────────────── /code/proj/Main.roc ─ The definition of the `Hash` ability includes type variables: - 4β”‚ Hash a b c has - ^^^^^ + 3β”‚ Hash a b c has + ^^^^^ Abilities cannot depend on type variables, but their member values can! - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `Hash` is not used anywhere in your code. - 4β”‚ Hash a b c has - ^^^^ + 3β”‚ Hash a b c has + ^^^^ If you didn't intend on using `Hash` then remove it so future readers of your code don't wonder why it is there. @@ -9169,21 +8965,22 @@ I need all branches in an `if` to have the same type! #[test] fn alias_in_has_clause() { new_report_problem_as( + "alias_in_has_clause", indoc!( r#" app "test" provides [ hash ] to "./platform" - Hash has hash : a, b -> U64 | a has Hash, b has Bool + Hash has hash : a, b -> Num.U64 | a has Hash, b has Bool.Bool "# ), indoc!( r#" - ── HAS CLAUSE IS NOT AN ABILITY ──────────────────────────────────────────────── + ── HAS CLAUSE IS NOT AN ABILITY ────────────────────────── /code/proj/Main.roc ─ The type referenced in this "has" clause is not an ability: - 3β”‚ Hash has hash : a, b -> U64 | a has Hash, b has Bool - ^^^^ + 3β”‚ Hash has hash : a, b -> Num.U64 | a has Hash, b has Bool.Bool + ^^^^^^^^^ "# ), ) @@ -9192,6 +8989,7 @@ I need all branches in an `if` to have the same type! #[test] fn shadowed_type_variable_in_has_clause() { new_report_problem_as( + "shadowed_type_variable_in_has_clause", indoc!( r#" app "test" provides [ ab1 ] to "./platform" @@ -9201,7 +8999,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── DUPLICATE NAME ────────────────────────────────────────────────────────────── + ── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─ The `a` name is first defined here: @@ -9223,24 +9021,26 @@ I need all branches in an `if` to have the same type! #[test] fn alias_using_ability() { new_report_problem_as( + "alias_using_ability", indoc!( r#" + app "test" provides [ a ] to "./platform" + Ability has ab : a -> {} | a has Ability Alias : Ability a : Alias - a "# ), indoc!( r#" - ── ALIAS USES ABILITY ────────────────────────────────────────────────────────── + ── ALIAS USES ABILITY ──────────────────────────────────── /code/proj/Main.roc ─ The definition of the `Alias` aliases references the ability `Ability`: - 6β”‚ Alias : Ability - ^^^^^ + 5β”‚ Alias : Ability + ^^^^^ Abilities are not types, but you can add an ability constraint to a type variable `a` by writing @@ -9248,16 +9048,6 @@ I need all branches in an `if` to have the same type! | a has Ability at the end of the type. - - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── - - `ab` is not used anywhere in your code. - - 4β”‚ Ability has ab : a -> {} | a has Ability - ^^ - - If you didn't intend on using `ab` then remove it so future readers of - your code don't wonder why it is there. "# ), ) @@ -9266,41 +9056,32 @@ I need all branches in an `if` to have the same type! #[test] fn ability_shadows_ability() { new_report_problem_as( + "ability_shadows_ability", indoc!( r#" + app "test" provides [ ab ] to "./platform" + Ability has ab : a -> U64 | a has Ability Ability has ab1 : a -> U64 | a has Ability - - 1 "# ), indoc!( r#" - ── DUPLICATE NAME ────────────────────────────────────────────────────────────── + ── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─ The `Ability` name is first defined here: - 4β”‚ Ability has ab : a -> U64 | a has Ability - ^^^^^^^ + 3β”‚ Ability has ab : a -> U64 | a has Ability + ^^^^^^^ But then it's defined a second time here: - 6β”‚ Ability has ab1 : a -> U64 | a has Ability - ^^^^^^^ + 5β”‚ Ability has ab1 : a -> U64 | a has Ability + ^^^^^^^ Since these abilities have the same name, it's easy to use the wrong one on accident. Give one of them a new name. - - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── - - `ab` is not used anywhere in your code. - - 4β”‚ Ability has ab : a -> U64 | a has Ability - ^^ - - If you didn't intend on using `ab` then remove it so future readers of - your code don't wonder why it is there. "# ), ) @@ -9309,6 +9090,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_member_does_not_bind_ability() { new_report_problem_as( + "ability_member_does_not_bind_ability", indoc!( r#" app "test" provides [ ] to "./platform" @@ -9318,7 +9100,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── ABILITY MEMBER MISSING HAS CLAUSE ─────────────────────────────────────────── + ── ABILITY MEMBER MISSING HAS CLAUSE ───────────────────── /code/proj/Main.roc ─ The definition of the ability member `ab` does not include a `has` clause binding a type variable to the ability `Ability`: @@ -9333,7 +9115,7 @@ I need all branches in an `if` to have the same type! Otherwise, the function does not need to be part of the ability! - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `Ability` is not used anywhere in your code. @@ -9342,16 +9124,6 @@ I need all branches in an `if` to have the same type! If you didn't intend on using `Ability` then remove it so future readers of your code don't wonder why it is there. - - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── - - `ab` is not used anywhere in your code. - - 3β”‚ Ability has ab : {} -> {} - ^^ - - If you didn't intend on using `ab` then remove it so future readers of - your code don't wonder why it is there. "# ), ) @@ -9360,38 +9132,29 @@ I need all branches in an `if` to have the same type! #[test] fn ability_member_binds_extra_ability() { new_report_problem_as( + "ability_member_binds_extra_ability", indoc!( r#" app "test" provides [ eq ] to "./platform" - Eq has eq : a, a -> Bool | a has Eq - Hash has hash : a, b -> U64 | a has Eq, b has Hash + Eq has eq : a, a -> Bool.Bool | a has Eq + Hash has hash : a, b -> Num.U64 | a has Eq, b has Hash "# ), indoc!( r#" - ── ABILITY MEMBER HAS EXTRANEOUS HAS CLAUSE ──────────────────────────────────── + ── ABILITY MEMBER HAS EXTRANEOUS HAS CLAUSE ────────────── /code/proj/Main.roc ─ The definition of the ability member `hash` includes a has clause binding an ability it is not a part of: - 4β”‚ Hash has hash : a, b -> U64 | a has Eq, b has Hash - ^^^^^^^^ + 4β”‚ Hash has hash : a, b -> Num.U64 | a has Eq, b has Hash + ^^^^^^^^ Currently, ability members can only bind variables to the ability they are a part of. Hint: Did you mean to bind the `Hash` ability instead? - - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── - - `hash` is not used anywhere in your code. - - 4β”‚ Hash has hash : a, b -> U64 | a has Eq, b has Hash - ^^^^ - - If you didn't intend on using `hash` then remove it so future readers of - your code don't wonder why it is there. "# ), ) @@ -9400,66 +9163,72 @@ I need all branches in an `if` to have the same type! #[test] fn ability_member_binds_parent_twice() { new_report_problem_as( + "ability_member_binds_parent_twice", indoc!( r#" app "test" provides [ ] to "./platform" - Eq has eq : a, b -> Bool | a has Eq, b has Eq + Eq has eq : a, b -> Bool.Bool | a has Eq, b has Eq "# ), indoc!( r#" - ── ABILITY MEMBER BINDS MULTIPLE VARIABLES ───────────────────────────────────── + ── ABILITY MEMBER BINDS MULTIPLE VARIABLES ─────────────── /code/proj/Main.roc ─ The definition of the ability member `eq` includes multiple variables bound to the `Eq`` ability:` - 3β”‚ Eq has eq : a, b -> Bool | a has Eq, b has Eq - ^^^^^^^^^^^^^^^^^^ + 3β”‚ Eq has eq : a, b -> Bool.Bool | a has Eq, b has Eq + ^^^^^^^^^^^^^^^^^^ Ability members can only bind one type variable to their parent ability. Otherwise, I wouldn't know what type implements an ability by looking at specializations! Hint: Did you mean to only bind `a` to `Eq`? - - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── - - `eq` is not used anywhere in your code. - - 3β”‚ Eq has eq : a, b -> Bool | a has Eq, b has Eq - ^^ - - If you didn't intend on using `eq` then remove it so future readers of - your code don't wonder why it is there. "# ), ) } #[test] - fn has_clause_outside_of_ability() { + fn has_clause_not_on_toplevel() { new_report_problem_as( + "has_clause_outside_of_ability", indoc!( r#" - app "test" provides [ hash, f ] to "./platform" + app "test" provides [ f ] to "./platform" - Hash has hash : a -> U64 | a has Hash + Hash has hash : (a | a has Hash) -> Num.U64 - f : a -> U64 | a has Hash + f : a -> Num.U64 | a has Hash "# ), indoc!( r#" - ── ILLEGAL HAS CLAUSE ────────────────────────────────────────────────────────── + ── ILLEGAL HAS CLAUSE ──────────────────────────────────── /code/proj/Main.roc ─ A `has` clause is not allowed here: - 5β”‚ f : a -> U64 | a has Hash - ^^^^^^^^^^ + 3β”‚ Hash has hash : (a | a has Hash) -> Num.U64 + ^^^^^^^^^^ - `has` clauses can only be specified on the top-level type annotation of - an ability member. + `has` clauses can only be specified on the top-level type annotations. + + ── ABILITY MEMBER MISSING HAS CLAUSE ───────────────────── /code/proj/Main.roc ─ + + The definition of the ability member `hash` does not include a `has` + clause binding a type variable to the ability `Hash`: + + 3β”‚ Hash has hash : (a | a has Hash) -> Num.U64 + ^^^^ + + Ability members must include a `has` clause binding a type variable to + an ability, like + + a has Hash + + Otherwise, the function does not need to be part of the ability! "# ), ) @@ -9468,18 +9237,19 @@ I need all branches in an `if` to have the same type! #[test] fn ability_specialization_with_non_implementing_type() { new_report_problem_as( + "ability_specialization_with_non_implementing_type", indoc!( r#" app "test" provides [ hash ] to "./platform" - Hash has hash : a -> U64 | a has Hash + Hash has hash : a -> Num.U64 | a has Hash hash = \{} -> 0u64 "# ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with this specialization of `hash`: @@ -9506,6 +9276,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_specialization_does_not_match_type() { new_report_problem_as( + "ability_specialization_does_not_match_type", indoc!( r#" app "test" provides [ hash ] to "./platform" @@ -9514,16 +9285,16 @@ I need all branches in an `if` to have the same type! Id := U32 - hash = \$Id n -> n + hash = \@Id n -> n "# ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with this specialization of `hash`: - 7β”‚ hash = \$Id n -> n + 7β”‚ hash = \@Id n -> n ^^^^ This value is a declared specialization of type: @@ -9541,6 +9312,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_specialization_is_incomplete() { new_report_problem_as( + "ability_specialization_is_incomplete", indoc!( r#" app "test" provides [ eq, le ] to "./platform" @@ -9551,12 +9323,12 @@ I need all branches in an `if` to have the same type! Id := U64 - eq = \$Id m, $Id n -> m == n + eq = \@Id m, @Id n -> m == n "# ), indoc!( r#" - ── INCOMPLETE ABILITY IMPLEMENTATION ─────────────────────────────────────────── + ── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────── /code/proj/Main.roc ─ The type `Id` does not fully implement the ability `Eq`. The following specializations are missing: @@ -9570,7 +9342,7 @@ I need all branches in an `if` to have the same type! `eq`, specialized here: - 9β”‚ eq = \$Id m, $Id n -> m == n + 9β”‚ eq = \@Id m, @Id n -> m == n ^^ "# ), @@ -9580,6 +9352,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_specialization_overly_generalized() { new_report_problem_as( + "ability_specialization_overly_generalized", indoc!( r#" app "test" provides [ hash ] to "./platform" @@ -9592,7 +9365,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This specialization of `hash` is overly general: @@ -9621,6 +9394,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_specialization_conflicting_specialization_types() { new_report_problem_as( + "ability_specialization_conflicting_specialization_types", indoc!( r#" app "test" provides [ eq ] to "./platform" @@ -9631,16 +9405,16 @@ I need all branches in an `if` to have the same type! You := {} AndI := {} - eq = \$You {}, $AndI {} -> False + eq = \@You {}, @AndI {} -> False "# ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with this specialization of `eq`: - 9β”‚ eq = \$You {}, $AndI {} -> False + 9β”‚ eq = \@You {}, @AndI {} -> False ^^ This value is a declared specialization of type: @@ -9663,6 +9437,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_specialization_checked_against_annotation() { new_report_problem_as( + "ability_specialization_checked_against_annotation", indoc!( r#" app "test" provides [ hash ] to "./platform" @@ -9673,17 +9448,17 @@ I need all branches in an `if` to have the same type! Id := U64 hash : Id -> U32 - hash = \$Id n -> n + hash = \@Id n -> n "# ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of this definition: 8β”‚ hash : Id -> U32 - 9β”‚ hash = \$Id n -> n + 9β”‚ hash = \@Id n -> n ^ This `n` value is a: @@ -9694,11 +9469,11 @@ I need all branches in an `if` to have the same type! U32 - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with this specialization of `hash`: - 9β”‚ hash = \$Id n -> n + 9β”‚ hash = \@Id n -> n ^^^^ This value is a declared specialization of type: @@ -9716,6 +9491,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_specialization_called_with_non_specializing() { new_report_problem_as( + "ability_specialization_called_with_non_specializing", indoc!( r#" app "test" provides [ noGoodVeryBadTerrible ] to "./platform" @@ -9725,27 +9501,27 @@ I need all branches in an `if` to have the same type! Id := U64 - hash = \$Id n -> n + hash = \@Id n -> n User := {} noGoodVeryBadTerrible = { - nope: hash ($User {}), + nope: hash (@User {}), notYet: hash (A 1), } "# ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `hash` is not what I expect: 15β”‚ notYet: hash (A 1), ^^^ - This `A` global tag application has the type: + This `A` tag application has the type: [ A (Num a) ]b @@ -9753,11 +9529,11 @@ I need all branches in an `if` to have the same type! a | a has Hash - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This expression has a type that does not implement the abilities it's expected to: - 14β”‚ nope: hash ($User {}), + 14β”‚ nope: hash (@User {}), ^^^^^^^^ This User opaque wrapping has the type: @@ -9780,4 +9556,277 @@ I need all branches in an `if` to have the same type! ), ) } + + #[test] + fn ability_not_on_toplevel() { + new_report_problem_as( + "ability_not_on_toplevel", + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + main = + Hash has + hash : a -> U64 | a has Hash + + 123 + "# + ), + indoc!( + r#" + ── ABILITY NOT ON TOP-LEVEL ────────────────────────────── /code/proj/Main.roc ─ + + This ability definition is not on the top-level of a module: + + 4β”‚> Hash has + 5β”‚> hash : a -> U64 | a has Hash + + Abilities can only be defined on the top-level of a Roc module. + "# + ), + ) + } + + #[test] + fn expression_generalization_to_ability_is_an_error() { + new_report_problem_as( + "expression_generalization_to_ability_is_an_error", + indoc!( + r#" + app "test" provides [ hash, hashable ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + Id := U64 + hash = \@Id n -> n + + hashable : a | a has Hash + hashable = @Id 15 + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + Something is off with the body of the `hashable` definition: + + 9β”‚ hashable : a | a has Hash + 10β”‚ hashable = @Id 15 + ^^^^^^ + + This Id opaque wrapping has the type: + + Id + + But the type annotation on `hashable` says it should be: + + a | a has Hash + + Tip: The type annotation uses the type variable `a` to say that this + definition can produce any value implementing the `Hash` ability. But in + the body I see that it will only produce a `Id` value of a single + specific type. Maybe change the type annotation to be more specific? + Maybe change the code to be more general? + "# + ), + ) + } + + #[test] + fn ability_value_annotations_are_an_error() { + new_report_problem_as( + "ability_value_annotations_are_an_error", + indoc!( + r#" + app "test" provides [ result ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + mulHashes : Hash, Hash -> U64 + mulHashes = \x, y -> hash x * hash y + + Id := U64 + hash = \@Id n -> n + + Three := {} + hash = \@Three _ -> 3 + + result = mulHashes (@Id 100) (@Three {}) + "# + ), + indoc!( + r#" + ── ABILITY USED AS TYPE ────────────────────────────────── /code/proj/Main.roc ─ + + You are attempting to use the ability `Hash` as a type directly: + + 6β”‚ mulHashes : Hash, Hash -> U64 + ^^^^ + + Abilities can only be used in type annotations to constrain type + variables. + + Hint: Perhaps you meant to include a `has` annotation, like + + a has Hash + + ── ABILITY USED AS TYPE ────────────────────────────────── /code/proj/Main.roc ─ + + You are attempting to use the ability `Hash` as a type directly: + + 6β”‚ mulHashes : Hash, Hash -> U64 + ^^^^ + + Abilities can only be used in type annotations to constrain type + variables. + + Hint: Perhaps you meant to include a `has` annotation, like + + b has Hash + "# + ), + ) + } + + #[test] + fn branches_have_more_cases_than_condition() { + new_report_problem_as( + "branches_have_more_cases_than_condition", + indoc!( + r#" + foo : Bool -> Str + foo = \bool -> + when bool is + True -> "true" + False -> "false" + Wat -> "surprise!" + foo + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + The branches of this `when` expression don't match the condition: + + 6β”‚> when bool is + 7β”‚ True -> "true" + 8β”‚ False -> "false" + 9β”‚ Wat -> "surprise!" + + This `bool` value is a: + + Bool + + But the branch patterns have type: + + [ False, True, Wat ] + + The branches must be cases of the `when` condition's type! + "# + ), + ) + } + + #[test] + fn always_function() { + // from https://github.com/rtfeldman/roc/commit/1372737f5e53ee5bb96d7e1b9593985e5537023a + // There was a bug where this reported UnusedArgument("val") + // since it was used only in the returned function only. + // + // we want this to not give any warnings/errors! + report_problem_as( + indoc!( + r#" + always = \val -> \_ -> val + + always + "# + ), + "", + ) + } + + #[test] + fn imports_missing_comma() { + new_report_problem_as( + "imports_missing_comma", + indoc!( + r#" + app "test-missing-comma" + packages { pf: "platform" } + imports [ pf.Task Base64 ] + provides [ main, @Foo ] to pf + "# + ), + indoc!( + r#" + ── WEIRD IMPORTS ────────────────────────── tmp/imports_missing_comma/Test.roc ─ + + I am partway through parsing a imports list, but I got stuck here: + + 2β”‚ packages { pf: "platform" } + 3β”‚ imports [ pf.Task Base64 ] + ^ + + I am expecting a comma or end of list, like + + imports [ Math, Util ]"# + ), + ) + } + + #[test] + fn not_enough_cases_for_open_union() { + new_report_problem_as( + "branches_have_more_cases_than_condition", + indoc!( + r#" + foo : [A, B]a -> Str + foo = \it -> + when it is + A -> "" + foo + "# + ), + indoc!( + r#" + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ + + This `when` does not cover all the possibilities: + + 6β”‚> when it is + 7β”‚> A -> "" + + Other possibilities include: + + B + _ + + I would have to crash if I saw one of those! Add branches for them! + "# + ), + ) + } + + #[test] + fn issue_2778_specialization_is_not_a_redundant_pattern() { + new_report_problem_as( + "issue_2778_specialization_is_not_a_redundant_pattern", + indoc!( + r#" + formatColor = \color -> + when color is + Red -> "red" + Yellow -> "yellow" + _ -> "unknown" + + Red |> formatColor |> Str.concat (formatColor Orange) + "# + ), + "", // no problem + ) + } } diff --git a/roc-for-elm-programmers.md b/roc-for-elm-programmers.md index 204ad125a9..3c2b09c8d1 100644 --- a/roc-for-elm-programmers.md +++ b/roc-for-elm-programmers.md @@ -674,20 +674,24 @@ includes in its union." ## Opaque Types -The tags discussed in the previous section are globally available, which means -they cannot be used to create opaque types. +In Elm, you can choose to expose (or not) custom types' constructors in order to create [opaque types](http://sporto.github.io/elm-patterns/advanced/opaque-types.html). +Since Roc's _tags_ can be constructed in any module without importing anything, Roc has a separate +_opaque type_ language feature to enable information hiding. -*Private tags* let you create opaque types. They work just like the *global tags* -from the previous section, except: +Opaque types in Roc have some similarities to type aliases, but also some important differences. -* Private tags begin with an `@` (e.g. `@Foo` instead of `Foo`) -* Private tags are scoped to the current module, rather than globally scoped -* Private tags can only be instantiated in the current module +* Opaque type are defined with `:=` (e.g. `Username := Str` instead of `Username : Str`) +* You can get an _opaque wrapper_ by writing an `@` symbol before the name of an opaque type. For example, `@Username` would be an opaque wrapper for the opaque type `Username`. +* Applying an _opaque wrapper_ to another value creates an _opaque value_, whose type is the one referred to by the opaque wrapper. So the expression `@Username "Sasha"` has the type `Username`. +* Applying and destructuring opaque wrappers works like tags; you can write `@Username str = user` to unwrap an opaque wrapper's payload, just like you would with a tag payload. +* Opaque types can only be wrapped and unwrapped in the same module where the opaque type itself is defined. +* You can export opaque type names (e.g. `Username`) to other modules, allowing them to be used in type annotations, but there is no way to export the opaque wrappers themselves. This means that an opaque type can only be wrapped and unwrapped (using `@` syntax) in the same module where it was defined. +* Opaque types are only equal if their names are the same _and_ they were defined in the same module. -For example, suppose I define these inside the `Username` module: +As an example, suppose I define these inside the `Username` module: ```elm -Username : [ @Username Str ] +Username := Str fromStr : Str -> Username fromStr = \str -> @@ -697,16 +701,17 @@ toStr : Username -> Str toStr = \@Username str -> str ``` -I can now expose the `Username` type alias, which other modules can use as an opaque type. -It's not even syntactically possible for me to expose the `@Username` tag, -because `@` tags are not allowed in the exposing list. Only code written in this -`Username` module can instantiate a `@Username` value. +I can now expose the `Username` opaque type, which other modules can use in type annotations. +However, it's not even syntactically possible for me to expose the `@Username` opaque wrapper, +because `@` is not allowed in the `exposing` list. Only code written in this `Username` module +can use the `@Username` wrapper. -> If I were to write `@Username` inside another module (e.g. `Main`), it would compile, -> but that `@Username` would be type-incompatible with the one created inside the `Username` module. -> Even trying to use `==` on them would be a type mismatch, because I would be comparing -> a `[ Username.@Username Str ]*` with a `[ Main.@Username Str ]*`, which are incompatible. +> If I were to define `Username := Str` inside another module (e.g. `Main`) and use `@Username`, +> it would compile, but that `Username` opaque type would not be considered equal to the one defined in +> the `Username` module. Although both opaque types have the name `Username`, they were defined in +> different modules. That means the two `Username` types would be type-incompatible with each other, +> and even attempting to use `==` to compare them would be a type mismatch. ## Modules and Shadowing @@ -744,9 +749,9 @@ these expose is very important. All imports and exports in Roc are enumerated explicitly; there is no `..` syntax. -> Since neither global tags nor private tags have a notion of "importing variants" -> (global tags are always available in all modules, and private tags are -> never available in other modules), there's also no `exposing (Foo(..))` equivalent. +> Since tags are available in all modules, Roc does not have a notion of +> "importing variants", and there's also no `exposing (Foo(..))` equivalent. +> (Later on, we'll talk about how opaque types work in Roc.) Like Elm, Roc does not allow shadowing. @@ -1274,7 +1279,7 @@ Roc's standard library has these modules: Some differences to note: -* All these standard modules are imported by default into every module. They also expose all their types (e.g. `Bool`, `List`, `Result`) but they do not expose any values - not even `negate` or `not`. (`True`, `False`, `Ok`, and `Err` are all global tags, so they do not need to be exposed; they are globally available regardless!) +* All these standard modules are imported by default into every module. They also expose all their types (e.g. `Bool`, `List`, `Result`) but they do not expose any values - not even `negate` or `not`. (`True`, `False`, `Ok`, and `Err` are all tags, so they do not need to be exposed; they are globally available regardless!) * In Roc it's called `Str` instead of `String`. * `List` refers to something more like Elm's `Array`, as noted earlier. * No `Char`. This is by design. 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). @@ -1294,10 +1299,9 @@ Here are various Roc expressions involving operators, and what they desugar to. | `a - b` | `Num.sub a b` | | `a * b` | `Num.mul a b` | | `a / b` | `Num.div a b` | -| `a // b` | `Num.divFloor a b` | +| `a // b` | `Num.divTrunc a b` | | `a ^ b` | `Num.pow a b` | | `a % b` | `Num.rem a b` | -| `a %% b` | `Num.mod a b` | | `a >> b` | `Num.shr a b` | | `a << b` | `Num.shl a b` | | `-a` | `Num.neg a` | diff --git a/roc_std/Cargo.lock b/roc_std/Cargo.lock new file mode 100644 index 0000000000..5494d0afd5 --- /dev/null +++ b/roc_std/Cargo.lock @@ -0,0 +1,252 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "ctor" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "diff" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" + +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "getrandom" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "indoc" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7906a9fababaeacb774f72410e497a1d18de916322e33797bb2cd29baa23c9e" +dependencies = [ + "unindent", +] + +[[package]] +name = "libc" +version = "0.2.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "output_vt100" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" +dependencies = [ + "winapi", +] + +[[package]] +name = "pretty_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d5b548b725018ab5496482b45cb8bef21e9fed1858a6d674e3a8a0f0bb5d50" +dependencies = [ + "ansi_term", + "ctor", + "diff", + "output_vt100", +] + +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quickcheck" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" +dependencies = [ + "env_logger", + "log", + "rand", +] + +[[package]] +name = "quickcheck_macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b22a693222d716a9587786f37ac3f6b4faedb5b80c23914e7303ff5a1d8016e9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "roc_std" +version = "0.1.0" +dependencies = [ + "indoc", + "libc", + "pretty_assertions", + "quickcheck", + "quickcheck_macros", +] + +[[package]] +name = "syn" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "unindent" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514672a55d7380da379785a4d70ca8386c8883ff7eaae877be4d2081cebe73d8" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/roc_std/src/roc_list.rs b/roc_std/src/roc_list.rs index 6c0b797b73..7581e36eda 100644 --- a/roc_std/src/roc_list.rs +++ b/roc_std/src/roc_list.rs @@ -63,7 +63,7 @@ where let new_size = elements_offset + core::mem::size_of::() * (self.len() + slice.len()); let new_ptr = if let Some((elements, storage)) = self.elements_and_storage() { - // Decrement the lists refence count. + // Decrement the list's refence count. let mut copy = storage.get(); let is_unique = copy.decrease(); @@ -275,6 +275,15 @@ where } } +impl From<&[T]> for RocList +where + T: ReferenceCount, +{ + fn from(slice: &[T]) -> Self { + Self::from_slice(slice) + } +} + impl IntoIterator for RocList where T: ReferenceCount, diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml index d71698cd2f..1a2efaceb1 100644 --- a/test_utils/Cargo.toml +++ b/test_utils/Cargo.toml @@ -8,5 +8,6 @@ description = "Utility functions used all over the code base." [dependencies] pretty_assertions = "1.0.0" +remove_dir_all = "0.7.0" [dev-dependencies] diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index bca9060a5d..f5e00b7817 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -16,3 +16,33 @@ macro_rules! assert_multiline_str_eq { $crate::_pretty_assert_eq!($crate::DebugAsDisplay($a), $crate::DebugAsDisplay($b)) }; } + +/** + * Creates a temporary empty directory that gets deleted when this goes out of scope. + */ +pub struct TmpDir { + path: std::path::PathBuf, +} + +impl TmpDir { + pub fn new(dir: &str) -> TmpDir { + let path = std::path::Path::new(dir); + // ensure_empty_dir will fail if the dir doesn't already exist + std::fs::create_dir_all(path).unwrap(); + remove_dir_all::ensure_empty_dir(&path).unwrap(); + + let mut pathbuf = std::path::PathBuf::new(); + pathbuf.push(path); + TmpDir { path: pathbuf } + } + + pub fn path(&self) -> &std::path::Path { + self.path.as_path() + } +} + +impl Drop for TmpDir { + fn drop(&mut self) { + remove_dir_all::remove_dir_all(&self.path).unwrap(); + } +} diff --git a/version.txt b/version.txt index 27ffb11fc3..3fed276742 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ - \ No newline at end of file +(built from source) \ No newline at end of file