Merge remote-tracking branch 'origin/trunk' into i/2792

This commit is contained in:
Folkert 2022-05-06 20:17:53 +02:00
commit 07383e96f0
No known key found for this signature in database
GPG Key ID: 1F17F6FFD112B97C
363 changed files with 29960 additions and 14183 deletions

View File

@ -8,3 +8,17 @@ test-gen-wasm = "test -p roc_gen_wasm -p test_gen --no-default-features --featur
# opt-level=s Optimizations should focus more on size than speed
# lto=fat Spend extra effort on link-time optimization across crates
rustflags = ["-Copt-level=s", "-Clto=fat"]
[env]
# Debug flags. Keep this up-to-date with compiler/debug_flags/src/lib.rs.
# Set = "1" to turn a debug flag on.
ROC_PRETTY_PRINT_ALIAS_CONTENTS = "0"
ROC_PRINT_UNIFICATIONS = "0"
ROC_PRINT_MISMATCHES = "0"
ROC_VERIFY_RIGID_LET_GENERALIZED = "0"
ROC_PRINT_IR_AFTER_SPECIALIZATION = "0"
ROC_PRINT_IR_AFTER_RESET_REUSE = "0"
ROC_PRINT_IR_AFTER_REFCOUNT = "0"
ROC_DEBUG_ALIAS_ANALYSIS = "0"
ROC_PRINT_LLVM_FN_VERIFICATION = "0"
ROC_PRINT_LOAD_LOG = "0"

View File

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

View File

@ -2,6 +2,10 @@ on: [pull_request]
name: CI
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
RUST_BACKTRACE: 1

View File

@ -2,6 +2,10 @@ on: [pull_request]
name: SpellCheck
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
RUST_BACKTRACE: 1

View File

@ -72,3 +72,10 @@ Elliot Waite <1767836+elliotwaite@users.noreply.github.com>
zimt28 <1764689+zimt28@users.noreply.github.com>
Ananda Umamil <zweimach@zweimach.org>
SylvanSign <jake.d.bray@gmail.com>
Nikita Mounier <36044205+nikitamounier@users.noreply.github.com>
Cai Bingjun <62678643+C-BJ@users.noreply.github.com>
Jared Cone <jared.cone@gmail.com>
Sean Hagstrom <sean@seanhagstrom.com>
Kas Buunk <kasbuunk@icloud.com>
Oskar Hahn <mail@oshahn.de>
Nuno Ferreira <nunogcferreira@gmail.com>

View File

@ -39,6 +39,8 @@ Use `cargo run help` to see all subcommands.
To use the `repl` subcommand, execute `cargo run repl`.
Use `cargo build` to build the whole project.
> When using `nix-shell`, make sure that if you start `nix-shell` and then run `echo "$PATH" | tr ':' '\n'`, you see the `usr/bin` path listed after all the `/nix/…` paths. Otherwise you might get some nasty rust compilation errors!
#### Extra tips
If you plan on using `nix-shell` regularly, check out [direnv](https://direnv.net/) and [lorri](https://github.com/nix-community/lorri). Whenever you `cd` into `roc/`, they will automatically load the Nix dependencies into your current shell, so you never have to run nix-shell directly!
@ -105,9 +107,9 @@ 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.
### libcxb libraries
### libxcb libraries
You may see an error like this during builds:

597
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -26,6 +26,7 @@ members = [
"compiler/arena_pool",
"compiler/test_gen",
"compiler/roc_target",
"compiler/debug_flags",
"vendor/ena",
"vendor/inkwell",
"vendor/pathfinding",
@ -45,6 +46,7 @@ members = [
"utils",
"docs",
"linker",
"wasi-libc-sys",
]
exclude = [
"ci/bench-runner",

View File

@ -1,4 +1,4 @@
FROM rust:1.58.0-slim-bullseye # make sure to update rust-toolchain.toml and nixpkgs-unstable in sources.json too so that it uses the same rust version > search for cargo on unstable here: https://search.nixos.org/packages
FROM rust:1.60.0-slim-bullseye # make sure to update rust-toolchain.toml too so that everything uses the same rust version
WORKDIR /earthbuild
prep-debian:
@ -10,6 +10,7 @@ install-other-libs:
RUN apt -y install libxcb-shape0-dev libxcb-xfixes0-dev # for editor clipboard
RUN apt -y install libasound2-dev # for editor sounds
RUN apt -y install libunwind-dev pkg-config libx11-dev zlib1g-dev
RUN apt -y install unzip # for www/build.sh
install-zig-llvm-valgrind-clippy-rustfmt:
FROM +install-other-libs
@ -18,7 +19,7 @@ install-zig-llvm-valgrind-clippy-rustfmt:
# zig
RUN wget -c https://ziglang.org/download/0.8.0/zig-linux-x86_64-0.8.0.tar.xz --no-check-certificate
RUN tar -xf zig-linux-x86_64-0.8.0.tar.xz
RUN ln -s /earthbuild/zig-linux-x86_64-0.8.0/zig /usr/bin/zig
RUN ln -s /earthbuild/zig-linux-x86_64-0.8.0/zig /bin/zig
# zig builtins wasm tests
RUN apt -y install build-essential
RUN cargo install wasmer-cli --features "singlepass"
@ -53,21 +54,26 @@ install-zig-llvm-valgrind-clippy-rustfmt:
copy-dirs:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
COPY --dir cli cli_utils compiler docs editor ast code_markup error_macros highlight utils test_utils reporting repl_cli repl_eval repl_test repl_wasm roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./
COPY --dir cli cli_utils compiler docs editor ast code_markup error_macros highlight utils test_utils reporting repl_cli repl_eval repl_test repl_wasm repl_www roc_std vendor examples linker Cargo.toml Cargo.lock version.txt www wasi-libc-sys ./
test-zig:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
COPY --dir compiler/builtins/bitcode ./
RUN cd bitcode && ./run-tests.sh && ./run-wasm-tests.sh
check-clippy:
build-rust-test:
FROM +copy-dirs
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo test --locked --release --features with_sound --workspace --no-run && sccache --show-stats
check-clippy:
FROM +build-rust-test
RUN cargo clippy -V
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo clippy -- -D warnings
check-rustfmt:
FROM +copy-dirs
FROM +build-rust-test
RUN cargo fmt --version
RUN cargo fmt --all -- --check
@ -77,7 +83,7 @@ check-typos:
RUN typos
test-rust:
FROM +copy-dirs
FROM +build-rust-test
ENV RUST_BACKTRACE=1
# for race condition problem with cli test
ENV ROC_NUM_WORKERS=1
@ -100,6 +106,9 @@ test-rust:
# RUN echo "4" | cargo run --locked --release --features="target-x86" -- --target=x86_32 examples/benchmarks/NQueens.roc
# RUN --mount=type=cache,target=$SCCACHE_DIR \
# cargo test --locked --release --features with_sound --test cli_run i386 --features="i386-cli-run" && sccache --show-stats
# make sure doc generation works (that is, make sure build.sh returns status code 0)
RUN bash www/build.sh
verify-no-git-changes:
FROM +test-rust

5
FAQ.md
View File

@ -1,5 +1,10 @@
# Frequently Asked Questions
# Why make a new editor instead of making an LSP plugin for VSCode, Vim or Emacs?
The Roc editor is one of the key areas where we want to innovate. Constraining ourselves to a plugin for existing editors would severely limit our possibilities for innovation.
A key part of our editor will be the use of plugins that are shipped with libraries. Think of a regex visualizer, parser debugger, or color picker. For library authors, it would be most convenient to write these plugins in Roc. Trying to dynamically load library plugins (written in Roc) in for example VSCode seems very difficult.
## Is there syntax highlighting for Vim/Emacs/VS Code or a LSP?
Not currently. Although they will presumably exist someday, while Roc is in the early days there's actually a conscious

View File

@ -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).
<img src="https://www.noredink.com/assets/logo-red-black-f6989d7567cf90b349409137595e99c52d036d755b4403d25528e0fd83a3b084.svg" height="60" alt="NoRedInk logo"/>
[<img src="https://www.noredink.com/assets/logo-red-black-f6989d7567cf90b349409137595e99c52d036d755b4403d25528e0fd83a3b084.svg" height="60" alt="NoRedInk logo"/>](https://www.noredink.com/)
&nbsp;&nbsp;&nbsp;&nbsp;
[<img src="https://www.rwx.com/build/_assets/rwx_banner_transparent_cropped-RYV7W2KL.svg" height="60" alt="rwx logo"/>](https://www.rwx.com)
## Applications and Platforms
@ -79,7 +81,7 @@ The core Roc language and standard library include no I/O operations, which give
## Project Goals
Roc is in relatively early stages of development. It's currently possible to build both platforms and applications (see the [examples](https://github.com/rtfeldman/roc/tree/trunk/examples) folder for some examples that aren't particularly organized at the moment), although [documentation](https://github.com/rtfeldman/roc/tree/trunk/compiler/builtins/docs) is in even earlier stages than the compiler itself.
Roc is in relatively early stages of development. It's currently possible to build both platforms and applications (see the [examples](https://github.com/rtfeldman/roc/tree/trunk/examples) folder for some examples that aren't particularly organized at the moment), although [documentation](https://github.com/rtfeldman/roc/tree/trunk/compiler/builtins/roc) is in even earlier stages than the compiler itself.
Besides the above language design, a separate goal is for Roc to ship with an ambitiously boundary-pushing graphical editor. Not like "an IDE," but rather something that makes people say "I have never seen anything remotely like this outside of Bret Victor demos."

View File

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

View File

@ -19,6 +19,7 @@ roc_unify = { path = "../compiler/unify"}
roc_load = { path = "../compiler/load" }
roc_target = { path = "../compiler/roc_target" }
roc_error_macros = { path = "../error_macros" }
roc_reporting = { path = "../reporting" }
arrayvec = "0.7.2"
bumpalo = { version = "3.8.0", features = ["collections"] }
page_size = "0.4.2"

View File

@ -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<ModuleId, IdentIds>,
dep_idents: IdentIdsByModule,
aliases: MutMap<Symbol, Alias>,
exposed_imports: MutMap<Ident, (Symbol, Region)>,
mut exposed_symbols: MutSet<Symbol>,

View File

@ -27,7 +27,7 @@ use crate::{
},
env::Env,
},
mem_pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone},
mem_pool::{pool::Pool, pool_vec::PoolVec, shallow_clone::ShallowClone},
};
/// A presence constraint is an additive constraint that defines the lower bound
@ -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<Type2>,
pattern_expected: impl Fn(HumanIndex, Region) -> PExpected<Type2>,
expr_expected: Expected<Type2>,
) -> 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,
@ -1872,7 +1846,7 @@ fn num_float(pool: &mut Pool, range: TypeId) -> Type2 {
Type2::Alias(
Symbol::NUM_FLOAT,
PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool),
PoolVec::new(vec![range].into_iter(), pool),
num_num_id,
)
}
@ -1881,21 +1855,11 @@ 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),
PoolVec::new(vec![range].into_iter(), pool),
pool.add(alias_content),
)
}
@ -1910,44 +1874,23 @@ fn num_int(pool: &mut Pool, range: TypeId) -> Type2 {
Type2::Alias(
Symbol::NUM_INT,
PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool),
PoolVec::new(vec![range].into_iter(), pool),
num_num_id,
)
}
#[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,21 +1903,11 @@ 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),
PoolVec::new(vec![range].into_iter(), pool),
pool.add(alias_content),
)
}
@ -1983,24 +1916,11 @@ 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(),
pool,
),
PoolVec::new(vec![type_id].into_iter(), pool),
pool.add(alias_content),
)
}
@ -2275,7 +2195,7 @@ pub mod test_constrain {
}
#[test]
fn constrain_global_tag() {
fn constrain_tag() {
infer_eq(
indoc!(
r#"
@ -2286,18 +2206,6 @@ pub mod test_constrain {
)
}
#[test]
fn constrain_private_tag() {
infer_eq(
indoc!(
r#"
@Foo
"#
),
"[ @Foo ]*",
)
}
#[test]
fn constrain_call_and_accessor() {
infer_eq(

View File

@ -18,7 +18,7 @@ use roc_module::ident::Lowercase;
use roc_module::symbol::Symbol;
use roc_parse::ast::{self, TypeDef, TypeHeader, ValueDef as AstValueDef};
use roc_parse::pattern::PatternType;
use roc_problem::can::{Problem, RuntimeError};
use roc_problem::can::{Problem, RuntimeError, ShadowKind};
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use std::collections::HashMap;
@ -251,9 +251,10 @@ fn to_pending_def<'a>(
}
Err((original_region, loc_shadowed_symbol)) => {
env.problem(Problem::ShadowingInAnnotation {
env.problem(Problem::Shadowing {
original_region,
shadow: loc_shadowed_symbol,
kind: ShadowKind::Variable,
});
Some((Output::default(), PendingDef::InvalidAlias))

View File

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

View File

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

View File

@ -12,7 +12,7 @@ use roc_error_macros::todo_opaques;
use roc_module::symbol::{Interns, Symbol};
use roc_parse::ast::{StrLiteral, StrSegment};
use roc_parse::pattern::PatternType;
use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError};
use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError, ShadowKind};
use roc_region::all::Region;
use roc_types::subs::Variable;
@ -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
@ -161,6 +155,7 @@ pub fn to_pattern2<'a>(
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region,
shadow: shadow.clone(),
kind: ShadowKind::Variable,
}));
let name: &str = shadow.value.as_ref();
@ -270,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!(),
@ -312,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"),
}
}
@ -364,6 +338,7 @@ pub fn to_pattern2<'a>(
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region,
shadow: shadow.clone(),
kind: ShadowKind::Variable,
}));
// let shadowed = Pattern2::Shadowed {
@ -443,6 +418,7 @@ pub fn to_pattern2<'a>(
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region,
shadow: shadow.clone(),
kind: ShadowKind::Variable,
}));
// No matter what the other patterns
@ -503,7 +479,7 @@ pub fn symbols_from_pattern(pool: &Pool, initial: &Pattern2) -> Vec<Symbol> {
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);
@ -540,7 +516,7 @@ pub fn symbols_from_pattern(pool: &Pool, initial: &Pattern2) -> Vec<Symbol> {
pub fn get_identifier_string(pattern: &Pattern2, interns: &Interns) -> ASTResult<String> {
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),
@ -564,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));

View File

@ -19,11 +19,14 @@ use crate::mem_pool::shallow_clone::ShallowClone;
pub type TypeId = NodeId<Type2>;
const TYPE2_SIZE: () = assert!(std::mem::size_of::<Type2>() == 3 * 8 + 4);
#[derive(Debug)]
pub enum Type2 {
Variable(Variable), // 4B
Alias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 8B + 4B + pad
Alias(Symbol, PoolVec<TypeId>, TypeId), // 24B = 8B + 8B + 4B + pad
Opaque(Symbol, PoolVec<TypeId>, TypeId), // 24B = 8B + 8B + 4B + pad
AsAlias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 8B + 4B + pad
// 24B
@ -46,11 +49,6 @@ pub enum Type2 {
Erroneous(Problem2), // 24B
}
#[test]
fn type2_size() {
assert_eq!(std::mem::size_of::<Type2>(), 32); // 24B + pad
}
#[derive(Debug)]
pub enum Problem2 {
CanonicalizationProblem,
@ -74,6 +72,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 +102,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 +691,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;
@ -747,7 +733,7 @@ fn can_tags<'a>(
enum TypeApply {
Apply(Symbol, PoolVec<Type2>),
Alias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId),
Alias(Symbol, PoolVec<TypeId>, TypeId),
Erroneous(roc_types::types::Problem),
}
@ -849,7 +835,17 @@ fn to_type_apply<'a>(
// instantiate variables
Type2::substitute(env.pool, &substitutions, actual);
TypeApply::Alias(symbol, arguments, actual)
let type_arguments = PoolVec::with_capacity(arguments.len() as u32, env.pool);
for (node_id, type_id) in arguments
.iter_node_ids()
.zip(type_arguments.iter_node_ids())
{
let typ = env.pool[node_id].1;
env.pool[type_id] = typ;
}
TypeApply::Alias(symbol, type_arguments, actual)
}
None => TypeApply::Apply(symbol, argument_type_ids),
}

View File

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

View File

@ -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};
@ -47,7 +48,7 @@ fn to_type2(
SolvedType::Alias(symbol, solved_type_variables, _todo, solved_actual, _kind) => {
let type_variables = PoolVec::with_capacity(solved_type_variables.len() as u32, pool);
for (type_variable_node_id, (lowercase, solved_arg)) in type_variables
for (type_variable_node_id, solved_arg) in type_variables
.iter_node_ids()
.zip(solved_type_variables.iter())
{
@ -55,7 +56,7 @@ fn to_type2(
let node = pool.add(typ2);
pool[type_variable_node_id] = (PoolStr::new(lowercase.as_str(), pool), node);
pool[type_variable_node_id] = node;
}
let actual_typ2 = to_type2(pool, solved_actual, free_vars, var_store);
@ -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_str(ident.as_str()),
};
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_str(ident.as_str());
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<ModuleId, IdentIds>,
) -> 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(),

View File

@ -19,6 +19,7 @@ pub fn load_module(src_file: &Path) -> LoadedModule {
}),
subs_by_module,
TargetInfo::default_x86_64(),
roc_reporting::report::RenderTarget::ColorTerminal,
);
match loaded {

View File

@ -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<ErrorType>),
BadPattern(Region, PatternCategory, ErrorType, PExpected<ErrorType>),
@ -227,12 +228,16 @@ fn solve<'a>(
);
match unify(subs, actual, expected, Mode::EQ) {
Success(vars) => {
Success {
vars,
must_implement_ability: _,
} => {
// TODO(abilities) record deferred ability checks
introduce(subs, rank, pools, &vars);
state
}
Failure(vars, actual_type, expected_type) => {
Failure(vars, actual_type, expected_type, _bad_impl) => {
introduce(subs, rank, pools, &vars);
let problem = TypeError::BadExpr(
@ -267,7 +272,7 @@ fn solve<'a>(
//
// state
// }
// Failure(vars, _actual_type, _expected_type) => {
// Failure(vars, _actual_type, _expected_type, _bad_impl) => {
// introduce(subs, rank, pools, &vars);
//
// // ERROR NOT REPORTED
@ -320,13 +325,17 @@ fn solve<'a>(
);
match unify(subs, actual, expected, Mode::EQ) {
Success(vars) => {
Success {
vars,
must_implement_ability: _,
} => {
// TODO(abilities) record deferred ability checks
introduce(subs, rank, pools, &vars);
state
}
Failure(vars, actual_type, expected_type) => {
Failure(vars, actual_type, expected_type, _bad_impl) => {
introduce(subs, rank, pools, &vars);
let problem = TypeError::BadExpr(
@ -391,12 +400,16 @@ fn solve<'a>(
// TODO(ayazhafiz): presence constraints for Expr2/Type2
match unify(subs, actual, expected, Mode::EQ) {
Success(vars) => {
Success {
vars,
must_implement_ability: _,
} => {
// TODO(abilities) record deferred ability checks
introduce(subs, rank, pools, &vars);
state
}
Failure(vars, actual_type, expected_type) => {
Failure(vars, actual_type, expected_type, _bad_impl) => {
introduce(subs, rank, pools, &vars);
let problem = TypeError::BadPattern(
@ -699,12 +712,16 @@ fn solve<'a>(
let includes = type_to_var(arena, mempool, subs, rank, pools, cached_aliases, &tag_ty);
match unify(subs, actual, includes, Mode::PRESENT) {
Success(vars) => {
Success {
vars,
must_implement_ability: _,
} => {
// TODO(abilities) record deferred ability checks
introduce(subs, rank, pools, &vars);
state
}
Failure(vars, actual_type, expected_type) => {
Failure(vars, actual_type, expected_type, _bad_impl) => {
introduce(subs, rank, pools, &vars);
// TODO: do we need a better error type here?
@ -852,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
@ -882,7 +899,7 @@ fn type_to_variable<'a>(
let mut arg_vars = Vec::with_capacity(args.len());
for (_, arg_type_id) in args.iter(mempool) {
for arg_type_id in args.iter(mempool) {
let arg_type = mempool.get(*arg_type_id);
let arg_var = type_to_variable(arena, mempool, subs, rank, pools, cached, arg_type);
@ -894,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);
@ -1281,7 +1302,7 @@ fn adjust_rank_content(
use roc_types::subs::FlatType::*;
match content {
FlexVar(_) | RigidVar(_) | Error => group_rank,
FlexVar(_) | RigidVar(_) | FlexAbleVar(..) | RigidAbleVar(..) | Error => group_rank,
RecursionVar { .. } => group_rank,
@ -1536,7 +1557,7 @@ fn instantiate_rigids_help(
};
}
FlexVar(_) | Error => {}
FlexVar(_) | FlexAbleVar(_, _) | Error => {}
RecursionVar { structure, .. } => {
instantiate_rigids_help(subs, max_rank, pools, structure);
@ -1547,6 +1568,11 @@ fn instantiate_rigids_help(
subs.set(copy, make_descriptor(FlexVar(Some(name))));
}
RigidAbleVar(name, ability) => {
// what it's all about: convert the rigid var into a flex var
subs.set(copy, make_descriptor(FlexAbleVar(Some(name), ability)));
}
Alias(_, args, real_type_var, _) => {
for var_index in args.all_variables() {
let var = subs[var_index];
@ -1772,7 +1798,7 @@ fn deep_copy_var_help(
copy
}
FlexVar(_) | Error => copy,
FlexVar(_) | FlexAbleVar(_, _) | Error => copy,
RecursionVar {
opt_name,
@ -1797,6 +1823,12 @@ fn deep_copy_var_help(
copy
}
RigidAbleVar(name, ability) => {
subs.set(copy, make_descriptor(FlexAbleVar(Some(name), ability)));
copy
}
Alias(symbol, mut args, real_type_var, kind) => {
let mut new_args = Vec::with_capacity(args.all_variables().len());

View File

@ -65,28 +65,26 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "3.0.0-beta.2"
version = "3.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bd1061998a501ee7d4b6d449020df3266ca3124b941ec56cf2005c3779ca142"
checksum = "85a35a599b11c089a7f49105658d089b8f2cf0882993c17daf6de15285c2c35d"
dependencies = [
"atty",
"bitflags",
"clap_derive",
"clap_lex",
"indexmap",
"lazy_static",
"os_str_bytes",
"strsim",
"termcolor",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "clap_derive"
version = "3.0.0-beta.2"
version = "3.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "370f715b81112975b1b69db93e0b56ea4cd4e5002ac43b2da8474106a54096a1"
checksum = "a3aab4734e083b809aaf5794e14e756d1c798d2c69c7f7de7a09a2f5214993c1"
dependencies = [
"heck",
"proc-macro-error",
@ -95,6 +93,15 @@ dependencies = [
"syn",
]
[[package]]
name = "clap_lex"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213"
dependencies = [
"os_str_bytes",
]
[[package]]
name = "data-encoding"
version = "2.3.2"
@ -109,12 +116,9 @@ checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]]
name = "heck"
version = "0.3.3"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]]
name = "hermit-abi"
@ -188,9 +192,9 @@ checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
[[package]]
name = "os_str_bytes"
version = "2.4.0"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85"
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
[[package]]
name = "proc-macro-error"
@ -300,24 +304,9 @@ dependencies = [
[[package]]
name = "textwrap"
version = "0.12.1"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789"
dependencies = [
"unicode-width",
]
[[package]]
name = "unicode-segmentation"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
[[package]]
name = "unicode-width"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
[[package]]
name = "unicode-xid"
@ -331,17 +320,11 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.9.3"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasm-bindgen"

View File

@ -6,7 +6,7 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = "3.0.0-beta.2"
clap = { version = "3.1.15", features = ["derive"] }
regex = "1.5.4"
is_executable = "1.0.1"
ring = "0.16.20"

View File

@ -1,4 +1,4 @@
use clap::{AppSettings, Clap};
use clap::Parser;
use data_encoding::HEXUPPER;
use is_executable::IsExecutable;
use regex::Regex;
@ -160,8 +160,7 @@ fn remove(file_or_folder: &str) {
.unwrap_or_else(|_| panic!("Something went wrong trying to remove {}", file_or_folder));
}
#[derive(Clap)]
#[clap(setting = AppSettings::ColoredHelp)]
#[derive(Parser)]
struct OptionalArgs {
/// How many times to repeat the benchmarks. A single benchmark has to fail every for a regression to be reported.
#[clap(long, default_value = "3")]

View File

@ -61,24 +61,24 @@ roc_error_macros = { path = "../error_macros" }
roc_editor = { path = "../editor", optional = true }
roc_linker = { path = "../linker" }
roc_repl_cli = { path = "../repl_cli", optional = true }
clap = { version = "3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] }
clap = { version = "3.1.15", default-features = false, features = ["std", "color", "suggestions"] }
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 }
wasmer-wasi = { version = "2.2.1", optional = true }
# Wasmer singlepass compiler only works on x86_64.
[target.'cfg(target_arch = "x86_64")'.dependencies]
wasmer = { version = "2.0.0", optional = true, default-features = false, features = ["default-singlepass", "default-universal"] }
wasmer = { version = "2.2.1", optional = true, default-features = false, features = ["singlepass", "universal"] }
[target.'cfg(not(target_arch = "x86_64"))'.dependencies]
wasmer = { version = "2.0.0", optional = true, default-features = false, features = ["default-cranelift", "default-universal"] }
wasmer = { version = "2.2.1", optional = true, default-features = false, features = ["cranelift", "universal"] }
[dev-dependencies]
wasmer-wasi = "2.0.0"
wasmer-wasi = "2.2.1"
pretty_assertions = "1.0.0"
roc_test_utils = { path = "../test_utils" }
indoc = "1.0.3"
@ -88,10 +88,10 @@ cli_utils = { path = "../cli_utils" }
# Wasmer singlepass compiler only works on x86_64.
[target.'cfg(target_arch = "x86_64")'.dev-dependencies]
wasmer = { version = "2.0.0", default-features = false, features = ["default-singlepass", "default-universal"] }
wasmer = { version = "2.2.1", default-features = false, features = ["singlepass", "universal"] }
[target.'cfg(not(target_arch = "x86_64"))'.dev-dependencies]
wasmer = { version = "2.0.0", default-features = false, features = ["default-cranelift", "default-universal"] }
wasmer = { version = "2.2.1", default-features = false, features = ["cranelift", "universal"] }
[[bench]]
name = "time_bench"

View File

@ -1,11 +1,12 @@
use bumpalo::Bump;
use roc_build::{
link::{link, rebuild_host, LinkType},
program,
program::{self, Problems},
};
use roc_builtins::bitcode;
use roc_load::LoadingProblem;
use roc_mono::ir::OptLevel;
use roc_reporting::report::RenderTarget;
use roc_target::TargetInfo;
use std::path::PathBuf;
use std::time::{Duration, SystemTime};
@ -20,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,
}
@ -68,6 +53,8 @@ pub fn build_file<'a>(
src_dir.as_path(),
subs_by_module,
target_info,
// TODO: expose this from CLI?
RenderTarget::ColorTerminal,
)?;
use target_lexicon::Architecture;
@ -181,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(
@ -240,12 +227,20 @@ 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(|_| {
todo!("gracefully handle failing to surgically link");
.map_err(|err| {
todo!(
"gracefully handle failing to surgically link with error: {:?}",
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();
problems
} else {
let mut inputs = vec![
host_input_path.as_path().to_str().unwrap(),
@ -270,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();
@ -287,7 +286,7 @@ pub fn build_file<'a>(
Ok(BuiltFile {
binary_path,
outcome,
problems,
total_time,
})
}
@ -307,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();
@ -339,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()
})
}
@ -353,7 +348,7 @@ pub fn check_file(
src_dir: PathBuf,
roc_file_path: PathBuf,
emit_timings: bool,
) -> Result<usize, LoadingProblem> {
) -> 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
@ -369,6 +364,8 @@ pub fn check_file(
src_dir.as_path(),
subs_by_module,
target_info,
// TODO: expose this from CLI?
RenderTarget::ColorTerminal,
)?;
let buf = &mut String::with_capacity(1024);
@ -424,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,
))
}

View File

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

View File

@ -1,9 +1,10 @@
#[macro_use]
extern crate const_format;
use build::{BuildOutcome, BuiltFile};
use build::BuiltFile;
use bumpalo::Bump;
use clap::{App, AppSettings, Arg, ArgMatches};
use clap::Command;
use clap::{Arg, ArgMatches};
use roc_build::link::LinkType;
use roc_error_macros::user_error;
use roc_load::LoadingProblem;
@ -13,15 +14,17 @@ use std::io;
use std::path::Path;
use std::path::PathBuf;
use std::process;
use std::process::Command;
use target_lexicon::BinaryFormat;
use target_lexicon::{Architecture, OperatingSystem, Triple, X86_32Architecture};
use target_lexicon::{
Architecture, Environment, OperatingSystem, Triple, Vendor, X86_32Architecture,
};
pub mod build;
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";
@ -34,9 +37,10 @@ pub const FLAG_DEV: &str = "dev";
pub const FLAG_OPTIMIZE: &str = "optimize";
pub const FLAG_OPT_SIZE: &str = "opt-size";
pub const FLAG_LIB: &str = "lib";
pub const FLAG_NO_LINK: &str = "no-link";
pub const FLAG_TARGET: &str = "target";
pub const FLAG_TIME: &str = "time";
pub const FLAG_LINK: &str = "roc-linker";
pub const FLAG_LINKER: &str = "linker";
pub const FLAG_PRECOMPILED: &str = "precompiled-host";
pub const FLAG_VALGRIND: &str = "valgrind";
pub const FLAG_CHECK: &str = "check";
@ -45,200 +49,179 @@ 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";
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!")
.subcommand(App::new(CMD_BUILD)
const VERSION: &str = include_str!("../../version.txt");
pub fn build_app<'a>() -> Command<'a> {
let flag_optimize = Arg::new(FLAG_OPTIMIZE)
.long(FLAG_OPTIMIZE)
.help("Optimize the compiled program to run faster. (Optimization takes time to complete.)")
.requires(ROC_FILE)
.required(false);
let flag_opt_size = Arg::new(FLAG_OPT_SIZE)
.long(FLAG_OPT_SIZE)
.help("Optimize the compiled program to have a small binary size. (Optimization takes time to complete.)")
.required(false);
let flag_dev = Arg::new(FLAG_DEV)
.long(FLAG_DEV)
.help("Make compilation finish as soon as possible, at the expense of runtime performance.")
.required(false);
let flag_debug = Arg::new(FLAG_DEBUG)
.long(FLAG_DEBUG)
.help("Store LLVM debug information in the generated program.")
.requires(ROC_FILE)
.required(false);
let flag_valgrind = Arg::new(FLAG_VALGRIND)
.long(FLAG_VALGRIND)
.help("Some assembly instructions are not supported by valgrind, this flag prevents those from being output when building the host.")
.required(false);
let flag_time = Arg::new(FLAG_TIME)
.long(FLAG_TIME)
.help("Prints detailed compilation time information.")
.required(false);
let flag_linker = Arg::new(FLAG_LINKER)
.long(FLAG_LINKER)
.help("Sets which linker to use. The surgical linker is enabled by default only when building for wasm32 or x86_64 Linux, because those are the only targets it currently supports. Otherwise the legacy linker is used by default.")
.possible_values(["surgical", "legacy"])
.required(false);
let flag_precompiled = Arg::new(FLAG_PRECOMPILED)
.long(FLAG_PRECOMPILED)
.help("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"])
.required(false);
let app = Command::new("roc")
.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(Command::new(CMD_BUILD)
.about("Build a binary from the given .roc file, but don't run it")
.arg(
Arg::new(ROC_FILE)
.about("The .roc file to build")
.required(true),
)
.arg(
Arg::new(FLAG_OPTIMIZE)
.long(FLAG_OPTIMIZE)
.about("Optimize your compiled Roc program to run faster. (Optimization takes time to complete.)")
.required(false),
)
.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.)")
.required(false),
)
.arg(
Arg::new(FLAG_DEV)
.long(FLAG_DEV)
.about("Make compilation as fast as possible. (Runtime performance may suffer)")
.required(false),
)
.arg(flag_optimize.clone())
.arg(flag_opt_size.clone())
.arg(flag_dev.clone())
.arg(flag_debug.clone())
.arg(flag_time.clone())
.arg(flag_linker.clone())
.arg(flag_precompiled.clone())
.arg(flag_valgrind.clone())
.arg(
Arg::new(FLAG_TARGET)
.long(FLAG_TARGET)
.about("Choose a different target")
.default_value(Target::default().as_str())
.help("Choose a different target")
.default_value(Target::default().as_str())
.possible_values(Target::OPTIONS)
.required(false),
)
.arg(
Arg::new(FLAG_LIB)
.long(FLAG_LIB)
.about("Build a C library instead of an executable.")
.help("Build a C library instead of an executable.")
.required(false),
)
.arg(
Arg::new(FLAG_DEBUG)
.long(FLAG_DEBUG)
.about("Store LLVM debug information in the generated program")
.required(false),
)
.arg(
Arg::new(FLAG_TIME)
.long(FLAG_TIME)
.about("Prints detailed compilation time information.")
.required(false),
)
.arg(
Arg::new(FLAG_LINK)
.long(FLAG_LINK)
.about("Uses the roc linker instead of the system linker.")
.required(false),
)
.arg(
Arg::new(FLAG_PRECOMPILED)
.long(FLAG_PRECOMPILED)
.about("Assumes the host has been precompiled and skips recompiling the host.")
.required(false),
)
.arg(
Arg::new(FLAG_VALGRIND)
.long(FLAG_VALGRIND)
.about("Some assembly instructions are not supported by valgrind, this flag prevents those from being output when building the host.")
.required(false),
)
)
.subcommand(App::new(CMD_REPL)
.about("Launch the interactive Read Eval Print Loop (REPL)")
)
.subcommand(App::new(CMD_FORMAT)
.about("Format Roc code")
.arg(
Arg::new(DIRECTORY_OR_FILES)
.index(1)
.multiple_values(true)
.required(false))
.arg(
Arg::new(FLAG_CHECK)
.long(FLAG_CHECK)
.about("Checks that specified files are formatted. If formatting is needed, it will return a non-zero exit code.")
.required(false),
)
)
.subcommand(App::new(CMD_VERSION)
.about("Print version information")
)
.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")
.arg(
Arg::new(FLAG_TIME)
.long(FLAG_TIME)
.about("Prints detailed compilation time information.")
Arg::new(FLAG_NO_LINK)
.long(FLAG_NO_LINK)
.help("Does not link. Instead just outputs the `.o` file")
.required(false),
)
.arg(
Arg::new(ROC_FILE)
.about("The .roc file of an app to run")
.help("The .roc file to build")
.required(true),
)
)
.subcommand(Command::new(CMD_REPL)
.about("Launch the interactive Read Eval Print Loop (REPL)")
)
.subcommand(Command::new(CMD_RUN)
.about("Run a .roc file even if it has build errors")
.arg(flag_optimize.clone())
.arg(flag_opt_size.clone())
.arg(flag_dev.clone())
.arg(flag_debug.clone())
.arg(flag_time.clone())
.arg(flag_linker.clone())
.arg(flag_precompiled.clone())
.arg(flag_valgrind.clone())
.arg(
Arg::new(ROC_FILE)
.help("The .roc file of an app to run")
.required(true),
)
)
.subcommand(Command::new(CMD_FORMAT)
.about("Format a .roc file using standard Roc formatting")
.arg(
Arg::new(DIRECTORY_OR_FILES)
.index(1)
.multiple_values(true)
.required(false)
.allow_invalid_utf8(true))
.arg(
Arg::new(FLAG_CHECK)
.long(FLAG_CHECK)
.help("Checks that specified files are formatted. If formatting is needed, it will return a non-zero exit code.")
.required(false),
)
)
.subcommand(Command::new(CMD_VERSION)
.about(concatcp!("Print the Roc compilers version, which is currently ", VERSION)))
.subcommand(Command::new(CMD_CHECK)
.about("Check the code for problems, but doesnt build or run it")
.arg(flag_time.clone())
.arg(
Arg::new(ROC_FILE)
.help("The .roc file of an app to check")
.required(true),
)
)
.subcommand(
App::new(CMD_DOCS)
Command::new(CMD_DOCS)
.about("Generate documentation for Roc modules (Work In Progress)")
.arg(Arg::new(DIRECTORY_OR_FILES)
.index(1)
.multiple_values(true)
.required(false)
.about("The directory or files to build documentation for")
.help("The directory or files to build documentation for")
.allow_invalid_utf8(true)
)
)
.setting(AppSettings::TrailingVarArg)
.arg(
Arg::new(FLAG_OPTIMIZE)
.long(FLAG_OPTIMIZE)
.about("Optimize the compiled program to run faster. (Optimization takes time to complete.)")
.requires(ROC_FILE)
.required(false),
)
.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.)")
.required(false),
)
.arg(
Arg::new(FLAG_DEV)
.long(FLAG_DEV)
.about("Make compilation as fast as possible. (Runtime performance may suffer)")
.required(false),
)
.arg(
Arg::new(FLAG_DEBUG)
.long(FLAG_DEBUG)
.about("Store LLVM debug information in the generated program")
.requires(ROC_FILE)
.required(false),
)
.arg(
Arg::new(FLAG_TIME)
.long(FLAG_TIME)
.about("Prints detailed compilation time information.")
.required(false),
)
.arg(
Arg::new(FLAG_LINK)
.long(FLAG_LINK)
.about("Uses the roc linker instead of the system linker.")
.required(false),
)
.arg(
Arg::new(FLAG_PRECOMPILED)
.long(FLAG_PRECOMPILED)
.about("Assumes the host has been precompiled and skips recompiling the host.")
.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),
)
.trailing_var_arg(true)
.arg(flag_optimize)
.arg(flag_opt_size)
.arg(flag_dev)
.arg(flag_debug)
.arg(flag_time)
.arg(flag_linker)
.arg(flag_precompiled)
.arg(flag_valgrind)
.arg(
Arg::new(ROC_FILE)
.about("The .roc file of an app to build and run")
.help("The .roc file of an app to build and run")
.required(false),
)
.arg(
Arg::new(ARGS_FOR_APP)
.about("Arguments to pass into the app being run")
.help("Arguments to pass into the app being run")
.requires(ROC_FILE)
.multiple_values(true),
);
if cfg!(feature = "editor") {
app.subcommand(
App::new(CMD_EDIT)
Command::new(CMD_EDIT)
.about("Launch the Roc editor (Work In Progress)")
.arg(
Arg::new(DIRECTORY_OR_FILES)
.index(1)
.multiple_values(true)
.required(false)
.about("(optional) The directory or files to open on launch."),
.help("(optional) The directory or files to open on launch."),
),
)
} else {
@ -254,6 +237,7 @@ pub fn docs(files: Vec<PathBuf>) {
pub enum BuildConfig {
BuildOnly,
BuildAndRun { roc_file_arg_index: usize },
BuildAndRunIfNoErrors { roc_file_arg_index: usize },
}
pub enum FormatMode {
@ -261,18 +245,15 @@ pub enum FormatMode {
CheckOnly,
}
pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
pub fn build(
matches: &ArgMatches,
config: BuildConfig,
triple: Triple,
link_type: LinkType,
) -> io::Result<i32> {
use build::build_file;
use std::str::FromStr;
use BuildConfig::*;
let target = match matches.value_of(FLAG_TARGET) {
Some(name) => Target::from_str(name).unwrap(),
None => Target::default(),
};
let triple = target.to_triple();
let arena = Bump::new();
let filename = matches.value_of(ROC_FILE).unwrap();
@ -291,21 +272,20 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
let emit_debug_info = matches.is_present(FLAG_DEBUG);
let emit_timings = matches.is_present(FLAG_TIME);
let link_type = if matches.is_present(FLAG_LIB) {
LinkType::Dylib
// Use surgical linking when supported, or when explicitly requested with --linker surgical
let surgically_link = if matches.is_present(FLAG_LINKER) {
matches.value_of(FLAG_LINKER) == Some("surgical")
} else {
LinkType::Executable
roc_linker::supported(&link_type, &triple)
};
let surgically_link = matches.is_present(FLAG_LINK);
let precompiled = matches.is_present(FLAG_PRECOMPILED);
if surgically_link && !roc_linker::supported(&link_type, &triple) {
panic!(
"Link type, {:?}, with target, {}, not supported by roc linker",
link_type, triple
);
}
let precompiled = if matches.is_present(FLAG_PRECOMPILED) {
matches.value_of(FLAG_PRECOMPILED) == Some("true")
} else {
// When compiling for a different target, default to assuming a precompiled host.
// Otherwise compilation would most likely fail!
triple != Triple::host()
};
let path = Path::new(filename);
// Spawn the root task
@ -346,7 +326,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
match res_binary_path {
Ok(BuiltFile {
binary_path,
outcome,
problems,
total_time,
}) => {
match config {
@ -361,56 +341,128 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
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::<Vec<_>>();
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())
}
}
}
@ -427,11 +479,55 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
}
#[cfg(target_family = "unix")]
fn roc_run(cmd: &mut Command) -> io::Result<i32> {
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<i32> {
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::<Vec<_>>();
run_with_wasmer(generated_filename, &args);
return Ok(0);
}
_ => std::process::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
@ -496,67 +592,76 @@ fn run_with_wasmer(_wasm_path: &std::path::Path, _args: &[String]) {
println!("Running wasm files not support");
}
enum Target {
Host,
X86_32,
X86_64,
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Target {
System,
Linux32,
Linux64,
Wasm32,
}
impl Default for Target {
fn default() -> Self {
Target::Host
Target::System
}
}
impl Target {
const fn as_str(&self) -> &'static str {
use Target::*;
match self {
Target::Host => "host",
Target::X86_32 => "x86_32",
Target::X86_64 => "x86_64",
Target::Wasm32 => "wasm32",
System => "system",
Linux32 => "linux32",
Linux64 => "linux64",
Wasm32 => "wasm32",
}
}
/// NOTE keep up to date!
const OPTIONS: &'static [&'static str] = &[
Target::Host.as_str(),
Target::X86_32.as_str(),
Target::X86_64.as_str(),
Target::System.as_str(),
Target::Linux32.as_str(),
Target::Linux64.as_str(),
Target::Wasm32.as_str(),
];
fn to_triple(&self) -> Triple {
let mut triple = Triple::unknown();
pub fn to_triple(self) -> Triple {
use Target::*;
match self {
Target::Host => Triple::host(),
Target::X86_32 => {
triple.architecture = Architecture::X86_32(X86_32Architecture::I386);
triple.binary_format = BinaryFormat::Elf;
// TODO make this user-specified?
triple.operating_system = OperatingSystem::Linux;
triple
}
Target::X86_64 => {
triple.architecture = Architecture::X86_64;
triple.binary_format = BinaryFormat::Elf;
triple
}
Target::Wasm32 => {
triple.architecture = Architecture::Wasm32;
triple.binary_format = BinaryFormat::Wasm;
triple
}
System => Triple::host(),
Linux32 => Triple {
architecture: Architecture::X86_32(X86_32Architecture::I386),
vendor: Vendor::Unknown,
operating_system: OperatingSystem::Linux,
environment: Environment::Musl,
binary_format: BinaryFormat::Elf,
},
Linux64 => Triple {
architecture: Architecture::X86_64,
vendor: Vendor::Unknown,
operating_system: OperatingSystem::Linux,
environment: Environment::Musl,
binary_format: BinaryFormat::Elf,
},
Wasm32 => Triple {
architecture: Architecture::Wasm32,
vendor: Vendor::Unknown,
operating_system: OperatingSystem::Unknown,
environment: Environment::Unknown,
binary_format: BinaryFormat::Wasm,
},
}
}
}
impl From<&Target> for Triple {
fn from(target: &Target) -> Self {
target.to_triple()
}
}
impl std::fmt::Display for Target {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.as_str())
@ -564,15 +669,15 @@ impl std::fmt::Display for Target {
}
impl std::str::FromStr for Target {
type Err = ();
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"host" => Ok(Target::Host),
"x86_32" => Ok(Target::X86_32),
"x86_64" => Ok(Target::X86_64),
fn from_str(string: &str) -> Result<Self, Self::Err> {
match string {
"system" => Ok(Target::System),
"linux32" => Ok(Target::Linux32),
"linux64" => Ok(Target::Linux64),
"wasm32" => Ok(Target::Wasm32),
_ => Err(()),
_ => Err(format!("Roc does not know how to compile to {}", string)),
}
}
}

View File

@ -1,12 +1,16 @@
use roc_build::link::LinkType;
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,
build_app, docs, format, BuildConfig, FormatMode, Target, CMD_BUILD, CMD_CHECK, CMD_DOCS,
CMD_EDIT, CMD_FORMAT, CMD_REPL, CMD_RUN, CMD_VERSION, DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_LIB,
FLAG_NO_LINK, FLAG_TARGET, FLAG_TIME, ROC_FILE,
};
use roc_error_macros::user_error;
use roc_load::LoadingProblem;
use std::fs::{self, FileType};
use std::io;
use std::path::{Path, PathBuf};
use target_lexicon::Triple;
#[macro_use]
extern crate const_format;
@ -27,7 +31,12 @@ 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 },
Triple::host(),
LinkType::Executable,
)
}
None => {
@ -37,7 +46,46 @@ fn main() -> io::Result<()> {
}
}
}
Some((CMD_BUILD, matches)) => Ok(build(matches, BuildConfig::BuildOnly)?),
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 },
Triple::host(),
LinkType::Executable,
)
}
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)) => {
let target: Target = matches.value_of_t(FLAG_TARGET).unwrap_or_default();
let link_type = match (
matches.is_present(FLAG_LIB),
matches.is_present(FLAG_NO_LINK),
) {
(true, false) => LinkType::Dylib,
(true, true) => user_error!("build can only be one of `--lib` or `--no-link`"),
(false, true) => LinkType::None,
(false, false) => LinkType::Executable,
};
Ok(build(
matches,
BuildConfig::BuildOnly,
target.to_triple(),
link_type,
)?)
}
Some((CMD_CHECK, matches)) => {
let arena = bumpalo::Bump::new();
@ -47,9 +95,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)) => {

View File

@ -2,13 +2,11 @@
extern crate pretty_assertions;
extern crate bumpalo;
extern crate indoc;
extern crate roc_collections;
extern crate roc_load;
extern crate roc_module;
#[macro_use]
extern crate indoc;
#[cfg(test)]
mod cli_run {
use cli_utils::helpers::{
@ -25,11 +23,12 @@ mod cli_run {
use roc_collections::all::MutMap;
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
const TEST_SURGICAL_LINKER: bool = true;
const TEST_LEGACY_LINKER: bool = true;
// Surgical linker currently only supports linux x86_64.
// Surgical linker currently only supports linux x86_64,
// so we're always testing the legacy linker on other targets.
#[cfg(not(all(target_os = "linux", target_arch = "x86_64")))]
const TEST_SURGICAL_LINKER: bool = false;
const TEST_LEGACY_LINKER: bool = false;
#[cfg(not(target_os = "macos"))]
const ALLOW_VALGRIND: bool = true;
@ -62,13 +61,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 <ignored for test> 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) {
@ -176,8 +181,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());
@ -228,6 +233,7 @@ mod cli_run {
($($test_name:ident:$name:expr => $example:expr,)+) => {
$(
#[test]
#[allow(non_snake_case)]
fn $test_name() {
let dir_name = $name;
let example = $example;
@ -250,14 +256,9 @@ mod cli_run {
return;
}
}
"hello-gui" => {
// Since this one requires opening a window, we do `roc build` on it but don't run it.
if cfg!(all(target_os = "linux", target_arch = "x86_64")) {
// The surgical linker can successfully link this on Linux, but the legacy linker errors!
build_example(&file_name, &["--optimize", "--roc-linker"]);
} else {
build_example(&file_name, &["--optimize"]);
}
"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;
}
@ -288,14 +289,14 @@ mod cli_run {
example.use_valgrind,
);
// Also check with the surgical linker.
// Also check with the legacy linker.
if TEST_SURGICAL_LINKER {
if TEST_LEGACY_LINKER {
check_output_with_stdin(
&file_name,
example.stdin,
example.executable_filename,
&["--roc-linker"],
&["--linker", "legacy"],
example.input_file.and_then(|file| Some(example_file(dir_name, file))),
example.expected_ending,
example.use_valgrind,
@ -393,6 +394,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 +858,7 @@ mod cli_run {
&[],
indoc!(
r#"
UNRECOGNIZED NAME
UNRECOGNIZED NAME tests/known_bad/TypeError.roc
I cannot find a `d` value
@ -863,7 +872,9 @@ mod cli_run {
I8
F64
"#
1 error and 0 warnings found in <ignored for test> ms."#
),
);
}
@ -875,14 +886,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 <ignored for test> ms."#
),
);
}
@ -894,7 +907,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 +916,9 @@ mod cli_run {
Since Symbol isn't used, you don't need to import it.
"#
0 errors and 1 warning found in <ignored for test> ms."#
),
);
}
@ -915,7 +930,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 +940,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 <ignored for test> ms."#
),
);
}

View File

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

View File

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

View File

@ -165,9 +165,19 @@ The compiler is invoked from the CLI via `build_file` in cli/src/build.rs
For a more detailed understanding of the compilation phases, see the `Phase`, `BuildTask`, and `Msg` enums in `load/src/file.rs`.
## Debugging intermediate representations
## Debugging the compiler
### The mono IR
Please see the [debug flags](./debug_flags/src/lib.rs) for information on how to
ask the compiler to emit debug information during various stages of compilation.
There are some goals for more sophisticated debugging tools:
- A nicer unification debugger, see https://github.com/rtfeldman/roc/issues/2486.
Any interest in helping out here is greatly appreciated.
### General Tips
#### Miscompilations
If you observe a miscomplication, you may first want to check the generated mono
IR for your code - maybe there was a problem during specialization or layout
@ -175,13 +185,16 @@ generation. One way to do this is to add a test to `test_mono/src/tests.rs`
and run the tests with `cargo test -p test_mono`; this will write the mono
IR to a file.
You may also want to set some or all of the following environment variables:
#### Typechecking errors
- `PRINT_IR_AFTER_SPECIALIZATION=1` prints the mono IR after function
specialization to stdout
- `PRINT_IR_AFTER_RESET_REUSE=1` prints the mono IR after insertion of
reset/reuse isntructions to stdout
- `PRINT_IR_AFTER_REFCOUNT=1` prints the mono IR after insertion of reference
counting instructions to stdout
- `PRETTY_PRINT_IR_SYMBOLS=1` instructs the pretty printer to dump all the
information it knows about the mono IR whenever it is printed
First, try to minimize your reproduction into a test that fits in
[`solve_expr`](./solve/tests/solve_expr.rs).
Once you've done this, check out the `ROC_PRINT_UNIFICATIONS` debug flag. It
will show you where type unification went right and wrong. This is usually
enough to figure out a fix for the bug.
If that doesn't work and you know your error has something to do with ranks,
you may want to instrument `deep_copy_var_help` in [solve](./solve/src/solve.rs).
If that doesn't work, chatting on Zulip is always a good strategy.

View File

@ -10,4 +10,4 @@ morphic_lib = {path = "../../vendor/morphic_lib"}
roc_collections = {path = "../collections"}
roc_module = {path = "../module"}
roc_mono = {path = "../mono"}
roc_debug_flags = {path = "../debug_flags"}

View File

@ -26,8 +26,16 @@ pub fn func_name_bytes(proc: &Proc) -> [u8; SIZE] {
func_name_bytes_help(proc.name, proc.args.iter().map(|x| x.0), &proc.ret_layout)
}
const DEBUG: bool = false;
const SIZE: usize = if DEBUG { 50 } else { 16 };
#[inline(always)]
fn debug() -> bool {
use roc_debug_flags::{dbg_do, ROC_DEBUG_ALIAS_ANALYSIS};
dbg_do!(ROC_DEBUG_ALIAS_ANALYSIS, {
return true;
});
false
}
const SIZE: usize = 16;
#[derive(Debug, Clone, Copy, Hash)]
struct TagUnionId(u64);
@ -87,7 +95,7 @@ where
*target = *source;
}
if DEBUG {
if debug() {
for (i, c) in (format!("{:?}", symbol)).chars().take(25).enumerate() {
name_bytes[25 + i] = c as u8;
}
@ -175,7 +183,7 @@ where
}
}
if DEBUG {
if debug() {
eprintln!(
"{:?}: {:?} with {:?} args",
proc.name,
@ -239,7 +247,7 @@ where
p.build()?
};
if DEBUG {
if debug() {
eprintln!("{}", program.to_source_string());
}
@ -279,7 +287,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 +317,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 +365,9 @@ fn proc_spec<'a>(proc: &Proc<'a>) -> Result<(FuncDef, MutSet<UnionLayout<'a>>)>
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 +471,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 +518,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 +574,15 @@ fn build_recursive_tuple_type(
builder.add_tuple_type(&field_types)
}
fn build_tuple_type(builder: &mut impl TypeContext, layouts: &[Layout]) -> Result<TypeId> {
fn build_tuple_type(
builder: &mut impl TypeContext,
layouts: &[Layout],
when_recursive: &WhenRecursive,
) -> Result<TypeId> {
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 +713,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 +783,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 +805,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 +830,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 +853,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 +878,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 +908,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 +933,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 +968,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 +1009,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 +1046,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 +1124,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 +1147,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 +1167,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 +1180,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 +1243,7 @@ fn lowlevel_spec(
) -> Result<ValueId> {
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 +1365,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 +1409,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 +1520,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 +1635,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 +1662,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 +1701,24 @@ fn literal_spec(
}
}
fn layout_spec(builder: &mut impl TypeContext, layout: &Layout) -> Result<TypeId> {
layout_spec_help(builder, layout, &WhenRecursive::Unreachable)
fn layout_spec(
builder: &mut impl TypeContext,
layout: &Layout,
when_recursive: &WhenRecursive,
) -> Result<TypeId> {
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<Vec<TypeId>> {
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 +1750,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(_)

View File

@ -24,12 +24,14 @@ roc_gen_llvm = { path = "../gen_llvm", optional = true }
roc_gen_wasm = { path = "../gen_wasm", optional = true }
roc_gen_dev = { path = "../gen_dev", default-features = false }
roc_reporting = { path = "../../reporting" }
roc_error_macros = { path = "../../error_macros" }
roc_std = { path = "../../roc_std", default-features = false }
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"
wasi_libc_sys = { path = "../../wasi-libc-sys" }
[target.'cfg(target_os = "macos")'.dependencies]
serde_json = "1.0.69"

View File

@ -2,6 +2,7 @@ use crate::target::{arch_str, target_zig_str};
#[cfg(feature = "llvm")]
use libloading::{Error, Library};
use roc_builtins::bitcode;
use roc_error_macros::internal_error;
// #[cfg(feature = "llvm")]
use roc_mono::ir::OptLevel;
use std::collections::HashMap;
@ -20,10 +21,10 @@ fn zig_executable() -> String {
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum LinkType {
// These numbers correspond to the --lib flag; if it's present
// (e.g. is_present returns `1 as bool`), this will be 1 as well.
// These numbers correspond to the --lib and --no-link flags
Executable = 0,
Dylib = 1,
None = 2,
}
/// input_paths can include the host as well as the app. e.g. &["host.o", "roc_app.o"]
@ -71,16 +72,12 @@ fn find_zig_str_path() -> PathBuf {
}
fn find_wasi_libc_path() -> PathBuf {
let wasi_libc_path = PathBuf::from("compiler/builtins/bitcode/wasi-libc.a");
use wasi_libc_sys::WASI_LIBC_PATH;
if std::path::Path::exists(&wasi_libc_path) {
return wasi_libc_path;
}
// when running the tests, we start in the /cli directory
let wasi_libc_path = PathBuf::from("../compiler/builtins/bitcode/wasi-libc.a");
if std::path::Path::exists(&wasi_libc_path) {
return wasi_libc_path;
// Environment variable defined in wasi-libc-sys/build.rs
let wasi_libc_pathbuf = PathBuf::from(WASI_LIBC_PATH);
if std::path::Path::exists(&wasi_libc_pathbuf) {
return wasi_libc_pathbuf;
}
panic!("cannot find `wasi-libc.a`")
@ -835,6 +832,7 @@ fn link_linux(
output_path,
)
}
LinkType::None => internal_error!("link_linux should not be called with link type of none"),
};
let env_path = env::var("PATH").unwrap_or_else(|_| "".to_string());
@ -904,6 +902,7 @@ fn link_macos(
("-dylib", output_path)
}
LinkType::None => internal_error!("link_macos should not be called with link type of none"),
};
let arch = match target.architecture {
@ -1069,7 +1068,7 @@ pub fn module_to_dylib(
opt_level: OptLevel,
) -> Result<Library, Error> {
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");
@ -1080,9 +1079,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)
@ -1102,6 +1100,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) }
}

View File

@ -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<ModuleId, (PathBuf, Box<str>)>,
interns: &Interns,
can_problems: &mut MutMap<ModuleId, Vec<roc_problem::can::Problem>>,
type_problems: &mut MutMap<ModuleId, Vec<roc_solve::solve::TypeError>>,
mono_problems: &mut MutMap<ModuleId, Vec<roc_mono::ir::MonoProblem>>,
) -> 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)

View File

@ -5,7 +5,7 @@ use inkwell::{
};
#[cfg(feature = "llvm")]
use roc_mono::ir::OptLevel;
use target_lexicon::{Architecture, OperatingSystem, Triple};
use target_lexicon::{Architecture, Environment, OperatingSystem, Triple};
pub fn target_triple_str(target: &Triple) -> &'static str {
// Best guide I've found on how to determine these magic strings:
@ -57,11 +57,23 @@ pub fn target_zig_str(target: &Triple) -> &'static str {
// and an open proposal to unify them with the more typical "target triples":
// https://github.com/ziglang/zig/issues/4911
match target {
Triple {
architecture: Architecture::X86_64,
operating_system: OperatingSystem::Linux,
environment: Environment::Musl,
..
} => "x86_64-linux-musl",
Triple {
architecture: Architecture::X86_64,
operating_system: OperatingSystem::Linux,
..
} => "x86_64-linux-gnu",
Triple {
architecture: Architecture::X86_32(target_lexicon::X86_32Architecture::I386),
operating_system: OperatingSystem::Linux,
environment: Environment::Musl,
..
} => "i386-linux-musl",
Triple {
architecture: Architecture::X86_32(target_lexicon::X86_32Architecture::I386),
operating_system: OperatingSystem::Linux,
@ -135,19 +147,28 @@ pub fn target_machine(
target: &Triple,
opt: OptimizationLevel,
reloc: RelocMode,
model: CodeModel,
) -> Option<TargetMachine> {
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,
)
}

View File

@ -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.
@ -60,7 +60,7 @@ Its one thing to actually write these functions, its _another_ thing to let the
## Specifying how we pass args to the function
### builtins/mono/src/borrow.rs
After we have all of this, we need to specify if the arguments we're passing are owned, borrowed or irrelevant. Towards the bottom of this file, add a new case for you builtin and specify each arg. Be sure to read the comment, as it explains this in more detail.
After we have all of this, we need to specify if the arguments we're passing are owned, borrowed or irrelevant. Towards the bottom of this file, add a new case for your builtin and specify each arg. Be sure to read the comment, as it explains this in more detail.
## Testing it
### solve/tests/solve_expr.rs
@ -87,7 +87,7 @@ In this directory, there are a couple files like `gen_num.rs`, `gen_str.rs`, etc
fn atan() {
assert_evals_to!("Num.atan 10", 1.4711276743037347, f64);
}
```
```
But replace `Num.atan`, the return value, and the return type with your new builtin.
# Mistakes that are easy to make!!

View File

@ -28,12 +28,14 @@ pub fn build(b: *Builder) void {
// TODO allow for native target for maximum speed
},
});
const i386_target = makeI386Target();
const linux32_target = makeLinux32Target();
const linux64_target = makeLinux64Target();
const wasm32_target = makeWasm32Target();
// LLVM IR
generateLlvmIrFile(b, mode, host_target, main_path, "ir", "builtins-host");
generateLlvmIrFile(b, mode, i386_target, main_path, "ir-i386", "builtins-i386");
generateLlvmIrFile(b, mode, linux32_target, main_path, "ir-i386", "builtins-i386");
generateLlvmIrFile(b, mode, linux64_target, main_path, "ir-x86_64", "builtins-x86_64");
generateLlvmIrFile(b, mode, wasm32_target, main_path, "ir-wasm32", "builtins-wasm32");
// Generate Object Files
@ -87,7 +89,7 @@ fn generateObjectFile(
obj_step.dependOn(&obj.step);
}
fn makeI386Target() CrossTarget {
fn makeLinux32Target() CrossTarget {
var target = CrossTarget.parse(.{}) catch unreachable;
target.cpu_arch = std.Target.Cpu.Arch.i386;
@ -97,6 +99,16 @@ fn makeI386Target() CrossTarget {
return target;
}
fn makeLinux64Target() CrossTarget {
var target = CrossTarget.parse(.{}) catch unreachable;
target.cpu_arch = std.Target.Cpu.Arch.x86_64;
target.os_tag = std.Target.Os.Tag.linux;
target.abi = std.Target.Abi.musl;
return target;
}
fn makeWasm32Target() CrossTarget {
var target = CrossTarget.parse(.{}) catch unreachable;

View File

@ -310,9 +310,7 @@ pub const RocDec = extern struct {
// (n / 0) is an error
if (denominator_i128 == 0) {
// The compiler frontend does the `denominator == 0` check for us,
// therefore this case is unreachable from roc user code
unreachable;
@panic("TODO runtime exception for dividing by 0!");
}
// If they're both negative, or if neither is negative, the final answer

View File

@ -1355,3 +1355,9 @@ pub fn listFindUnsafe(
return .{ .value = null, .found = false };
}
}
pub fn listIsUnique(
list: RocList,
) callconv(.C) bool {
return list.isEmpty() or list.isUnique();
}

View File

@ -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");
@ -55,6 +56,7 @@ comptime {
exportListFn(list.listAny, "any");
exportListFn(list.listAll, "all");
exportListFn(list.listFindUnsafe, "find_unsafe");
exportListFn(list.listIsUnique, "is_unique");
}
// Dict Module
@ -163,6 +165,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
@ -195,7 +223,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 {

View File

@ -111,7 +111,7 @@ pub fn exportRoundF64(comptime T: type, comptime name: []const u8) void {
pub fn exportDivCeil(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(a: T, b: T) callconv(.C) T {
return math.divCeil(T, a, b) catch unreachable;
return math.divCeil(T, a, b) catch @panic("TODO runtime exception for dividing by 0!");
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });

View File

@ -468,8 +468,10 @@ fn strFromIntHelp(comptime T: type, int: T) RocStr {
const size = comptime blk: {
// the string representation of the minimum i128 value uses at most 40 characters
var buf: [40]u8 = undefined;
var result = std.fmt.bufPrint(&buf, "{}", .{std.math.minInt(T)}) catch unreachable;
break :blk result.len;
var resultMin = std.fmt.bufPrint(&buf, "{}", .{std.math.minInt(T)}) catch unreachable;
var resultMax = std.fmt.bufPrint(&buf, "{}", .{std.math.maxInt(T)}) catch unreachable;
var result = if (resultMin.len > resultMax.len) resultMin.len else resultMax.len;
break :blk result;
};
var buf: [size]u8 = undefined;

View File

@ -54,6 +54,13 @@ fn main() {
"builtins-i386",
);
generate_bc_file(
&bitcode_path,
&build_script_dir_path,
"ir-x86_64",
"builtins-x86_64",
);
// OBJECT FILES
#[cfg(windows)]
const BUILTINS_HOST_FILE: &str = "builtins-host.obj";
@ -115,7 +122,12 @@ fn generate_object_file(
println!("Moving zig object `{}` to: {}", zig_object, dest_obj);
// we store this .o file in rust's `target` folder (for wasm we need to leave a copy here too)
fs::copy(src_obj, dest_obj).expect("Failed to copy object file.");
fs::copy(src_obj, dest_obj).unwrap_or_else(|err| {
panic!(
"Failed to copy object file {} to {}: {:?}",
src_obj, dest_obj, err
);
});
}
}

View File

@ -1,90 +0,0 @@
interface Bool
exposes [ and, isEq, isNotEq, not, or, xor ]
imports []
## Returns `False` when given `True`, and vice versa.
not : [True, False] -> [True, False]
## Returns `True` when given `True` and `True`, and `False` when either argument is `False`.
##
## `a && b` is shorthand for `Bool.and a b`
##
## >>> True && True
##
## >>> True && False
##
## >>> False && True
##
## >>> False && False
##
## ## Performance Notes
##
## In some languages, `&&` and `||` are special-cased in the compiler to skip
## evaluating the expression after the operator under certain circumstances.
## For example, in some languages, `enablePets && likesDogs user` would compile
## to the equivalent of:
##
## if enablePets then
## likesDogs user
## else
## False
##
## In Roc, however, `&&` and `||` are not special. They work the same way as
## other functions. Conditionals like `if` and `when` have a performance cost,
## and sometimes calling a function like `likesDogs user` can be faster across
## the board than doing an `if` to decide whether to skip calling it.
##
## (Naturally, if you expect the `if` to improve performance, you can always add
## one explicitly!)
and : Bool, Bool -> Bool
## Returns `True` when given `True` for either argument, and `False` only when given `False` and `False`.
##
## `a || b` is shorthand for `Bool.or a b`.
##
## >>> True || True
##
## >>> True || False
##
## >>> False || True
##
## >>> False || False
##
## ## Performance Notes
##
## In some languages, `&&` and `||` are special-cased in the compiler to skip
## evaluating the expression after the operator under certain circumstances.
## In Roc, this is not the case. See the performance notes for [Bool.and] for details.
or : Bool, Bool -> Bool
## Exclusive or
xor : Bool, Bool -> Bool
# TODO: removed `'` from signature because parser does not support it yet
# Original signature: `isEq : 'val, 'val -> Bool`
## Returns `True` if the two values are *structurally equal*, and `False` otherwise.
##
## `a == b` is shorthand for `Bool.isEq a b`
##
## 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*.
##
## Note that `isEq` takes `'val` instead of `val`, which means `isEq` does not
## accept arguments whose types contain functions.
isEq : val, val -> Bool
# TODO: removed `'` from signature because parser does not support it yet
# Original signature: `isNotEq : 'val, 'val -> Bool`
## Calls [isEq] on the given values, then calls [not] on the result.
##
## `a != b` is shorthand for `Bool.isNotEq a b`
##
## Note that `isNotEq` takes `'val` instead of `val`, which means `isNotEq` does not
## accept arguments whose types contain functions.
isNotEq : val, val -> Bool

View File

@ -1,210 +0,0 @@
interface Dict
exposes
[
Dict,
contains,
difference,
empty,
get,
keys,
insert,
intersection,
len,
remove,
single,
union,
values,
walk
]
imports []
## A [dictionary](https://en.wikipedia.org/wiki/Associative_array) that lets you can associate keys with values.
##
## ### Inserting
##
## The most basic way to use a dictionary is to start with an empty one and then:
## 1. Call [Dict.insert] passing a key and a value, to associate that key with that value in the dictionary.
## 2. Later, call [Dict.get] passing the same key as before, and it will return the value you stored.
##
## Here's an example of a dictionary which uses a city's name as the key, and its population as the associated value.
##
## populationByCity =
## Dict.empty
## |> Dict.insert "London" 8_961_989
## |> Dict.insert "Philadelphia" 1_603_797
## |> Dict.insert "Shanghai" 24_870_895
## |> Dict.insert "Delhi" 16_787_941
## |> Dict.insert "Amsterdam" 872_680
##
## ### Converting to a [List]
##
## We can call [Dict.toList] on `populationByCity` to turn it into a list of key-value pairs:
##
## Dict.toList populationByCity == [
## { k: "London", v: 8961989 },
## { k: "Philadelphia", v: 1603797 },
## { k: "Shanghai", v: 24870895 },
## { k: "Delhi", v: 16787941 },
## { k: "Amsterdam", v: 872680 },
## ]
##
## We can use the similar [Dict.keyList] and [Dict.values] functions to get only the keys or only the values,
## instead of getting these `{ k, v }` records that contain both.
##
## You may notice that these lists have the same order as the original insertion order. This will be true if
## all you ever do is [insert] and [get] operations on the dictionary, but [remove] operations can change this order.
## Let's see how that looks.
##
## ### Removing
##
## We can remove an element from the dictionary, like so:
##
## populationByCity
## |> Dict.remove "Philadelphia"
## |> Dict.toList
## ==
## [
## { k: "London", v: 8961989 },
## { k: "Amsterdam", v: 872680 },
## { k: "Shanghai", v: 24870895 },
## { k: "Delhi", v: 16787941 },
## ]
##
## Notice that the order changed! Philadelphia has been not only removed from the list, but Amsterdam - the last
## entry we inserted - has been moved into the spot where Philadelphia was previously. This is exactly what
## [Dict.remove] does: it removes an element and moves the most recent insertion into the vacated spot.
##
## This move is done as a performance optimization, and it lets [remove] have
## [constant time complexity](https://en.wikipedia.org/wiki/Time_complexity#Constant_time). If you need a removal
## operation which preserves ordering, [Dict.removeShift] will remove the element and then shift everything after it
## over one spot. Be aware that this shifting requires copying every single entry after the removed element, though,
## so it can be massively more costly than [remove]! This makes [remove] the recommended default choice;
## [removeShift] should only be used if maintaining original insertion order is absolutely necessary.
##
##
## ### Removing
##
## ### Equality
##
## When comparing two dictionaries for equality, they are `==` only if their both their contents and their
## orderings match. This preserves the property that if `dict1 == dict2`, you should be able to rely on
## `fn dict1 == fn dict2` also being `True`, even if `fn` relies on the dictionary's ordering (for example, if
## `fn` is `Dict.toList` or calls it internally.)
##
## The [Dict.hasSameContents] function gives an alternative to `==` which ignores ordering
## and returns `True` if both dictionaries have the same keys and associated values.
Dict k v : [ @Dict k v ] # TODO k should require a hashing and equating constraint
## An empty dictionary.
empty : Dict * *
size : Dict * * -> Nat
isEmpty : Dict * * -> Bool
## Returns a [List] of the dictionary's key/value pairs.
##
## See [walk] to walk over the key/value pairs without creating an intermediate data structure.
toList : Dict k v -> List { k, v }
## Returns a [List] of the dictionary's keys.
##
## See [keySet] to get a [Set] of keys instead, or [walkKeys] to walk over the keys without creating
## an intermediate data structure.
keyList : Dict key * -> List key
## Returns a [Set] of the dictionary's keys.
##
## See [keyList] to get a [List] of keys instead, or [walkKeys] to walk over the keys without creating
## an intermediate data structure.
keySet : Dict key * -> Set key
## Returns a [List] of the dictionary's values.
##
## See [walkValues] to walk over the values without creating an intermediate data structure.
values : Dict * value -> List value
walk : Dict k v, state, (state, k, v -> state) -> state
walkKeys : Dict key *, state, (state, key -> state) -> state
walkValues : Dict * value, state, (state, value -> state) -> state
## Convert each key and value in the #Dict to something new, by calling a conversion
## function on each of them. Then return a new #Map of the converted keys and values.
##
## >>> Dict.map {{ 3.14 => "pi", 1.0 => "one" }} \{ key, value } -> { key:
##
## >>> Dict.map {[ "", "a", "bc" ]} Str.isEmpty
##
## `map` functions like this are common in Roc, and they all work similarly.
## See for example [List.map], [Result.map], and `Set.map`.
map :
Dict beforeKey beforeVal,
({ k: beforeKey, v: beforeVal } -> { k: afterKey, v: afterVal })
-> Dict afterKey afterVal
# DESIGN NOTES: The reason for panicking when given NaN is that:
# * If we allowed NaN in, Dict.insert would no longer be idempotent.
# * If we allowed NaN but overrode its semantics to make it feel like "NaN == NaN" we'd need isNaN checks in all hashing operations as well as all equality checks (during collision detection), not just insert. This would be much worse for performance than panicking on insert, which only requires one extra conditional on insert.
# * It's obviously invalid; the whole point of NaN is that an error occurred. Giving a runtime error notifies you when this problem happens. Giving it only on insert is the best for performance, because it means you aren't paying for isNaN checks on lookups as well.
# TODO: removed `'` from signature because parser does not support it yet
# Original signature: insert : Dict 'key val, 'key, val -> Dict 'key val
## Make sure never to insert a key of *NaN* into a [Dict]! Because *NaN* is
## defined to be unequal to *NaN*, inserting a *NaN* key results in an entry
## that can never be retrieved or removed from the [Dict].
insert : Dict key val, key, val -> Dict key val
## Removes a key from the dictionary in [constant time](https://en.wikipedia.org/wiki/Time_complexity#Constant_time), without preserving insertion order.
##
## Since the internal [List] which determines the order of operations like [toList] and [walk] cannot have gaps in it,
## whenever an element is removed from the middle of that list, something must be done to eliminate the resulting gap.
##
## * [removeShift] eliminates the gap by shifting over every element after the removed one. This takes [linear time](https://en.wikipedia.org/wiki/Time_complexity#Linear_time),
## and preserves the original ordering.
## * [remove] eliminates the gap by replacing the removed element with the one at the end of the list - that is, the most recent insertion. This takes [constant time](https://en.wikipedia.org/wiki/Time_complexity#Constant_time), but does not preserve the original ordering.
##
## For example, suppose we have a `populationByCity` with these contents:
##
## Dict.toList populationByCity == [
## { k: "London", v: 8961989 },
## { k: "Philadelphia", v: 1603797 },
## { k: "Shanghai", v: 24870895 },
## { k: "Delhi", v: 16787941 },
## { k: "Amsterdam", v: 872680 },
## ]
##
## Using `Dict.remove "Philadelphia"` on this will replace the `"Philadelphia"` entry with the most recent insertion,
## which is `"Amsterdam"` in this case.
##
## populationByCity
## |> Dict.remove "Philadelphia"
## |> Dict.toList
## ==
## [
## { k: "London", v: 8961989 },
## { k: "Amsterdam", v: 872680 },
## { k: "Shanghai", v: 24870895 },
## { k: "Delhi", v: 16787941 },
## ]
##
## Both [remove] and [removeShift] leave the dictionary with the same contents; they only differ in ordering and in
## performance. Since ordering only affects operations like [toList] and [walk], [remove] is the better default
## choice because it has much better performance characteristics; [removeShift] should only be used when it's
## absolutely necessary for operations like [toList] and [walk] to preserve the exact original insertion order.
remove : Dict k v, k -> Dict k v
## Removes a key from the dictionary in [linear time](https://en.wikipedia.org/wiki/Time_complexity#Linear_time), while preserving insertion order.
##
## It's better to use [remove] than this by default, since [remove] has [constant time complexity](https://en.wikipedia.org/wiki/Time_complexity#Constant_time),
## which commonly leads [removeShift] to take many times as long to run as [remove] does. However, [remove] does not
## preserve insertion order, so the slower [removeShift] exists only for use cases where it's abolutely necessary for
## ordering-sensitive functions like [toList] and [walk] to preserve the exact original insertion order.
##
## See the [remove] documentation for more details about the differences between [remove] and [removeShift].
removeShift : Dict k v, k -> Dict k v
## Returns whether both dictionaries have the same keys, and the same values associated with those keys.
## This is different from `==` in that it disregards the ordering of the keys and values.
hasSameContents : Dict k v, Dict k v -> Bool

View File

@ -1,705 +0,0 @@
interface List
exposes
[
List,
append,
concat,
contains,
drop,
dropAt,
dropLast,
first,
get,
isEmpty,
join,
keepErrs,
keepIf,
keepOks,
last,
len,
map,
map2,
map3,
map4,
mapJoin,
mapOrDrop,
mapWithIndex,
prepend,
product,
range,
repeat,
reverse,
set,
single,
sortWith,
split,
sublist,
sum,
swap,
walk,
walkBackwards,
walkUntil
]
imports []
## Types
## A sequential list of values.
##
## >>> [ 1, 2, 3 ] # a list of numbers
## >>> [ "a", "b", "c" ] # a list of strings
## >>> [ [ 1.1 ], [], [ 2.2, 3.3 ] ] # a list of lists of numbers
##
## The list `[ 1, "a" ]` gives an error, because each element in a list must have
## the same type. If you want to put a mix of [I64] and [Str] values into a list, try this:
##
## ```
## mixedList : List [ IntElem I64, StrElem Str ]*
## mixedList = [ IntElem 1, IntElem 2, StrElem "a", StrElem "b" ]
## ```
##
## The maximum size of a [List] is limited by the amount of heap memory available
## to the current process. If there is not enough memory available, attempting to
## create the list could crash. (On Linux, where [overcommit](https://www.etalabs.net/overcommit.html)
## is normally enabled, not having enough memory could result in the list appearing
## to be created just fine, but then crashing later.)
##
## > The theoretical maximum length for a list created in Roc is half of
## > `Num.maxNat`. Attempting to create a list bigger than that
## > in Roc code will always fail, although in practice it is likely to fail
## > at much smaller lengths due to insufficient memory being available.
##
## ## Performance Details
##
## Under the hood, a list is a record containing a `len : Nat` field as well
## as a pointer to a reference count and a flat array of bytes. Unique lists
## store a capacity #Nat instead of a reference count.
##
## ## Shared Lists
##
## Shared lists are [reference counted](https://en.wikipedia.org/wiki/Reference_counting).
##
## Each time a given list gets referenced, its reference count ("refcount" for short)
## gets incremented. Each time a list goes out of scope, its refcount count gets
## decremented. Once a refcount, has been decremented more times than it has been
## incremented, we know nothing is referencing it anymore, and the list's memory
## will be immediately freed.
##
## Let's look at an example.
##
## ratings = [ 5, 4, 3 ]
##
## { foo: ratings, bar: ratings }
##
## The first line binds the name `ratings` to the list `[ 5, 4, 3 ]`. The list
## begins with a refcount of 1, because so far only `ratings` is referencing it.
##
## The second line alters this refcount. `{ foo: ratings` references
## the `ratings` list, which will result in its refcount getting incremented
## from 0 to 1. Similarly, `bar: ratings }` also references the `ratings` list,
## which will result in its refcount getting incremented from 1 to 2.
##
## Let's turn this example into a function.
##
## getRatings = \first ->
## ratings = [ first, 4, 3 ]
##
## { foo: ratings, bar: ratings }
##
## getRatings 5
##
## At the end of the `getRatings` function, when the record gets returned,
## the original `ratings =` binding has gone out of scope and is no longer
## accessible. (Trying to reference `ratings` outside the scope of the
## `getRatings` function would be an error!)
##
## Since `ratings` represented a way to reference the list, and that way is no
## longer accessible, the list's refcount gets decremented when `ratings` goes
## out of scope. It will decrease from 2 back down to 1.
##
## Putting these together, when we call `getRatings 5`, what we get back is
## a record with two fields, `foo`, and `bar`, each of which refers to the same
## list, and that list has a refcount of 1.
##
## Let's change the last line to be `(getRatings 5).bar` instead of `getRatings 5`:
##
## getRatings = \first ->
## ratings = [ first, 4, 3 ]
##
## { foo: ratings, bar: ratings }
##
## (getRatings 5).bar
##
## Now, when this expression returns, only the `bar` field of the record will
## be returned. This will mean that the `foo` field becomes inaccessible, causing
## the list's refcount to get decremented from 2 to 1. At this point, the list is back
## where it started: there is only 1 reference to it.
##
## Finally let's suppose the final line were changed to this:
##
## List.first (getRatings 5).bar
##
## This call to [List.first] means that even the list in the `bar` field has become
## inaccessible. As such, this line will cause the list's refcount to get
## decremented all the way to 0. At that point, nothing is referencing the list
## anymore, and its memory will get freed.
##
## Things are different if this is a list of lists instead of a list of numbers.
## Let's look at a simpler example using [List.first] - first with a list of numbers,
## and then with a list of lists, to see how they differ.
##
## Here's the example using a list of numbers.
##
## nums = [ 1, 2, 3, 4, 5, 6, 7 ]
##
## first = List.first nums
## last = List.last nums
##
## first
##
## It makes a list, calls [List.first] and [List.last] on it, and then returns `first`.
##
## Here's the equivalent code with a list of lists:
##
## lists = [ [ 1 ], [ 2, 3 ], [], [ 4, 5, 6, 7 ] ]
##
## first = List.first lists
## last = List.last lists
##
## first
##
## TODO explain how in the former example, when we go to free `nums` at the end,
## we can free it immediately because there are no other refcounts. However,
## in the case of `lists`, we have to iterate through the list and decrement
## the refcounts of each of its contained lists - because they, too, have
## refcounts! Importantly, because the first element had its refcount incremented
## because the function returned `first`, that element will actually end up
## *not* getting freed at the end - but all the others will be.
##
## In the `lists` example, `lists = [ ... ]` also creates a list with an initial
## refcount of 1. Separately, it also creates several other lists - each with
## their own refcounts - to go inside that list. (The empty list at the end
## does not use heap memory, and thus has no refcount.)
##
## At the end, we once again call [List.first] on the list, but this time
##
## * Copying small lists (64 elements or fewer) is typically slightly faster than copying small persistent data structures. This is because, at small sizes, persistent data structures tend to be thin wrappers around flat arrays anyway. They don't have any copying advantage until crossing a certain minimum size threshold.
## * 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 ]
## Initialize
## A list with a single element in it.
##
## This is useful in pipelines, like so:
##
## websites =
## Str.concat domain ".com"
## |> List.single
##
single : elem -> List elem
## An empty list.
empty : List *
## Returns a list with the given length, where every element is the given value.
##
##
repeat : elem, Nat -> List elem
## Returns a list of all the integers between one and another,
## including both of the given numbers.
##
## >>> List.range 2 8
range : Int a, Int a -> List (Int a)
## Transform
## Returns the list with its elements reversed.
##
## >>> List.reverse [ 1, 2, 3 ]
reverse : List elem -> List elem
## Sorts a list using a function which specifies how two elements are ordered.
##
## When sorting by numeric values, it's more efficient to use [sortAsc] or
## [sortDesc] instead.
sort : List elem, (elem, elem -> [ Lt, Eq, Gt ]) -> List elem
## Sorts a list in ascending order (lowest to highest), using a function which
## specifies a way to represent each element as a number.
##
## This is more efficient than [sort] because it skips
## calculating the `[ Lt, Eq, Gt ]` value and uses the number directly instead.
##
## To sort in descending order (highest to lowest), use [List.sortDesc] instead.
sortAsc : List elem, (elem -> Num *) -> List elem
## Sorts a list in descending order (highest to lowest), using a function which
## specifies a way to represent each element as a number.
##
## This is more efficient than [sort] because it skips
## calculating the `[ Lt, Eq, Gt ]` value and uses the number directly instead.
##
## To sort in ascending order (lowest to highest), use [List.sortAsc] instead.
sortDesc : List elem, (elem -> Num *) -> List elem
## Convert each element in the list to something new, by calling a conversion
## function on each of them. Then return a new list of the converted values.
##
## > List.map [ 1, 2, 3 ] (\num -> num + 1)
##
## > List.map [ "", "a", "bc" ] Str.isEmpty
##
## `map` functions like this are common in Roc, and they all work similarly.
## See for example `Set.map`, `Dict.map`, and [Result.map].
map : List before, (before -> after) -> List after
## Run a transformation function on the first element of each list,
## and use that as the first element in the returned list.
## Repeat until a list runs out of elements.
##
## Some languages have a function named `zip`, which does something similar to
## calling [List.map2] passing two lists and `Pair`:
##
## >>> zipped = List.map2 [ "a", "b", "c" ] [ 1, 2, 3 ] Pair
map2 : List a, List b, (a, b -> c) -> List c
## Run a transformation function on the first element of each list,
## and use that as the first element in the returned list.
## Repeat until a list runs out of elements.
map3 : List a, List b, List c, (a, b, c -> d) -> List d
## Run a transformation function on the first element of each list,
## and use that as the first element in the returned list.
## Repeat until a list runs out of elements.
map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e
## This works like [List.map], except it also passes the index
## of the element to the conversion function.
mapWithIndex : List before, (before, Nat -> after) -> List after
## This works like [List.map], except at any time you can return `Err` to
## cancel the entire operation immediately, and return that #Err.
mapOrCancel : List before, (before -> Result after err) -> Result (List after) err
## Like [List.map], except the transformation function specifies whether to
## `Keep` or `Drop` each element from the final [List].
##
## You may know a similar function named `filterMap` in other languages.
mapOrDrop : List before, (before -> [ Keep after, Drop ]) -> List after
## Like [List.map], except the transformation function wraps the return value
## in a list. At the end, all the lists get joined together into one list.
##
## You may know a similar function named `concatMap` in other languages.
mapJoin : List before, (before -> List after) -> List after
## This works like [List.map], except only the transformed values that are
## wrapped in `Ok` are kept. Any that are wrapped in `Err` are dropped.
##
## >>> List.mapOks [ [ "a", "b" ], [], [], [ "c", "d", "e" ] ] List.last
##
## >>> fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str)
## >>>
## >>> List.mapOks [ "", "a", "bc", "", "d", "ef", "" ]
mapOks : List before, (before -> Result after *) -> List after
## Returns a list with the element at the given index having been transformed by
## the given function.
##
## For a version of this which gives you more control over when to perform
## the transformation, see `List.updater`
##
## ## Performance notes
##
## In particular when updating nested collections, this is potentially much more
## efficient than using [List.get] to obtain the element, transforming it,
## and then putting it back in the same place.
update : List elem, Nat, (elem -> elem) -> List elem
## A more flexible version of `List.update`, which returns an "updater" function
## that lets you delay performing the update until later.
updater : List elem, Nat -> { elem, new : (elem -> List elem) }
## If all the elements in the list are #Ok, return a new list containing the
## contents of those #Ok tags. If any elements are #Err, return #Err.
allOks : List (Result ok err) -> Result (List ok) err
## Add a single element to the end of a list.
##
## >>> List.append [ 1, 2, 3 ] 4
##
## >>> [ 0, 1, 2 ]
## >>> |> List.append 3
append : List elem, elem -> List elem
## Add a single element to the beginning of a list.
##
## >>> List.prepend [ 1, 2, 3 ] 0
##
## >>> [ 2, 3, 4 ]
## >>> |> List.prepend 1
prepend : List elem, elem -> List elem
## Put two lists together.
##
## >>> List.concat [ 1, 2, 3 ] [ 4, 5 ]
##
## >>> [ 0, 1, 2 ]
## >>> |> List.concat [ 3, 4 ]
concat : List elem, List elem -> List elem
## Join the given lists together into one list.
##
## >>> List.join [ [ 1, 2, 3 ], [ 4, 5 ], [], [ 6, 7 ] ]
##
## >>> List.join [ [], [] ]
##
## >>> List.join []
join : List (List elem) -> List elem
## Like [List.join], but only keeps elements tagged with `Ok`. Elements
## tagged with `Err` are dropped.
##
## This can be useful after using an operation that returns a #Result
## on each element of a list, for example [List.first]:
##
## >>> [ [ 1, 2, 3 ], [], [], [ 4, 5 ] ]
## >>> |> List.map List.first
## >>> |> List.joinOks
##
## Eventually, `oks` type signature will be `List [Ok elem]* -> List elem`.
## The implementation for that is a lot tricker then `List (Result elem *)`
## so we're sticking with `Result` for now.
oks : List (Result elem *) -> List elem
## Filter
## Run the given function on each element of a list, and return all the
## elements for which the function returned `True`.
##
## >>> List.keepIf [ 1, 2, 3, 4 ] (\num -> num > 2)
##
## ## Performance Details
##
## [List.keepIf] always returns a list that takes up exactly the same amount
## of memory as the original, even if its length decreases. This is because it
## can't know in advance exactly how much space it will need, and if it guesses a
## length that's too low, it would have to re-allocate.
##
## (If you want to do an operation like this which reduces the memory footprint
## of the resulting list, you can do two passes over the lis with [List.walk] - one
## to calculate the precise new size, and another to populate the new list.)
##
## If given a unique list, [List.keepIf] will mutate it in place to assemble the appropriate list.
## If that happens, this function will not allocate any new memory on the heap.
## If all elements in the list end up being kept, Roc will return the original
## list unaltered.
##
keepIf : List elem, (elem -> Bool) -> List elem
## Run the given function on each element of a list, and return all the
## elements for which the function returned `False`.
##
## >>> List.dropIf [ 1, 2, 3, 4 ] (\num -> num > 2)
##
## ## Performance Details
##
## `List.dropIf` has the same performance characteristics as [List.keepIf].
## See its documentation for details on those characteristics!
dropIf : List elem, (elem -> Bool) -> List elem
## Access
## Returns the first element in the list, or `ListWasEmpty` if it was empty.
first : List elem -> Result elem [ ListWasEmpty ]*
## Returns the last element in the list, or `ListWasEmpty` if it was empty.
last : List elem -> Result elem [ ListWasEmpty ]*
get : List elem, Nat -> Result elem [ OutOfBounds ]*
max : List (Num a) -> Result (Num a) [ ListWasEmpty ]*
min : List (Num a) -> Result (Num a) [ ListWasEmpty ]*
## Modify
## Replaces the element at the given index with a replacement.
##
## >>> List.set [ "a", "b", "c" ] 1 "B"
##
## If the given index is outside the bounds of the list, returns the original
## list unmodified.
##
## To drop the element at a given index, instead of replacing it, see [List.dropAt].
set : List elem, Nat, elem -> List elem
## Drops n elements from the beginning of the list.
drop : List elem, Nat -> List elem
## Drops the element at the given index from the list.
##
## This has no effect if the given index is outside the bounds of the list.
##
## To replace the element at a given index, instead of dropping it, see [List.set].
dropAt : List elem, Nat -> List elem
## Adds a new element to the end of the list.
##
## >>> List.append [ "a", "b" ] "c"
##
## ## Performance Details
##
## When given a Unique list, this adds the new element in-place if possible.
## This is only possible if the list has enough capacity. Otherwise, it will
## have to *clone and grow*. See the section on [capacity](#capacity) in this
## module's documentation.
append : List elem, elem -> List elem
## Adds a new element to the beginning of the list.
##
## >>> List.prepend [ "b", "c" ] "a"
##
## ## Performance Details
##
## This always clones the entire list, even when given a Unique list. That means
## it runs about as fast as `List.addLast` when both are given a Shared list.
##
## If you have a Unique list instead, [List.append] will run much faster than
## [List.append] except in the specific case where the list has no excess capacity,
## and needs to *clone and grow*. In that uncommon case, both [List.append] and
## [List.append] will run at about the same speed—since [List.append] always
## has to clone and grow.
##
## | Unique list | Shared list |
##---------+--------------------------------+----------------+
## append | in-place given enough capacity | clone and grow |
## prepend | clone and grow | clone and grow |
prepend : List elem, elem -> List elem
## Remove the last element from the list.
##
## Returns both the removed element as well as the new list (with the removed
## element missing), or `Err ListWasEmpty` if the list was empty.
##
## Here's one way you can use this:
##
## when List.pop list is
## Ok { others, last } -> ...
## Err ListWasEmpty -> ...
##
## ## Performance Details
##
## Calling `List.pop` on a Unique list runs extremely fast. It's essentially
## the same as a [List.last] except it also returns the [List] it was given,
## with its length decreased by 1.
##
## In contrast, calling `List.pop` on a Shared list creates a new list, then
## copies over every element in the original list except the last one. This
## takes much longer.
dropLast : List elem -> Result { others : List elem, last : elem } [ ListWasEmpty ]*
##
## Here's one way you can use this:
##
## when List.pop list is
## Ok { others, last } -> ...
## Err ListWasEmpty -> ...
##
## ## Performance Details
##
## When calling either `List.dropFirst` or `List.dropLast` on a Unique list, `List.dropLast`
## runs *much* faster. This is because for `List.dropLast`, removing the last element
## in-place is as easy as reducing the length of the list by 1. In contrast,
## removing the first element from the list involves copying every other element
## in the list into the index before it - which is massively more costly.
##
## In the case of a Shared list,
##
## | Unique list | Shared list |
##-----------+----------------------------------+---------------------------------+
## dropFirst | [List.last] + length change | [List.last] + clone rest of list |
## dropLast | [List.last] + clone rest of list | [List.last] + clone rest of list |
dropFirst : List elem -> Result { first: elem, others : List elem } [ ListWasEmpty ]*
## Returns the given number of elements from the beginning of the list.
##
## >>> List.takeFirst 4 [ 1, 2, 3, 4, 5, 6, 7, 8 ]
##
## If there are fewer elements in the list than the requested number,
## returns the entire list.
##
## >>> List.takeFirst 5 [ 1, 2 ]
##
## To *remove* elements from the beginning of the list, use `List.takeLast`.
##
## To remove elements from both the beginning and end of the list,
## use `List.sublist`.
##
## To split the list into two lists, use `List.split`.
##
## ## Performance Details
##
## When given a Unique list, this runs extremely fast. It sets the list's length
## to the given length value, and frees the leftover elements. This runs very
## slightly faster than `List.takeLast`.
##
## In fact, `List.takeFirst 1 list` runs faster than `List.first list` when given
## a Unique list, because [List.first] returns the first element as well -
## which introduces a conditional bounds check as well as a memory load.
takeFirst : List elem, Nat -> List elem
## Returns the given number of elements from the end of the list.
##
## >>> List.takeLast 4 [ 1, 2, 3, 4, 5, 6, 7, 8 ]
##
## If there are fewer elements in the list than the requested number,
## returns the entire list.
##
## >>> List.takeLast 5 [ 1, 2 ]
##
## To *remove* elements from the end of the list, use `List.takeFirst`.
##
## To remove elements from both the beginning and end of the list,
## use `List.sublist`.
##
## To split the list into two lists, use `List.split`.
##
## ## Performance Details
##
## When given a Unique list, this runs extremely fast. It moves the list's
## pointer to the index at the given length value, updates its length,
## and frees the leftover elements. This runs very nearly as fast as
## `List.takeFirst` on a Unique list.
##
## In fact, `List.takeLast 1 list` runs faster than `List.first list` when given
## a Unique list, because [List.first] returns the first element as well -
## which introduces a conditional bounds check as well as a memory load.
takeLast : List elem, Nat -> List elem
## Deconstruct
## Splits the list into two lists, around the given index.
##
## The returned lists are labeled `before` and `others`. The `before` list will
## contain all the elements whose index in the original list was **less than**
## than the given index, # and the `others` list will be all the others. (This
## means if you give an index of 0, the `before` list will be empty and the
## `others` list will have the same elements as the original list.)
split : List elem, Nat -> { before: List elem, others: List elem }
## Returns a subsection of the given list, beginning at the `start` index and
## including a total of `len` elements.
##
## If `start` is outside the bounds of the given list, returns the empty list.
##
## >>> List.sublist { start: 4, len: 0 } [ 1, 2, 3 ]
##
## If more elements are requested than exist in the list, returns as many as it can.
##
## >>> List.sublist { start: 2, len: 10 } [ 1, 2, 3, 4, 5 ]
##
## > If you want a sublist which goes all the way to the end of the list, no
## > matter how long the list is, `List.takeLast` can do that more efficiently.
##
## Some languages have a function called **`slice`** which works similarly to this.
sublist : List elem, { start : Nat, len : Nat } -> List elem
## Build a value using each element in the list.
##
## Starting with a given `state` value, this walks through each element in the
## list from first to last, running a given `step` function on that element
## which updates the `state`. It returns the final `state` at the end.
##
## You can use it in a pipeline:
##
## [ 2, 4, 8 ]
## |> List.walk { start: 0, step: Num.add }
##
## This returns 14 because:
## * `state` starts at 0 (because of `start: 0`)
## * Each `step` runs `Num.add state elem`, and the return value becomes the new `state`.
##
## Here is a table of how `state` changes as [List.walk] walks over the elements
## `[ 2, 4, 8 ]` using #Num.add as its `step` function to determine the next `state`.
##
## `state` | `elem` | `step state elem` (`Num.add state elem`)
## --------+--------+-----------------------------------------
## 0 | |
## 0 | 2 | 2
## 2 | 4 | 6
## 6 | 8 | 14
##
## So `state` goes through these changes:
## 1. `0` (because of `start: 0`)
## 2. `1` (because of `Num.add state elem` with `state` = 0 and `elem` = 1
##
## [ 1, 2, 3 ]
## |> List.walk { start: 0, step: Num.sub }
##
## This returns -6 because
##
## Note that in other languages, `walk` is sometimes called `reduce`,
## `fold`, `foldLeft`, or `foldl`.
walk : List elem, state, (state, elem -> state) -> state
## Note that in other languages, `walkBackwards` is sometimes called `reduceRight`,
## `fold`, `foldRight`, or `foldr`.
walkBackwards : List elem, state, (state, elem -> state) -> state
## Same as [List.walk], except you can stop walking early.
##
## ## Performance Details
##
## Compared to [List.walk], this can potentially visit fewer elements (which can
## improve performance) at the cost of making each step take longer.
## However, the added cost to each step is extremely small, and can easily
## be outweighed if it results in skipping even a small number of elements.
##
## As such, it is typically better for performance to use this over [List.walk]
## if returning `Done` earlier than the last element is expected to be common.
walkUntil : List elem, state, (state, elem -> [ Continue state, Done state ]) -> state
# Same as [List.walk]Backwards, except you can stop walking early.
walkBackwardsUntil : List elem, state, (state, elem -> [ Continue state, Done state ]) -> state
## Check
## Returns the length of the list - the number of elements it contains.
##
## One [List] can store up to 2,147,483,648 elements (just over 2 billion), which
## is exactly equal to the highest valid #I32 value. This means the #U32 this function
## returns can always be safely converted to an #I32 without losing any data.
len : List * -> Nat
isEmpty : List * -> Bool
contains : List elem, elem -> Bool
startsWith : List elem, List elem -> Bool
endsWith : List elem, List elem -> Bool
## Run the given predicate on each element of the list, returning `True` if
## any of the elements satisfy it.
any : List elem, (elem -> Bool) -> Bool
## Run the given predicate on each element of the list, returning `True` if
## all of the elements satisfy it.
all : List elem, (elem -> Bool) -> Bool
## Returns the first element of the list satisfying a predicate function.
## If no satisfying element is found, an `Err NotFound` is returned.
find : List elem, (elem -> Bool) -> Result elem [ NotFound ]*
## Apply a function that returns a Result on a list, only successful
## Results are kept and returned unwrapped.
keepOks : List before, (before -> Result after *) -> List after
## Apply a function that returns a Result on a list, only unsuccessful
## Results are kept and returned unwrapped.
keepErrs : List before, (before -> Result * after) -> List after

File diff suppressed because it is too large Load Diff

View File

@ -1,67 +0,0 @@
interface Result
exposes
[
Result,
after,
isOk,
isErr,
map,
mapErr,
withDefault
]
imports []
## 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 ]
## Return True if the result indicates a success, else return False
##
## >>> Result.isOk (Ok 5)
isOk : Result * * -> bool
## Return True if the result indicates a failure, else return False
##
## >>> Result.isErr (Err "uh oh")
isErr : Result * * -> bool
## If the result is `Ok`, return the value it holds. Otherwise, return
## the given default value.
##
## >>> Result.withDefault (Ok 7) 42
##
## >>> Result.withDefault (Err "uh oh") 42
withDefault : Result ok err, ok -> ok
## If the result is `Ok`, transform the entire result by running a conversion
## function on the value the `Ok` holds. Then return that new result.
##
## (If the result is `Err`, this has no effect. Use `afterErr` to transform an `Err`.)
##
## >>> Result.after (Ok -1) \num -> if num < 0 then Err "negative!" else Ok -num
##
## >>> Result.after (Err "yipes!") \num -> if num < 0 then Err "negative!" else Ok -num
after : Result before err, (before -> Result after err) -> Result after err
## If the result is `Ok`, transform the value it holds by running a conversion
## function on it. Then return a new `Ok` holding the transformed value.
##
## (If the result is `Err`, this has no effect. Use [mapErr] to transform an `Err`.)
##
## >>> Result.map (Ok 12) Num.negate
##
## >>> Result.map (Err "yipes!") Num.negate
##
## `map` functions like this are common in Roc, and they all work similarly.
## See for example [List.map], `Set.map`, and `Dict.map`.
map : Result before err, (before -> after) -> Result after err
## If the result is `Err`, transform the value it holds by running a conversion
## function on it. Then return a new `Err` holding the transformed value.
##
## (If the result is `Ok`, this has no effect. Use [map] to transform an `Ok`.)
##
## >>> Result.mapErr (Err "yipes!") Str.isEmpty
##
## >>> Result.mapErr (Ok 12) Str.isEmpty
mapErr : Result ok before, (before -> after) -> Result ok after

View File

@ -1,59 +0,0 @@
interface Set
exposes
[
Set,
contains,
difference,
empty,
fromList,
insert,
intersection,
len,
remove,
single,
toList,
union,
walk
]
imports []
## A Set is an unordered collection of unique elements.
Set elem : [ @Set elem ]
## An empty set.
empty : Set *
## Check
isEmpty : Set * -> Bool
len : Set * -> Nat
## Modify
# TODO: removed `'` from signature because parser does not support it yet
# Original signature: `add : Set 'elem, 'elem -> Set 'elem`
## Make sure never to add a *NaN* to a [Set]! Because *NaN* is defined to be
## unequal to *NaN*, adding a *NaN* results in an entry that can never be
## retrieved or removed from the [Set].
add : Set elem, elem -> Set elem
## Drops the given element from the set.
# TODO: removed `'` from signature because parser does not support it yet
# Original signature: `drop : Set 'elem, 'elem -> Set 'elem`
drop : Set elem, elem -> Set elem
## Transform
## Convert each element in the set to something new, by calling a conversion
## function on each of them. Then return a new set of the converted values.
##
## >>> Set.map {: -1, 1, 3 :} Num.negate
##
## >>> Set.map {: "", "a", "bc" :} Str.isEmpty
##
## `map` functions like this are common in Roc, and they all work similarly.
## See for example [List.map], `Dict.map`, and [Result.map].
# TODO: removed `'` from signature because parser does not support it yet
# Original signature: `map : Set 'elem, ('before -> 'after) -> Set 'after`
map : Set elem, (before -> after) -> Set after

View File

@ -1,470 +0,0 @@
interface Str
exposes
[
Str,
append,
concat,
countGraphemes,
endsWith,
fromUtf8,
isEmpty,
joinWith,
split,
startsWith,
startsWithCodePt,
toUtf8,
Utf8Problem,
Utf8ByteProblem
]
imports []
## # Types
##
## Dealing with text is a deep topic, so by design, Roc's `Str` module sticks
## to the basics.
##
## _For more advanced use cases like working with raw [code points](https://unicode.org/glossary/#code_point),
## see the [roc/unicode](roc/unicode) package. For locale-specific text
## functions (including uppercasing strings, as capitalization rules vary by locale;
## in English, `"i"` capitalizes to `"I"`, but [in Turkish](https://en.wikipedia.org/wiki/Dotted_and_dotless_I#In_computing),
## the same `"i"` capitalizes to `"İ"` - as well as sorting strings, which also varies
## by locale; `"ö"` is sorted differently in German and Swedish) see the [roc/locale](roc/locale) package._
##
## ### Unicode
##
## Unicode can represent text values which span multiple languages, symbols, and emoji.
## Here are some valid Roc strings:
##
## "Roc!"
## "鹏"
## "🕊"
##
## Every Unicode string is a sequence of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster).
## An extended grapheme cluster represents what a person reading a string might
## call a "character" - like "A" or "ö" or "👩‍👩‍👦‍👦".
## Because the term "character" means different things in different areas of
## programming, and "extended grapheme cluster" is a mouthful, in Roc we use the
## term "grapheme" as a shorthand for the more precise "extended grapheme cluster."
##
## You can get the number of graphemes in a string by calling [Str.countGraphemes] on it:
##
## Str.countGraphemes "Roc!"
## Str.countGraphemes "折り紙"
## Str.countGraphemes "🕊"
##
## > The `countGraphemes` function walks through the entire string to get its answer,
## > so if you want to check whether a string is empty, you'll get much better performance
## > by calling `Str.isEmpty myStr` instead of `Str.countGraphemes myStr == 0`.
##
## ### Escape sequences
##
## If you put a `\` in a Roc string literal, it begins an *escape sequence*.
## An escape sequence is a convenient way to insert certain strings into other strings.
## For example, suppose you write this Roc string:
##
## "I took the one less traveled by,\nAnd that has made all the difference."
##
## The `"\n"` in the middle will insert a line break into this string. There are
## other ways of getting a line break in there, but `"\n"` is the most common.
##
## Another way you could insert a newlines is by writing `\u{0x0A}` instead of `\n`.
## That would result in the same string, because the `\u` escape sequence inserts
## [Unicode code points](https://unicode.org/glossary/#code_point) directly into
## the string. The Unicode code point 10 is a newline, and 10 is `0A` in hexadecimal.
## `0x0A` is a Roc hexadecimal literal, and `\u` escape sequences are always
## followed by a hexadecimal literal inside `{` and `}` like this.
##
## As another example, `"R\u{0x6F}c"` is the same string as `"Roc"`, because
## `"\u{0x6F}"` corresponds to the Unicode code point for lowercase `o`. If you
## want to [spice things up a bit](https://en.wikipedia.org/wiki/Metal_umlaut),
## you can write `"R\u{0xF6}c"` as an alternative way to get the string `"Röc"\.
##
## Roc strings also support these escape sequences:
##
## * `\\` - an actual backslash (writing a single `\` always begins an escape sequence!)
## * `\"` - an actual quotation mark (writing a `"` without a `\` ends the string)
## * `\r` - [carriage return](https://en.wikipedia.org/wiki/Carriage_Return)
## * `\t` - [horizontal tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters)
## * `\v` - [vertical tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters)
##
## You can also use escape sequences to insert named strings into other strings, like so:
##
## name = "Lee"
## city = "Roctown"
##
## greeting = "Hello there, \(name)! Welcome to \(city)."
##
## Here, `greeting` will become the string `"Hello there, Lee! Welcome to Roctown."`.
## This is known as [string interpolation](https://en.wikipedia.org/wiki/String_interpolation),
## and you can use it as many times as you like inside a string. The name
## between the parentheses must refer to a `Str` value that is currently in
## scope, and it must be a name - it can't be an arbitrary expression like a function call.
##
## ### Encoding
##
## Roc strings are not coupled to any particular
## [encoding](https://en.wikipedia.org/wiki/Character_encoding). As it happens,
## they are currently encoded in UTF-8, but this module is intentionally designed
## not to rely on that implementation detail so that a future release of Roc can
## potentially change it without breaking existing Roc applications. (UTF-8
## seems pretty great today, but so did UTF-16 at an earlier point in history.)
##
## This module has functions to can convert a [Str] to a [List] of raw [code unit](https://unicode.org/glossary/#code_unit)
## integers (not to be confused with the [code points](https://unicode.org/glossary/#code_point)
## mentioned earlier) in a particular encoding. If you need encoding-specific functions,
## you should take a look at the [roc/unicode](roc/unicode) package.
## It has many more tools than this module does!
## A [Unicode](https://unicode.org) text value.
Str : [ @Str ]
## Convert
## Convert a [Float] to a decimal string, rounding off to the given number of decimal places.
##
## If you want to keep all the digits, use [Str.num] instead.
decimal : Float *, Nat -> Str
## Convert a [Num] to a string.
num : Float *, Nat -> Str
## Split a string around a separator.
##
## >>> Str.split "1,2,3" ","
##
## Passing `""` for the separator is not useful; it returns the original string
## wrapped in a list.
##
## >>> Str.split "1,2,3" ""
##
## To split a string into its individual graphemes, use `Str.graphemes`
split : Str, Str -> List Str
## Split a string around newlines.
##
## On strings that use `"\n"` for their line endings, this gives the same answer
## as passing `"\n"` to [Str.split]. However, on strings that use `"\n\r"` (such
## as [in Windows files](https://en.wikipedia.org/wiki/Newline#History)), this
## will consume the entire `"\n\r"` instead of just the `"\n"`.
##
## >>> Str.lines "Hello, World!\nNice to meet you!"
##
## >>> Str.lines "Hello, World!\n\rNice to meet you!"
##
## To split a string using a custom separator, use [Str.split]. For more advanced
## string splitting, use a #Parser.
lines : Str, Str -> List Str
## Check
## Returns `True` if the string is empty, and `False` otherwise.
##
## >>> Str.isEmpty "hi!"
##
## >>> Str.isEmpty ""
isEmpty : Str -> Bool
startsWith : Str, Str -> Bool
## If the string begins with a [Unicode code point](http://www.unicode.org/glossary/#code_point)
## equal to the given [U32], return `True`. Otherwise return `False`.
##
## If the given [Str] is empty, or if the given [U32] is not a valid
## code point, this will return `False`.
##
## **Performance Note:** This runs slightly faster than [Str.startsWith], so
## if you want to check whether a string begins with something that's representable
## in a single code point, you can use (for example) `Str.startsWithCodePt '鹏'`
## instead of `Str.startsWithCodePt "鹏"`. ('鹏' evaluates to the [U32]
## value `40527`.) This will not work for graphemes which take up multiple code
## points, however; `Str.startsWithCodePt '👩‍👩‍👦‍👦'` would be a compiler error
## because 👩‍👩‍👦‍👦 takes up multiple code points and cannot be represented as a
## single [U32]. You'd need to use `Str.startsWithCodePt "🕊"` instead.
startsWithCodePt : Str, U32 -> Bool
endsWith : Str, Str -> Bool
contains : Str, Str -> Bool
anyGraphemes : Str, (Str -> Bool) -> Bool
allGraphemes : Str, (Str -> Bool) -> Bool
## Combine
## Combine a list of strings into a single string.
##
## >>> Str.join [ "a", "bc", "def" ]
join : List Str -> Str
## Combine a list of strings into a single string, with a separator
## string in between each.
##
## >>> Str.joinWith [ "one", "two", "three" ] ", "
joinWith : List Str, Str -> Str
## Add to the start of a string until it has at least the given number of
## graphemes.
##
## >>> Str.padGraphemesStart "0" 5 "36"
##
## >>> Str.padGraphemesStart "0" 1 "36"
##
## >>> Str.padGraphemesStart "0" 5 "12345"
##
## >>> Str.padGraphemesStart "✈️"" 5 "👩‍👩‍👦‍👦👩‍👩‍👦‍👦👩‍👩‍👦‍👦"
padGraphemesStart : Str, Nat, Str -> Str
## Add to the end of a string until it has at least the given number of
## graphemes.
##
## >>> Str.padGraphemesStart "0" 5 "36"
##
## >>> Str.padGraphemesStart "0" 1 "36"
##
## >>> Str.padGraphemesStart "0" 5 "12345"
##
## >>> Str.padGraphemesStart "✈️"" 5 "👩‍👩‍👦‍👦👩‍👩‍👦‍👦👩‍👩‍👦‍👦"
padGraphemesEnd : Str, Nat, Str -> Str
## Graphemes
## Split a string into its individual graphemes.
##
## >>> Str.graphemes "1,2,3"
##
## >>> Str.graphemes "👍👍👍"
##
graphemes : Str -> List Str
## Count the number of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster)
## in the string.
##
## Str.countGraphemes "Roc!" # 4
## Str.countGraphemes "七巧板" # 3
## Str.countGraphemes "🕊" # 1
countGraphemes : Str -> Nat
## Reverse the order of the string's individual graphemes.
##
## >>> Str.reverseGraphemes "1-2-3"
##
## >>> Str.reverseGraphemes "🐦✈️"👩‍👩‍👦‍👦"
##
## >>> Str.reversegraphemes "Crème Brûlée"
reverseGraphemes : Str -> Str
## Returns `True` if the two strings are equal when ignoring case.
##
## >>> Str.caseInsensitiveEq "hi" "Hi"
isCaseInsensitiveEq : Str, Str -> Bool
isCaseInsensitiveNeq : Str, Str -> Bool
walkGraphemes : Str, { start: state, step: (state, Str -> state) } -> state
walkGraphemesUntil : Str, { start: state, step: (state, Str -> [ Continue state, Done state ]) } -> state
walkGraphemesBackwards : Str, { start: state, step: (state, Str -> state) } -> state
walkGraphemesBackwardsUntil : Str, { start: state, step: (state, Str -> [ Continue state, Done state ]) } -> state
## Returns `True` if the string begins with an uppercase letter.
##
## >>> Str.isCapitalized "Hi"
##
## >>> Str.isCapitalized " Hi"
##
## >>> Str.isCapitalized "hi"
##
## >>> Str.isCapitalized "Česká"
##
## >>> Str.isCapitalized "Э"
##
## >>> Str.isCapitalized "東京"
##
## >>> Str.isCapitalized "🐦"
##
## >>> Str.isCapitalized ""
##
## Since the rules for how to capitalize a string vary by locale,
## (for example, in English, `"i"` capitalizes to `"I"`, but
## [in Turkish](https://en.wikipedia.org/wiki/Dotted_and_dotless_I#In_computing),
## the same `"i"` capitalizes to `"İ"`) see the [roc/locale](roc/locale) package
## package for functions which capitalize strings.
isCapitalized : Str -> Bool
## Returns `True` if the string consists entirely of uppercase letters.
##
## >>> Str.isAllUppercase "hi"
##
## >>> Str.isAllUppercase "Hi"
##
## >>> Str.isAllUppercase "HI"
##
## >>> Str.isAllUppercase " Hi"
##
## >>> Str.isAllUppercase "Česká"
##
## >>> Str.isAllUppercase "Э"
##
## >>> Str.isAllUppercase "東京"
##
## >>> Str.isAllUppercase "🐦"
##
## >>> Str.isAllUppercase ""
isAllUppercase : Str -> Bool
## Returns `True` if the string consists entirely of lowercase letters.
##
## >>> Str.isAllLowercase "hi"
##
## >>> Str.isAllLowercase "Hi"
##
## >>> Str.isAllLowercase "HI"
##
## >>> Str.isAllLowercase " Hi"
##
## >>> Str.isAllLowercase "Česká"
##
## >>> Str.isAllLowercase "Э"
##
## >>> Str.isAllLowercase "東京"
##
## >>> Str.isAllLowercase "🐦"
##
## >>> Str.isAllLowercase ""
isAllLowercase : Str -> Bool
## Return the string with any blank spaces removed from both the beginning
## as well as the end.
trim : Str -> Str
## If the given [U32] is a valid [Unicode Scalar Value](http://www.unicode.org/glossary/#unicode_scalar_value),
## return a [Str] containing only that scalar.
fromScalar : U32 -> Result Str [ BadScalar ]*
fromCodePts : List U32 -> Result Str [ BadCodePt U32 ]*
fromUtf8 : List U8 -> Result Str [ BadUtf8 ]*
## Create a [Str] from bytes encoded as [UTF-16LE](https://en.wikipedia.org/wiki/UTF-16#Byte-order_encoding_schemes).
# fromUtf16Le : List U8 -> Result Str [ BadUtf16Le Endi ]*
# ## Create a [Str] from bytes encoded as [UTF-16BE](https://en.wikipedia.org/wiki/UTF-16#Byte-order_encoding_schemes).
# fromUtf16Be : List U8 -> Result Str [ BadUtf16Be Endi ]*
# ## Create a [Str] from bytes encoded as UTF-16 with a [Byte Order Mark](https://en.wikipedia.org/wiki/Byte_order_mark).
# fromUtf16Bom : List U8 -> Result Str [ BadUtf16 Endi, NoBom ]*
# ## Create a [Str] from bytes encoded as [UTF-32LE](https://web.archive.org/web/20120322145307/http://mail.apps.ietf.org/ietf/charsets/msg01095.html)
# fromUtf32Le : List U8 -> Result Str [ BadUtf32Le Endi ]*
# ## Create a [Str] from bytes encoded as [UTF-32BE](https://web.archive.org/web/20120322145307/http://mail.apps.ietf.org/ietf/charsets/msg01095.html)
# fromUtf32Be : List U8 -> Result Str [ BadUtf32Be Endi ]*
# ## Create a [Str] from bytes encoded as UTF-32 with a [Byte Order Mark](https://en.wikipedia.org/wiki/Byte_order_mark).
# fromUtf32Bom : List U8 -> Result Str [ BadUtf32 Endi, NoBom ]*
# ## Convert from UTF-8, substituting the replacement character ("<22>") for any
# ## invalid sequences encountered.
# fromUtf8Sub : List U8 -> Str
# fromUtf16Sub : List U8, Endi -> Str
# fromUtf16BomSub : List U8 -> Result Str [ NoBom ]*
## Return a [List] of the string's [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit).
## (To split the string into a [List] of smaller [Str] values instead of [U8] values,
## see [Str.split] and `Str.graphemes`.)
##
## >>> Str.toUtf8 "👩‍👩‍👦‍👦"
##
## >>> Str.toUtf8 "Roc"
##
## >>> Str.toUtf8 "鹏"
##
## >>> Str.toUtf8 "🐦"
##
## For a more flexible function that walks through each of these [U8] code units
## without creating a [List], see `Str.walkUtf8` and `Str.walkRevUtf8`.
toUtf8 : Str -> List U8
toUtf16Be : Str -> List U8
toUtf16Le : Str -> List U8
# toUtf16Bom : Str, Endi -> List U8
toUtf32Be : Str -> List U8
toUtf32Le : Str -> List U8
# toUtf32Bom : Str, Endi -> List U8
# Parsing
## If the bytes begin with a valid [extended grapheme cluster](http://www.unicode.org/glossary/#extended_grapheme_cluster)
## encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8), return it along with the number of bytes it took up.
##
## If the bytes do not begin with a valid grapheme, for example because the list was
## empty or began with an invalid grapheme, return `Err`.
parseUtf8Grapheme : List U8 -> Result { grapheme : Str, bytesParsed: Nat } [ InvalidGrapheme ]*
## If the bytes begin with a valid [Unicode code point](http://www.unicode.org/glossary/#code_point)
## encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8), return it along with the number of bytes it took up.
##
## If the string does not begin with a valid code point, for example because the list was
## empty or began with an invalid code point, return an `Err`.
parseUtf8CodePt : List U8 -> Result { codePt : U32, bytesParsed: Nat } [ InvalidCodePt ]*
## If the string represents a valid [U8] number, return that number.
##
## For more advanced options, see [parseU8].
toU8 : Str -> Result U8 [ InvalidU8 ]*
toI8 : Str -> Result I8 [ InvalidI8 ]*
toU16 : Str -> Result U16 [ InvalidU16 ]*
toI16 : Str -> Result I16 [ InvalidI16 ]*
toU32 : Str -> Result U32 [ InvalidU32 ]*
toI32 : Str -> Result I32 [ InvalidI32 ]*
toU64 : Str -> Result U64 [ InvalidU64 ]*
toI64 : Str -> Result I64 [ InvalidI64 ]*
toU128 : Str -> Result U128 [ InvalidU128 ]*
toI128 : Str -> Result I128 [ InvalidI128 ]*
toF64 : Str -> Result U128 [ InvalidF64 ]*
toF32 : Str -> Result I128 [ InvalidF32 ]*
toDec : Str -> Result Dec [ InvalidDec ]*
## If the string represents a valid number, return that number.
##
## The exact number type to look for will be inferred from usage.
## In the example below, the usage of I64 in the type signature will require that type instead of (Num *).
##
## >>> strToI64 : Str -> Result I64 [ InvalidNumStr ]*
## >>> strToI64 = \inputStr ->
## >>> Str.toNum inputStr
##
## If the string is exactly `"NaN"`, `"∞"`, or `"-∞"`, they will be accepted
## only when converting to [F64] or [F32] numbers, and will be translated accordingly.
##
## This never accepts numbers with underscores or commas in them. For more
## advanced options, see [parseNum].
toNum : Str -> Result (Num *) [ InvalidNumStr ]*
## If the string begins with an [Int] or a [finite](Num.isFinite) [Frac], return
## that number along with the rest of the string after it.
##
## The exact number type to look for will be inferred from usage.
## In the example below, the usage of Float64 in the type signature will require that type instead of (Num *).
##
## >>> parseFloat64 : Str -> Result { val: Float64, rest: Str } [ InvalidNumStr ]*
## >>> Str.parseNum input {}
##
## If the string begins with `"NaN"`, `"∞"`, and `"-∞"` (which do not represent
## [finite](Num.isFinite) numbers), they will be accepted only when parsing
## [F64] or [F32] numbers, and translated accordingly.
# parseNum : Str, NumParseConfig -> Result { val : Num *, rest : Str } [ InvalidNumStr ]*
## Notes:
## * You can allow a decimal mark for integers; they'll only parse if the numbers after it are all 0.
## * For `wholeSep`, `Required` has a payload for how many digits (e.g. "required every 3 digits")
## * For `wholeSep`, `Allowed` allows the separator to appear anywhere.
# NumParseConfig :
# {
# base ? [ Decimal, Hexadecimal, Octal, Binary ],
# notation ? [ Standard, Scientific, Any ],
# decimalMark ? [ Allowed Str, Required Str, Disallowed ],
# decimalDigits ? [ Any, AtLeast U16, Exactly U16 ],
# wholeDigits ? [ Any, AtLeast U16, Exactly U16 ],
# leadingZeroes ? [ Allowed, Disallowed ],
# trailingZeroes ? [ Allowed, Disallowed ],
# wholeSep ? { mark : Str, policy : [ Allowed, Required U64 ] }
# }

View File

@ -4,11 +4,81 @@ interface Bool
Bool : [ True, False ]
## Returns `True` when given `True` and `True`, and `False` when either argument is `False`.
##
## `a && b` is shorthand for `Bool.and a b`
##
## >>> True && True
##
## >>> True && False
##
## >>> False && True
##
## >>> False && False
##
## ## Performance Notes
##
## In some languages, `&&` and `||` are special-cased in the compiler to skip
## evaluating the expression after the operator under certain circumstances.
## For example, in some languages, `enablePets && likesDogs user` would compile
## to the equivalent of:
##
## if enablePets then
## likesDogs user
## else
## False
##
## In Roc, however, `&&` and `||` are not special. They work the same way as
## other functions. Conditionals like `if` and `when` have a performance cost,
## and sometimes calling a function like `likesDogs user` can be faster across
## the board than doing an `if` to decide whether to skip calling it.
##
## (Naturally, if you expect the `if` to improve performance, you can always add
## one explicitly!)
and : Bool, Bool -> Bool
## Returns `True` when given `True` for either argument, and `False` only when given `False` and `False`.
##
## `a || b` is shorthand for `Bool.or a b`.
##
## >>> True || True
##
## >>> True || False
##
## >>> False || True
##
## >>> False || False
##
## ## Performance Notes
##
## In some languages, `&&` and `||` are special-cased in the compiler to skip
## evaluating the expression after the operator under certain circumstances.
## In Roc, this is not the case. See the performance notes for [Bool.and] for details.
or : Bool, Bool -> Bool
# xor : Bool, Bool -> Bool # currently unimplemented
## Returns `False` when given `True`, and vice versa.
not : Bool -> Bool
## Returns `True` if the two values are *structurally equal*, and `False` otherwise.
##
## `a == b` is shorthand for `Bool.isEq a b`
##
## Structural equality works as follows:
##
## 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](Num#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.
isEq : a, a -> Bool
## Calls [isEq] on the given values, then calls [not] on the result.
##
## `a != b` is shorthand for `Bool.isNotEq a b`
##
## Note that `isNotEq` takes `'val` instead of `val`, which means `isNotEq` does not
## accept arguments whose types contain functions.
isNotEq : a, a -> Bool

View File

@ -1,6 +1,6 @@
interface Dict
exposes
[
exposes
[
empty,
single,
get,
@ -15,8 +15,62 @@ interface Dict
intersection,
difference,
]
imports [ ]
imports
[
Bool.{ Bool }
]
## A [dictionary](https://en.wikipedia.org/wiki/Associative_array) that lets you can associate keys with values.
##
## ### Inserting
##
## The most basic way to use a dictionary is to start with an empty one and then:
## 1. Call [Dict.insert] passing a key and a value, to associate that key with that value in the dictionary.
## 2. Later, call [Dict.get] passing the same key as before, and it will return the value you stored.
##
## Here's an example of a dictionary which uses a city's name as the key, and its population as the associated value.
##
## populationByCity =
## Dict.empty
## |> Dict.insert "London" 8_961_989
## |> Dict.insert "Philadelphia" 1_603_797
## |> Dict.insert "Shanghai" 24_870_895
## |> Dict.insert "Delhi" 16_787_941
## |> Dict.insert "Amsterdam" 872_680
##
## ### Accessing keys or values
##
## We can use [Dict.keys] and [Dict.values] functions to get only the keys or only the values.
##
## You may notice that these lists have the same order as the original insertion order. This will be true if
## all you ever do is [insert] and [get] operations on the dictionary, but [remove] operations can change this order.
## Let's see how that looks.
##
## ### Removing
##
## We can remove an element from the dictionary, like so:
##
## populationByCity
## |> Dict.remove "Philadelphia"
## |> Dict.keys
## ==
## [ "London", "Amsterdam", "Shanghai", "Delhi" ]
##
## Notice that the order changed! Philadelphia has been not only removed from the list, but Amsterdam - the last
## entry we inserted - has been moved into the spot where Philadelphia was previously. This is exactly what
## [Dict.remove] does: it removes an element and moves the most recent insertion into the vacated spot.
##
## This move is done as a performance optimization, and it lets [remove] have
## [constant time complexity](https://en.wikipedia.org/wiki/Time_complexity#Constant_time). ##
##
## ### Equality
##
## When comparing two dictionaries for equality, they are `==` only if their both their contents and their
## orderings match. This preserves the property that if `dict1 == dict2`, you should be able to rely on
## `fn dict1 == fn dict2` also being `True`, even if `fn` relies on the dictionary's ordering.
## An empty dictionary.
empty : Dict k v
single : k, v -> Dict k v
get : Dict k v, k -> Result v [ KeyNotFound ]*
@ -25,7 +79,11 @@ insert : Dict k v, k, v -> Dict k v
len : Dict k v -> Nat
remove : Dict k v, k -> Dict k v
contains : Dict k v, k -> Bool
## Returns a [List] of the dictionary's keys.
keys : Dict k v -> List k
## Returns a [List] of the dictionary's values.
values : Dict k v -> List v
union : Dict k v, Dict k v -> Dict k v
intersection : Dict k v, Dict k v -> Dict k v

View File

@ -1,121 +1,619 @@
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 }
]
## Types
## A sequential list of values.
##
## >>> [ 1, 2, 3 ] # a list of numbers
## >>> [ "a", "b", "c" ] # a list of strings
## >>> [ [ 1.1 ], [], [ 2.2, 3.3 ] ] # a list of lists of numbers
##
## The maximum size of a [List] is limited by the amount of heap memory available
## to the current process. If there is not enough memory available, attempting to
## create the list could crash. (On Linux, where [overcommit](https://www.etalabs.net/overcommit.html)
## is normally enabled, not having enough memory could result in the list appearing
## to be created just fine, but then crashing later.)
##
## > The theoretical maximum length for a list created in Roc is half of
## > `Num.maxNat`. Attempting to create a list bigger than that
## > in Roc code will always fail, although in practice it is likely to fail
## > at much smaller lengths due to insufficient memory being available.
##
## ## Performance Details
##
## Under the hood, a list is a record containing a `len : Nat` field as well
## as a pointer to a reference count and a flat array of bytes. Unique lists
## store a capacity #Nat instead of a reference count.
##
## ## Shared Lists
##
## Shared lists are [reference counted](https://en.wikipedia.org/wiki/Reference_counting).
##
## Each time a given list gets referenced, its reference count ("refcount" for short)
## gets incremented. Each time a list goes out of scope, its refcount count gets
## decremented. Once a refcount, has been decremented more times than it has been
## incremented, we know nothing is referencing it anymore, and the list's memory
## will be immediately freed.
##
## Let's look at an example.
##
## ratings = [ 5, 4, 3 ]
##
## { foo: ratings, bar: ratings }
##
## The first line binds the name `ratings` to the list `[ 5, 4, 3 ]`. The list
## begins with a refcount of 1, because so far only `ratings` is referencing it.
##
## The second line alters this refcount. `{ foo: ratings` references
## the `ratings` list, which will result in its refcount getting incremented
## from 0 to 1. Similarly, `bar: ratings }` also references the `ratings` list,
## which will result in its refcount getting incremented from 1 to 2.
##
## Let's turn this example into a function.
##
## getRatings = \first ->
## ratings = [ first, 4, 3 ]
##
## { foo: ratings, bar: ratings }
##
## getRatings 5
##
## At the end of the `getRatings` function, when the record gets returned,
## the original `ratings =` binding has gone out of scope and is no longer
## accessible. (Trying to reference `ratings` outside the scope of the
## `getRatings` function would be an error!)
##
## Since `ratings` represented a way to reference the list, and that way is no
## longer accessible, the list's refcount gets decremented when `ratings` goes
## out of scope. It will decrease from 2 back down to 1.
##
## Putting these together, when we call `getRatings 5`, what we get back is
## a record with two fields, `foo`, and `bar`, each of which refers to the same
## list, and that list has a refcount of 1.
##
## Let's change the last line to be `(getRatings 5).bar` instead of `getRatings 5`:
##
## getRatings = \first ->
## ratings = [ first, 4, 3 ]
##
## { foo: ratings, bar: ratings }
##
## (getRatings 5).bar
##
## Now, when this expression returns, only the `bar` field of the record will
## be returned. This will mean that the `foo` field becomes inaccessible, causing
## the list's refcount to get decremented from 2 to 1. At this point, the list is back
## where it started: there is only 1 reference to it.
##
## Finally let's suppose the final line were changed to this:
##
## List.first (getRatings 5).bar
##
## This call to [List.first] means that even the list in the `bar` field has become
## inaccessible. As such, this line will cause the list's refcount to get
## decremented all the way to 0. At that point, nothing is referencing the list
## anymore, and its memory will get freed.
##
## Things are different if this is a list of lists instead of a list of numbers.
## Let's look at a simpler example using [List.first] - first with a list of numbers,
## and then with a list of lists, to see how they differ.
##
## Here's the example using a list of numbers.
##
## nums = [ 1, 2, 3, 4, 5, 6, 7 ]
##
## first = List.first nums
## last = List.last nums
##
## first
##
## It makes a list, calls [List.first] and [List.last] on it, and then returns `first`.
##
## Here's the equivalent code with a list of lists:
##
## lists = [ [ 1 ], [ 2, 3 ], [], [ 4, 5, 6, 7 ] ]
##
## first = List.first lists
## last = List.last lists
##
## first
##
## TODO explain how in the former example, when we go to free `nums` at the end,
## we can free it immediately because there are no other refcounts. However,
## in the case of `lists`, we have to iterate through the list and decrement
## the refcounts of each of its contained lists - because they, too, have
## refcounts! Importantly, because the first element had its refcount incremented
## because the function returned `first`, that element will actually end up
## *not* getting freed at the end - but all the others will be.
##
## In the `lists` example, `lists = [ ... ]` also creates a list with an initial
## refcount of 1. Separately, it also creates several other lists - each with
## their own refcounts - to go inside that list. (The empty list at the end
## does not use heap memory, and thus has no refcount.)
##
## At the end, we once again call [List.first] on the list, but this time
##
## * Copying small lists (64 elements or fewer) is typically slightly faster than copying small persistent data structures. This is because, at small sizes, persistent data structures tend to be thin wrappers around flat arrays anyway. They don't have any copying advantage until crossing a certain minimum size threshold.
## * 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!
## Check if the list is empty.
##
## >>> List.isEmpty [ 1, 2, 3 ]
##
## >>> List.isEmpty []
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 }
## Replaces the element at the given index with a replacement.
##
## >>> List.set [ "a", "b", "c" ] 1 "B"
##
## If the given index is outside the bounds of the list, returns the original
## list unmodified.
##
## To drop the element at a given index, instead of replacing it, see [List.dropAt].
set : List a, Nat, a -> List a
set = \list, index, value ->
(List.replace list index value).list
## Add a single element to the end of a list.
##
## >>> List.append [ 1, 2, 3 ] 4
##
## >>> [ 0, 1, 2 ]
## >>> |> List.append 3
append : List a, a -> List a
## Add a single element to the beginning of a list.
##
## >>> List.prepend [ 1, 2, 3 ] 0
##
## >>> [ 2, 3, 4 ]
## >>> |> List.prepend 1
prepend : List a, a -> List a
## Returns the length of the list - the number of elements it contains.
##
## One [List] can store up to 2,147,483,648 elements (just over 2 billion), which
## is exactly equal to the highest valid #I32 value. This means the #U32 this function
## returns can always be safely converted to an #I32 without losing any data.
len : List a -> Nat
## Put two lists together.
##
## >>> List.concat [ 1, 2, 3 ] [ 4, 5 ]
##
## >>> [ 0, 1, 2 ]
## >>> |> List.concat [ 3, 4 ]
concat : List a, List a -> List a
## Returns the last element in the list, or `ListWasEmpty` if it was empty.
last : List a -> Result a [ ListWasEmpty ]*
## A list with a single element in it.
##
## This is useful in pipelines, like so:
##
## websites =
## Str.concat domain ".com"
## |> List.single
##
single : a -> List a
## Returns a list with the given length, where every element is the given value.
##
##
repeat : a, Nat -> List a
## Returns the list with its elements reversed.
##
## >>> List.reverse [ 1, 2, 3 ]
reverse : List a -> List a
## Join the given lists together into one list.
##
## >>> List.join [ [ 1, 2, 3 ], [ 4, 5 ], [], [ 6, 7 ] ]
##
## >>> List.join [ [], [] ]
##
## >>> List.join []
join : List (List a) -> List a
contains : List a, a -> Bool
## Build a value using each element in the list.
##
## Starting with a given `state` value, this walks through each element in the
## list from first to last, running a given `step` function on that element
## which updates the `state`. It returns the final `state` at the end.
##
## You can use it in a pipeline:
##
## [ 2, 4, 8 ]
## |> List.walk { start: 0, step: Num.add }
##
## This returns 14 because:
## * `state` starts at 0 (because of `start: 0`)
## * Each `step` runs `Num.add state elem`, and the return value becomes the new `state`.
##
## Here is a table of how `state` changes as [List.walk] walks over the elements
## `[ 2, 4, 8 ]` using #Num.add as its `step` function to determine the next `state`.
##
## `state` | `elem` | `step state elem` (`Num.add state elem`)
## --------+--------+-----------------------------------------
## 0 | |
## 0 | 2 | 2
## 2 | 4 | 6
## 6 | 8 | 14
##
## So `state` goes through these changes:
## 1. `0` (because of `start: 0`)
## 2. `1` (because of `Num.add state elem` with `state` = 0 and `elem` = 1
##
## [ 1, 2, 3 ]
## |> List.walk { start: 0, step: Num.sub }
##
## This returns -6 because
##
## Note that in other languages, `walk` is sometimes called `reduce`,
## `fold`, `foldLeft`, or `foldl`.
walk : List elem, state, (state, elem -> state) -> state
## Note that in other languages, `walkBackwards` is sometimes called `reduceRight`,
## `fold`, `foldRight`, or `foldr`.
walkBackwards : List elem, state, (state, elem -> state) -> state
## Same as [List.walk], except you can stop walking early.
##
## ## Performance Details
##
## Compared to [List.walk], this can potentially visit fewer elements (which can
## improve performance) at the cost of making each step take longer.
## However, the added cost to each step is extremely small, and can easily
## be outweighed if it results in skipping even a small number of elements.
##
## As such, it is typically better for performance to use this over [List.walk]
## if returning `Done` earlier than the last element is expected to be common.
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
## Run the given predicate on each element of the list, returning `True` if
## any of the elements satisfy it.
any : List a, (a -> Bool) -> Bool
## Run the given predicate on each element of the list, returning `True` if
## all of the elements satisfy it.
all : List a, (a -> Bool) -> Bool
## Run the given function on each element of a list, and return all the
## elements for which the function returned `True`.
##
## >>> List.keepIf [ 1, 2, 3, 4 ] (\num -> num > 2)
##
## ## Performance Details
##
## [List.keepIf] always returns a list that takes up exactly the same amount
## of memory as the original, even if its length decreases. This is because it
## can't know in advance exactly how much space it will need, and if it guesses a
## length that's too low, it would have to re-allocate.
##
## (If you want to do an operation like this which reduces the memory footprint
## of the resulting list, you can do two passes over the lis with [List.walk] - one
## to calculate the precise new size, and another to populate the new list.)
##
## If given a unique list, [List.keepIf] will mutate it in place to assemble the appropriate list.
## If that happens, this function will not allocate any new memory on the heap.
## If all elements in the list end up being kept, Roc will return the original
## list unaltered.
##
keepIf : List a, (a -> Bool) -> List a
dropIf : List a, (a -> Bool) -> List a
## Run the given function on each element of a list, and return all the
## elements for which the function returned `False`.
##
## >>> List.dropIf [ 1, 2, 3, 4 ] (\num -> num > 2)
##
## ## Performance Details
##
## `List.dropIf` has the same performance characteristics as [List.keepIf].
## See its documentation for details on those characteristics!
dropIf : List a, (a -> Bool) -> List a
dropIf = \list, predicate ->
List.keepIf list (\e -> Bool.not (predicate e))
## This works like [List.map], except only the transformed values that are
## wrapped in `Ok` are kept. Any that are wrapped in `Err` are dropped.
##
## >>> List.keepOks [ [ "a", "b" ], [], [], [ "c", "d", "e" ] ] List.last
##
## >>> fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str)
## >>>
## >>> List.keepOks [ "", "a", "bc", "", "d", "ef", "" ]
keepOks : List before, (before -> Result after *) -> List after
## This works like [List.map], except only the transformed values that are
## wrapped in `Err` are kept. Any that are wrapped in `Ok` are dropped.
##
## >>> List.keepErrs [ [ "a", "b" ], [], [], [ "c", "d", "e" ] ] List.last
##
## >>> fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str)
## >>>
## >>> List.keepErrs [ "", "a", "bc", "", "d", "ef", "" ]
keepErrs: List before, (before -> Result * after) -> List after
## Convert each element in the list to something new, by calling a conversion
## function on each of them. Then return a new list of the converted values.
##
## > List.map [ 1, 2, 3 ] (\num -> num + 1)
##
## > List.map [ "", "a", "bc" ] Str.isEmpty
map : List a, (a -> b) -> List b
## Run a transformation function on the first element of each list,
## and use that as the first element in the returned list.
## Repeat until a list runs out of elements.
##
## Some languages have a function named `zip`, which does something similar to
## calling [List.map2] passing two lists and `Pair`:
##
## >>> zipped = List.map2 [ "a", "b", "c" ] [ 1, 2, 3 ] Pair
map2 : List a, List b, (a, b -> c) -> List c
## Run a transformation function on the first element of each list,
## and use that as the first element in the returned list.
## Repeat until a list runs out of elements.
map3 : List a, List b, List c, (a, b, c -> d) -> List d
## Run a transformation function on the first element of each list,
## and use that as the first element in the returned list.
## Repeat until a list runs out of elements.
map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e
mapWithIndex : List a, (a -> b) -> List b
## This works like [List.map], except it also passes the index
## of the element to the conversion function.
mapWithIndex : List a, (a, Nat -> b) -> List b
## Returns a list of all the integers between one and another,
## including both of the given numbers.
##
## >>> List.range 2 8
range : Int a, Int a -> List (Int a)
sortWith : List a, (a, a -> [ LT, EQ, GT ] ) -> List a
## Sorts a list in ascending order (lowest to highest), using a function which
## specifies a way to represent each element as a number.
##
## To sort in descending order (highest to lowest), use [List.sortDesc] instead.
sortAsc : List (Num a) -> List (Num a)
sortAsc = \list -> List.sortWith list Num.compare
## Sorts a list in descending order (highest to lowest), using a function which
## specifies a way to represent each element as a number.
##
## To sort in ascending order (lowest to highest), use [List.sortAsc] instead.
sortDesc : List (Num a) -> List (Num a)
sortDesc = \list -> List.sortWith list (\a, b -> Num.compare b a)
swap : List a, Nat, Nat -> List a
## Returns the first element in the list, or `ListWasEmpty` if it was empty.
first : List a -> Result a [ ListWasEmpty ]*
## Remove the first element from the list.
##
## Returns the new list (with the removed element missing).
dropFirst : List elem -> List elem
## Remove the last element from the list.
##
## Returns the new list (with the removed element missing).
dropLast : List elem -> List elem
## Returns the given number of elements from the beginning of the list.
##
## >>> List.takeFirst 4 [ 1, 2, 3, 4, 5, 6, 7, 8 ]
##
## If there are fewer elements in the list than the requested number,
## returns the entire list.
##
## >>> List.takeFirst 5 [ 1, 2 ]
##
## To *remove* elements from the beginning of the list, use `List.takeLast`.
##
## To remove elements from both the beginning and end of the list,
## use `List.sublist`.
##
## To split the list into two lists, use `List.split`.
##
## ## Performance Details
##
## When given a Unique list, this runs extremely fast. It sets the list's length
## to the given length value, and frees the leftover elements. This runs very
## slightly faster than `List.takeLast`.
##
## In fact, `List.takeFirst 1 list` runs faster than `List.first list` when given
## a Unique list, because [List.first] returns the first element as well -
## which introduces a conditional bounds check as well as a memory load.
takeFirst : List elem, Nat -> List elem
## Returns the given number of elements from the end of the list.
##
## >>> List.takeLast 4 [ 1, 2, 3, 4, 5, 6, 7, 8 ]
##
## If there are fewer elements in the list than the requested number,
## returns the entire list.
##
## >>> List.takeLast 5 [ 1, 2 ]
##
## To *remove* elements from the end of the list, use `List.takeFirst`.
##
## To remove elements from both the beginning and end of the list,
## use `List.sublist`.
##
## To split the list into two lists, use `List.split`.
##
## ## Performance Details
##
## When given a Unique list, this runs extremely fast. It moves the list's
## pointer to the index at the given length value, updates its length,
## and frees the leftover elements. This runs very nearly as fast as
## `List.takeFirst` on a Unique list.
##
## In fact, `List.takeLast 1 list` runs faster than `List.first list` when given
## a Unique list, because [List.first] returns the first element as well -
## which introduces a conditional bounds check as well as a memory load.
takeLast : List elem, Nat -> List elem
## Drops n elements from the beginning of the list.
drop : List elem, Nat -> List elem
## Drops the element at the given index from the list.
##
## This has no effect if the given index is outside the bounds of the list.
##
## To replace the element at a given index, instead of dropping it, see [List.set].
dropAt : List elem, Nat -> List elem
min : List (Num a) -> Result (Num a) [ ListWasEmpty ]*
max : 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
## Like [List.map], except the transformation function wraps the return value
## in a list. At the end, all the lists get joined together into one list.
##
## You may know a similar function named `concatMap` in other languages.
joinMap : List a, (a -> List b) -> List b
joinMap = \list, mapper ->
List.walk list [] (\state, elem -> List.concat state (mapper elem))
## Returns the first element of the list satisfying a predicate function.
## If no satisfying element is found, an `Err NotFound` is returned.
find : List elem, (elem -> Bool) -> Result elem [ NotFound ]*
## Returns a subsection of the given list, beginning at the `start` index and
## including a total of `len` elements.
##
## If `start` is outside the bounds of the given list, returns the empty list.
##
## >>> List.sublist { start: 4, len: 0 } [ 1, 2, 3 ]
##
## If more elements are requested than exist in the list, returns as many as it can.
##
## >>> List.sublist { start: 2, len: 10 } [ 1, 2, 3, 4, 5 ]
##
## > If you want a sublist which goes all the way to the end of the list, no
## > matter how long the list is, `List.takeLast` can do that more efficiently.
##
## Some languages have a function called **`slice`** which works similarly to this.
sublist : List elem, { start : Nat, len : Nat } -> List elem
intersperse : List elem, elem -> List elem
## Splits the list into two lists, around the given index.
##
## The returned lists are labeled `before` and `others`. The `before` list will
## contain all the elements whose index in the original list was **less than**
## than the given index, # and the `others` list will be all the others. (This
## means if you give an index of 0, the `before` list will be empty and the
## `others` list will have the same elements as the original list.)
split : List elem, Nat -> { before: List elem, others: List elem }

File diff suppressed because it is too large Load Diff

View File

@ -1,39 +1,80 @@
interface Result
exposes [ Result, isOk, isErr, map, mapErr, after, withDefault ]
imports [ ]
imports [ Bool.{ Bool } ]
## 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 : [ Ok ok, Err err ]
## Return True if the result indicates a success, else return False
##
## >>> Result.isOk (Ok 5)
isOk : Result ok err -> Bool
isOk = \result ->
when result is
Ok _ -> True
Err _ -> False
## Return True if the result indicates a failure, else return False
##
## >>> Result.isErr (Err "uh oh")
isErr : Result ok err -> Bool
isErr = \result ->
when result is
Ok _ -> False
Err _ -> True
## If the result is `Ok`, return the value it holds. Otherwise, return
## the given default value.
##
## >>> Result.withDefault (Ok 7) 42
##
## >>> Result.withDefault (Err "uh oh") 42
withDefault : Result ok err, ok -> ok
withDefault = \result, default ->
when result is
Ok value -> value
Err _ -> default
## If the result is `Ok`, transform the value it holds by running a conversion
## function on it. Then return a new `Ok` holding the transformed value.
##
## (If the result is `Err`, this has no effect. Use [mapErr] to transform an `Err`.)
##
## >>> Result.map (Ok 12) Num.negate
##
## >>> Result.map (Err "yipes!") Num.negate
##
## `map` functions like this are common in Roc, and they all work similarly.
## See for example [List.map], `Set.map`, and `Dict.map`.
map : Result a err, (a -> b) -> Result b err
map = \result, transform ->
when result is
Ok v -> Ok (transform v)
Err e -> Err e
## If the result is `Err`, transform the value it holds by running a conversion
## function on it. Then return a new `Err` holding the transformed value.
##
## (If the result is `Ok`, this has no effect. Use [map] to transform an `Ok`.)
##
## >>> Result.mapErr (Err "yipes!") Str.isEmpty
##
## >>> Result.mapErr (Ok 12) Str.isEmpty
mapErr : Result ok a, (a -> b) -> Result ok b
mapErr = \result, transform ->
when result is
Ok v -> Ok v
Err e -> Err (transform e)
## If the result is `Ok`, transform the entire result by running a conversion
## function on the value the `Ok` holds. Then return that new result.
##
## (If the result is `Err`, this has no effect. Use `afterErr` to transform an `Err`.)
##
## >>> Result.after (Ok -1) \num -> if num < 0 then Err "negative!" else Ok -num
##
## >>> Result.after (Err "yipes!") \num -> if num < 0 then Err "negative!" else Ok -num
after : Result a err, (a -> Result b err) -> Result b err
after = \result, transform ->
when result is

View File

@ -1,6 +1,6 @@
interface Dict
exposes
[
interface Set
exposes
[
empty,
single,
walk,
@ -14,12 +14,19 @@ interface Dict
intersection,
difference,
]
imports [ ]
imports [ List, Bool.{ Bool }, Dict.{ values } ]
## An empty set.
empty : Set k
single : k -> Set k
## Make sure never to insert a *NaN* to a [Set]! Because *NaN* is defined to be
## unequal to *NaN*, adding a *NaN* results in an entry that can never be
## retrieved or removed from the [Set].
insert : Set k, k -> Set k
len : Set k -> Nat
## Drops the given element from the set.
remove : Set k, k -> Set k
contains : Set k, k -> Bool
@ -35,4 +42,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)

View File

@ -34,8 +34,83 @@ interface Str
toU8,
toI8,
]
imports [ ]
imports [ Bool.{ Bool }, Result.{ Result } ]
## # Types
##
## Dealing with text is a deep topic, so by design, Roc's `Str` module sticks
## to the basics.
##
## ### Unicode
##
## Unicode can represent text values which span multiple languages, symbols, and emoji.
## Here are some valid Roc strings:
##
## "Roc!"
## "鹏"
## "🕊"
##
## Every Unicode string is a sequence of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster).
## An extended grapheme cluster represents what a person reading a string might
## call a "character" - like "A" or "ö" or "👩‍👩‍👦‍👦".
## Because the term "character" means different things in different areas of
## programming, and "extended grapheme cluster" is a mouthful, in Roc we use the
## term "grapheme" as a shorthand for the more precise "extended grapheme cluster."
##
## You can get the number of graphemes in a string by calling [Str.countGraphemes] on it:
##
## Str.countGraphemes "Roc!"
## Str.countGraphemes "折り紙"
## Str.countGraphemes "🕊"
##
## > The `countGraphemes` function walks through the entire string to get its answer,
## > so if you want to check whether a string is empty, you'll get much better performance
## > by calling `Str.isEmpty myStr` instead of `Str.countGraphemes myStr == 0`.
##
## ### Escape sequences
##
## If you put a `\` in a Roc string literal, it begins an *escape sequence*.
## An escape sequence is a convenient way to insert certain strings into other strings.
## For example, suppose you write this Roc string:
##
## "I took the one less traveled by,\nAnd that has made all the difference."
##
## The `"\n"` in the middle will insert a line break into this string. There are
## other ways of getting a line break in there, but `"\n"` is the most common.
##
## Another way you could insert a newlines is by writing `\u{0x0A}` instead of `\n`.
## That would result in the same string, because the `\u` escape sequence inserts
## [Unicode code points](https://unicode.org/glossary/#code_point) directly into
## the string. The Unicode code point 10 is a newline, and 10 is `0A` in hexadecimal.
## `0x0A` is a Roc hexadecimal literal, and `\u` escape sequences are always
## followed by a hexadecimal literal inside `{` and `}` like this.
##
## As another example, `"R\u{0x6F}c"` is the same string as `"Roc"`, because
## `"\u{0x6F}"` corresponds to the Unicode code point for lowercase `o`. If you
## want to [spice things up a bit](https://en.wikipedia.org/wiki/Metal_umlaut),
## you can write `"R\u{0xF6}c"` as an alternative way to get the string `"Röc"\.
##
## Roc strings also support these escape sequences:
##
## * `\\` - an actual backslash (writing a single `\` always begins an escape sequence!)
## * `\"` - an actual quotation mark (writing a `"` without a `\` ends the string)
## * `\r` - [carriage return](https://en.wikipedia.org/wiki/Carriage_Return)
## * `\t` - [horizontal tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters)
## * `\v` - [vertical tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters)
##
## You can also use escape sequences to insert named strings into other strings, like so:
##
## name = "Lee"
## city = "Roctown"
##
## greeting = "Hello there, \(name)! Welcome to \(city)."
##
## Here, `greeting` will become the string `"Hello there, Lee! Welcome to Roctown."`.
## This is known as [string interpolation](https://en.wikipedia.org/wiki/String_interpolation),
## and you can use it as many times as you like inside a string. The name
## between the parentheses must refer to a `Str` value that is currently in
## scope, and it must be a name - it can't be an arbitrary expression like a function call.
Utf8ByteProblem :
@ -50,15 +125,68 @@ Utf8ByteProblem :
Utf8Problem : { byteIndex : Nat, problem : Utf8ByteProblem }
## Returns `True` if the string is empty, and `False` otherwise.
##
## >>> Str.isEmpty "hi!"
##
## >>> Str.isEmpty ""
isEmpty : Str -> Bool
concat : Str, Str -> Str
## Combine a list of strings into a single string, with a separator
## string in between each.
##
## >>> Str.joinWith [ "one", "two", "three" ] ", "
joinWith : List Str, Str -> Str
## Split a string around a separator.
##
## >>> Str.split "1,2,3" ","
##
## Passing `""` for the separator is not useful; it returns the original string
## wrapped in a list.
##
## >>> Str.split "1,2,3" ""
##
## To split a string into its individual graphemes, use `Str.graphemes`
split : Str, Str -> List Str
repeat : Str, Nat -> Str
## Count the number of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster)
## in the string.
##
## Str.countGraphemes "Roc!" # 4
## Str.countGraphemes "七巧板" # 3
## Str.countGraphemes "üïä" # 1
countGraphemes : Str -> Nat
## If the string begins with a [Unicode code point](http://www.unicode.org/glossary/#code_point)
## equal to the given [U32], return `True`. Otherwise return `False`.
##
## If the given [Str] is empty, or if the given [U32] is not a valid
## code point, this will return `False`.
##
## **Performance Note:** This runs slightly faster than [Str.startsWith], so
## if you want to check whether a string begins with something that's representable
## in a single code point, you can use (for example) `Str.startsWithCodePt '鹏'`
## instead of `Str.startsWithCodePt "鹏"`. ('鹏' evaluates to the [U32]
## value `40527`.) This will not work for graphemes which take up multiple code
## points, however; `Str.startsWithCodePt '👩‍👩‍👦‍👦'` would be a compiler error
## because 👩‍👩‍👦‍👦 takes up multiple code points and cannot be represented as a
## single [U32]. You'd need to use `Str.startsWithCodePt "🕊"` instead.
startsWithCodePt : Str, U32 -> Bool
## Return a [List] of the string's [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit).
## (To split the string into a [List] of smaller [Str] values instead of [U8] values,
## see [Str.split].)
##
## >>> Str.toUtf8 "👩‍👩‍👦‍👦"
##
## >>> Str.toUtf8 "Roc"
##
## >>> Str.toUtf8 "鹏"
##
## >>> Str.toUtf8 "🐦"
toUtf8 : Str -> List U8
# fromUtf8 : List U8 -> Result Str [ BadUtf8 Utf8Problem ]*
@ -70,6 +198,8 @@ fromUtf8Range : List U8, { start : Nat, count : Nat } -> Result Str [ BadUtf8 Ut
startsWith : Str, Str -> Bool
endsWith : Str, Str -> Bool
## Return the string with any blank spaces removed from both the beginning
## as well as the end.
trim : Str -> Str
trimLeft : Str -> Str
trimRight : Str -> Str

View File

@ -68,13 +68,9 @@ impl FloatWidth {
pub const fn try_from_symbol(symbol: Symbol) -> Option<Self> {
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<Self> {
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,
}
}
@ -361,6 +347,7 @@ pub const LIST_REPLACE_IN_PLACE: &str = "roc_builtins.list.replace_in_place";
pub const LIST_ANY: &str = "roc_builtins.list.any";
pub const LIST_ALL: &str = "roc_builtins.list.all";
pub const LIST_FIND_UNSAFE: &str = "roc_builtins.list.find_unsafe";
pub const LIST_IS_UNIQUE: &str = "roc_builtins.list.is_unique";
pub const DEC_FROM_STR: &str = "roc_builtins.dec.from_str";
pub const DEC_FROM_F64: &str = "roc_builtins.dec.from_f64";
@ -381,6 +368,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],

View File

@ -131,7 +131,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
fn overflow() -> SolvedType {
SolvedType::TagUnion(
vec![(TagName::Global("Overflow".into()), vec![])],
vec![(TagName::Tag("Overflow".into()), vec![])],
Box::new(SolvedType::Wildcard),
)
}
@ -312,21 +312,35 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
);
let div_by_zero = SolvedType::TagUnion(
vec![(TagName::Global("DivByZero".into()), vec![])],
vec![(TagName::Tag("DivByZero".into()), vec![])],
Box::new(SolvedType::Wildcard),
);
// divInt : Int a, Int a -> Result (Int a) [ DivByZero ]*
// divTrunc : Int a, Int a -> Int a
add_top_level_function_type!(
Symbol::NUM_DIV_INT,
Symbol::NUM_DIV_TRUNC,
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(int_type(flex(TVAR1)))
);
// divTruncChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
add_top_level_function_type!(
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())),
);
//divCeil: Int a, Int a -> Result (Int a) [ DivByZero ]*
// divCeil : Int a, Int a -> Int a
add_top_level_function_type!(
Symbol::NUM_DIV_CEIL,
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(int_type(flex(TVAR1)))
);
// divCeilChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
add_top_level_function_type!(
Symbol::NUM_DIV_CEIL_CHECKED,
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())),
);
@ -379,16 +393,16 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
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())),
);
@ -462,7 +476,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
);
let out_of_bounds = SolvedType::TagUnion(
vec![(TagName::Global("OutOfBounds".into()), vec![])],
vec![(TagName::Tag("OutOfBounds".into()), vec![])],
Box::new(SolvedType::Wildcard),
);
@ -537,7 +551,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
);
let out_of_bounds = SolvedType::TagUnion(
vec![(TagName::Global("OutOfBounds".into()), vec![])],
vec![(TagName::Tag("OutOfBounds".into()), vec![])],
Box::new(SolvedType::Wildcard),
);
@ -615,7 +629,35 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
add_top_level_function_type!(
Symbol::NUM_TO_NAT_CHECKED,
vec![int_type(flex(TVAR1))],
Box::new(result_type(nat_type(), out_of_bounds)),
Box::new(result_type(nat_type(), out_of_bounds.clone())),
);
// toF32 : Num * -> F32
add_top_level_function_type!(
Symbol::NUM_TO_F32,
vec![num_type(flex(TVAR1))],
Box::new(f32_type()),
);
// toF32Checked : Num * -> Result F32 [ OutOfBounds ]*
add_top_level_function_type!(
Symbol::NUM_TO_F32_CHECKED,
vec![num_type(flex(TVAR1))],
Box::new(result_type(f32_type(), out_of_bounds.clone())),
);
// toF64 : Num * -> F64
add_top_level_function_type!(
Symbol::NUM_TO_F64,
vec![num_type(flex(TVAR1))],
Box::new(f64_type()),
);
// toF64Checked : Num * -> Result F64 [ OutOfBounds ]*
add_top_level_function_type!(
Symbol::NUM_TO_F64_CHECKED,
vec![num_type(flex(TVAR1))],
Box::new(result_type(f64_type(), out_of_bounds)),
);
// toStr : Num a -> Str
@ -631,36 +673,50 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
add_top_level_function_type!(
Symbol::NUM_DIV_FLOAT,
vec![float_type(flex(TVAR1)), float_type(flex(TVAR1))],
Box::new(result_type(float_type(flex(TVAR1)), div_by_zero.clone())),
Box::new(float_type(flex(TVAR1)))
);
// mod : Float a, Float a -> Result (Float a) [ DivByZero ]*
// divChecked : Float a, Float a -> Result (Float a) [ DivByZero ]*
add_top_level_function_type!(
Symbol::NUM_MOD_FLOAT,
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)),
);
// 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)),
);
@ -751,7 +807,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// 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!(
@ -764,7 +820,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// 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!(
@ -886,7 +942,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
{
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),
@ -899,15 +955,15 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
);
}
// 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),
);
@ -943,7 +999,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// `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),
)
};
@ -1050,7 +1106,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// 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),
);
@ -1062,7 +1118,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// 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),
);
@ -1167,8 +1223,8 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// [ 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),
)
@ -1529,7 +1585,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// 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);
@ -1571,7 +1627,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// 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),
);

View File

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

View File

@ -0,0 +1,171 @@
use roc_collections::all::MutMap;
use roc_module::symbol::Symbol;
use roc_region::all::Region;
use roc_types::{subs::Variable, types::Type};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MemberVariables {
pub able_vars: Vec<Variable>,
pub rigid_vars: Vec<Variable>,
pub flex_vars: Vec<Variable>,
}
/// Stores information about an ability member definition, including the parent ability, the
/// defining type, and what type variables need to be instantiated with instances of the ability.
// TODO: SoA and put me in an arena
#[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,
}
/// A particular specialization of an ability member.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MemberSpecialization {
pub symbol: Symbol,
pub region: Region,
}
/// Stores information about what abilities exist in a scope, what it means to implement an
/// ability, and what types implement them.
// TODO(abilities): this should probably go on the Scope, I don't put it there for now because we
// are only dealing with inter-module abilities for now.
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct AbilitiesStore {
/// Maps an ability to the members defining it.
members_of_ability: MutMap<Symbol, Vec<Symbol>>,
/// Information about all members composing abilities.
ability_members: MutMap<Symbol, AbilityMemberData>,
/// Map of symbols that specialize an ability member to the root ability symbol name.
/// For example, for the program
/// Hash has hash : a -> U64 | a has Hash
/// ^^^^ gets the symbol "#hash"
/// hash = \@Id n -> n
/// ^^^^ gets the symbol "#hash1"
///
/// We keep the mapping #hash1->#hash
specialization_to_root: MutMap<Symbol, Symbol>,
/// Maps a tuple (member, type) specifying that `type` declares an implementation of an ability
/// member `member`, to the exact symbol that implements the ability.
declared_specializations: MutMap<(Symbol, Symbol), MemberSpecialization>,
}
impl AbilitiesStore {
/// Records the definition of an ability, including its members.
pub fn register_ability(
&mut self,
ability: Symbol,
members: Vec<(Symbol, Region, Variable, Type, MemberVariables)>,
) {
let mut members_vec = Vec::with_capacity(members.len());
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,
},
);
debug_assert!(old_member.is_none(), "Replacing existing member definition");
}
let old_ability = self.members_of_ability.insert(ability, members_vec);
debug_assert!(
old_ability.is_none(),
"Replacing existing ability definition"
);
}
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.
pub fn register_specialization_for_type(
&mut self,
ability_member: Symbol,
implementing_type: Symbol,
specialization: MemberSpecialization,
) {
let old_spec = self
.declared_specializations
.insert((ability_member, implementing_type), specialization);
debug_assert!(old_spec.is_none(), "Replacing existing specialization");
}
/// Checks if `name` is a root ability member symbol name.
/// Note that this will return `false` for specializations of an ability member, which have
/// different symbols from the root.
pub fn is_ability_member_name(&self, name: Symbol) -> bool {
self.ability_members.contains_key(&name)
}
/// Returns information about all known ability members and their root symbols.
pub fn root_ability_members(&self) -> &MutMap<Symbol, AbilityMemberData> {
&self.ability_members
}
/// Records that the symbol `specializing_symbol` claims to specialize `ability_member`; for
/// example the symbol of `hash : Id -> U64` specializing `hash : a -> U64 | a has Hash`.
pub fn register_specializing_symbol(
&mut self,
specializing_symbol: Symbol,
ability_member: Symbol,
) {
self.specialization_to_root
.insert(specializing_symbol, ability_member);
}
/// Returns whether a symbol is declared to specialize an ability member.
pub fn is_specialization_name(&self, symbol: Symbol) -> bool {
self.specialization_to_root.contains_key(&symbol)
}
/// Finds the symbol name and ability member definition for a symbol specializing the ability
/// member, if it specializes any.
/// For example, suppose `hash : Id -> U64` has symbol #hash1 and specializes
/// `hash : a -> U64 | a has Hash` with symbol #hash. Calling this with #hash1 would retrieve
/// the ability member data for #hash.
pub fn root_name_and_def(
&self,
specializing_symbol: Symbol,
) -> Option<(Symbol, &AbilityMemberData)> {
let root_symbol = self.specialization_to_root.get(&specializing_symbol)?;
debug_assert!(self.ability_members.contains_key(root_symbol));
let root_data = self.ability_members.get(root_symbol).unwrap();
Some((*root_symbol, root_data))
}
/// Finds the ability member definition for a member name.
pub fn member_def(&self, member: Symbol) -> Option<&AbilityMemberData> {
self.ability_members.get(&member)
}
/// Returns an iterator over pairs (ability member, type) specifying that
/// "ability member" has a specialization with type "type".
pub fn get_known_specializations(&self) -> impl Iterator<Item = (Symbol, Symbol)> + '_ {
self.declared_specializations.keys().copied()
}
/// Retrieves the specialization of `member` for `typ`, if it exists.
pub fn get_specialization(&self, member: Symbol, typ: Symbol) -> Option<MemberSpecialization> {
self.declared_specializations.get(&(member, typ)).copied()
}
/// Returns pairs of (type, ability member) specifying that "ability member" has a
/// specialization with type "type".
pub fn members_of_ability(&self, ability: Symbol) -> Option<&[Symbol]> {
self.members_of_ability.get(&ability).map(|v| v.as_ref())
}
}

View File

@ -1,24 +1,77 @@
use crate::env::Env;
use crate::procedure::References;
use crate::scope::Scope;
use roc_collections::all::{ImMap, MutMap, MutSet, SendMap};
use roc_error_macros::todo_abilities;
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, Pattern, Tag, TypeAnnotation, TypeHeader};
use roc_module::symbol::Symbol;
use roc_parse::ast::{AssignedField, ExtractSpaces, Pattern, Tag, TypeAnnotation, TypeHeader};
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<Symbol>,
pub references: VecSet<Symbol>,
pub aliases: SendMap<Symbol, Alias>,
}
impl Annotation {
pub fn add_to(
&self,
aliases: &mut VecMap<Symbol, Alias>,
references: &mut References,
introduced_variables: &mut IntroducedVariables,
) {
for symbol in self.references.iter() {
references.insert_type_lookup(*symbol);
}
introduced_variables.union(&self.introduced_variables);
for (name, alias) in self.aliases.iter() {
if !aliases.contains_key(name) {
aliases.insert(*name, alias.clone());
}
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum NamedOrAbleVariable<'a> {
Named(&'a NamedVariable),
Able(&'a AbleVariable),
}
impl<'a> NamedOrAbleVariable<'a> {
pub fn first_seen(&self) -> Region {
match self {
NamedOrAbleVariable::Named(nv) => nv.first_seen,
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.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct NamedVariable {
pub variable: Variable,
@ -27,21 +80,40 @@ pub struct NamedVariable {
pub first_seen: Region,
}
#[derive(Clone, Debug, PartialEq, Default)]
/// A type variable bound to an ability, like "a has Hash".
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct AbleVariable {
pub variable: Variable,
pub name: Lowercase,
pub ability: Symbol,
// NB: there may be multiple occurrences of a variable
pub first_seen: Region,
}
#[derive(Clone, Debug, Default)]
pub struct IntroducedVariables {
pub wildcards: Vec<Loc<Variable>>,
pub lambda_sets: Vec<Variable>,
pub inferred: Vec<Loc<Variable>>,
pub named: Vec<NamedVariable>,
pub host_exposed_aliases: MutMap<Symbol, Variable>,
pub named: VecSet<NamedVariable>,
pub able: VecSet<AbleVariable>,
pub host_exposed_aliases: VecMap<Symbol, Variable>,
}
impl IntroducedVariables {
#[inline(always)]
fn debug_assert_not_already_present(&self, var: Variable) {
debug_assert!((self.wildcards.iter().map(|v| &v.value))
.chain(self.lambda_sets.iter())
.chain(self.inferred.iter().map(|v| &v.value))
.chain(self.named.iter().map(|nv| &nv.variable))
.chain(self.able.iter().map(|av| &av.variable))
.chain(self.host_exposed_aliases.values())
.all(|&v| v != var));
}
pub fn insert_named(&mut self, name: Lowercase, var: Loc<Variable>) {
debug_assert!(!self
.named
.iter()
.any(|nv| nv.name == name || nv.variable == var.value));
self.debug_assert_not_already_present(var.value);
let named_variable = NamedVariable {
name,
@ -49,22 +121,39 @@ 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<Variable>, ability: Symbol) {
self.debug_assert_not_already_present(var.value);
let able_variable = AbleVariable {
name,
ability,
variable: var.value,
first_seen: var.region,
};
self.able.insert(able_variable);
}
pub fn insert_wildcard(&mut self, var: Loc<Variable>) {
self.debug_assert_not_already_present(var.value);
self.wildcards.push(var);
}
pub fn insert_inferred(&mut self, var: Loc<Variable>) {
self.debug_assert_not_already_present(var.value);
self.inferred.push(var);
}
fn insert_lambda_set(&mut self, var: Variable) {
self.debug_assert_not_already_present(var);
self.lambda_sets.push(var);
}
pub fn insert_host_exposed_alias(&mut self, symbol: Symbol, var: Variable) {
self.debug_assert_not_already_present(var);
self.host_exposed_aliases.insert(symbol, var);
}
@ -73,11 +162,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());
}
pub fn union_owned(&mut self, other: Self) {
@ -87,22 +175,39 @@ 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<&Variable> {
self.named
.iter()
.find(|nv| &nv.name == name)
.map(|nv| &nv.variable)
pub fn var_by_name(&self, name: &Lowercase) -> Option<Variable> {
(self.named.iter().map(|nv| (&nv.name, nv.variable)))
.chain(self.able.iter().map(|av| (&av.name, av.variable)))
.find(|(cand, _)| cand == &name)
.map(|(_, var)| var)
}
pub fn name_by_var(&self, var: Variable) -> Option<&Lowercase> {
self.named
.iter()
.find(|nv| nv.variable == var)
.map(|nv| &nv.name)
pub fn iter_named(&self) -> impl Iterator<Item = NamedOrAbleVariable> {
(self.named.iter().map(NamedOrAbleVariable::Named))
.chain(self.able.iter().map(NamedOrAbleVariable::Able))
}
pub fn named_var_by_name(&self, name: &Lowercase) -> Option<NamedOrAbleVariable> {
self.iter_named().find(|v| v.name() == name)
}
pub fn collect_able(&self) -> Vec<Variable> {
self.able.iter().map(|av| av.variable).collect()
}
pub fn collect_rigid(&self) -> Vec<Variable> {
(self.named.iter().map(|nv| nv.variable))
.chain(self.wildcards.iter().map(|wc| wc.value))
// For our purposes, lambda set vars are treated like rigids
.chain(self.lambda_sets.iter().copied())
.collect()
}
pub fn collect_flex(&self) -> Vec<Variable> {
self.inferred.iter().map(|iv| iv.value).collect()
}
}
@ -113,17 +218,47 @@ 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,
annotation: &TypeAnnotation,
region: Region,
var_store: &mut VarStore,
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 {
TypeAnnotation::Where(annotation, clauses) => {
// Add each "has" clause. The association of a variable to an ability will be saved on
// `introduced_variables`, which we'll process later.
for clause in clauses.iter() {
let opt_err = canonicalize_has_clause(
env,
scope,
var_store,
&mut introduced_variables,
clause,
abilities_in_scope,
&mut references,
);
if let Err(err_type) = opt_err {
return Annotation {
typ: err_type,
introduced_variables,
references,
aliases,
};
}
}
(&annotation.value, annotation.region)
}
annot => (annot, region),
};
let typ = can_annotation_help(
env,
annotation,
@ -164,7 +299,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
@ -185,8 +320,7 @@ fn make_apply_symbol(
/// For example, in `[ A Age U8, B Str {} ]`, there are three type definition references - `Age`,
/// `U8`, and `Str`.
pub fn find_type_def_symbols(
module_id: ModuleId,
ident_ids: &mut IdentIds,
scope: &mut Scope,
initial_annotation: &roc_parse::ast::TypeAnnotation,
) -> Vec<Symbol> {
use roc_parse::ast::TypeAnnotation::*;
@ -199,9 +333,8 @@ pub fn find_type_def_symbols(
match annotation {
Apply(_module_name, ident, arguments) => {
let ident: Ident = (*ident).into();
let ident_id = ident_ids.get_or_insert(&ident);
let symbol = scope.scopeless_symbol(&ident, Region::zero());
let symbol = Symbol::new(module_id, ident_id);
result.push(symbol);
for t in arguments.iter() {
@ -252,7 +385,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);
}
@ -271,7 +404,13 @@ pub fn find_type_def_symbols(
SpaceBefore(inner, _) | SpaceAfter(inner, _) => {
stack.push(inner);
}
Where(..) => todo_abilities!(),
Where(annotation, clauses) => {
stack.push(&annotation.value);
for has_clause in clauses.iter() {
stack.push(&has_clause.value.ability.value);
}
}
Inferred | Wildcard | Malformed(_) => {}
}
}
@ -279,6 +418,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,
@ -288,7 +434,7 @@ fn can_annotation_help(
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
local_aliases: &mut SendMap<Symbol, Alias>,
references: &mut MutSet<Symbol>,
references: &mut VecSet<Symbol>,
) -> Type {
use roc_parse::ast::TypeAnnotation::*;
@ -300,7 +446,7 @@ fn can_annotation_help(
let arg_ann = can_annotation_help(
env,
&arg.value,
region,
arg.region,
scope,
var_store,
introduced_variables,
@ -338,6 +484,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,
@ -367,51 +528,28 @@ fn can_annotation_help(
return error;
}
let is_structural = alias.kind == AliasKind::Structural;
if is_structural {
let mut type_var_to_arg = Vec::new();
let mut type_var_to_arg = Vec::new();
for (loc_var, arg_ann) in alias.type_variables.iter().zip(args) {
let name = loc_var.value.0.clone();
type_var_to_arg.push((name, arg_ann));
}
let mut lambda_set_variables =
Vec::with_capacity(alias.lambda_set_variables.len());
for _ in 0..alias.lambda_set_variables.len() {
let lvar = var_store.fresh();
introduced_variables.insert_lambda_set(lvar);
lambda_set_variables.push(LambdaSet(Type::Variable(lvar)));
}
Type::DelayedAlias(AliasCommon {
symbol,
type_arguments: type_var_to_arg,
lambda_set_variables,
})
} else {
let (type_arguments, lambda_set_variables, actual) =
instantiate_and_freshen_alias_type(
var_store,
introduced_variables,
&alias.type_variables,
args,
&alias.lambda_set_variables,
alias.typ.clone(),
);
Type::Alias {
symbol,
type_arguments,
lambda_set_variables,
actual: Box::new(actual),
kind: alias.kind,
}
for (_, arg_ann) in alias.type_variables.iter().zip(args) {
type_var_to_arg.push(arg_ann);
}
let mut lambda_set_variables =
Vec::with_capacity(alias.lambda_set_variables.len());
for _ in 0..alias.lambda_set_variables.len() {
let lvar = var_store.fresh();
introduced_variables.insert_lambda_set(lvar);
lambda_set_variables.push(LambdaSet(Type::Variable(lvar)));
}
Type::DelayedAlias(AliasCommon {
symbol,
type_arguments: type_var_to_arg,
lambda_set_variables,
})
}
None => Type::Apply(symbol, args, region),
}
@ -420,7 +558,7 @@ fn can_annotation_help(
let name = Lowercase::from(*v);
match introduced_variables.var_by_name(&name) {
Some(var) => Type::Variable(*var),
Some(var) => Type::Variable(var),
None => {
let var = var_store.fresh();
@ -438,20 +576,16 @@ 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)) => {
let problem = Problem::Shadowed(original_region, shadow.clone());
env.problem(roc_problem::can::Problem::ShadowingInAnnotation {
env.problem(roc_problem::can::Problem::Shadowing {
original_region,
shadow,
kind: ShadowKind::Variable,
});
return Type::Erroneous(problem);
@ -483,20 +617,20 @@ fn can_annotation_help(
let var_name = Lowercase::from(var);
if let Some(var) = introduced_variables.var_by_name(&var_name) {
vars.push((var_name.clone(), Type::Variable(*var)));
lowercase_vars.push(Loc::at(loc_var.region, (var_name, *var)));
vars.push(Type::Variable(var));
lowercase_vars.push(Loc::at(loc_var.region, (var_name, var)));
} else {
let var = var_store.fresh();
introduced_variables
.insert_named(var_name.clone(), Loc::at(loc_var.region, var));
vars.push((var_name.clone(), Type::Variable(var)));
vars.push(Type::Variable(var));
lowercase_vars.push(Loc::at(loc_var.region, (var_name, var)));
}
}
let alias_args = vars.iter().map(|(_, v)| v.clone()).collect::<Vec<_>>();
let alias_args = vars.clone();
let alias_actual = if let Type::TagUnion(tags, ext) = inner_type {
let rec_var = var_store.fresh();
@ -555,8 +689,6 @@ fn can_annotation_help(
let alias = scope.lookup_alias(symbol).unwrap();
local_aliases.insert(symbol, alias.clone());
// Type::Alias(symbol, vars, Box::new(alias.typ.clone()))
if vars.is_empty() && env.home == symbol.module_id() {
let actual_var = var_store.fresh();
introduced_variables.insert_host_exposed_alias(symbol, actual_var);
@ -685,7 +817,16 @@ fn can_annotation_help(
Type::Variable(var)
}
Where(..) => todo_abilities!(),
Where(_annotation, clauses) => {
debug_assert!(!clauses.is_empty());
// 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)),
});
Type::Erroneous(Problem::CanonicalizationProblem)
}
Malformed(string) => {
malformed(env, region, string);
@ -698,6 +839,68 @@ fn can_annotation_help(
}
}
fn canonicalize_has_clause(
env: &mut Env,
scope: &mut Scope,
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
clause: &Loc<roc_parse::ast::HasClause<'_>>,
abilities_in_scope: &[Symbol],
references: &mut VecSet<Symbol>,
) -> Result<(), Type> {
let Loc {
region,
value: roc_parse::ast::HasClause { var, ability },
} = clause;
let region = *region;
let var_name = var.extract_spaces().item;
debug_assert!(
var_name.starts_with(char::is_lowercase),
"Vars should have been parsed as lowercase"
);
let var_name = Lowercase::from(var_name);
let ability = match ability.value {
TypeAnnotation::Apply(module_name, ident, _type_arguments) => {
let symbol = make_apply_symbol(env, ability.region, scope, module_name, ident)?;
if !abilities_in_scope.contains(&symbol) {
let region = ability.region;
env.problem(roc_problem::can::Problem::HasClauseIsNotAbility { region });
return Err(Type::Erroneous(Problem::HasClauseIsNotAbility(region)));
}
symbol
}
_ => {
let region = ability.region;
env.problem(roc_problem::can::Problem::HasClauseIsNotAbility { region });
return Err(Type::Erroneous(Problem::HasClauseIsNotAbility(region)));
}
};
references.insert(ability);
if let Some(shadowing) = introduced_variables.named_var_by_name(&var_name) {
let var_name_ident = var_name.to_string().into();
let shadow = Loc::at(region, var_name_ident);
env.problem(roc_problem::can::Problem::Shadowing {
original_region: shadowing.first_seen(),
shadow: shadow.clone(),
kind: ShadowKind::Variable,
});
return Err(Type::Erroneous(Problem::Shadowed(
shadowing.first_seen(),
shadow,
)));
}
let var = var_store.fresh();
introduced_variables.insert_able(var_name, Loc::at(region, var), ability);
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn can_extension_type<'a>(
env: &mut Env,
@ -705,7 +908,7 @@ fn can_extension_type<'a>(
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
local_aliases: &mut SendMap<Symbol, Alias>,
references: &mut MutSet<Symbol>,
references: &mut VecSet<Symbol>,
opt_ext: &Option<&Loc<TypeAnnotation<'a>>>,
ext_problem_kind: roc_problem::can::ExtensionTypeKind,
) -> Type {
@ -837,26 +1040,35 @@ pub fn instantiate_and_freshen_alias_type(
pub fn freshen_opaque_def(
var_store: &mut VarStore,
opaque: &Alias,
) -> (Vec<(Lowercase, Type)>, Vec<LambdaSet>, Type) {
) -> (Vec<Variable>, Vec<LambdaSet>, Type) {
debug_assert!(opaque.kind == AliasKind::Opaque);
let fresh_arguments = opaque
let fresh_variables: Vec<Variable> = opaque
.type_variables
.iter()
.map(|_| Type::Variable(var_store.fresh()))
.map(|_| var_store.fresh())
.collect();
// TODO this gets ignored; is that a problem
let fresh_type_arguments = fresh_variables
.iter()
.copied()
.map(Type::Variable)
.collect();
// NB: We don't introduce the fresh variables here, we introduce them during constraint gen.
// NB: If there are bugs, check whether this is a problem!
let mut introduced_variables = IntroducedVariables::default();
instantiate_and_freshen_alias_type(
let (_fresh_type_arguments, fresh_lambda_set, fresh_type) = instantiate_and_freshen_alias_type(
var_store,
&mut introduced_variables,
&opaque.type_variables,
fresh_arguments,
fresh_type_arguments,
&opaque.lambda_set_variables,
opaque.typ.clone(),
)
);
(fresh_variables, fresh_lambda_set, fresh_type)
}
fn insertion_sort_by<T, F>(arr: &mut [T], mut compare: F)
@ -887,7 +1099,7 @@ fn can_assigned_fields<'a>(
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
local_aliases: &mut SendMap<Symbol, Alias>,
references: &mut MutSet<Symbol>,
references: &mut VecSet<Symbol>,
) -> SendMap<Lowercase, RecordField<Type>> {
use roc_parse::ast::AssignedField::*;
use roc_types::types::RecordField::*;
@ -946,7 +1158,7 @@ fn can_assigned_fields<'a>(
let field_name = Lowercase::from(loc_field_name.value);
let field_type = {
if let Some(var) = introduced_variables.var_by_name(&field_name) {
Type::Variable(*var)
Type::Variable(var)
} else {
let field_var = var_store.fresh();
introduced_variables.insert_named(
@ -1000,7 +1212,7 @@ fn can_tags<'a>(
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
local_aliases: &mut SendMap<Symbol, Alias>,
references: &mut MutSet<Symbol>,
references: &mut VecSet<Symbol>,
) -> Vec<(TagName, Vec<Type>)> {
let mut tag_types = Vec::with_capacity(tags.len());
@ -1016,7 +1228,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());
@ -1035,32 +1247,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;

View File

@ -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)*) => (());
@ -150,6 +150,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_ANY => list_any,
LIST_ALL => list_all,
LIST_FIND => list_find,
LIST_IS_UNIQUE => list_is_unique,
DICT_LEN => dict_len,
DICT_EMPTY => dict_empty,
DICT_SINGLE => dict_single,
@ -171,6 +172,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
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,
@ -195,14 +197,20 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
NUM_COS => num_cos,
NUM_TAN => num_tan,
NUM_DIV_FLOAT => num_div_float,
NUM_DIV_INT => num_div_int,
NUM_DIV_FLOAT_CHECKED => num_div_float_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,
@ -226,24 +234,6 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
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,
@ -266,6 +256,10 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
NUM_TO_U128_CHECKED => num_to_u128_checked,
NUM_TO_NAT => num_to_nat,
NUM_TO_NAT_CHECKED => num_to_nat_checked,
NUM_TO_F32 => num_to_f32,
NUM_TO_F32_CHECKED => num_to_f32_checked,
NUM_TO_F64 => num_to_f64,
NUM_TO_F64_CHECKED => num_to_f64_checked,
NUM_TO_STR => num_to_str,
RESULT_MAP => result_map,
RESULT_MAP_ERR => result_map_err,
@ -482,6 +476,18 @@ fn num_to_nat(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
// Num.toF32 : Num * -> F32
fn num_to_f32(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to NumToFloatCast
lowlevel_1(symbol, LowLevel::NumToFloatCast, var_store)
}
// Num.toF64 : Num * -> F64
fn num_to_f64(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to NumToFloatCast
lowlevel_1(symbol, LowLevel::NumToFloatCast, var_store)
}
fn to_num_checked(symbol: Symbol, var_store: &mut VarStore, lowlevel: LowLevel) -> Def {
let bool_var = var_store.fresh();
let num_var_1 = var_store.fresh();
@ -589,6 +595,8 @@ num_to_checked! {
num_to_u64_checked
num_to_u128_checked
num_to_nat_checked
num_to_f32_checked
num_to_f64_checked
}
// Num.toStr : Num a -> Str
@ -709,6 +717,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();
@ -1148,8 +1173,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();
@ -1197,8 +1227,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();
@ -1452,106 +1487,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::<i8>(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::<i8>(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::<u8>(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::<u8>(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::<i16>(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::<i16>(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::<u16>(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::<u16>(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::<i32>(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::<i32>(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::<u32>(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::<u32>(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::<i64>(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::<i64>(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::<u64>(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::<u64>(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::<i128>(
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::<i128>(
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();
@ -2572,7 +2507,10 @@ fn list_take_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
let get_sub = RunLowLevel {
op: LowLevel::NumSubWrap,
args: vec![(len_var, get_list_len), (len_var, Var(Symbol::ARG_2))],
args: vec![
(len_var, get_list_len.clone()),
(len_var, Var(Symbol::ARG_2)),
],
ret_var: len_var,
};
@ -2582,7 +2520,7 @@ fn list_take_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
branches: vec![(
no_region(RunLowLevel {
op: LowLevel::NumGt,
args: vec![(len_var, get_sub.clone()), (len_var, zero.clone())],
args: vec![(len_var, get_list_len), (len_var, Var(Symbol::ARG_2))],
ret_var: bool_var,
}),
no_region(get_sub),
@ -2688,8 +2626,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 {
@ -2774,9 +2720,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 {
@ -2960,7 +2911,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((
@ -3144,8 +3099,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((
@ -3675,8 +3638,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 {
@ -3803,6 +3774,11 @@ fn list_find(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// List.isUnique : List * -> Bool
fn list_is_unique(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_1(symbol, LowLevel::ListIsUnique, var_store)
}
/// Dict.len : Dict * * -> Nat
fn dict_len(symbol: Symbol, var_store: &mut VarStore) -> Def {
let arg1_var = var_store.fresh();
@ -4077,6 +4053,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();
@ -4145,9 +4126,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)),
});
@ -4175,8 +4168,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();
@ -4277,8 +4275,13 @@ fn num_abs(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// Num.div : Float, Float -> Result Float [ DivByZero ]*
/// Num.div : Float, Float -> Float
fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumDivUnchecked)
}
/// Num.divChecked : Float, Float -> Result Float [ DivByZero ]*
fn num_div_float_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();
@ -4343,8 +4346,13 @@ fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// Num.div : Int a , Int a -> Result (Int a) [ DivByZero ]*
fn num_div_int(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.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();
@ -4414,8 +4422,13 @@ fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// Num.divCeil : Int a , Int a -> Result (Int a) [ DivByZero ]*
/// Num.divCeil : Int a, Int a -> Int a
fn num_div_ceil(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumDivCeilUnchecked)
}
/// Num.divCeilChecked : Int a , Int a -> Result (Int a) [ DivByZero ]*
fn num_div_ceil_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();
@ -4703,7 +4716,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 {
@ -4727,6 +4740,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);
@ -4734,7 +4748,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(),
@ -4757,6 +4771,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);
@ -4768,6 +4783,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(
@ -4800,7 +4817,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 {
@ -4824,6 +4841,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);
@ -4831,7 +4849,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(),
@ -4854,6 +4872,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);
@ -4865,6 +4884,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(
@ -4884,7 +4905,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,
@ -4897,6 +4918,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);
@ -4904,7 +4926,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,
@ -4917,6 +4939,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);
@ -4928,6 +4951,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(
@ -4947,7 +4972,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,
@ -4959,7 +4984,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![],
};
@ -4967,6 +4992,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);
@ -4974,7 +5000,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,
@ -4986,7 +5012,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![],
};
@ -4994,6 +5020,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);
@ -5005,6 +5032,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(
@ -5024,7 +5053,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,
@ -5036,7 +5065,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![],
};
@ -5044,6 +5073,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);
@ -5051,7 +5081,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,
@ -5063,7 +5093,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![],
};
@ -5071,6 +5101,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);
@ -5082,6 +5113,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(
@ -5114,7 +5147,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;
@ -5133,6 +5166,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);
@ -5140,7 +5174,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(),
@ -5163,6 +5197,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);
@ -5174,6 +5209,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(
@ -5198,7 +5235,7 @@ fn tag(name: &'static str, args: Vec<Expr>, 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)))
@ -5359,7 +5396,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 {
@ -5375,36 +5418,6 @@ fn defn_help(
})
}
#[inline(always)]
fn int_min_or_max<I128>(symbol: Symbol, var_store: &mut VarStore, i: I128, bound: IntBound) -> Def
where
I128: Into<i128>,
{
let int_var = var_store.fresh();
let int_precision_var = var_store.fresh();
let body = int::<I128>(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
}

View File

@ -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<PExpected<Type>>,
pub includes_tags: Vec<IncludesTag>,
pub strings: Vec<&'static str>,
pub sketched_rows: Vec<SketchedRows>,
pub eq: Vec<Eq>,
pub pattern_eq: Vec<PatternEq>,
}
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<Type>),
(PatternCategory, PExpected<Type>),
>,
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<Type, Variable>,
pub Index<Expected<Type>>,
pub Index<Category>,
pub Region,
);
#[derive(Clone, Copy, Debug)]
pub struct PatternEq(
pub EitherIndex<Type, Variable>,
pub Index<PExpected<Type>>,
pub Index<PatternCategory>,
pub Region,
);
#[derive(Clone, Copy)]
pub enum Constraint {
Eq(
EitherIndex<Type, Variable>,
Index<Expected<Type>>,
Index<Category>,
Region,
),
Eq(Eq),
Store(
EitherIndex<Type, Variable>,
Variable,
@ -641,15 +699,21 @@ pub enum Constraint {
Index<PatternCategory>,
Region,
),
Exhaustive(
Result<Index<Eq>, Index<PatternEq>>,
Index<SketchedRows>,
ExhaustiveContext,
ExhaustiveMark,
),
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[derive(Debug, Clone, Copy, Default)]
pub struct DefTypes {
pub types: Slice<Type>,
pub loc_symbols: Slice<(Symbol, Region)>,
}
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone)]
pub struct LetConstraint {
pub rigid_vars: Slice<Variable>,
pub flex_vars: Slice<Variable>,
@ -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<Type>,
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
)
}
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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<ModuleId, IdentIds>,
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<Symbol>,
/// current closure name (if any)
pub closure_name_symbol: Option<Symbol>,
/// Symbols of values/functions which were referenced by qualified lookups.
pub qualified_value_lookups: MutSet<Symbol>,
pub qualified_value_lookups: VecSet<Symbol>,
/// Symbols of types which were referenced by qualified lookups.
pub qualified_type_lookups: MutSet<Symbol>,
pub qualified_type_lookups: VecSet<Symbol>,
pub top_level_symbols: MutSet<Symbol>,
pub ident_ids: IdentIds,
pub exposed_ident_ids: IdentIds,
pub top_level_symbols: VecSet<Symbol>,
}
impl<'a> Env<'a> {
pub fn new(
home: ModuleId,
dep_idents: &'a MutMap<ModuleId, IdentIds>,
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.locals.ident_ids.get_id(&ident) {
Some(ident_id) => {
let symbol = Symbol::new(module_id, ident_id);
@ -97,16 +89,21 @@ 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
.locals
.ident_ids
.ident_strs()
.map(|(_, string)| string.into())
.collect(),
);
Err(error)
}
}
} else {
match self.dep_idents.get(&module_id) {
@ -124,11 +121,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 +162,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);
}
}

View File

@ -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<Error>,
pub exhaustive: bool,
pub redundancies: Vec<RedundantMark>,
}
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<SketchedPattern>),
KnownCtor(Union, TagId, Vec<SketchedPattern>),
}
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<SketchedPattern>,
region: Region,
guard: Guard,
redundant_mark: RedundantMark,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SketchedRows {
rows: Vec<SketchedRow>,
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<SketchedRow> = 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<Vec<Pattern>>,
redundancies: Vec<RedundantMark>,
errors: Vec<Error>,
}
/// 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<Pattern> = 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,
}
}
}

View File

@ -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<T> {
NoExpectation(T),
FromAnnotation(Loc<Pattern>, usize, AnnotationSource, T),
@ -10,7 +10,7 @@ pub enum Expected<T> {
}
/// Like Expected, but for Patterns.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone)]
pub enum PExpected<T> {
NoExpectation(T),
ForReason(PReason, T, Region),
@ -44,6 +44,15 @@ impl<T> PExpected<T> {
PExpected::ForReason(reason, _val, region) => PExpected::ForReason(reason, new, region),
}
}
pub fn replace_ref<U>(&self, new: U) -> PExpected<U> {
match self {
PExpected::NoExpectation(_val) => PExpected::NoExpectation(new),
PExpected::ForReason(reason, _val, region) => {
PExpected::ForReason(reason.clone(), new, *region)
}
}
}
}
impl<T> Expected<T> {

View File

@ -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<Symbol>,
pub introduced_variables: IntroducedVariables,
pub aliases: SendMap<Symbol, Alias>,
pub non_closures: MutSet<Symbol>,
pub aliases: VecMap<Symbol, Alias>,
pub non_closures: VecSet<Symbol>,
}
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<Loc<Expr>>,
cond_var: Variable,
/// Result type produced by the branches.
expr_var: Variable,
region: Region,
loc_cond: Box<Loc<Expr>>,
/// The branches of the when, and the type of the condition that they expect to be matched
/// against.
branches: Vec<WhenBranch>,
branches_cond_var: Variable,
/// Whether the branches are exhaustive.
exhaustive: ExhaustiveMark,
},
If {
cond_var: Variable,
@ -161,11 +168,9 @@ pub enum Expr {
variant_var: Variable,
ext_var: Variable,
name: TagName,
arguments: Vec<(Variable, Loc<Expr>)>,
},
/// 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,
@ -185,7 +190,7 @@ pub enum Expr {
// for the expression from the opaque definition. `type_arguments` is something like
// [(n, fresh1)], and `specialized_def_type` becomes "[ Id U64 fresh1 ]".
specialized_def_type: Box<Type>,
type_arguments: Vec<(Lowercase, Type)>,
type_arguments: Vec<Variable>,
lambda_set_variables: Vec<LambdaSet>,
},
@ -195,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,
@ -204,7 +276,7 @@ pub struct ClosureData {
pub name: Symbol,
pub captured_symbols: Vec<(Symbol, Variable)>,
pub recursive: Recursive,
pub arguments: Vec<(Variable, Loc<Pattern>)>,
pub arguments: Vec<(Variable, AnnotatedMark, Loc<Pattern>)>,
pub loc_body: Box<Loc<Expr>>,
}
@ -256,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,
@ -272,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`
@ -280,18 +356,37 @@ pub struct Field {
pub loc_expr: Box<Loc<Expr>>,
}
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Recursive {
NotRecursive = 0,
Recursive = 1,
TailRecursive = 2,
}
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug)]
pub struct WhenBranch {
pub patterns: Vec<Loc<Pattern>>,
pub value: Loc<Expr>,
pub guard: Option<Loc<Expr>>,
/// 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>(
@ -355,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(),
@ -433,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);
}
@ -467,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 {
@ -488,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);
@ -519,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 {
@ -598,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<Symbol> = 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.
@ -751,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);
}
@ -773,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)
@ -793,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(),
@ -804,16 +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()),
arguments: vec![],
name: TagName::Tag((*tag).into()),
variant_var,
closure_name: symbol,
ext_var,
@ -821,24 +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),
arguments: vec![],
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.
@ -889,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(
@ -901,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 {
@ -1027,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<ast::Pattern<'a>>],
loc_body_expr: &'a Loc<ast::Expr<'a>>,
opt_def_name: Option<Symbol>,
) -> (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<ast::Pattern<'a>>],
loc_body_expr: &'a Loc<ast::Expr<'a>>,
opt_def_name: Option<Symbol>,
) -> (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>,
@ -1038,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,
);
@ -1069,69 +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)
{
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<Symbol, References>,
) -> Vec<Symbol> {
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,
@ -1169,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,
@ -1257,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)
}
@ -1270,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)
}
@ -1338,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,
@ -1362,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);
@ -1373,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 {
@ -1560,15 +1612,13 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
variant_var,
ext_var,
name,
arguments,
} => {
todo!(
"Inlining for ZeroArgumentTag with closure_name {:?}, variant_var {:?}, ext_var {:?}, name {:?}, arguments {:?}",
"Inlining for ZeroArgumentTag with closure_name {:?}, variant_var {:?}, ext_var {:?}, name {:?}",
closure_name,
variant_var,
ext_var,
name,
arguments
);
}
@ -1604,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.
@ -1730,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()));

View File

@ -1,12 +1,14 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
pub mod abilities;
pub mod annotation;
pub mod builtins;
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;
@ -14,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;

View File

@ -1,3 +1,4 @@
use crate::abilities::AbilitiesStore;
use crate::def::{canonicalize_defs, sort_can_defs, Declaration, Def};
use crate::effect_module::HostedGeneratedFunctions;
use crate::env::Env;
@ -6,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;
@ -22,18 +23,20 @@ use roc_types::types::{Alias, AliasKind, Type};
pub struct Module {
pub module_id: ModuleId,
pub exposed_imports: MutMap<Symbol, Variable>,
pub exposed_symbols: MutSet<Symbol>,
pub referenced_values: MutSet<Symbol>,
pub referenced_types: MutSet<Symbol>,
pub exposed_symbols: VecSet<Symbol>,
pub referenced_values: VecSet<Symbol>,
pub referenced_types: VecSet<Symbol>,
/// all aliases. `bool` indicates whether it is exposed
pub aliases: MutMap<Symbol, (bool, Alias)>,
pub rigid_variables: RigidVariables,
pub abilities_store: AbilitiesStore,
}
#[derive(Debug, Default)]
pub struct RigidVariables {
pub named: MutMap<Variable, Lowercase>,
pub wildcards: MutSet<Variable>,
pub able: MutMap<Variable, (Lowercase, Symbol)>,
pub wildcards: VecSet<Variable>,
}
#[derive(Debug)]
@ -44,9 +47,8 @@ pub struct ModuleOutput {
pub exposed_imports: MutMap<Symbol, Variable>,
pub lookups: Vec<(Symbol, Variable, Region)>,
pub problems: Vec<Problem>,
pub ident_ids: IdentIds,
pub referenced_values: MutSet<Symbol>,
pub referenced_types: MutSet<Symbol>,
pub referenced_values: VecSet<Symbol>,
pub referenced_types: VecSet<Symbol>,
pub scope: Scope,
}
@ -74,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>(
@ -83,15 +162,15 @@ pub fn canonicalize_module_defs<'a>(
home: ModuleId,
module_ids: &ModuleIds,
exposed_ident_ids: IdentIds,
dep_idents: &'a MutMap<ModuleId, IdentIds>,
dep_idents: &'a IdentIdsByModule,
aliases: MutMap<Symbol, Alias>,
exposed_imports: MutMap<Ident, (Symbol, Region)>,
exposed_symbols: &MutSet<Symbol>,
exposed_symbols: &VecSet<Symbol>,
var_store: &mut VarStore,
) -> Result<ModuleOutput, RuntimeError> {
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() {
@ -104,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.
@ -212,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
@ -228,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,
);
@ -247,9 +289,9 @@ 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)
{
env.problem(Problem::UnusedDef(symbol, region));
}
@ -259,19 +301,25 @@ pub fn canonicalize_module_defs<'a>(
rigid_variables.named.insert(named.variable, named.name);
}
for able in output.introduced_variables.able {
rigid_variables
.able
.insert(able.variable, (able.name, able.ability));
}
for var in output.introduced_variables.wildcards {
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());
@ -304,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,
@ -340,13 +387,29 @@ 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();
let ident = scope
.locals
.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,
@ -356,37 +419,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!();
}
_ => {}
_ => (),
}
}
}
@ -421,7 +464,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
@ -444,6 +487,10 @@ pub fn canonicalize_module_defs<'a>(
aliases.insert(symbol, alias);
}
for member in scope.abilities_store.root_ability_members().keys() {
exposed_but_not_defined.remove(member);
}
// By this point, all exposed symbols should have been removed from
// exposed_symbols and added to exposed_vars_by_symbol. If any were
// not, that means they were declared as exposed but there was
@ -469,11 +516,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());
@ -481,25 +528,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,
@ -510,7 +546,6 @@ pub fn canonicalize_module_defs<'a>(
exposed_imports: can_exposed_imports,
problems: env.problems,
lookups,
ident_ids: env.ident_ids,
};
Ok(output)
@ -521,7 +556,7 @@ pub fn canonicalize_module_defs<'a>(
fn fix_values_captured_in_closure_def(
def: &mut crate::def::Def,
no_capture_symbols: &mut MutSet<Symbol>,
no_capture_symbols: &mut VecSet<Symbol>,
) {
// 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);
@ -530,8 +565,8 @@ fn fix_values_captured_in_closure_def(
}
fn fix_values_captured_in_closure_defs(
defs: &mut Vec<crate::def::Def>,
no_capture_symbols: &mut MutSet<Symbol>,
defs: &mut [crate::def::Def],
no_capture_symbols: &mut VecSet<Symbol>,
) {
// recursive defs cannot capture each other
for def in defs.iter() {
@ -547,7 +582,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<Symbol>,
no_capture_symbols: &mut VecSet<Symbol>,
) {
use crate::pattern::Pattern::*;
@ -589,13 +624,14 @@ fn fix_values_captured_in_closure_pattern(
| Shadowed(..)
| MalformedPattern(_, _)
| UnsupportedPattern(_)
| OpaqueNotInScope(..) => (),
| OpaqueNotInScope(..)
| AbilityMemberSpecialization { .. } => (),
}
}
fn fix_values_captured_in_closure_expr(
expr: &mut crate::expr::Expr,
no_capture_symbols: &mut MutSet<Symbol>,
no_capture_symbols: &mut VecSet<Symbol>,
) {
use crate::expr::Expr::*;
@ -631,7 +667,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);
}
@ -646,6 +682,7 @@ fn fix_values_captured_in_closure_expr(
| Var(_)
| EmptyRecord
| RuntimeError(_)
| ZeroArgumentTag { .. }
| Accessor { .. } => {}
List { loc_elems, .. } => {
@ -712,7 +749,7 @@ fn fix_values_captured_in_closure_expr(
fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols);
}
Tag { arguments, .. } | ZeroArgumentTag { arguments, .. } => {
Tag { arguments, .. } => {
for (_, loc_arg) in arguments.iter_mut() {
fix_values_captured_in_closure_expr(&mut loc_arg.value, no_capture_symbols);
}

View File

@ -150,8 +150,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'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"),

View File

@ -10,14 +10,14 @@ use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::Symbol;
use roc_parse::ast::{self, StrLiteral, StrSegment};
use roc_parse::pattern::PatternType;
use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError};
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 {
@ -47,7 +47,7 @@ pub enum Pattern {
// for the expression from the opaque definition. `type_arguments` is something like
// [(n, fresh1)], and `specialized_def_type` becomes "[ Id U64 fresh1 ]".
specialized_def_type: Box<Type>,
type_arguments: Vec<(Lowercase, Type)>,
type_arguments: Vec<Variable>,
lambda_set_variables: Vec<LambdaSet>,
},
RecordDestructure {
@ -62,6 +62,17 @@ pub enum Pattern {
SingleQuote(char),
Underscore,
/// An identifier that marks a specialization of an ability member.
/// For example, given an ability member definition `hash : a -> U64 | a has Hash`,
/// there may be the specialization `hash : Bool -> U64`. In this case we generate a
/// new symbol for the specialized "hash" identifier.
AbilityMemberSpecialization {
/// The symbol for this specialization.
ident: Symbol,
/// The ability name being specialized.
specializes: Symbol,
},
// Runtime Exceptions
Shadowed(Region, Loc<Ident>, Symbol),
OpaqueNotInScope(Loc<Ident>),
@ -71,7 +82,85 @@ pub enum Pattern {
MalformedPattern(MalformedPatternProblem, Region),
}
#[derive(Clone, Debug, PartialEq)]
impl Pattern {
pub fn opt_var(&self) -> Option<Variable> {
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,
@ -79,7 +168,7 @@ pub struct RecordDestruct {
pub typ: DestructType,
}
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug)]
pub enum DestructType {
Required,
Optional(Variable, Loc<Expr>),
@ -101,6 +190,11 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
symbols.push(*symbol);
}
AbilityMemberSpecialization { ident, specializes } => {
symbols.push(*ident);
symbols.push(*specializes);
}
AppliedTag { arguments, .. } => {
for (_, nested) in arguments {
symbols_from_pattern_help(&nested.value, symbols);
@ -136,27 +230,67 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
}
}
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,
) -> Loc<Pattern> {
use roc_parse::ast::Pattern::*;
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(), 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);
Loc::at(region, can_pattern)
}
}
}
_ => canonicalize_pattern(env, var_store, scope, output, pattern_type, pattern, region),
}
}
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<Pattern>) {
) -> Loc<Pattern> {
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_str(name, region) {
Ok(symbol) => {
output.references.bound_symbols.insert(symbol);
output.references.insert_bound(symbol);
Pattern::Identifier(symbol)
}
@ -164,29 +298,19 @@ pub fn canonicalize_pattern<'a>(
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region,
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![],
}
}
@ -201,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(),
@ -253,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(),
@ -378,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();
@ -389,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,
@ -412,6 +526,7 @@ pub fn canonicalize_pattern<'a>(
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region,
shadow: shadow.clone(),
kind: ShadowKind::Variable,
}));
// No matter what the other patterns
@ -426,18 +541,18 @@ 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.scopeless_symbol(&Ident::from(label), loc_pattern.region);
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 {
@ -450,12 +565,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,
@ -466,7 +576,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);
@ -484,6 +594,7 @@ pub fn canonicalize_pattern<'a>(
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region,
shadow: shadow.clone(),
kind: ShadowKind::Variable,
}));
// No matter what the other patterns
@ -531,13 +642,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
@ -572,64 +680,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<Item = &'a Loc<Pattern>>,
{
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<Pattern>),
Many(Vec<BindingsFromPatternWork<'a>>),
}
/// 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<Pattern>),
Destruct(&'a Loc<RecordDestruct>),
}
match pattern {
Identifier(symbol) | Shadowed(_, _, symbol) => {
answer.push((*symbol, *region));
impl<'a> BindingsFromPattern<'a> {
pub fn new(initial: &'a Loc<Pattern>) -> Self {
Self::One(initial)
}
pub fn new_many<I>(mut it: I) -> Self
where
I: Iterator<Item = &'a Loc<Pattern>>,
{
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<BindingsFromPatternWork<'a>>) -> 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<Self::Item> {
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(..) => (),
}
}

View File

@ -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<Box<str>>,
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<Symbol>,
pub type_lookups: ImSet<Symbol>,
pub value_lookups: ImSet<Symbol>,
/// Aliases or opaque types referenced
pub referenced_type_defs: ImSet<Symbol>,
pub calls: ImSet<Symbol>,
symbols: Vec<Symbol>,
bitflags: Vec<ReferencesBitflags>,
}
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<Item = &'a Symbol> {
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<Item = &Symbol> {
self.retain(|b| b.0 & ReferencesBitflags::VALUE_LOOKUP.0 > 0)
}
pub fn type_lookups(&self) -> impl Iterator<Item = &Symbol> {
self.retain(|b| b.0 & ReferencesBitflags::TYPE_LOOKUP.0 > 0)
}
pub fn bound_symbols(&self) -> impl Iterator<Item = &Symbol> {
self.retain(|b| b.0 & ReferencesBitflags::BOUND.0 > 0)
}
pub fn calls(&self) -> impl Iterator<Item = &Symbol> {
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)
}
}

View File

@ -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<Element, Order>;
type BitSlice = bitvec::prelude::BitSlice<Element, Order>;
/// 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<Item = usize> + '_ {
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 <sam@rfc1149.net>,
// 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<i64> = 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::<Vec<u32>>::new();
// the initial group contains all symbols with no predecessors
let mut prev_group: Vec<u32> = 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<u32> = (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::<u32>::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<u32> = (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<Vec<u32>> },
/// 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<Vec<u32>>,
nodes_in_cycle: Vec<u32>,
},
}
#[derive(Clone, Copy)]
enum Preorder {
Empty,
Filled(usize),
Removed,
}
struct Params {
preorders: Vec<Preorder>,
c: usize,
p: Vec<u32>,
s: Vec<u32>,
scc: Sccs,
scca: Vec<u32>,
}
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<bitvec::slice::Chunks<'_, Element, Order>> {
// 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)
}
}

View File

@ -1,149 +1,140 @@
use roc_collections::all::{MutSet, SendMap};
use roc_collections::VecMap;
use roc_module::ident::{Ident, Lowercase};
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_module::symbol::{IdentId, IdentIds, ModuleId, Symbol};
use roc_problem::can::RuntimeError;
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::subs::Variable;
use roc_types::types::{Alias, AliasKind, Type};
#[derive(Clone, Debug, PartialEq)]
use crate::abilities::AbilitiesStore;
use bitvec::vec::BitVec;
#[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<Ident, (Symbol, Region)>,
/// A cache of all the symbols in scope. This makes lookups much
/// faster when checking for unused defs and unused arguments.
symbols: SendMap<Symbol, Region>,
/// The type aliases currently in scope
pub aliases: SendMap<Symbol, Alias>,
pub aliases: VecMap<Symbol, Alias>,
/// The abilities currently in scope, and their implementors.
pub abilities_store: AbilitiesStore,
/// The current module being processed. This will be used to turn
/// unqualified idents into Symbols.
home: ModuleId,
/// The first `exposed_ident_count` identifiers are exposed
exposed_ident_count: usize,
/// Identifiers that are imported (and introduced in the header)
imports: Vec<(Ident, Symbol, Region)>,
/// Identifiers that are in scope, and defined in the current module
pub locals: ScopedIdentIds,
}
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 {
let imports = Symbol::default_in_scope()
.into_iter()
.map(|(a, (b, c))| (a, b, c))
.collect();
Scope {
home,
idents: Symbol::default_in_scope(),
symbols: SendMap::default(),
aliases,
exposed_ident_count: initial_ident_ids.len(),
locals: ScopedIdentIds::from_ident_ids(home, initial_ident_ids),
aliases: VecMap::default(),
// TODO(abilities): default abilities in scope
abilities_store: AbilitiesStore::default(),
imports,
}
}
pub fn idents(&self) -> impl Iterator<Item = (&Ident, &(Symbol, Region))> {
self.idents.iter()
}
pub fn symbols(&self) -> impl Iterator<Item = (&Symbol, &Region)> {
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 lookup(&self, ident: &Ident, region: Region) -> Result<Symbol, RuntimeError> {
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(),
)),
use ContainsIdent::*;
match self.scope_contains_ident(ident.as_str()) {
InScope(symbol, _) => Ok(symbol),
NotInScope(_) | NotPresent => {
let error = RuntimeError::LookupNotInScope(
Loc {
region,
value: ident.clone(),
},
self.idents_in_scope().map(|v| v.as_ref().into()).collect(),
);
Err(error)
}
}
}
pub fn lookup_alias(&self, symbol: Symbol) -> Option<&Alias> {
self.aliases.get(&symbol)
fn idents_in_scope(&self) -> impl Iterator<Item = Ident> + '_ {
let it1 = self.locals.idents_in_scope();
let it2 = self.imports.iter().map(|t| t.0.clone());
it2.chain(it1)
}
/// 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('$'));
let opaque = opaque_ref[1..].into();
debug_assert!(opaque_ref.starts_with('@'));
let opaque_str = &opaque_ref[1..];
let opaque = opaque_str.into();
match self.idents.get(&opaque) {
// TODO: is it worth caching any of these results?
Some((symbol, decl_region)) => {
if symbol.module_id() != self.home {
// The reference is to an opaque type declared in another module - this is
// illegal, as opaque types can only be wrapped/unwrapped in the scope they're
// declared.
return Err(RuntimeError::OpaqueOutsideScope {
match self.locals.has_in_scope(&opaque) {
Some((symbol, _)) => match self.lookup_opaque_alias(symbol) {
Ok(alias) => Ok((symbol, alias)),
Err(opt_alias_def_region) => {
Err(self.opaque_not_defined_error(opaque, lookup_region, opt_alias_def_region))
}
},
None => {
// opaque types can only be wrapped/unwrapped in the scope they are defined in (and below)
let error = if let Some((_, decl_region)) = self.has_imported(opaque_str) {
// specific error for when the opaque is imported, which definitely does not work
RuntimeError::OpaqueOutsideScope {
opaque,
referenced_region: lookup_region,
imported_region: *decl_region,
});
}
imported_region: decl_region,
}
} else {
self.opaque_not_defined_error(opaque, lookup_region, None)
};
match self.aliases.get(symbol) {
None => Err(self.opaque_not_defined_error(opaque, lookup_region, None)),
Some(alias) => match alias.kind {
// The reference is to a proper alias like `Age : U32`, not an opaque type!
AliasKind::Structural => Err(self.opaque_not_defined_error(
opaque,
lookup_region,
Some(alias.header_region()),
)),
// All is good
AliasKind::Opaque => Ok((*symbol, alias)),
},
}
Err(error)
}
None => Err(self.opaque_not_defined_error(opaque, lookup_region, None)),
}
}
fn lookup_opaque_alias(&self, symbol: Symbol) -> Result<&Alias, Option<Region>> {
match self.aliases.get(&symbol) {
None => Err(None),
Some(alias) => match alias.kind {
AliasKind::Opaque => Ok(alias),
AliasKind::Structural => Err(Some(alias.header_region())),
},
}
}
fn is_opaque(&self, ident_id: IdentId, string: &str) -> Option<Box<str>> {
if string.is_empty() {
return None;
}
let symbol = Symbol::new(self.home, ident_id);
if let Some(AliasKind::Opaque) = self.aliases.get(&symbol).map(|alias| alias.kind) {
Some(string.into())
} else {
None
}
}
@ -153,16 +144,13 @@ impl Scope {
lookup_region: Region,
opt_defined_alias: Option<Region>,
) -> RuntimeError {
// for opaques, we only look at the locals because opaques can only be matched
// on in the module that defines them.
let opaques_in_scope = self
.idents()
.filter(|(_, (sym, _))| {
self.aliases
.get(sym)
.map(|alias| alias.kind)
.unwrap_or(AliasKind::Structural)
== AliasKind::Opaque
})
.map(|(v, _)| v.as_ref().into())
.locals
.ident_ids
.ident_strs()
.filter_map(|(ident_id, string)| self.is_opaque(ident_id, string))
.collect();
RuntimeError::OpaqueNotDefined {
@ -172,57 +160,156 @@ impl Scope {
}
}
/// Introduce a new ident to scope.
///
/// Returns Err if this would shadow an existing ident, including the
/// Symbol and Region of the ident we already had in scope under that name.
pub fn introduce(
&mut self,
ident: Ident,
exposed_ident_ids: &IdentIds,
all_ident_ids: &mut IdentIds,
region: Region,
) -> Result<Symbol, (Region, Loc<Ident>, 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());
let symbol = Symbol::new(self.home, ident_id);
self.symbols.insert(symbol, region);
self.idents.insert(ident, (symbol, region));
Err((original_region, shadow, symbol))
fn has_imported(&self, ident: &str) -> Option<(Symbol, Region)> {
for (import, shadow, original_region) in self.imports.iter() {
if ident == import.as_str() {
return Some((*shadow, *original_region));
}
None => {
// 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()),
};
}
let symbol = Symbol::new(self.home, ident_id);
None
}
self.symbols.insert(symbol, region);
self.idents.insert(ident, (symbol, region));
/// Is an identifier in scope, either in the locals or imports
fn scope_contains_ident(&self, ident: &str) -> ContainsIdent {
// exposed imports are likely to be small
match self.has_imported(ident) {
Some((symbol, region)) => ContainsIdent::InScope(symbol, region),
None => self.locals.contains_ident(ident),
}
}
Ok(symbol)
fn introduce_help(&mut self, ident: &str, region: Region) -> Result<Symbol, (Symbol, Region)> {
match self.scope_contains_ident(ident) {
ContainsIdent::InScope(original_symbol, original_region) => {
// the ident is already in scope; up to the caller how to handle that
// (usually it's shadowing, but it is valid to shadow ability members)
Err((original_symbol, original_region))
}
ContainsIdent::NotPresent => {
// We know nothing about this ident yet; introduce it to the scope
let ident_id = self.locals.introduce_into_scope(ident, region);
Ok(Symbol::new(self.home, ident_id))
}
ContainsIdent::NotInScope(existing) => {
// The ident is not in scope, but its name is already in the string interner
if existing.index() < self.exposed_ident_count {
// if the identifier is exposed, use the IdentId we already have for it
// other modules depend on the symbol having that IdentId
let symbol = Symbol::new(self.home, existing);
self.locals.in_scope.set(existing.index(), true);
self.locals.regions[existing.index()] = region;
Ok(symbol)
} else {
// create a new IdentId that under the hood uses the same string bytes as an existing one
let ident_id = self.locals.introduce_into_scope_duplicate(existing, region);
Ok(Symbol::new(self.home, ident_id))
}
}
}
}
/// Ignore an identifier.
/// Introduce a new ident to 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);
Symbol::new(self.home, ident_id)
/// Returns Err if this would shadow an existing ident, including the
/// Symbol and Region of the ident we already had in scope under that name.
///
/// If this ident shadows an existing one, a new ident is allocated for the shadow. This is
/// done so that all identifiers have unique symbols, which is important in particular when
/// we generate code for value identifiers.
/// If this behavior is undesirable, use [`Self::introduce_without_shadow_symbol`].
pub fn introduce(
&mut self,
ident: Ident,
region: Region,
) -> Result<Symbol, (Region, Loc<Ident>, Symbol)> {
self.introduce_str(ident.as_str(), region)
}
pub fn introduce_str(
&mut self,
ident: &str,
region: Region,
) -> Result<Symbol, (Region, Loc<Ident>, Symbol)> {
match self.introduce_help(ident, region) {
Ok(symbol) => Ok(symbol),
Err((_, original_region)) => {
let shadow = Loc {
value: Ident::from(ident),
region,
};
let symbol = self.locals.scopeless_symbol(ident, region);
Err((original_region, shadow, symbol))
}
}
}
/// Like [Self::introduce], but does not introduce a new symbol for the shadowing symbol.
pub fn introduce_without_shadow_symbol(
&mut self,
ident: &Ident,
region: Region,
) -> Result<Symbol, (Region, Loc<Ident>)> {
match self.introduce_help(ident.as_str(), region) {
Err((_, original_region)) => {
let shadow = Loc {
value: ident.clone(),
region,
};
Err((original_region, shadow))
}
Ok(symbol) => Ok(symbol),
}
}
/// Like [Self::introduce], but handles the case of when an ident matches an ability member
/// name. In such cases a new symbol is created for the ident (since it's expected to be a
/// specialization of the ability member), but the ident is not added to the ident->symbol map.
///
/// If the ident does not match an ability name, the behavior of this function is exactly that
/// of `introduce`.
#[allow(clippy::type_complexity)]
pub fn introduce_or_shadow_ability_member(
&mut self,
ident: Ident,
region: Region,
) -> Result<(Symbol, Option<Symbol>), (Region, Loc<Ident>, Symbol)> {
let ident = &ident;
match self.introduce_help(ident.as_str(), region) {
Err((original_symbol, original_region)) => {
let shadow_symbol = self.scopeless_symbol(ident, region);
if self.abilities_store.is_ability_member_name(original_symbol) {
self.abilities_store
.register_specializing_symbol(shadow_symbol, original_symbol);
Ok((shadow_symbol, Some(original_symbol)))
} else {
// This is an illegal shadow.
let shadow = Loc {
value: ident.clone(),
region,
};
Err((original_region, shadow, shadow_symbol))
}
}
Ok(symbol) => Ok((symbol, None)),
}
}
/// Create a new symbol, but don't add it to the scope (yet)
///
/// Used for record guards like { x: Just _ } where the `x` is not added to the scope,
/// but also in other places where we need to create a symbol and we don't have the right
/// scope information yet. An identifier can be introduced later, and will use the same IdentId
pub fn scopeless_symbol(&mut self, ident: &Ident, region: Region) -> Symbol {
self.locals.scopeless_symbol(ident.as_str(), region)
}
/// Import a Symbol from another module into this module's top-level scope.
@ -235,15 +322,13 @@ impl Scope {
symbol: Symbol,
region: Region,
) -> Result<(), (Symbol, Region)> {
match self.idents.get(&ident) {
Some(shadowed) => Err(*shadowed),
None => {
self.symbols.insert(symbol, region);
self.idents.insert(ident, (symbol, region));
Ok(())
}
if let Some((s, r)) = self.has_imported(ident.as_str()) {
return Err((s, r));
}
self.imports.push((ident, symbol, region));
Ok(())
}
pub fn add_alias(
@ -258,9 +343,52 @@ impl Scope {
self.aliases.insert(name, alias);
}
pub fn lookup_alias(&self, symbol: Symbol) -> Option<&Alias> {
self.aliases.get(&symbol)
}
pub fn contains_alias(&mut self, name: Symbol) -> bool {
self.aliases.contains_key(&name)
}
pub fn inner_scope<F, T>(&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
// - locals: everything introduced in the inner scope is marked as not in scope in the rollback
// - aliases: stored in a VecMap, we just discard anything added in an inner scope
// - exposed_ident_count: unchanged
// - home: unchanged
let aliases_count = self.aliases.len();
let locals_snapshot = self.locals.in_scope.len();
let result = f(self);
self.aliases.truncate(aliases_count);
// anything added in the inner scope is no longer in scope now
for i in locals_snapshot..self.locals.in_scope.len() {
self.locals.in_scope.set(i, false);
}
result
}
pub fn register_debug_idents(&self) {
self.home.register_debug_idents(&self.locals.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 {
Symbol::new(self.home, self.locals.gen_unique())
}
}
pub fn create_alias(
@ -307,3 +435,317 @@ pub fn create_alias(
kind,
}
}
#[derive(Debug)]
enum ContainsIdent {
InScope(Symbol, Region),
NotInScope(IdentId),
NotPresent,
}
#[derive(Clone, Debug)]
pub struct ScopedIdentIds {
pub ident_ids: IdentIds,
in_scope: BitVec,
regions: Vec<Region>,
home: ModuleId,
}
impl ScopedIdentIds {
fn from_ident_ids(home: ModuleId, ident_ids: IdentIds) -> Self {
let capacity = ident_ids.len();
Self {
in_scope: BitVec::repeat(false, capacity),
ident_ids,
regions: vec![Region::zero(); capacity],
home,
}
}
fn has_in_scope(&self, ident: &Ident) -> Option<(Symbol, Region)> {
match self.contains_ident(ident.as_str()) {
ContainsIdent::InScope(symbol, region) => Some((symbol, region)),
ContainsIdent::NotInScope(_) | ContainsIdent::NotPresent => None,
}
}
fn contains_ident(&self, ident: &str) -> ContainsIdent {
use ContainsIdent::*;
let mut result = NotPresent;
for ident_id in self.ident_ids.get_id_many(ident) {
let index = ident_id.index();
if self.in_scope[index] {
return InScope(Symbol::new(self.home, ident_id), self.regions[index]);
} else {
result = NotInScope(ident_id)
}
}
result
}
fn idents_in_scope(&self) -> impl Iterator<Item = Ident> + '_ {
self.ident_ids
.ident_strs()
.zip(self.in_scope.iter())
.filter_map(|((_, string), keep)| {
if *keep {
Some(Ident::from(string))
} else {
None
}
})
}
fn introduce_into_scope(&mut self, ident_name: &str, region: Region) -> IdentId {
let id = self.ident_ids.add_str(ident_name);
debug_assert_eq!(id.index(), self.in_scope.len());
debug_assert_eq!(id.index(), self.regions.len());
self.in_scope.push(true);
self.regions.push(region);
id
}
fn introduce_into_scope_duplicate(&mut self, existing: IdentId, region: Region) -> IdentId {
let id = self.ident_ids.duplicate_ident(existing);
debug_assert_eq!(id.index(), self.in_scope.len());
debug_assert_eq!(id.index(), self.regions.len());
self.in_scope.push(true);
self.regions.push(region);
id
}
/// Adds an IdentId, but does not introduce it to the scope
fn scopeless_symbol(&mut self, ident_name: &str, region: Region) -> Symbol {
let id = self.ident_ids.add_str(ident_name);
debug_assert_eq!(id.index(), self.in_scope.len());
debug_assert_eq!(id.index(), self.regions.len());
self.in_scope.push(false);
self.regions.push(region);
Symbol::new(self.home, id)
}
fn gen_unique(&mut self) -> IdentId {
let id = self.ident_ids.gen_unique();
debug_assert_eq!(id.index(), self.in_scope.len());
debug_assert_eq!(id.index(), self.regions.len());
self.in_scope.push(false);
self.regions.push(Region::zero());
id
}
}
#[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 default_idents_in_scope() {
let _register_module_debug_names = ModuleIds::default();
let 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"),
]
);
}
#[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,]);
}
#[test]
fn import_is_in_scope() {
let _register_module_debug_names = ModuleIds::default();
let mut scope = Scope::new(ModuleId::ATTR, IdentIds::default());
let ident = Ident::from("product");
let symbol = Symbol::LIST_PRODUCT;
let region = Region::zero();
assert!(scope.lookup(&ident, region).is_err());
assert!(scope.import(ident.clone(), symbol, region).is_ok());
assert!(scope.lookup(&ident, region).is_ok());
assert!(scope.idents_in_scope().any(|x| x == ident));
}
#[test]
fn shadow_of_import() {
let _register_module_debug_names = ModuleIds::default();
let mut scope = Scope::new(ModuleId::ATTR, IdentIds::default());
let ident = Ident::from("product");
let symbol = Symbol::LIST_PRODUCT;
let region1 = Region::from_pos(Position { offset: 10 });
let region2 = Region::from_pos(Position { offset: 20 });
scope.import(ident.clone(), symbol, region1).unwrap();
let (original_region, _ident, shadow_symbol) =
scope.introduce(ident.clone(), region2).unwrap_err();
scope.register_debug_idents();
assert_ne!(symbol, shadow_symbol);
assert_eq!(original_region, region1);
let lookup = scope.lookup(&ident, Region::zero()).unwrap();
assert_eq!(symbol, lookup);
}
}

View File

@ -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<V: Visitor>(visitor: &mut V, decls: &[Declaration]) {
visit_list!(visitor, visit_decl, decls)
}
fn walk_decl<V: Visitor>(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<V: Visitor>(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<V: Visitor>(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<V: Visitor>(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<V: Visitor>(
visitor: &mut V,
cond_var: Variable,
expr_var: Variable,
loc_cond: &Loc<Expr>,
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<V: Visitor>(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<V: Visitor>(_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<Variable>) {
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<Variable>,
}
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<Variable>) {
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<Variable> {
let mut visitor = TypeAtVisitor { region, typ: None };
visitor.visit_decls(decls);
visitor.typ
}

View File

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

View File

@ -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.locals.ident_ids);
let interns = Interns {
module_ids: env.module_ids.clone(),

View File

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

View File

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

View File

@ -0,0 +1,266 @@
use std::{fmt::Debug, mem::ManuallyDrop};
/// Collection of small (length < u16::MAX) strings, stored compactly.
#[derive(Clone, Default, PartialEq, Eq)]
pub struct SmallStringInterner {
buffer: Vec<u8>,
// lengths could be Vec<u8>, but the mono refcount generation
// stringifies Layout's and that creates > 256 character strings
lengths: Vec<Length>,
offsets: Vec<u32>,
}
#[derive(Copy, Clone, PartialEq, Eq)]
#[repr(transparent)]
struct Length(i16);
impl std::fmt::Debug for Length {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.kind().fmt(f)
}
}
impl Length {
#[inline(always)]
const fn kind(self) -> Kind {
if self.0 == 0 {
Kind::Empty
} else if self.0 > 0 {
Kind::Interned(self.0 as usize)
} else {
Kind::Generated(self.0.abs() as usize)
}
}
#[inline(always)]
const fn from_usize(len: usize) -> Self {
Self(len as i16)
}
}
enum Kind {
Generated(usize),
Empty,
Interned(usize),
}
impl Debug for Kind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Generated(arg0) => write!(f, "Generated({})", arg0),
Self::Empty => write!(f, "Empty"),
Self::Interned(arg0) => write!(f, "Interned({})", arg0),
}
}
}
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),
}
}
/// # Safety
///
/// lengths must be non-negative integers less than 2^15
pub unsafe fn from_parts(buffer: Vec<u8>, lengths: Vec<u16>, offsets: Vec<u32>) -> Self {
// the recommended way of transmuting a vector
let mut lengths = ManuallyDrop::new(lengths);
let lengths = Vec::from_raw_parts(
lengths.as_mut_ptr().cast(),
lengths.len(),
lengths.capacity(),
);
Self {
buffer,
lengths,
offsets,
}
}
pub fn iter(&self) -> impl Iterator<Item = &str> {
(0..self.offsets.len()).map(move |index| self.get(index))
}
/// Insert without deduplicating
pub fn insert(&mut self, string: &str) -> usize {
let bytes = string.as_bytes();
assert!(bytes.len() < (1 << 15));
let offset = self.buffer.len() as u32;
let length = Length::from_usize(bytes.len());
let index = self.lengths.len();
self.lengths.push(length);
self.offsets.push(offset);
self.buffer.extend(bytes);
index
}
/// Create a new entry that uses the same string bytes as an existing entry
pub fn duplicate(&mut self, existing: usize) -> usize {
let offset = self.offsets[existing];
let length = self.lengths[existing];
let index = self.lengths.len();
self.lengths.push(length);
self.offsets.push(offset);
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();
// this is a generated name, so store it as a negative length
let length = Length(-((self.buffer.len() - offset) as i16));
self.lengths.push(length);
self.offsets.push(offset as u32);
index
}
#[inline(always)]
pub fn find_index(&self, string: &str) -> Option<usize> {
self.find_indices(string).next()
}
#[inline(always)]
pub fn find_indices<'a>(&'a self, string: &'a str) -> impl Iterator<Item = usize> + 'a {
let target_length = string.len();
// 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
self.lengths
.iter()
.enumerate()
.filter_map(move |(index, length)| match length.kind() {
Kind::Generated(_) => None,
Kind::Empty => {
if target_length == 0 {
Some(index)
} else {
None
}
}
Kind::Interned(length) => {
if target_length == length {
let offset = self.offsets[index];
let slice = &self.buffer[offset as usize..][..length];
if string.as_bytes() == slice {
return Some(index);
}
}
None
}
})
}
fn get(&self, index: usize) -> &str {
match self.lengths[index].kind() {
Kind::Empty => "",
Kind::Generated(length) | Kind::Interned(length) => {
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::from_usize(length);
self.offsets[index] = offset as u32;
}
pub fn find_and_update(&mut self, old_string: &str, new_string: &str) -> Option<usize> {
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());
}
}

View File

@ -0,0 +1,182 @@
#[derive(Debug, Clone)]
pub struct VecMap<K, V> {
keys: Vec<K>,
values: Vec<V>,
}
impl<K, V> Default for VecMap<K, V> {
fn default() -> Self {
Self {
keys: Vec::new(),
values: Vec::new(),
}
}
}
impl<K: PartialEq, V> VecMap<K, V> {
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<V> {
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<Item = (&K, &V)> {
self.keys.iter().zip(self.values.iter())
}
pub fn keys(&self) -> impl Iterator<Item = &K> {
self.keys.iter()
}
pub fn values(&self) -> impl Iterator<Item = &V> {
self.values.iter()
}
pub fn truncate(&mut self, len: usize) {
self.keys.truncate(len);
self.values.truncate(len);
}
pub fn unzip(self) -> (Vec<K>, Vec<V>) {
(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<K>, values: Vec<V>) -> Self {
Self { keys, values }
}
}
impl<K: Ord, V> Extend<(K, V)> for VecMap<K, V> {
#[inline(always)]
fn extend<T: IntoIterator<Item = (K, V)>>(&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<K, V> IntoIterator for VecMap<K, V> {
type Item = (K, V);
type IntoIter = IntoIter<K, V>;
fn into_iter(self) -> Self::IntoIter {
IntoIter {
keys: self.keys.into_iter(),
values: self.values.into_iter(),
}
}
}
pub struct IntoIter<K, V> {
keys: std::vec::IntoIter<K>,
values: std::vec::IntoIter<V>,
}
impl<K, V> Iterator for IntoIter<K, V> {
type Item = (K, V);
fn next(&mut self) -> Option<Self::Item> {
match (self.keys.next(), self.values.next()) {
(Some(k), Some(v)) => Some((k, v)),
_ => None,
}
}
}

View File

@ -0,0 +1,110 @@
#[derive(Clone, Debug, PartialEq)]
pub struct VecSet<T> {
elements: Vec<T>,
}
impl<T> Default for VecSet<T> {
fn default() -> Self {
Self {
elements: Vec::new(),
}
}
}
impl<T: PartialEq> VecSet<T> {
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
}
}
/// Returns true iff any of the given elements previoously existed in the set.
pub fn insert_all<I: Iterator<Item = T>>(&mut self, values: I) -> bool {
let mut any_existed = false;
for value in values {
any_existed = any_existed || self.insert(value);
}
any_existed
}
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<Item = &T> {
self.elements.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
self.elements.iter_mut()
}
}
impl<A: Ord> Extend<A> for VecSet<A> {
fn extend<T: IntoIterator<Item = A>>(&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<T> IntoIterator for VecSet<T> {
type Item = T;
type IntoIter = std::vec::IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
self.elements.into_iter()
}
}

View File

@ -2,13 +2,12 @@ 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::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)]
@ -161,16 +160,16 @@ pub fn str_type() -> Type {
#[inline(always)]
fn builtin_alias(
symbol: Symbol,
type_arguments: Vec<(Lowercase, Type)>,
type_arguments: Vec<Type>,
actual: Box<Type>,
kind: AliasKind,
) -> Type {
Type::Alias {
symbol,
type_arguments,
actual,
lambda_set_variables: vec![],
// TODO(opaques): revisit later
kind: AliasKind::Structural,
kind,
}
}
@ -178,100 +177,89 @@ fn builtin_alias(
pub fn num_float(range: Type) -> Type {
builtin_alias(
Symbol::NUM_FLOAT,
vec![("range".into(), range.clone())],
vec![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.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)]
pub fn num_int(range: Type) -> Type {
builtin_alias(
Symbol::NUM_INT,
vec![("range".into(), range.clone())],
vec![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.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![typ.clone()],
Box::new(typ),
AliasKind::Opaque,
)
}

View File

@ -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<Pattern>)],
arguments: &[(Variable, AnnotatedMark, Loc<Pattern>)],
closure_type: Type,
return_type: Type,
) -> (Vec<Variable>, 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<Type>, 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,
@ -881,7 +946,9 @@ pub fn constrain_expr(
name,
arguments,
} => {
let mut vars = Vec::with_capacity(arguments.len());
// +2 because we push all the arguments, plus variant_var and ext_var
let num_vars = arguments.len() + 2;
let mut vars = Vec::with_capacity(num_vars);
let mut types = Vec::with_capacity(arguments.len());
let mut arg_cons = Vec::with_capacity(arguments.len());
@ -923,27 +990,8 @@ pub fn constrain_expr(
variant_var,
ext_var,
name,
arguments,
closure_name,
} => {
let mut vars = Vec::with_capacity(arguments.len());
let mut types = Vec::with_capacity(arguments.len());
let mut arg_cons = Vec::with_capacity(arguments.len());
for (var, loc_expr) in arguments {
let arg_con = constrain_expr(
constraints,
env,
loc_expr.region,
&loc_expr.value,
Expected::NoExpectation(Type::Variable(*var)),
);
arg_cons.push(arg_con);
vars.push(*var);
types.push(Type::Variable(*var));
}
let union_con = constraints.equal_types_with_storage(
Type::FunctionOrTagUnion(
name.clone(),
@ -953,19 +1001,14 @@ pub fn constrain_expr(
expected.clone(),
Category::TagApply {
tag_name: name.clone(),
args_count: arguments.len(),
args_count: 0,
},
region,
*variant_var,
);
vars.push(*variant_var);
vars.push(*ext_var);
arg_cons.push(union_con);
constraints.exists_many(vars, arg_cons)
constraints.exists_many([*variant_var, *ext_var], [union_con])
}
OpaqueRef {
opaque_var,
name,
@ -979,7 +1022,7 @@ pub fn constrain_expr(
let opaque_type = Type::Alias {
symbol: *name,
type_arguments: type_arguments.clone(),
type_arguments: type_arguments.iter().copied().map(Type::Variable).collect(),
lambda_set_variables: lambda_set_variables.clone(),
actual: Box::new(arg_type.clone()),
kind: AliasKind::Opaque,
@ -1016,9 +1059,7 @@ pub fn constrain_expr(
let mut vars = vec![*arg_var, *opaque_var];
// Also add the fresh variables we created for the type argument and lambda sets
vars.extend(type_arguments.iter().map(|(_, t)| {
t.expect_variable("all type arguments should be fresh variables here")
}));
vars.extend(type_arguments);
vars.extend(lambda_set_variables.iter().map(|v| {
v.0.expect_variable("all lambda sets should be fresh variables here")
}));
@ -1109,15 +1150,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<Type>,
pattern_expected: impl Fn(HumanIndex, Region) -> PExpected<Type>,
expr_expected: Expected<Type>,
) -> Constraint {
) -> (
Vec<Variable>,
SendMap<Symbol, Loc<Type>>,
Constraint,
Constraint,
) {
let ret_constraint = constrain_expr(
constraints,
env,
@ -1128,24 +1176,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,
@ -1159,20 +1210,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(
@ -1254,6 +1312,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(
@ -1351,6 +1410,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;
@ -1486,7 +1546,7 @@ fn constrain_typed_function_arguments(
def: &Def,
def_pattern_state: &mut PatternState,
argument_pattern_state: &mut PatternState,
arguments: &[(Variable, Loc<Pattern>)],
arguments: &[(Variable, AnnotatedMark, Loc<Pattern>)],
arg_types: &[Type],
) {
// ensure type matches the one in the annotation
@ -1497,38 +1557,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)
}
}
}
}
@ -1671,18 +1806,18 @@ fn instantiate_rigids(
let mut new_rigid_variables: Vec<Variable> = Vec::new();
let mut rigid_substitution: MutMap<Variable, Variable> = 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());
}
}
}
@ -1830,9 +1965,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;
@ -1842,53 +1977,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,

View File

@ -1,12 +1,15 @@
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)]
@ -64,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;
@ -100,10 +94,58 @@ pub enum ExposedModuleTypes {
pub fn constrain_module(
constraints: &mut Constraints,
abilities_store: &AbilitiesStore,
declarations: &[Declaration],
home: ModuleId,
) -> 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();
let let_constr = constraints.let_constraint(
rigids,
flex,
[(
*member_name,
Loc::at_zero(Type::Variable(member_data.signature_var)),
)],
Constraint::True,
constraint,
);
constraint = constraints.and_constraint([unify_with_signature_var, let_constr]);
}
constraint
}
#[derive(Debug, Clone)]
@ -147,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;
}
};

View File

@ -18,6 +18,7 @@ pub struct PatternState {
pub headers: SendMap<Symbol, Loc<Type>>,
pub vars: Vec<Variable>,
pub constraints: Vec<Constraint>,
pub delayed_is_open_constraints: Vec<Constraint>,
}
/// If there is a type annotation, the pattern state headers can be optimized by putting the
@ -50,7 +51,13 @@ fn headers_from_annotation_help(
headers: &mut SendMap<Symbol, Loc<Type>>,
) -> bool {
match pattern {
Identifier(symbol) | Shadowed(_, _, symbol) => {
Identifier(symbol)
| Shadowed(_, _, symbol)
// TODO(abilities): handle linking the member def to the specialization ident
| AbilityMemberSpecialization {
ident: symbol,
specializes: _,
} => {
let typ = Loc::at(annotation.region, annotation.value.clone());
headers.insert(*symbol, typ);
true
@ -174,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()));
}
}
@ -183,6 +190,25 @@ pub fn constrain_pattern(
}
Identifier(symbol) | Shadowed(_, _, symbol) => {
if could_be_a_tag_union(expected.get_type_ref()) {
state
.delayed_is_open_constraints
.push(constraints.is_open_type(expected.get_type_ref().clone()));
}
state.headers.insert(
*symbol,
Loc {
region,
value: expected.get_type(),
},
);
}
AbilityMemberSpecialization {
ident: symbol,
specializes: _,
} => {
if could_be_a_tag_union(expected.get_type_ref()) {
state
.constraints
@ -469,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 {
@ -485,7 +514,7 @@ pub fn constrain_pattern(
let opaque_type = Type::Alias {
symbol: *opaque,
type_arguments: type_arguments.clone(),
type_arguments: type_arguments.iter().copied().map(Type::Variable).collect(),
lambda_set_variables: lambda_set_variables.clone(),
actual: Box::new(arg_pattern_type.clone()),
kind: AliasKind::Opaque,
@ -542,9 +571,7 @@ pub fn constrain_pattern(
.vars
.extend_from_slice(&[*arg_pattern_var, *whole_var]);
// Also add the fresh variables we created for the type argument and lambda sets
state.vars.extend(type_arguments.iter().map(|(_, t)| {
t.expect_variable("all type arguments should be fresh variables here")
}));
state.vars.extend(type_arguments);
state.vars.extend(lambda_set_variables.iter().map(|v| {
v.0.expect_variable("all lambda sets should be fresh variables here")
}));

View File

@ -0,0 +1,6 @@
[package]
name = "roc_debug_flags"
version = "0.1.0"
edition = "2021"
[dependencies]

View File

@ -0,0 +1,111 @@
//! Flags for debugging the Roc compiler.
//!
//! Lists environment variable flags that can be enabled for verbose debugging features in debug
//! builds of the compiler.
//!
//! For example, I might define the following alias to run cargo with all unifications and
//! expanded type aliases printed:
//!
//! ```bash
//! alias cargo="\
//! ROC_PRINT_UNIFICATIONS=1 \
//! ROC_PRETTY_PRINT_ALIAS_CONTENTS=1 \
//! cargo"
//! ```
//!
//! More generally, I have the following:
//!
//! ```bash
//! alias cargo="\
//! ROC_PRETTY_PRINT_ALIAS_CONTENTS=0 \
//! ROC_PRINT_UNIFICATIONS=0 \
//! ROC_PRINT_MISMATCHES=0 \
//! ROC_PRINT_IR_AFTER_SPECIALIZATION=0 \
//! ROC_PRINT_IR_AFTER_RESET_REUSE=0 \
//! ROC_PRINT_IR_AFTER_REFCOUNT=0 \
//! ROC_PRETTY_PRINT_IR_SYMBOLS=0 \
//! # ...other flags
//! cargo"
//! ```
//!
//! Now you can turn debug flags on and off as you like.
//!
//! These flags are also set in .cargo/config found at the repository root. You can modify them
//! there to avoid maintaining a separate script.
#[macro_export]
macro_rules! dbg_do {
($flag:path, $expr:expr) => {
#[cfg(debug_assertions)]
{
let flag = std::env::var($flag);
if !flag.is_err() && flag.as_deref() != Ok("0") {
$expr
}
}
};
}
macro_rules! flags {
($($(#[doc = $doc:expr])+ $flag:ident)*) => {$(
$(#[doc = $doc])+
pub static $flag: &str = stringify!($flag);
)*};
}
flags! {
// ===Types===
/// Expands the contents of aliases during pretty-printing of types.
ROC_PRETTY_PRINT_ALIAS_CONTENTS
// ===Solve===
/// Prints type unifications, before and after they happen.
ROC_PRINT_UNIFICATIONS
/// Prints all type mismatches hit during type unification.
ROC_PRINT_MISMATCHES
/// Verifies that after let-generalization of a def, any rigid variables in the type annotation
/// of the def are indeed generalized.
///
/// Note that rigids need not always be generalized in a def. For example, they may be
/// constrained by a type from a lower rank, as `b` is in the following def:
///
/// F a : { foo : a }
/// foo = \arg ->
/// x : F b
/// x = arg
/// x.foo
///
/// Instead, this flag is useful for checking that in general, introduction is correct, when
/// chainging how defs are constrained.
ROC_VERIFY_RIGID_LET_GENERALIZED
// ===Mono===
/// Writes a pretty-printed mono IR to stderr after function specialization.
ROC_PRINT_IR_AFTER_SPECIALIZATION
/// Writes a pretty-printed mono IR to stderr after insertion of reset/reuse
/// instructions.
ROC_PRINT_IR_AFTER_RESET_REUSE
/// Writes a pretty-printed mono IR to stderr after insertion of refcount
/// instructions.
ROC_PRINT_IR_AFTER_REFCOUNT
/// Prints debug information during the alias analysis pass.
ROC_DEBUG_ALIAS_ANALYSIS
// ===LLVM Gen===
/// Prints LLVM function verification output.
ROC_PRINT_LLVM_FN_VERIFICATION
// ===Load===
/// Print load phases as they complete.
ROC_PRINT_LOAD_LOG
}

Some files were not shown because too many files have changed in this diff Show More