Merge remote-tracking branch 'origin/trunk' into update_zig_09

This commit is contained in:
Folkert 2022-04-30 23:34:48 +02:00
commit b7b86c0cde
No known key found for this signature in database
GPG Key ID: 1F17F6FFD112B97C
299 changed files with 19230 additions and 9669 deletions

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

@ -76,3 +76,5 @@ 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>

View File

@ -107,7 +107,7 @@ To run the test suite (via `cargo test`), you additionally need to install:
* [`valgrind`](https://www.valgrind.org/) (needs special treatment to [install on macOS](https://stackoverflow.com/a/61359781)
Alternatively, you can use `cargo test --no-fail-fast` or `cargo test -p specific_tests` to skip over the valgrind failures & tests.
For debugging LLVM IR, we use [DebugIR](https://github.com/vaivaswatha/debugir). This dependency is only required to build with the `--debug` flag, and for normal developtment you should be fine without it.
For debugging LLVM IR, we use [DebugIR](https://github.com/vaivaswatha/debugir). This dependency is only required to build with the `--debug` flag, and for normal development you should be fine without it.
### libxcb libraries

72
Cargo.lock generated
View File

@ -242,10 +242,22 @@ version = "0.22.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527"
dependencies = [
"funty",
"radium",
"funty 1.2.0",
"radium 0.6.2",
"tap",
"wyz",
"wyz 0.4.0",
]
[[package]]
name = "bitvec"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b"
dependencies = [
"funty 2.0.0",
"radium 0.7.0",
"tap",
"wyz 0.5.0",
]
[[package]]
@ -1393,6 +1405,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e"
[[package]]
name = "funty"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "futures"
version = "0.3.17"
@ -2645,7 +2663,7 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c48e482b9a59ad6c2cdb06f7725e7bd33fe3525baaf4699fde7bfea6a5b77b1"
dependencies = [
"bitvec",
"bitvec 0.22.3",
"packed_struct_codegen",
"serde",
]
@ -3092,6 +3110,12 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb"
[[package]]
name = "radium"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "radix_trie"
version = "0.2.1"
@ -3229,9 +3253,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.5.4"
version = "1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
dependencies = [
"aho-corasick",
"memchr",
@ -3271,6 +3295,19 @@ dependencies = [
"winapi",
]
[[package]]
name = "remove_dir_all"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "882f368737489ea543bc5c340e6f3d34a28c39980bd9a979e47322b26f60ac40"
dependencies = [
"libc",
"log",
"num_cpus",
"rayon",
"winapi",
]
[[package]]
name = "renderdoc-sys"
version = "0.7.1"
@ -3409,12 +3446,14 @@ dependencies = [
name = "roc_can"
version = "0.1.0"
dependencies = [
"bitvec 1.0.0",
"bumpalo",
"indoc",
"pretty_assertions",
"roc_builtins",
"roc_collections",
"roc_error_macros",
"roc_exhaustive",
"roc_module",
"roc_parse",
"roc_problem",
@ -3735,6 +3774,7 @@ dependencies = [
"roc_reporting",
"roc_solve",
"roc_target",
"roc_test_utils",
"roc_types",
"roc_unify",
"tempfile",
@ -3897,7 +3937,6 @@ dependencies = [
"roc_exhaustive",
"roc_load",
"roc_module",
"roc_mono",
"roc_parse",
"roc_problem",
"roc_region",
@ -3916,11 +3955,14 @@ dependencies = [
"arrayvec 0.7.2",
"bumpalo",
"indoc",
"lazy_static",
"pretty_assertions",
"regex",
"roc_builtins",
"roc_can",
"roc_collections",
"roc_error_macros",
"roc_exhaustive",
"roc_load",
"roc_module",
"roc_parse",
@ -3953,6 +3995,7 @@ name = "roc_test_utils"
version = "0.1.0"
dependencies = [
"pretty_assertions",
"remove_dir_all 0.7.0",
]
[[package]]
@ -4449,9 +4492,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "target-lexicon"
version = "0.12.2"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9bffcddbc2458fa3e6058414599e3c838a022abae82e5c67b4f7f80298d5bff"
checksum = "d7fa7e55043acb85fca6b3c01485a2eeb6b69c5d21002e273c79e465f43b7ac1"
[[package]]
name = "tempfile"
@ -4463,7 +4506,7 @@ dependencies = [
"libc",
"rand",
"redox_syscall",
"remove_dir_all",
"remove_dir_all 0.5.3",
"winapi",
]
@ -5580,6 +5623,15 @@ dependencies = [
"tap",
]
[[package]]
name = "wyz"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e"
dependencies = [
"tap",
]
[[package]]
name = "x11-clipboard"
version = "0.5.3"

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

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

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

@ -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,
@ -1881,19 +1855,9 @@ fn num_float(pool: &mut Pool, range: TypeId) -> Type2 {
fn num_floatingpoint(pool: &mut Pool, range: TypeId) -> Type2 {
let range_type = pool.get(range);
let alias_content = Type2::TagUnion(
PoolVec::new(
vec![(
TagName::Private(Symbol::NUM_AT_FLOATINGPOINT),
PoolVec::new(vec![range_type.shallow_clone()].into_iter(), pool),
)]
.into_iter(),
pool,
),
pool.add(Type2::EmptyTagUnion),
);
let alias_content = range_type.shallow_clone();
Type2::Alias(
Type2::Opaque(
Symbol::NUM_FLOATINGPOINT,
PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool),
pool.add(alias_content),
@ -1917,37 +1881,16 @@ fn num_int(pool: &mut Pool, range: TypeId) -> Type2 {
#[inline(always)]
fn _num_signed64(pool: &mut Pool) -> Type2 {
let alias_content = Type2::TagUnion(
PoolVec::new(
vec![(
TagName::Private(Symbol::NUM_AT_SIGNED64),
PoolVec::empty(pool),
)]
.into_iter(),
pool,
),
pool.add(Type2::EmptyTagUnion),
);
Type2::Alias(
Symbol::NUM_SIGNED64,
PoolVec::empty(pool),
pool.add(alias_content),
pool.add(Type2::EmptyTagUnion),
)
}
#[inline(always)]
fn num_unsigned32(pool: &mut Pool) -> Type2 {
let alias_content = Type2::TagUnion(
PoolVec::new(
std::iter::once((
TagName::Private(Symbol::NUM_UNSIGNED32),
PoolVec::empty(pool),
)),
pool,
),
pool.add(Type2::EmptyTagUnion),
);
let alias_content = Type2::EmptyTagUnion;
Type2::Alias(
Symbol::NUM_UNSIGNED32,
@ -1960,19 +1903,9 @@ fn num_unsigned32(pool: &mut Pool) -> Type2 {
fn _num_integer(pool: &mut Pool, range: TypeId) -> Type2 {
let range_type = pool.get(range);
let alias_content = Type2::TagUnion(
PoolVec::new(
vec![(
TagName::Private(Symbol::NUM_AT_INTEGER),
PoolVec::new(vec![range_type.shallow_clone()].into_iter(), pool),
)]
.into_iter(),
pool,
),
pool.add(Type2::EmptyTagUnion),
);
let alias_content = range_type.shallow_clone();
Type2::Alias(
Type2::Opaque(
Symbol::NUM_INTEGER,
PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool),
pool.add(alias_content),
@ -1983,19 +1916,9 @@ fn _num_integer(pool: &mut Pool, range: TypeId) -> Type2 {
fn num_num(pool: &mut Pool, type_id: TypeId) -> Type2 {
let range_type = pool.get(type_id);
let alias_content = Type2::TagUnion(
PoolVec::new(
vec![(
TagName::Private(Symbol::NUM_AT_NUM),
PoolVec::new(vec![range_type.shallow_clone()].into_iter(), pool),
)]
.into_iter(),
pool,
),
pool.add(Type2::EmptyTagUnion),
);
let alias_content = range_type.shallow_clone();
Type2::Alias(
Type2::Opaque(
Symbol::NUM_NUM,
PoolVec::new(
vec![(PoolStr::new("range", pool), type_id)].into_iter(),
@ -2275,7 +2198,7 @@ pub mod test_constrain {
}
#[test]
fn constrain_global_tag() {
fn constrain_tag() {
infer_eq(
indoc!(
r#"
@ -2286,18 +2209,6 @@ pub mod test_constrain {
)
}
#[test]
fn constrain_private_tag() {
infer_eq(
indoc!(
r#"
@Foo
"#
),
"[ @Foo ]*",
)
}
#[test]
fn constrain_call_and_accessor() {
infer_eq(

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

@ -41,18 +41,12 @@ pub enum Pattern2 {
StrLiteral(PoolStr), // 8B
CharacterLiteral(char), // 4B
Underscore, // 0B
GlobalTag {
Tag {
whole_var: Variable, // 4B
ext_var: Variable, // 4B
tag_name: PoolStr, // 8B
arguments: PoolVec<(Variable, PatternId)>, // 8B
},
PrivateTag {
whole_var: Variable, // 4B
ext_var: Variable, // 4B
tag_name: Symbol, // 8B
arguments: PoolVec<(Variable, PatternId)>, // 8B
},
RecordDestructure {
whole_var: Variable, // 4B
ext_var: Variable, // 4B
@ -271,26 +265,15 @@ pub fn to_pattern2<'a>(
ptype => unsupported_pattern(env, ptype, region),
},
GlobalTag(name) => {
Tag(name) => {
// Canonicalize the tag's name.
Pattern2::GlobalTag {
Pattern2::Tag {
whole_var: env.var_store.fresh(),
ext_var: env.var_store.fresh(),
tag_name: PoolStr::new(name, env.pool),
arguments: PoolVec::empty(env.pool),
}
}
PrivateTag(name) => {
let ident_id = env.ident_ids.get_or_insert(&(*name).into());
// Canonicalize the tag's name.
Pattern2::PrivateTag {
whole_var: env.var_store.fresh(),
ext_var: env.var_store.fresh(),
tag_name: Symbol::new(env.home, ident_id),
arguments: PoolVec::empty(env.pool),
}
}
OpaqueRef(..) => todo_opaques!(),
@ -313,22 +296,12 @@ pub fn to_pattern2<'a>(
}
match tag.value {
GlobalTag(name) => Pattern2::GlobalTag {
Tag(name) => Pattern2::Tag {
whole_var: env.var_store.fresh(),
ext_var: env.var_store.fresh(),
tag_name: PoolStr::new(name, env.pool),
arguments: can_patterns,
},
PrivateTag(name) => {
let ident_id = env.ident_ids.get_or_insert(&name.into());
Pattern2::PrivateTag {
whole_var: env.var_store.fresh(),
ext_var: env.var_store.fresh(),
tag_name: Symbol::new(env.home, ident_id),
arguments: can_patterns,
}
}
_ => unreachable!("Other patterns cannot be applied"),
}
}
@ -506,7 +479,7 @@ pub fn symbols_from_pattern(pool: &Pool, initial: &Pattern2) -> Vec<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);
@ -543,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),
@ -567,7 +540,7 @@ pub fn symbols_and_variables_from_pattern(
symbols.push((*symbol, variable));
}
GlobalTag { arguments, .. } | PrivateTag { arguments, .. } => {
Tag { arguments, .. } => {
for (var, pat_id) in arguments.iter(pool) {
let pat = pool.get(*pat_id);
stack.push((*var, pat));

View File

@ -24,6 +24,7 @@ pub enum Type2 {
Variable(Variable), // 4B
Alias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 8B + 4B + pad
Opaque(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 8B + 4B + pad
AsAlias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 8B + 4B + pad
// 24B
@ -74,6 +75,9 @@ impl ShallowClone for Type2 {
Self::Alias(symbol, args, alias_type_id) => {
Self::Alias(*symbol, args.shallow_clone(), alias_type_id.clone())
}
Self::Opaque(symbol, args, alias_type_id) => {
Self::Opaque(*symbol, args.shallow_clone(), alias_type_id.clone())
}
Self::Record(fields, ext_id) => Self::Record(fields.shallow_clone(), ext_id.clone()),
Self::Function(args, closure_type_id, ret_type_id) => Self::Function(
args.shallow_clone(),
@ -101,7 +105,7 @@ impl Type2 {
Variable(v) => {
result.insert(*v);
}
Alias(_, _, actual) | AsAlias(_, _, actual) => {
Alias(_, _, actual) | AsAlias(_, _, actual) | Opaque(_, _, actual) => {
stack.push(pool.get(*actual));
}
HostExposedAlias {
@ -690,29 +694,14 @@ fn can_tags<'a>(
// a duplicate
let new_name = 'inner: loop {
match tag {
Tag::Global { name, args } => {
Tag::Apply { name, args } => {
let arg_types = PoolVec::with_capacity(args.len() as u32, env.pool);
for (type_id, loc_arg) in arg_types.iter_node_ids().zip(args.iter()) {
as_type_id(env, scope, rigids, type_id, &loc_arg.value, loc_arg.region);
}
let tag_name = TagName::Global(name.value.into());
tag_types.push((tag_name.clone(), arg_types));
break 'inner tag_name;
}
Tag::Private { name, args } => {
let ident_id = env.ident_ids.get_or_insert(&name.value.into());
let symbol = Symbol::new(env.home, ident_id);
let arg_types = PoolVec::with_capacity(args.len() as u32, env.pool);
for (type_id, loc_arg) in arg_types.iter_node_ids().zip(args.iter()) {
as_type_id(env, scope, rigids, type_id, &loc_arg.value, loc_arg.region);
}
let tag_name = TagName::Private(symbol);
let tag_name = TagName::Tag(name.value.into());
tag_types.push((tag_name.clone(), arg_types));
break 'inner tag_name;

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};
@ -245,7 +246,7 @@ impl Scope {
// use that existing IdentId. Otherwise, create a fresh one.
let ident_id = match exposed_ident_ids.get_id(&ident) {
Some(ident_id) => ident_id,
None => all_ident_ids.add(ident.clone().into()),
None => all_ident_ids.add_ident(&ident),
};
let symbol = Symbol::new(self.home, ident_id);
@ -262,7 +263,7 @@ impl Scope {
///
/// Used for record guards like { x: Just _ }
pub fn ignore(&mut self, ident: Ident, all_ident_ids: &mut IdentIds) -> Symbol {
let ident_id = all_ident_ids.add(ident.into());
let ident_id = all_ident_ids.add_ident(&ident);
Symbol::new(self.home, ident_id)
}
@ -320,16 +321,12 @@ impl Scope {
self.aliases.contains_key(&name)
}
pub fn fill_scope(
&mut self,
env: &Env,
all_ident_ids: &mut MutMap<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

@ -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>),
@ -868,7 +869,7 @@ fn type_to_variable<'a>(
register(subs, rank, pools, content)
}
Alias(symbol, args, alias_type_id) => {
Alias(symbol, args, alias_type_id) | Opaque(symbol, args, alias_type_id) => {
// TODO cache in uniqueness inference gives problems! all Int's get the same uniqueness var!
// Cache aliases without type arguments. Commonly used aliases like `Int` would otherwise get O(n)
// different variables (once for each occurrence). The recursion restriction is required
@ -910,8 +911,12 @@ fn type_to_variable<'a>(
let alias_var = type_to_variable(arena, mempool, subs, rank, pools, cached, alias_type);
// TODO(opaques): take opaques into account
let content = Content::Alias(*symbol, arg_vars, alias_var, AliasKind::Structural);
let kind = match typ {
Alias(..) => AliasKind::Structural,
Opaque(..) => AliasKind::Opaque,
_ => internal_error!(),
};
let content = Content::Alias(*symbol, arg_vars, alias_var, kind);
let result = register(subs, rank, pools, content);

View File

@ -66,7 +66,7 @@ const_format = "0.2.22"
bumpalo = { version = "3.8.0", features = ["collections"] }
mimalloc = { version = "0.1.26", default-features = false }
target-lexicon = "0.12.2"
target-lexicon = "0.12.3"
tempfile = "3.2.0"
wasmer-wasi = { version = "2.0.0", optional = true }

View File

@ -1,7 +1,7 @@
use bumpalo::Bump;
use roc_build::{
link::{link, rebuild_host, LinkType},
program,
program::{self, Problems},
};
use roc_builtins::bitcode;
use roc_load::LoadingProblem;
@ -21,25 +21,9 @@ fn report_timing(buf: &mut String, label: &str, duration: Duration) {
));
}
pub enum BuildOutcome {
NoProblems,
OnlyWarnings,
Errors,
}
impl BuildOutcome {
pub fn status_code(&self) -> i32 {
match self {
Self::NoProblems => 0,
Self::OnlyWarnings => 1,
Self::Errors => 2,
}
}
}
pub struct BuiltFile {
pub binary_path: PathBuf,
pub outcome: BuildOutcome,
pub problems: Problems,
pub total_time: Duration,
}
@ -184,7 +168,7 @@ pub fn build_file<'a>(
// This only needs to be mutable for report_problems. This can't be done
// inside a nested scope without causing a borrow error!
let mut loaded = loaded;
program::report_problems_monomorphized(&mut loaded);
let problems = program::report_problems_monomorphized(&mut loaded);
let loaded = loaded;
let code_gen_timing = program::gen_from_mono_module(
@ -243,7 +227,7 @@ pub fn build_file<'a>(
// Step 2: link the precompiled host and compiled app
let link_start = SystemTime::now();
let outcome = if surgically_link {
let problems = if surgically_link {
roc_linker::link_preprocessed_host(target, &host_input_path, app_o_file, &binary_path)
.map_err(|err| {
todo!(
@ -251,12 +235,12 @@ pub fn build_file<'a>(
err
);
})?;
BuildOutcome::NoProblems
problems
} else if matches!(link_type, LinkType::None) {
// Just copy the object file to the output folder.
binary_path.set_extension(app_extension);
std::fs::copy(app_o_file, &binary_path).unwrap();
BuildOutcome::NoProblems
problems
} else {
let mut inputs = vec![
host_input_path.as_path().to_str().unwrap(),
@ -281,11 +265,15 @@ pub fn build_file<'a>(
todo!("gracefully handle error after `ld` spawned");
})?;
// TODO change this to report whether there were errors or warnings!
if exit_status.success() {
BuildOutcome::NoProblems
problems
} else {
BuildOutcome::Errors
let mut problems = problems;
// Add an error for `ld` failing
problems.errors += 1;
problems
}
};
let linking_time = link_start.elapsed().unwrap();
@ -298,7 +286,7 @@ pub fn build_file<'a>(
Ok(BuiltFile {
binary_path,
outcome,
problems,
total_time,
})
}
@ -318,7 +306,7 @@ fn spawn_rebuild_thread(
let thread_local_target = target.clone();
std::thread::spawn(move || {
if !precompiled {
print!("🔨 Rebuilding host... ");
println!("🔨 Rebuilding host...");
}
let rebuild_host_start = SystemTime::now();
@ -350,10 +338,6 @@ fn spawn_rebuild_thread(
}
let rebuild_host_end = rebuild_host_start.elapsed().unwrap();
if !precompiled {
println!("Done!");
}
rebuild_host_end.as_millis()
})
}
@ -364,7 +348,7 @@ pub fn check_file(
src_dir: PathBuf,
roc_file_path: PathBuf,
emit_timings: bool,
) -> Result<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
@ -437,5 +421,8 @@ pub fn check_file(
println!("Finished checking in {} ms\n", compilation_end.as_millis(),);
}
Ok(program::report_problems_typechecked(&mut loaded))
Ok((
program::report_problems_typechecked(&mut loaded),
compilation_end,
))
}

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,7 +1,7 @@
#[macro_use]
extern crate const_format;
use build::{BuildOutcome, BuiltFile};
use build::BuiltFile;
use bumpalo::Bump;
use clap::{App, AppSettings, Arg, ArgMatches};
use roc_build::link::LinkType;
@ -24,6 +24,7 @@ mod format;
pub use format::format;
pub const CMD_BUILD: &str = "build";
pub const CMD_RUN: &str = "run";
pub const CMD_REPL: &str = "repl";
pub const CMD_EDIT: &str = "edit";
pub const CMD_DOCS: &str = "docs";
@ -49,10 +50,12 @@ pub const ROC_DIR: &str = "ROC_DIR";
pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES";
pub const ARGS_FOR_APP: &str = "ARGS_FOR_APP";
const VERSION: &str = include_str!("../../version.txt");
pub fn build_app<'a>() -> App<'a> {
let app = App::new("roc")
.version(concatcp!(include_str!("../../version.txt"), "\n"))
.about("Runs the given .roc file. Use one of the SUBCOMMANDS below to do something else!")
.version(concatcp!(VERSION, "\n"))
.about("Runs the given .roc file, if there are no compilation errors.\nUse one of the SUBCOMMANDS below to do something else!")
.subcommand(App::new(CMD_BUILD)
.about("Build a binary from the given .roc file, but don't run it")
.arg(
@ -128,7 +131,6 @@ pub fn build_app<'a>() -> App<'a> {
.long(FLAG_PRECOMPILED)
.about("Assumes the host has been precompiled and skips recompiling the host. (Enabled by default when using a --target other than `--target host`)")
.possible_values(["true", "false"])
.default_value("false")
.required(false),
)
.arg(
@ -141,8 +143,16 @@ pub fn build_app<'a>() -> App<'a> {
.subcommand(App::new(CMD_REPL)
.about("Launch the interactive Read Eval Print Loop (REPL)")
)
.subcommand(App::new(CMD_RUN)
.about("Run a .roc file even if it has build errors")
.arg(
Arg::new(ROC_FILE)
.about("The .roc file of an app to run")
.required(true),
)
)
.subcommand(App::new(CMD_FORMAT)
.about("Format Roc code")
.about("Format a .roc file using standard Roc formatting")
.arg(
Arg::new(DIRECTORY_OR_FILES)
.index(1)
@ -156,10 +166,9 @@ pub fn build_app<'a>() -> App<'a> {
)
)
.subcommand(App::new(CMD_VERSION)
.about("Print version information")
)
.about(concatcp!("Print the Roc compilers version, which is currently ", VERSION)))
.subcommand(App::new(CMD_CHECK)
.about("When developing, it's recommended to run `check` before `build`. It may provide a useful error message in cases where `build` panics")
.about("Check the code for problems, but doesnt build or run it")
.arg(
Arg::new(FLAG_TIME)
.long(FLAG_TIME)
@ -168,7 +177,7 @@ pub fn build_app<'a>() -> App<'a> {
)
.arg(
Arg::new(ROC_FILE)
.about("The .roc file of an app to run")
.about("The .roc file of an app to check")
.required(true),
)
)
@ -194,19 +203,19 @@ pub fn build_app<'a>() -> App<'a> {
.arg(
Arg::new(FLAG_OPT_SIZE)
.long(FLAG_OPT_SIZE)
.about("Optimize your compiled Roc program to have a small binary size. (Optimization takes time to complete.)")
.about("Optimize the compiled program to have a small binary size. (Optimization takes time to complete.)")
.required(false),
)
.arg(
Arg::new(FLAG_DEV)
.long(FLAG_DEV)
.about("Make compilation as fast as possible. (Runtime performance may suffer)")
.about("Make compilation finish as soon as possible, at the expense of runtime performance.")
.required(false),
)
.arg(
Arg::new(FLAG_DEBUG)
.long(FLAG_DEBUG)
.about("Store LLVM debug information in the generated program")
.about("Store LLVM debug information in the generated program.")
.requires(ROC_FILE)
.required(false),
)
@ -232,17 +241,8 @@ pub fn build_app<'a>() -> App<'a> {
.arg(
Arg::new(FLAG_PRECOMPILED)
.long(FLAG_PRECOMPILED)
.about("Assumes the host has been precompiled and skips recompiling the host. (Enabled by default when using a --target other than `--target host`)")
.about("Assumes the host has been precompiled and skips recompiling the host. (Enabled by default when using `roc build` with a --target other than `--target host`)")
.possible_values(["true", "false"])
.default_value("false")
.required(false),
)
.arg(
Arg::new(FLAG_TARGET)
.long(FLAG_TARGET)
.about("Choose a different target")
.default_value(Target::default().as_str())
.possible_values(Target::OPTIONS)
.required(false),
)
.arg(
@ -282,6 +282,7 @@ pub fn docs(files: Vec<PathBuf>) {
pub enum BuildConfig {
BuildOnly,
BuildAndRun { roc_file_arg_index: usize },
BuildAndRunIfNoErrors { roc_file_arg_index: usize },
}
pub enum FormatMode {
@ -389,7 +390,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
match res_binary_path {
Ok(BuiltFile {
binary_path,
outcome,
problems,
total_time,
}) => {
match config {
@ -404,56 +405,128 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<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())
}
}
}
@ -470,11 +543,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);
}
_ => 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

View File

@ -1,7 +1,8 @@
use roc_cli::build::check_file;
use roc_cli::{
build_app, docs, format, BuildConfig, FormatMode, CMD_BUILD, CMD_CHECK, CMD_DOCS, CMD_EDIT,
CMD_FORMAT, CMD_REPL, CMD_VERSION, DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_TIME, ROC_FILE,
CMD_FORMAT, CMD_REPL, CMD_RUN, CMD_VERSION, DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_TIME,
ROC_FILE,
};
use roc_load::LoadingProblem;
use std::fs::{self, FileType};
@ -27,7 +28,10 @@ fn main() -> io::Result<()> {
Some(arg_index) => {
let roc_file_arg_index = arg_index + 1; // Not sure why this +1 is necessary, but it is!
build(&matches, BuildConfig::BuildAndRun { roc_file_arg_index })
build(
&matches,
BuildConfig::BuildAndRunIfNoErrors { roc_file_arg_index },
)
}
None => {
@ -37,6 +41,21 @@ fn main() -> io::Result<()> {
}
}
}
Some((CMD_RUN, matches)) => {
match matches.index_of(ROC_FILE) {
Some(arg_index) => {
let roc_file_arg_index = arg_index + 1; // Not sure why this +1 is necessary, but it is!
build(matches, BuildConfig::BuildAndRun { roc_file_arg_index })
}
None => {
eprintln!("What .roc file do you want to run? Specify it at the end of the `roc run` command.");
Ok(1)
}
}
}
Some((CMD_BUILD, matches)) => Ok(build(matches, BuildConfig::BuildOnly)?),
Some((CMD_CHECK, matches)) => {
let arena = bumpalo::Bump::new();
@ -47,9 +66,35 @@ fn main() -> io::Result<()> {
let src_dir = roc_file_path.parent().unwrap().to_owned();
match check_file(&arena, src_dir, roc_file_path, emit_timings) {
Ok(number_of_errors) => {
let exit_code = if number_of_errors != 0 { 1 } else { 0 };
Ok(exit_code)
Ok((problems, total_time)) => {
println!(
"\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.",
if problems.errors == 0 {
32 // green
} else {
33 // yellow
},
problems.errors,
if problems.errors == 1 {
"error"
} else {
"errors"
},
if problems.warnings == 0 {
32 // green
} else {
33 // yellow
},
problems.warnings,
if problems.warnings == 1 {
"warning"
} else {
"warnings"
},
total_time.as_millis(),
);
Ok(problems.exit_code())
}
Err(LoadingProblem::FormattedReport(report)) => {

View File

@ -63,13 +63,19 @@ mod cli_run {
.replace(ANSI_STYLE_CODES.bold, "")
.replace(ANSI_STYLE_CODES.underline, "")
.replace(ANSI_STYLE_CODES.reset, "")
.replace(ANSI_STYLE_CODES.color_reset, "")
}
fn check_compile_error(file: &Path, flags: &[&str], expected: &str) {
let compile_out = run_roc(&[&["check", file.to_str().unwrap()], flags].concat());
let err = compile_out.stdout.trim();
let err = strip_colors(err);
assert_multiline_str_eq!(err, expected.into());
// e.g. "1 error and 0 warnings found in 123 ms."
let (before_first_digit, _) = err.split_at(err.rfind("found in ").unwrap());
let err = format!("{}found in <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) {
@ -180,8 +186,8 @@ mod cli_run {
};
if !&out.stdout.ends_with(expected_ending) {
panic!(
"expected output to end with {:?} but instead got {:#?}",
expected_ending, out.stdout
"expected output to end with {:?} but instead got {:#?} - stderr was: {:#?}",
expected_ending, out.stdout, out.stderr
);
}
assert!(out.status.success());
@ -255,8 +261,8 @@ mod cli_run {
return;
}
}
"hello-gui" => {
// Since this one requires opening a window, we do `roc build` on it but don't run it.
"hello-gui" | "breakout" => {
// Since these require opening a window, we do `roc build` on them but don't run them.
build_example(&file_name, &["--optimize"]);
return;
@ -393,6 +399,14 @@ mod cli_run {
expected_ending: "",
use_valgrind: false,
},
breakout:"breakout" => Example {
filename: "breakout.roc",
executable_filename: "breakout",
stdin: &[],
input_file: None,
expected_ending: "",
use_valgrind: false,
},
quicksort:"algorithms" => Example {
filename: "quicksort.roc",
executable_filename: "quicksort",
@ -849,7 +863,7 @@ mod cli_run {
&[],
indoc!(
r#"
UNRECOGNIZED NAME
UNRECOGNIZED NAME tests/known_bad/TypeError.roc
I cannot find a `d` value
@ -863,7 +877,9 @@ mod cli_run {
I8
F64
"#
1 error and 0 warnings found in <ignored for test> ms."#
),
);
}
@ -875,14 +891,16 @@ mod cli_run {
&[],
indoc!(
r#"
MISSING DEFINITION
MISSING DEFINITION tests/known_bad/ExposedNotDefined.roc
bar is listed as exposed, but it isn't defined in this module.
You can fix this by adding a definition for bar, or by removing it
from exposes.
"#
1 error and 0 warnings found in <ignored for test> ms."#
),
);
}
@ -894,7 +912,7 @@ mod cli_run {
&[],
indoc!(
r#"
UNUSED IMPORT
UNUSED IMPORT tests/known_bad/UnusedImport.roc
Nothing from Symbol is used in this module.
@ -903,7 +921,9 @@ mod cli_run {
Since Symbol isn't used, you don't need to import it.
"#
0 errors and 1 warning found in <ignored for test> ms."#
),
);
}
@ -915,7 +935,7 @@ mod cli_run {
&[],
indoc!(
r#"
UNKNOWN GENERATES FUNCTION
UNKNOWN GENERATES FUNCTION tests/known_bad/UnknownGeneratesWith.roc
I don't know how to generate the foobar function.
@ -925,7 +945,9 @@ mod cli_run {
Only specific functions like `after` and `map` can be generated.Learn
more about hosted modules at TODO.
"#
1 error and 0 warnings found in <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

@ -167,6 +167,20 @@ For a more detailed understanding of the compilation phases, see the `Phase`, `B
## Debugging intermediate representations
### Debugging the typechecker
Setting the following environment variables:
- `ROC_PRINT_UNIFICATIONS` prints all type unifications that are done,
before and after the unification.
- `ROC_PRINT_MISMATCHES` prints all type mismatches hit during unification.
- `ROC_PRETTY_PRINT_ALIAS_CONTENTS` expands the contents of aliases during
pretty-printing of types.
Note that this is only relevant during debug builds. Eventually we should have
some better debugging tools here, see https://github.com/rtfeldman/roc/issues/2486
for one.
### The mono IR
If you observe a miscomplication, you may first want to check the generated mono

View File

@ -279,7 +279,8 @@ fn build_entry_point(
let block = builder.add_block();
// to the modelling language, the arguments appear out of thin air
let argument_type = build_tuple_type(&mut builder, layout.arguments)?;
let argument_type =
build_tuple_type(&mut builder, layout.arguments, &WhenRecursive::Unreachable)?;
// does not make any assumptions about the input
// let argument = builder.add_unknown_with(block, &[], argument_type)?;
@ -308,7 +309,11 @@ fn build_entry_point(
let block = builder.add_block();
let type_id = layout_spec(&mut builder, &Layout::struct_no_name_order(layouts))?;
let type_id = layout_spec(
&mut builder,
&Layout::struct_no_name_order(layouts),
&WhenRecursive::Unreachable,
)?;
let argument = builder.add_unknown_with(block, &[], type_id)?;
@ -352,8 +357,9 @@ fn proc_spec<'a>(proc: &Proc<'a>) -> Result<(FuncDef, MutSet<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 +463,14 @@ fn stmt_spec<'a>(
let mut type_ids = Vec::new();
for p in parameters.iter() {
type_ids.push(layout_spec(builder, &p.layout)?);
type_ids.push(layout_spec(
builder,
&p.layout,
&WhenRecursive::Unreachable,
)?);
}
let ret_type_id = layout_spec(builder, layout)?;
let ret_type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
let jp_arg_type_id = builder.add_tuple_type(&type_ids)?;
@ -500,14 +510,14 @@ fn stmt_spec<'a>(
builder.add_sub_block(block, BlockExpr(cont_block, cont_value_id))
}
Jump(id, symbols) => {
let ret_type_id = layout_spec(builder, layout)?;
let ret_type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
let argument = build_tuple_value(builder, env, block, symbols)?;
let jpid = env.join_points[id];
builder.add_jump(block, jpid, argument, ret_type_id)
}
RuntimeError(_) => {
let type_id = layout_spec(builder, layout)?;
let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
builder.add_terminate(block, type_id)
}
@ -556,11 +566,15 @@ fn build_recursive_tuple_type(
builder.add_tuple_type(&field_types)
}
fn build_tuple_type(builder: &mut impl TypeContext, layouts: &[Layout]) -> Result<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 +705,7 @@ fn call_spec(
.map(|symbol| env.symbols[symbol])
.collect();
let result_type = layout_spec(builder, ret_layout)?;
let result_type = layout_spec(builder, ret_layout, &WhenRecursive::Unreachable)?;
builder.add_unknown_with(block, &arguments, result_type)
}
@ -761,7 +775,8 @@ fn call_spec(
};
let state_layout = argument_layouts[0];
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = state;
add_loop(builder, block, state_type, init_state, loop_body)
@ -782,7 +797,8 @@ fn call_spec(
};
let state_layout = argument_layouts[0];
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = state;
add_loop(builder, block, state_type, init_state, loop_body)
@ -806,7 +822,8 @@ fn call_spec(
};
let state_layout = argument_layouts[0];
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = state;
add_loop(builder, block, state_type, init_state, loop_body)
@ -828,10 +845,12 @@ fn call_spec(
list_append(builder, block, update_mode_var, state, new_element)
};
let output_element_type = layout_spec(builder, return_layout)?;
let output_element_type =
layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?;
let state_layout = Layout::Builtin(Builtin::List(return_layout));
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = new_list(builder, block, output_element_type)?;
@ -851,10 +870,12 @@ fn call_spec(
list_append(builder, block, update_mode_var, state, new_element)
};
let output_element_type = layout_spec(builder, return_layout)?;
let output_element_type =
layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?;
let state_layout = Layout::Builtin(Builtin::List(return_layout));
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = new_list(builder, block, output_element_type)?;
@ -879,7 +900,8 @@ fn call_spec(
};
let state_layout = Layout::Builtin(Builtin::List(&argument_layouts[0]));
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = list;
add_loop(builder, block, state_type, init_state, loop_body)
@ -903,10 +925,12 @@ fn call_spec(
list_append(builder, block, update_mode_var, state, new_element)
};
let output_element_type = layout_spec(builder, return_layout)?;
let output_element_type =
layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?;
let state_layout = Layout::Builtin(Builtin::List(return_layout));
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = new_list(builder, block, output_element_type)?;
@ -936,10 +960,12 @@ fn call_spec(
list_append(builder, block, update_mode_var, state, new_element)
};
let output_element_type = layout_spec(builder, return_layout)?;
let output_element_type =
layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?;
let state_layout = Layout::Builtin(Builtin::List(return_layout));
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = new_list(builder, block, output_element_type)?;
@ -975,10 +1001,12 @@ fn call_spec(
list_append(builder, block, update_mode_var, state, new_element)
};
let output_element_type = layout_spec(builder, return_layout)?;
let output_element_type =
layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?;
let state_layout = Layout::Builtin(Builtin::List(return_layout));
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = new_list(builder, block, output_element_type)?;
@ -1010,7 +1038,8 @@ fn call_spec(
};
let state_layout = Layout::Builtin(Builtin::List(&argument_layouts[0]));
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = list;
add_loop(builder, block, state_type, init_state, loop_body)
@ -1087,11 +1116,13 @@ fn call_spec(
)
};
let output_element_type = layout_spec(builder, &output_element_layout)?;
let output_element_type =
layout_spec(builder, &output_element_layout, &WhenRecursive::Unreachable)?;
let init_state = new_list(builder, block, output_element_type)?;
let state_layout = Layout::Builtin(Builtin::List(&output_element_layout));
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
add_loop(builder, block, state_type, init_state, loop_body)
}
@ -1108,7 +1139,8 @@ fn call_spec(
};
let state_layout = Layout::Builtin(Builtin::Bool);
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = new_num(builder, block)?;
@ -1127,7 +1159,8 @@ fn call_spec(
};
let state_layout = Layout::Builtin(Builtin::Bool);
let state_type = layout_spec(builder, &state_layout)?;
let state_type =
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
let init_state = new_num(builder, block)?;
@ -1139,7 +1172,8 @@ fn call_spec(
// ListFindUnsafe returns { value: v, found: Bool=Int1 }
let output_layouts = vec![argument_layouts[0], Layout::Builtin(Builtin::Bool)];
let output_layout = Layout::struct_no_name_order(&output_layouts);
let output_type = layout_spec(builder, &output_layout)?;
let output_type =
layout_spec(builder, &output_layout, &WhenRecursive::Unreachable)?;
let loop_body = |builder: &mut FuncDefBuilder, block, output| {
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
@ -1201,7 +1235,7 @@ fn lowlevel_spec(
) -> Result<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 +1357,8 @@ fn lowlevel_spec(
}
DictEmpty => match layout {
Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => {
let key_id = layout_spec(builder, key_layout)?;
let value_id = layout_spec(builder, value_layout)?;
let key_id = layout_spec(builder, key_layout, &WhenRecursive::Unreachable)?;
let value_id = layout_spec(builder, value_layout, &WhenRecursive::Unreachable)?;
new_dict(builder, block, key_id, value_id)
}
_ => unreachable!("empty array does not have a list layout"),
@ -1367,7 +1401,7 @@ fn lowlevel_spec(
// TODO overly pessimstic
let arguments: Vec<_> = arguments.iter().map(|symbol| env.symbols[symbol]).collect();
let result_type = layout_spec(builder, layout)?;
let result_type = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
builder.add_unknown_with(block, &arguments, result_type)
}
@ -1478,7 +1512,8 @@ fn expr_spec<'a>(
let value_id = match tag_layout {
UnionLayout::NonRecursive(tags) => {
let variant_types = non_recursive_variant_types(builder, tags)?;
let variant_types =
non_recursive_variant_types(builder, tags, &WhenRecursive::Unreachable)?;
let value_id = build_tuple_value(builder, env, block, arguments)?;
return builder.add_make_union(block, &variant_types, *tag_id as u32, value_id);
}
@ -1592,7 +1627,7 @@ fn expr_spec<'a>(
builder.add_get_tuple_field(block, value_id, *index as u32)
}
Array { elem_layout, elems } => {
let type_id = layout_spec(builder, elem_layout)?;
let type_id = layout_spec(builder, elem_layout, &WhenRecursive::Unreachable)?;
let list = new_list(builder, block, type_id)?;
@ -1619,19 +1654,19 @@ fn expr_spec<'a>(
EmptyArray => match layout {
Layout::Builtin(Builtin::List(element_layout)) => {
let type_id = layout_spec(builder, element_layout)?;
let type_id = layout_spec(builder, element_layout, &WhenRecursive::Unreachable)?;
new_list(builder, block, type_id)
}
_ => unreachable!("empty array does not have a list layout"),
},
Reset { symbol, .. } => {
let type_id = layout_spec(builder, layout)?;
let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
let value_id = env.symbols[symbol];
builder.add_unknown_with(block, &[value_id], type_id)
}
RuntimeErrorFunction(_) => {
let type_id = layout_spec(builder, layout)?;
let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
builder.add_terminate(block, type_id)
}
@ -1658,18 +1693,24 @@ fn literal_spec(
}
}
fn layout_spec(builder: &mut impl TypeContext, layout: &Layout) -> Result<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 +1742,7 @@ fn layout_spec_help(
builder.add_tuple_type(&[])
}
UnionLayout::NonRecursive(tags) => {
let variant_types = non_recursive_variant_types(builder, tags)?;
let variant_types = non_recursive_variant_types(builder, tags, when_recursive)?;
builder.add_union_type(&variant_types)
}
UnionLayout::Recursive(_)

View File

@ -30,7 +30,7 @@ bumpalo = { version = "3.8.0", features = ["collections"] }
libloading = "0.7.1"
tempfile = "3.2.0"
inkwell = { path = "../../vendor/inkwell", optional = true }
target-lexicon = "0.12.2"
target-lexicon = "0.12.3"
[target.'cfg(target_os = "macos")'.dependencies]
serde_json = "1.0.69"

View File

@ -1072,7 +1072,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");
@ -1083,9 +1083,8 @@ pub fn module_to_dylib(
// Emit the .o file using position-independent code (PIC) - needed for dylibs
let reloc = RelocMode::PIC;
let model = CodeModel::Default;
let target_machine =
target::target_machine(target, convert_opt_level(opt_level), reloc, model).unwrap();
target::target_machine(target, convert_opt_level(opt_level), reloc).unwrap();
target_machine
.write_to_file(module, FileType::Object, &app_o_file)
@ -1105,6 +1104,21 @@ pub fn module_to_dylib(
// Load the dylib
let path = dylib_path.as_path().to_str().unwrap();
if matches!(target.architecture, Architecture::Aarch64(_)) {
// On AArch64 darwin machines, calling `ldopen` on Roc-generated libs from multiple threads
// sometimes fails with
// cannot dlopen until fork() handlers have completed
// This may be due to codesigning. In any case, spinning until we are able to dlopen seems
// to be okay.
loop {
match unsafe { Library::new(path) } {
Ok(lib) => return Ok(lib),
Err(Error::DlOpen { .. }) => continue,
Err(other) => return Err(other),
}
}
}
unsafe { Library::new(path) }
}

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

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

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");
@ -161,6 +162,32 @@ comptime {
exportExpectFn(expect.deinitFailuresC, "deinit_failures");
@export(utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak });
if (builtin.target.cpu.arch == .aarch64) {
@export(__roc_force_setjmp, .{ .name = "__roc_force_setjmp", .linkage = .Weak });
@export(__roc_force_longjmp, .{ .name = "__roc_force_longjmp", .linkage = .Weak });
}
}
// Utils continued - SJLJ
// For tests (in particular test_gen), roc_panic is implemented in terms of
// setjmp/longjmp. LLVM is unable to generate code for longjmp on AArch64 (https://github.com/rtfeldman/roc/issues/2965),
// so instead we ask Zig to please provide implementations for us, which is does
// (seemingly via musl).
pub usingnamespace @import("std").c.builtins;
pub extern fn setjmp([*c]c_int) c_int;
pub extern fn longjmp([*c]c_int, c_int) noreturn;
pub extern fn _setjmp([*c]c_int) c_int;
pub extern fn _longjmp([*c]c_int, c_int) noreturn;
pub extern fn sigsetjmp([*c]c_int, c_int) c_int;
pub extern fn siglongjmp([*c]c_int, c_int) noreturn;
pub extern fn longjmperror() void;
// Zig won't expose the externs (and hence link correctly) unless we force them to be used.
fn __roc_force_setjmp(it: [*c]c_int) callconv(.C) c_int {
return setjmp(it);
}
fn __roc_force_longjmp(a0: [*c]c_int, a1: c_int) callconv(.C) noreturn {
longjmp(a0, a1);
}
// Export helpers - Must be run inside a comptime
@ -193,7 +220,6 @@ fn exportExpectFn(comptime func: anytype, comptime func_name: []const u8) void {
// Custom panic function, as builtin Zig version errors during LLVM verification
pub fn panic(message: []const u8, stacktrace: ?*std.builtin.StackTrace) noreturn {
const builtin = @import("builtin");
if (builtin.is_test) {
std.debug.print("{s}: {?}", .{ message, stacktrace });
} else {

View File

@ -69,11 +69,10 @@ xor : Bool, Bool -> Bool
##
## Structural equality works as follows:
##
## 1. Global tags are equal if they are the same tag, and also their contents (if any) are equal.
## 2. Private tags are equal if they are the same tag, in the same module, and also their contents (if any) are equal.
## 3. Records are equal if all their fields are equal.
## 4. Collections ([Str], [List], [Dict], and [Set]) are equal if they are the same length, and also all their corresponding elements are equal.
## 5. [Num] values are equal if their numbers are equal, with one exception: if both arguments to `isEq` are *NaN*, then `isEq` returns `False`. See `Num.isNaN` for more about *NaN*.
## 1. Tags are equal if they have the same tag name, and also their contents (if any) are equal.
## 2. Records are equal if all their fields are equal.
## 3. Collections ([Str], [List], [Dict], and [Set]) are equal if they are the same length, and also all their corresponding elements are equal.
## 4. [Num] values are equal if their numbers are equal, with one exception: if both arguments to `isEq` are *NaN*, then `isEq` returns `False`. See `Num.isNaN` for more about *NaN*.
##
## Note that `isEq` takes `'val` instead of `val`, which means `isEq` does not
## accept arguments whose types contain functions.

View File

@ -93,7 +93,7 @@ interface Dict
##
## The [Dict.hasSameContents] function gives an alternative to `==` which ignores ordering
## and returns `True` if both dictionaries have the same keys and associated values.
Dict k v : [ @Dict k v ] # TODO k should require a hashing and equating constraint
Dict k v := [ Dict k v ] # TODO k should require a hashing and equating constraint
## An empty dictionary.
empty : Dict * *

View File

@ -187,7 +187,7 @@ interface List
## * Even when copying is faster, other list operations may still be slightly slower with persistent data structures. For example, even if it were a persistent data structure, [List.map], [List.walk], and [List.keepIf] would all need to traverse every element in the list and build up the result from scratch. These operations are all
## * Roc's compiler optimizes many list operations into in-place mutations behind the scenes, depending on how the list is being used. For example, [List.map], [List.keepIf], and [List.set] can all be optimized to perform in-place mutations.
## * If possible, it is usually best for performance to use large lists in a way where the optimizer can turn them into in-place mutations. If this is not possible, a persistent data structure might be faster - but this is a rare enough scenario that it would not be good for the average Roc program's performance if this were the way [List] worked by default. Instead, you can look outside Roc's standard modules for an implementation of a persistent data structure - likely built using [List] under the hood!
List elem : [ @List elem ]
List elem := [ List elem ]
## Initialize

View File

@ -47,7 +47,7 @@ interface Num
compare,
cos,
div,
divFloor,
divTrunc,
floor,
intCast,
isEven,
@ -61,6 +61,7 @@ interface Num
isPositive,
isZero,
log,
logChecked,
maxFloat,
maxI8,
maxU8,
@ -81,8 +82,6 @@ interface Num
minI64,
minU64,
minI128,
modInt,
modFloat,
mul,
mulChecked,
mulWrap,
@ -90,6 +89,7 @@ interface Num
pow,
powInt,
rem,
remChecked,
round,
shiftLeftBy,
shiftRightBy,
@ -99,6 +99,7 @@ interface Num
subChecked,
subWrap,
sqrt,
sqrtChecked,
tan,
toI8,
toI8Checked,
@ -188,7 +189,7 @@ interface Num
##
## In practice, these are rarely needed. It's most common to write
## number literals without any suffix.
Num a : [ @Num a ]
Num a := a
## A decimal number.
##
@ -222,7 +223,7 @@ Num a : [ @Num a ]
## [Dec] typically takes slightly less time than [F64] to perform addition and
## subtraction, but 10-20 times longer to perform multiplication and division.
## [sqrt] and trigonometry are massively slower with [Dec] than with [F64].
Dec : Float [ @Decimal128 ]
Dec : Num (FloatingPoint Decimal)
## A fixed-size number with a fractional component.
##
@ -291,7 +292,7 @@ Dec : Float [ @Decimal128 ]
## loops and conditionals. If you need to do performance-critical trigonometry
## or square roots, either [F64] or [F32] is probably a better choice than the
## usual default choice of [Dec], despite the precision problems they bring.
Float a : Num [ @Fraction a ]
Float range : Num (FloatingPoint range)
## A fixed-size integer - that is, a number with no fractional component.
##
@ -342,19 +343,19 @@ Float a : Num [ @Fraction a ]
## * Start by deciding if this integer should allow negative numbers, and choose signed or unsigned accordingly.
## * Next, think about the range of numbers you expect this number to hold. Choose the smallest size you will never expect to overflow, no matter the inputs your program receives. (Validating inputs for size, and presenting the user with an error if they are too big, can help guard against overflow.)
## * Finally, if a particular numeric calculation is running too slowly, you can try experimenting with other number sizes. This rarely makes a meaningful difference, but some processors can operate on different number sizes at different speeds.
Int size : Num [ @Integer size ]
Int range : Num (Integer range)
## A signed 8-bit integer, ranging from -128 to 127
I8 : Int [ @Signed8 ]
U8 : Int [ @Unsigned8 ]
I16 : Int [ @Signed16 ]
U16 : Int [ @Unsigned16 ]
I32 : Int [ @Signed32 ]
U32 : Int [ @Unsigned32 ]
I64 : Int [ @Signed64 ]
U64 : Int [ @Unsigned64 ]
I128 : Int [ @Signed128 ]
U128 : Int [ @Unsigned128 ]
I8 : Int Signed8
U8 : Int Unsigned8
I16 : Int Signed16
U16 : Int Unsigned16
I32 : Int Signed32
U32 : Int Unsigned32
I64 : Int Signed64
U64 : Int Unsigned64
I128 : Int Signed128
U128 : Int Unsigned128
## A [natural number](https://en.wikipedia.org/wiki/Natural_number) represented
## as a 64-bit unsigned integer on 64-bit systems, a 32-bit unsigned integer
@ -366,7 +367,7 @@ U128 : Int [ @Unsigned128 ]
## a [List] can hold on a 64-bit system fits in a 64-bit unsigned integer, and
## on a 32-bit system it fits in 32-bit unsigned integer. This makes [Nat] a
## good fit for [List.len] regardless of system.
Nat : Int [ @Natural ]
Nat : Num (Integer Natural)
## A 64-bit signed integer. All number literals without decimal points are compatible with #Int values.
##
@ -442,7 +443,7 @@ Nat : Int [ @Natural ]
##
## As such, it's very important to design your code not to exceed these bounds!
## If you need to do math outside these bounds, consider using a larger numeric size.
Int size : Num [ @Int size ]
Int range : Num (Integer range)
## Convert
@ -781,7 +782,7 @@ toU128 : Int * -> U128
## there will be a loss of precision.
toDec : Num * -> Dec
## Divide two integers and #Num.round the resulut.
## Divide two integers, truncating the result towards zero.
##
## Division by zero is undefined in mathematics. As such, you should make
## sure never to pass zero as the denomaintor to this function!
@ -791,40 +792,31 @@ toDec : Num * -> Dec
## * In a development build, you'll get an assertion failure.
## * In an optimized build, the function will return 0.
##
## `a // b` is shorthand for `Num.divRound a b`.
## `a // b` is shorthand for `Num.divTrunc a b`.
##
## >>> 5 // 7
##
## >>> Num.divRound 5 7
## >>> Num.divTrunc 5 7
##
## >>> 8 // -3
##
## >>> Num.divRound 8 -3
## >>> Num.divTrunc 8 -3
##
## This is the same as the #// operator.
divRound : Int a, Int a -> Int a
divTrunc : Int a, Int a -> Int a
## Perform flooring modulo on two integers.
## Obtain the remainder (truncating modulo) from the division of two integers.
##
## Modulo is the same as remainder when working with positive numbers,
## but if either number is negative, then modulo works differently.
## `a % b` is shorthand for `Num.rem a b`.
##
## Additionally, flooring modulo uses [Float].floor on the result.
## >>> 5 % 7
##
## (Use [Float].mod for non-flooring modulo.)
## >>> Num.rem 5 7
##
## Return `Err DivByZero` if the second integer is zero, because division by zero is undefined in mathematics.
## >>> -8 % -3
##
## `a %% b` is shorthand for `Int.modFloor a b`.
##
## >>> 5 %% 7
##
## >>> Int.modFloor 5 7
##
## >>> -8 %% -3
##
## >>> Int.modFloor -8 -3
#modFloor : Int a, Int a -> Result (Int a) [ DivByZero ]*
## >>> Num.rem -8 -3
rem : Int a, Int a -> Int a
## Bitwise
@ -1096,31 +1088,6 @@ atan : Float a -> Float a
## >>> |> Num.div 2.0
div : Float a, Float a -> Float a
## Perform modulo on two [Float]s.
##
## Modulo is the same as remainder when working with positive numbers,
## but if either number is negative, then modulo works differently.
##
## `a % b` is shorthand for `Num.mod a b`.
##
## [Division by zero is undefined in mathematics](https://en.wikipedia.org/wiki/Division_by_zero),
## and as such, so is modulo by zero. Because of this, you should make sure never
## to pass zero for the second argument to this function!
##
## Passing [mod] a [Dec] value of zero for its second argument will cause a panic.
## Passing [mod] a [F32] and [F64] value for its second argument will cause it
## to return [*NaN*](Num.isNaN).
##
## >>> 5.0 % 7.0
##
## >>> Num.mod 5 7
##
## `Num.mod` can be convenient in pipelines.
##
## >>> Num.pi
## >>> |> Num.mod 2.0
mod : Float a, Float a -> Float a
## Raises a [Float] to the power of another [Float].
##
## `
@ -1316,7 +1283,7 @@ isInfinite : Float * -> Bool
##
## >>> Num.isNaN 12.3
##
## >>> Num.isNaN (Num.sqrt -2)
## >>> Num.isNaN (Num.pow -1 0.5)
##
## *NaN* is unusual from other numberic values in that:
## * *NaN* is not equal to any other number, even itself. [Bool.isEq] always returns `False` if either argument is *NaN*.

View File

@ -13,7 +13,7 @@ interface Result
## The result of an operation that could fail: either the operation went
## okay, or else there was an error of some sort.
Result ok err : [ @Result ok err ]
Result ok err : [ Ok ok, Err err ]
## Return True if the result indicates a success, else return False
##

View File

@ -18,7 +18,7 @@ interface Set
imports []
## A Set is an unordered collection of unique elements.
Set elem : [ @Set elem ]
Set elem := [ Set elem ]
## An empty set.
empty : Set *

View File

@ -116,7 +116,7 @@ interface Str
## It has many more tools than this module does!
## A [Unicode](https://unicode.org) text value.
Str : [ @Str ]
Str := [ Str ]
## Convert

View File

@ -1,6 +1,6 @@
interface Dict
exposes
[
exposes
[
empty,
single,
get,
@ -15,7 +15,10 @@ interface Dict
intersection,
difference,
]
imports [ ]
imports
[
Bool.{ Bool }
]
empty : Dict k v
single : k, v -> Dict k v

View File

@ -1,60 +1,72 @@
isEmpty,
get,
set,
replace,
append,
map,
len,
walkBackwards,
concat,
first,
single,
repeat,
reverse,
prepend,
join,
keepIf,
contains,
sum,
walk,
last,
keepOks,
keepErrs,
mapWithIndex,
map2,
map3,
product,
walkUntil,
range,
sortWith,
drop,
swap,
dropAt,
dropLast,
min,
max,
map4,
dropFirst,
joinMap,
any,
takeFirst,
takeLast,
find,
sublist,
intersperse,
split,
all,
dropIf,
sortAsc,
sortDesc,
interface List
exposes
[
isEmpty,
get,
set,
replace,
append,
map,
len,
walkBackwards,
concat,
first,
single,
repeat,
reverse,
prepend,
join,
keepIf,
contains,
sum,
walk,
last,
keepOks,
keepErrs,
mapWithIndex,
map2,
map3,
product,
walkUntil,
range,
sortWith,
drop,
swap,
dropAt,
dropLast,
min,
max,
map4,
dropFirst,
joinMap,
any,
takeFirst,
takeLast,
find,
sublist,
intersperse,
split,
all,
dropIf,
sortAsc,
sortDesc,
]
imports
[
Bool.{ Bool }
]
isEmpty : List a -> Bool
isEmpty = \list ->
List.len list == 0
get : List a, Nat -> Result a [ OutOfBounds ]*
set : List a, Nat, a -> List a
replace : List a, Nat, a -> { list : List a, value : a }
set : List a, Nat, a -> List a
set = \list, index, value ->
(List.replace list index value).list
append : List a, a -> List a
prepend : List a, a -> List a
len : List a -> Nat
@ -70,11 +82,11 @@ walkBackwards : List elem, state, (state, elem -> state) -> state
walkUntil : List elem, state, (state, elem -> [ Continue state, Stop state ]) -> state
sum : List (Num a) -> Num a
sum = \list ->
sum = \list ->
List.walk list 0 Num.add
product : List (Num a) -> Num a
product = \list ->
product = \list ->
List.walk list 1 Num.mul
any : List a, (a -> Bool) -> Bool
@ -82,6 +94,8 @@ all : List a, (a -> Bool) -> Bool
keepIf : List a, (a -> Bool) -> List a
dropIf : List a, (a -> Bool) -> List a
dropIf = \list, predicate ->
List.keepIf list (\e -> Bool.not (predicate e))
keepOks : List before, (before -> Result after *) -> List after
keepErrs: List before, (before -> Result * after) -> List after
@ -89,7 +103,7 @@ map : List a, (a -> b) -> List b
map2 : List a, List b, (a, b -> c) -> List c
map3 : List a, List b, List c, (a, b, c -> d) -> List d
map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e
mapWithIndex : List a, (a -> b) -> List b
mapWithIndex : List a, (a, Nat -> b) -> List b
range : Int a, Int a -> List (Int a)
sortWith : List a, (a, a -> [ LT, EQ, GT ] ) -> List a
sortAsc : List (Num a) -> List (Num a)
@ -112,9 +126,47 @@ drop : List elem, Nat -> List elem
dropAt : List elem, Nat -> List elem
min : List (Num a) -> Result (Num a) [ ListWasEmpty ]*
min = \list ->
when List.first list is
Ok initial ->
Ok (minHelp list initial)
Err ListWasEmpty ->
Err ListWasEmpty
minHelp : List (Num a), Num a -> Num a
minHelp = \list, initial ->
List.walk list initial \bestSoFar, current ->
if current < bestSoFar then
current
else
bestSoFar
max : List (Num a) -> Result (Num a) [ ListWasEmpty ]*
max = \list ->
when List.first list is
Ok initial ->
Ok (maxHelp list initial)
Err ListWasEmpty ->
Err ListWasEmpty
maxHelp : List (Num a), Num a -> Num a
maxHelp = \list, initial ->
List.walk list initial \bestSoFar, current ->
if current > bestSoFar then
current
else
bestSoFar
joinMap : List a, (a -> List b) -> List b
joinMap = \list, mapper ->
List.walk list [] (\state, elem -> List.concat state (mapper elem))
find : List elem, (elem -> Bool) -> Result elem [ NotFound ]*
sublist : List elem, { start : Nat, len : Nat } -> List elem
intersperse : List elem, elem -> List elem

View File

@ -44,8 +44,6 @@ interface Num
Binary32,
Binary64,
maxFloat,
minFloat,
abs,
neg,
add,
@ -68,12 +66,13 @@ interface Num
isPositive,
isNegative,
rem,
remChecked,
div,
divChecked,
modInt,
modFloat,
sqrt,
sqrtChecked,
log,
logChecked,
round,
ceiling,
floor,
@ -99,8 +98,8 @@ interface Num
bytesToU32,
divCeil,
divCeilChecked,
divFloor,
divFloorChecked,
divTrunc,
divTruncChecked,
toStr,
isMultipleOf,
minI8,
@ -123,6 +122,10 @@ interface Num
maxI128,
minU128,
maxU128,
minF32,
maxF32,
minF64,
maxF64,
toI8,
toI8Checked,
toI16,
@ -143,28 +146,37 @@ interface Num
toU64Checked,
toU128,
toU128Checked,
toNat,
toNatChecked,
toF32,
toF32Checked,
toF64,
toF64Checked,
]
imports
[
Bool.{ Bool }
]
imports [ ]
Num range : [ @Num range ]
Num range := range
Int range : Num (Integer range)
Float range : Num (FloatingPoint range)
Signed128 : [ @Signed128 ]
Signed64 : [ @Signed64 ]
Signed32 : [ @Signed32 ]
Signed16 : [ @Signed16 ]
Signed8 : [ @Signed8 ]
Signed128 := []
Signed64 := []
Signed32 := []
Signed16 := []
Signed8 := []
Unsigned128 : [ @Unsigned128 ]
Unsigned64 : [ @Unsigned64 ]
Unsigned32 : [ @Unsigned32 ]
Unsigned16 : [ @Unsigned16 ]
Unsigned8 : [ @Unsigned8 ]
Unsigned128 := []
Unsigned64 := []
Unsigned32 := []
Unsigned16 := []
Unsigned8 := []
Natural : [ @Natural ]
Natural := []
Integer range : [ @Integer range ]
Integer range := range
I128 : Num (Integer Signed128)
I64 : Num (Integer Signed64)
@ -180,11 +192,11 @@ U8 : Num (Integer Unsigned8)
Nat : Num (Integer Natural)
Decimal : [ @Decimal ]
Binary64 : [ @Binary64 ]
Binary32 : [ @Binary32 ]
Decimal := []
Binary64 := []
Binary32 := []
FloatingPoint range : [ @FloatingPoint range ]
FloatingPoint range := range
F64 : Num (FloatingPoint Binary64)
F32 : Num (FloatingPoint Binary32)
@ -230,19 +242,22 @@ asin : Float a -> Float a
acos : Float a -> Float a
atan : Float a -> Float a
sqrt : Float a -> Result (Float a) [ SqrtOfNegative ]*
log : Float a -> Result (Float a) [ LogNeedsPositive ]*
sqrt : Float a -> Float a
sqrtChecked : Float a -> Result (Float a) [ SqrtOfNegative ]*
log : Float a -> Float a
logChecked : Float a -> Result (Float a) [ LogNeedsPositive ]*
div : Float a, Float a -> Float a
divChecked : Float a, Float a -> Result (Float a) [ DivByZero ]*
divCeil : Int a, Int a -> Int a
divCeilChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
divFloor : Int a, Int a -> Int a
divFloorChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
# mod : Float a, Float a -> Result (Float a) [ DivByZero ]*
rem : Int a, Int a -> Result (Int a) [ DivByZero ]*
# mod : Int a, Int a -> Result (Int a) [ DivByZero ]*
divTrunc : Int a, Int a -> Int a
divTruncChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
rem : Int a, Int a -> Int a
remChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
isMultipleOf : Int a, Int a -> Bool
bitwiseAnd : Int a, Int a -> Int a
@ -331,6 +346,18 @@ minU128 = 0
maxU128 : U128
maxU128 = 0340282366920938463463374607431768211455
minF32 : F32
minF32 = -3.40282347e38
maxF32 : F32
maxF32 = 3.40282347e38
minF64 : F64
minF64 = -1.7976931348623157e308
maxF64 : F64
maxF64 = 1.7976931348623157e308
toI8 : Int * -> I8
toI16 : Int * -> I16
toI32 : Int * -> I32
@ -341,6 +368,7 @@ toU16 : Int * -> U16
toU32 : Int * -> U32
toU64 : Int * -> U64
toU128 : Int * -> U128
toNat : Int * -> Nat
toF32 : Num * -> F32
toF64 : Num * -> F64
@ -355,5 +383,6 @@ toU16Checked : Int * -> Result U16 [ OutOfBounds ]*
toU32Checked : Int * -> Result U32 [ OutOfBounds ]*
toU64Checked : Int * -> Result U64 [ OutOfBounds ]*
toU128Checked : Int * -> Result U128 [ OutOfBounds ]*
toNatChecked : Int * -> Result Nat [ OutOfBounds ]*
toF32Checked : Num * -> Result F32 [ OutOfBounds ]*
toF64Checked : Num * -> Result F64 [ OutOfBounds ]*

View File

@ -1,6 +1,6 @@
interface Result
exposes [ Result, isOk, isErr, map, mapErr, after, withDefault ]
imports [ ]
imports [ Bool.{ Bool } ]
Result ok err : [ Ok ok, Err err ]

View File

@ -1,6 +1,6 @@
interface Dict
exposes
[
interface Set
exposes
[
empty,
single,
walk,
@ -14,7 +14,7 @@ interface Dict
intersection,
difference,
]
imports [ ]
imports [ List, Bool.{ Bool }, Dict.{ values } ]
empty : Set k
single : k -> Set k
@ -35,4 +35,4 @@ toDict : Set k -> Dict k {}
walk : Set k, state, (state, k -> state) -> state
walk = \set, state, step ->
Dict.walk (toDict set) state (\s, k, _ -> step s k)
Dict.walk (Set.toDict set) state (\s, k, _ -> step s k)

View File

@ -34,7 +34,7 @@ interface Str
toU8,
toI8,
]
imports [ ]
imports [ Bool.{ Bool }, Result.{ Result } ]

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,
}
}
@ -379,6 +365,9 @@ pub const UTILS_EXPECT_FAILED: &str = "roc_builtins.expect.expect_failed";
pub const UTILS_GET_EXPECT_FAILURES: &str = "roc_builtins.expect.get_expect_failures";
pub const UTILS_DEINIT_FAILURES: &str = "roc_builtins.expect.deinit_failures";
pub const UTILS_LONGJMP: &str = "longjmp";
pub const UTILS_SETJMP: &str = "setjmp";
#[derive(Debug, Default)]
pub struct IntToIntrinsicName {
pub options: [IntrinsicName; 10],

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,20 +312,20 @@ 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),
);
// divFloor : Int a, Int a -> Int a
// divTrunc : Int a, Int a -> Int a
add_top_level_function_type!(
Symbol::NUM_DIV_FLOOR,
Symbol::NUM_DIV_TRUNC,
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(int_type(flex(TVAR1)))
);
// divFloorChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
// divTruncChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
add_top_level_function_type!(
Symbol::NUM_DIV_FLOOR_CHECKED,
Symbol::NUM_DIV_TRUNC_CHECKED,
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())),
);
@ -393,16 +393,16 @@ pub fn types() -> MutMap<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())),
);
@ -476,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),
);
@ -551,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),
);
@ -680,36 +680,43 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
add_top_level_function_type!(
Symbol::NUM_DIV_FLOAT_CHECKED,
vec![float_type(flex(TVAR1)), float_type(flex(TVAR1))],
Box::new(result_type(float_type(flex(TVAR1)), div_by_zero.clone())),
);
// mod : Float a, Float a -> Result (Float a) [ DivByZero ]*
add_top_level_function_type!(
Symbol::NUM_MOD_FLOAT,
vec![float_type(flex(TVAR1)), float_type(flex(TVAR1))],
Box::new(result_type(float_type(flex(TVAR1)), div_by_zero)),
);
// sqrt : Float a -> Float a
add_top_level_function_type!(
Symbol::NUM_SQRT,
vec![float_type(flex(TVAR1))],
Box::new(float_type(flex(TVAR1))),
);
// sqrtChecked : Float a -> Result (Float a) [ SqrtOfNegative ]*
let sqrt_of_negative = SolvedType::TagUnion(
vec![(TagName::Global("SqrtOfNegative".into()), vec![])],
vec![(TagName::Tag("SqrtOfNegative".into()), vec![])],
Box::new(SolvedType::Wildcard),
);
add_top_level_function_type!(
Symbol::NUM_SQRT,
Symbol::NUM_SQRT_CHECKED,
vec![float_type(flex(TVAR1))],
Box::new(result_type(float_type(flex(TVAR1)), sqrt_of_negative)),
);
// log : Float a -> Float a
add_top_level_function_type!(
Symbol::NUM_LOG,
vec![float_type(flex(TVAR1))],
Box::new(float_type(flex(TVAR1))),
);
// logChecked : Float a -> Result (Float a) [ LogNeedsPositive ]*
let log_needs_positive = SolvedType::TagUnion(
vec![(TagName::Global("LogNeedsPositive".into()), vec![])],
vec![(TagName::Tag("LogNeedsPositive".into()), vec![])],
Box::new(SolvedType::Wildcard),
);
add_top_level_function_type!(
Symbol::NUM_LOG,
Symbol::NUM_LOG_CHECKED,
vec![float_type(flex(TVAR1))],
Box::new(result_type(float_type(flex(TVAR1)), log_needs_positive)),
);
@ -800,7 +807,7 @@ pub fn types() -> MutMap<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!(
@ -813,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!(
@ -935,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),
@ -948,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),
);
@ -992,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),
)
};
@ -1099,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),
);
@ -1111,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),
);
@ -1216,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),
)
@ -1578,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);
@ -1620,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

@ -16,6 +16,7 @@ pub struct MemberVariables {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AbilityMemberData {
pub parent_ability: Symbol,
pub signature_var: Variable,
pub signature: Type,
pub variables: MemberVariables,
pub region: Region,
@ -60,15 +61,16 @@ impl AbilitiesStore {
pub fn register_ability(
&mut self,
ability: Symbol,
members: Vec<(Symbol, Region, Type, MemberVariables)>,
members: Vec<(Symbol, Region, Variable, Type, MemberVariables)>,
) {
let mut members_vec = Vec::with_capacity(members.len());
for (member, region, signature, variables) in members.into_iter() {
for (member, region, signature_var, signature, variables) in members.into_iter() {
members_vec.push(member);
let old_member = self.ability_members.insert(
member,
AbilityMemberData {
parent_ability: ability,
signature_var,
signature,
region,
variables,
@ -83,6 +85,10 @@ impl AbilitiesStore {
);
}
pub fn is_ability(&self, ability: Symbol) -> bool {
self.members_of_ability.contains_key(&ability)
}
/// Records a specialization of `ability_member` with specialized type `implementing_type`.
/// Entries via this function are considered a source of truth. It must be ensured that a
/// specialization is validated before being registered here.

View File

@ -1,6 +1,6 @@
use crate::env::Env;
use crate::scope::Scope;
use roc_collections::all::{ImMap, MutMap, MutSet, SendMap};
use roc_collections::{ImMap, MutSet, SendMap, VecMap, VecSet};
use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_parse::ast::{AssignedField, ExtractSpaces, Pattern, Tag, TypeAnnotation, TypeHeader};
@ -8,14 +8,15 @@ use roc_problem::can::ShadowKind;
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::{
Alias, AliasCommon, AliasKind, LambdaSet, Problem, RecordField, Type, TypeExtension,
name_type_var, Alias, AliasCommon, AliasKind, LambdaSet, Problem, RecordField, Type,
TypeExtension,
};
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug)]
pub struct Annotation {
pub typ: Type,
pub introduced_variables: IntroducedVariables,
pub references: MutSet<Symbol>,
pub references: VecSet<Symbol>,
pub aliases: SendMap<Symbol, Alias>,
}
@ -32,6 +33,20 @@ impl<'a> NamedOrAbleVariable<'a> {
NamedOrAbleVariable::Able(av) => av.first_seen,
}
}
pub fn name(&self) -> &Lowercase {
match self {
NamedOrAbleVariable::Named(nv) => &nv.name,
NamedOrAbleVariable::Able(av) => &av.name,
}
}
pub fn variable(&self) -> Variable {
match self {
NamedOrAbleVariable::Named(nv) => nv.variable,
NamedOrAbleVariable::Able(av) => av.variable,
}
}
}
/// A named type variable, not bound to an ability.
@ -53,14 +68,14 @@ pub struct AbleVariable {
pub first_seen: Region,
}
#[derive(Clone, Debug, PartialEq, Default)]
#[derive(Clone, Debug, Default)]
pub struct IntroducedVariables {
pub wildcards: Vec<Loc<Variable>>,
pub lambda_sets: Vec<Variable>,
pub inferred: Vec<Loc<Variable>>,
pub named: Vec<NamedVariable>,
pub able: Vec<AbleVariable>,
pub host_exposed_aliases: MutMap<Symbol, Variable>,
pub named: VecSet<NamedVariable>,
pub able: VecSet<AbleVariable>,
pub host_exposed_aliases: VecMap<Symbol, Variable>,
}
impl IntroducedVariables {
@ -84,7 +99,7 @@ impl IntroducedVariables {
first_seen: var.region,
};
self.named.push(named_variable);
self.named.insert(named_variable);
}
pub fn insert_able(&mut self, name: Lowercase, var: Loc<Variable>, ability: Symbol) {
@ -97,7 +112,7 @@ impl IntroducedVariables {
first_seen: var.region,
};
self.able.push(able_variable);
self.able.insert(able_variable);
}
pub fn insert_wildcard(&mut self, var: Loc<Variable>) {
@ -125,15 +140,10 @@ impl IntroducedVariables {
self.lambda_sets.extend(other.lambda_sets.iter().copied());
self.inferred.extend(other.inferred.iter().copied());
self.host_exposed_aliases
.extend(other.host_exposed_aliases.clone());
.extend(other.host_exposed_aliases.iter().map(|(k, v)| (*k, *v)));
self.named.extend(other.named.iter().cloned());
self.named.sort();
self.named.dedup();
self.able.extend(other.able.iter().cloned());
self.able.sort();
self.able.dedup();
}
pub fn union_owned(&mut self, other: Self) {
@ -143,8 +153,7 @@ impl IntroducedVariables {
self.host_exposed_aliases.extend(other.host_exposed_aliases);
self.named.extend(other.named);
self.named.sort();
self.named.dedup();
self.able.extend(other.able.iter().cloned());
}
pub fn var_by_name(&self, name: &Lowercase) -> Option<Variable> {
@ -154,19 +163,13 @@ impl IntroducedVariables {
.map(|(_, var)| var)
}
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> {
if let Some(nav) = self
.named
.iter()
.find(|nv| &nv.name == name)
.map(NamedOrAbleVariable::Named)
{
return Some(nav);
}
self.able
.iter()
.find(|av| &av.name == name)
.map(NamedOrAbleVariable::Able)
self.iter_named().find(|v| v.name() == name)
}
pub fn collect_able(&self) -> Vec<Variable> {
@ -193,37 +196,8 @@ fn malformed(env: &mut Env, region: Region, name: &str) {
env.problem(roc_problem::can::Problem::RuntimeError(problem));
}
/// Canonicalizes a top-level type annotation.
pub fn canonicalize_annotation(
env: &mut Env,
scope: &mut Scope,
annotation: &roc_parse::ast::TypeAnnotation,
region: Region,
var_store: &mut VarStore,
) -> Annotation {
let mut introduced_variables = IntroducedVariables::default();
let mut references = MutSet::default();
let mut aliases = SendMap::default();
let typ = can_annotation_help(
env,
annotation,
region,
scope,
var_store,
&mut introduced_variables,
&mut aliases,
&mut references,
);
Annotation {
typ,
introduced_variables,
references,
aliases,
}
}
pub fn canonicalize_annotation_with_possible_clauses(
env: &mut Env,
scope: &mut Scope,
annotation: &TypeAnnotation,
@ -232,7 +206,7 @@ pub fn canonicalize_annotation_with_possible_clauses(
abilities_in_scope: &[Symbol],
) -> Annotation {
let mut introduced_variables = IntroducedVariables::default();
let mut references = MutSet::default();
let mut references = VecSet::default();
let mut aliases = SendMap::default();
let (annotation, region) = match annotation {
@ -303,7 +277,7 @@ fn make_apply_symbol(
}
}
} else {
match env.qualified_lookup(module_name, ident, region) {
match env.qualified_lookup(scope, module_name, ident, region) {
Ok(symbol) => Ok(symbol),
Err(problem) => {
// Either the module wasn't imported, or
@ -391,7 +365,7 @@ pub fn find_type_def_symbols(
while let Some(tag) = inner_stack.pop() {
match tag {
Tag::Global { args, .. } | Tag::Private { args, .. } => {
Tag::Apply { args, .. } => {
for t in args.iter() {
stack.push(&t.value);
}
@ -424,6 +398,13 @@ pub fn find_type_def_symbols(
result
}
fn find_fresh_var_name(introduced_variables: &IntroducedVariables) -> Lowercase {
name_type_var(0, &mut introduced_variables.iter_named(), |var, str| {
var.name().as_str() == str
})
.0
}
#[allow(clippy::too_many_arguments)]
fn can_annotation_help(
env: &mut Env,
@ -433,7 +414,7 @@ fn can_annotation_help(
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
local_aliases: &mut SendMap<Symbol, Alias>,
references: &mut MutSet<Symbol>,
references: &mut VecSet<Symbol>,
) -> Type {
use roc_parse::ast::TypeAnnotation::*;
@ -445,7 +426,7 @@ fn can_annotation_help(
let arg_ann = can_annotation_help(
env,
&arg.value,
region,
arg.region,
scope,
var_store,
introduced_variables,
@ -483,6 +464,21 @@ fn can_annotation_help(
references.insert(symbol);
if scope.abilities_store.is_ability(symbol) {
let fresh_ty_var = find_fresh_var_name(introduced_variables);
env.problem(roc_problem::can::Problem::AbilityUsedAsType(
fresh_ty_var.clone(),
symbol,
region,
));
// Generate an variable bound to the ability so we can keep compiling.
let var = var_store.fresh();
introduced_variables.insert_able(fresh_ty_var, Loc::at(region, var), symbol);
return Type::Variable(var);
}
for arg in *type_arguments {
let arg_ann = can_annotation_help(
env,
@ -583,12 +579,7 @@ fn can_annotation_help(
vars: loc_vars,
},
) => {
let symbol = match scope.introduce(
name.value.into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
region,
) {
let symbol = match scope.introduce(name.value.into(), region) {
Ok(symbol) => symbol,
Err((original_region, shadow, _new_symbol)) => {
@ -834,8 +825,7 @@ fn can_annotation_help(
Where(_annotation, clauses) => {
debug_assert!(!clauses.is_empty());
// Has clauses are allowed only on the top level of an ability member signature (for
// now), which we handle elsewhere.
// Has clauses are allowed only on the top level of a signature, which we handle elsewhere.
env.problem(roc_problem::can::Problem::IllegalHasClause {
region: Region::across_all(clauses.iter().map(|clause| &clause.region)),
});
@ -861,7 +851,7 @@ fn canonicalize_has_clause(
introduced_variables: &mut IntroducedVariables,
clause: &Loc<roc_parse::ast::HasClause<'_>>,
abilities_in_scope: &[Symbol],
references: &mut MutSet<Symbol>,
references: &mut VecSet<Symbol>,
) -> Result<(), Type> {
let Loc {
region,
@ -923,7 +913,7 @@ fn can_extension_type<'a>(
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
local_aliases: &mut SendMap<Symbol, Alias>,
references: &mut MutSet<Symbol>,
references: &mut VecSet<Symbol>,
opt_ext: &Option<&Loc<TypeAnnotation<'a>>>,
ext_problem_kind: roc_problem::can::ExtensionTypeKind,
) -> Type {
@ -1105,7 +1095,7 @@ fn can_assigned_fields<'a>(
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
local_aliases: &mut SendMap<Symbol, Alias>,
references: &mut MutSet<Symbol>,
references: &mut VecSet<Symbol>,
) -> SendMap<Lowercase, RecordField<Type>> {
use roc_parse::ast::AssignedField::*;
use roc_types::types::RecordField::*;
@ -1218,7 +1208,7 @@ fn can_tags<'a>(
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
local_aliases: &mut SendMap<Symbol, Alias>,
references: &mut MutSet<Symbol>,
references: &mut VecSet<Symbol>,
) -> Vec<(TagName, Vec<Type>)> {
let mut tag_types = Vec::with_capacity(tags.len());
@ -1234,7 +1224,7 @@ fn can_tags<'a>(
// a duplicate
let new_name = 'inner: loop {
match tag {
Tag::Global { name, args } => {
Tag::Apply { name, args } => {
let name = name.value.into();
let mut arg_types = Vec::with_capacity(args.len());
@ -1253,32 +1243,7 @@ fn can_tags<'a>(
arg_types.push(ann);
}
let tag_name = TagName::Global(name);
tag_types.push((tag_name.clone(), arg_types));
break 'inner tag_name;
}
Tag::Private { name, args } => {
let ident_id = env.ident_ids.get_or_insert(&name.value.into());
let symbol = Symbol::new(env.home, ident_id);
let mut arg_types = Vec::with_capacity(args.len());
for arg in args.iter() {
let ann = can_annotation_help(
env,
&arg.value,
arg.region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
);
arg_types.push(ann);
}
let tag_name = TagName::Private(symbol);
let tag_name = TagName::Tag(name);
tag_types.push((tag_name.clone(), arg_types));
break 'inner tag_name;

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)*) => (());
@ -171,6 +171,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,
@ -196,16 +197,19 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
NUM_TAN => num_tan,
NUM_DIV_FLOAT => num_div_float,
NUM_DIV_FLOAT_CHECKED => num_div_float_checked,
NUM_DIV_FLOOR => num_div_floor,
NUM_DIV_FLOOR_CHECKED => num_div_floor_checked,
NUM_DIV_TRUNC => num_div_trunc,
NUM_DIV_TRUNC_CHECKED => num_div_trunc_checked,
NUM_DIV_CEIL => num_div_ceil,
NUM_DIV_CEIL_CHECKED => num_div_ceil_checked,
NUM_ABS => num_abs,
NUM_NEG => num_neg,
NUM_REM => num_rem,
NUM_REM_CHECKED => num_rem_checked,
NUM_IS_MULTIPLE_OF => num_is_multiple_of,
NUM_SQRT => num_sqrt,
NUM_SQRT_CHECKED => num_sqrt_checked,
NUM_LOG => num_log,
NUM_LOG_CHECKED => num_log_checked,
NUM_ROUND => num_round,
NUM_IS_ODD => num_is_odd,
NUM_IS_EVEN => num_is_even,
@ -229,24 +233,6 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<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,
@ -730,6 +716,23 @@ fn bool_and(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
fn num_unaryop(symbol: Symbol, var_store: &mut VarStore, op: LowLevel) -> Def {
let num_var = var_store.fresh();
let body = RunLowLevel {
op,
args: vec![(num_var, Var(Symbol::ARG_1))],
ret_var: num_var,
};
defn(
symbol,
vec![(num_var, Symbol::ARG_1)],
var_store,
body,
num_var,
)
}
/// Num a, Num a -> Num a
fn num_binop(symbol: Symbol, var_store: &mut VarStore, op: LowLevel) -> Def {
let num_var = var_store.fresh();
@ -1169,8 +1172,13 @@ fn num_to_float(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// Num.sqrt : Float -> Result Float [ SqrtOfNegative ]*
/// Num.sqrt : Float a -> Float a
fn num_sqrt(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_unaryop(symbol, var_store, LowLevel::NumSqrtUnchecked)
}
/// Num.sqrtChecked : Float a -> Result (Float a) [ SqrtOfNegative ]*
fn num_sqrt_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh();
let float_var = var_store.fresh();
let unbound_zero_var = var_store.fresh();
@ -1218,8 +1226,13 @@ fn num_sqrt(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// Num.log : Float -> Result Float [ LogNeedsPositive ]*
/// Num.log : Float a -> Float a
fn num_log(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_unaryop(symbol, var_store, LowLevel::NumLogUnchecked)
}
/// Num.logChecked : Float a -> Result (Float a) [ LogNeedsPositive ]*
fn num_log_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh();
let float_var = var_store.fresh();
let unbound_zero_var = var_store.fresh();
@ -1473,106 +1486,6 @@ fn num_int_cast(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
/// Num.minI8: I8
fn num_min_i8(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<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();
@ -2709,8 +2622,16 @@ fn list_intersperse(symbol: Symbol, var_store: &mut VarStore) -> Def {
recursive: Recursive::NotRecursive,
captured_symbols: vec![(sep_sym, sep_var)],
arguments: vec![
(clos_acc_var, no_region(Pattern::Identifier(clos_acc_sym))),
(sep_var, no_region(Pattern::Identifier(clos_elem_sym))),
(
clos_acc_var,
AnnotatedMark::new(var_store),
no_region(Pattern::Identifier(clos_acc_sym)),
),
(
sep_var,
AnnotatedMark::new(var_store),
no_region(Pattern::Identifier(clos_elem_sym)),
),
],
loc_body: {
let append_sep = RunLowLevel {
@ -2795,9 +2716,14 @@ fn list_split(symbol: Symbol, var_store: &mut VarStore) -> Def {
arguments: vec![
(
clos_start_var,
AnnotatedMark::new(var_store),
no_region(Pattern::Identifier(clos_start_sym)),
),
(clos_len_var, no_region(Pattern::Identifier(clos_len_sym))),
(
clos_len_var,
AnnotatedMark::new(var_store),
no_region(Pattern::Identifier(clos_len_sym)),
),
],
loc_body: {
Box::new(no_region(RunLowLevel {
@ -2981,7 +2907,11 @@ fn list_drop_if(symbol: Symbol, var_store: &mut VarStore) -> Def {
name: Symbol::LIST_DROP_IF_PREDICATE,
recursive: Recursive::NotRecursive,
captured_symbols: vec![(sym_predicate, t_predicate)],
arguments: vec![(t_elem, no_region(Pattern::Identifier(Symbol::ARG_3)))],
arguments: vec![(
t_elem,
AnnotatedMark::new(var_store),
no_region(Pattern::Identifier(Symbol::ARG_3)),
)],
loc_body: {
let should_drop = Call(
Box::new((
@ -3165,8 +3095,16 @@ fn list_join_map(symbol: Symbol, var_store: &mut VarStore) -> Def {
recursive: Recursive::NotRecursive,
captured_symbols: vec![(Symbol::ARG_2, before2list_after)],
arguments: vec![
(list_after, no_region(Pattern::Identifier(Symbol::ARG_3))),
(before, no_region(Pattern::Identifier(Symbol::ARG_4))),
(
list_after,
AnnotatedMark::new(var_store),
no_region(Pattern::Identifier(Symbol::ARG_3)),
),
(
before,
AnnotatedMark::new(var_store),
no_region(Pattern::Identifier(Symbol::ARG_4)),
),
],
loc_body: {
let mapper = Box::new((
@ -3696,8 +3634,16 @@ fn list_sort_desc(symbol: Symbol, var_store: &mut VarStore) -> Def {
recursive: Recursive::NotRecursive,
captured_symbols: vec![],
arguments: vec![
(num_var, no_region(Pattern::Identifier(Symbol::ARG_2))),
(num_var, no_region(Pattern::Identifier(Symbol::ARG_3))),
(
num_var,
AnnotatedMark::new(var_store),
no_region(Pattern::Identifier(Symbol::ARG_2)),
),
(
num_var,
AnnotatedMark::new(var_store),
no_region(Pattern::Identifier(Symbol::ARG_3)),
),
],
loc_body: {
Box::new(no_region(RunLowLevel {
@ -4098,6 +4044,11 @@ fn set_from_list(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_1(symbol, LowLevel::SetFromList, var_store)
}
/// Set.toDict : Set k -> Dict k {}
fn set_to_dict(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_1(symbol, LowLevel::SetToDict, var_store)
}
/// Set.insert : Set k, k -> Set k
fn set_insert(symbol: Symbol, var_store: &mut VarStore) -> Def {
let dict_var = var_store.fresh();
@ -4166,9 +4117,21 @@ fn set_walk(symbol: Symbol, var_store: &mut VarStore) -> Def {
recursive: Recursive::NotRecursive,
captured_symbols: vec![(Symbol::ARG_3, func_var)],
arguments: vec![
(accum_var, no_region(Pattern::Identifier(Symbol::ARG_5))),
(key_var, no_region(Pattern::Identifier(Symbol::ARG_6))),
(Variable::EMPTY_RECORD, no_region(Pattern::Underscore)),
(
accum_var,
AnnotatedMark::new(var_store),
no_region(Pattern::Identifier(Symbol::ARG_5)),
),
(
key_var,
AnnotatedMark::new(var_store),
no_region(Pattern::Identifier(Symbol::ARG_6)),
),
(
Variable::EMPTY_RECORD,
AnnotatedMark::new(var_store),
no_region(Pattern::Underscore),
),
],
loc_body: Box::new(no_region(call_func)),
});
@ -4196,8 +4159,13 @@ fn set_walk(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// Num.rem : Int a, Int a -> Result (Int a) [ DivByZero ]*
/// Num.rem : Int a, Int a -> Int a
fn num_rem(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumRemUnchecked)
}
/// Num.remChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
fn num_rem_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
let num_var = var_store.fresh();
let unbound_zero_var = var_store.fresh();
let bool_var = var_store.fresh();
@ -4369,13 +4337,13 @@ fn num_div_float_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// Num.divFloor : Int a, Int a -> Int a
fn num_div_floor(symbol: Symbol, var_store: &mut VarStore) -> Def {
/// Num.divTrunc : Int a, Int a -> Int a
fn num_div_trunc(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumDivUnchecked)
}
/// Num.divFloorChecked : Int a , Int a -> Result (Int a) [ DivByZero ]*
fn num_div_floor_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
/// Num.divTruncChecked : Int a , Int a -> Result (Int a) [ DivByZero ]*
fn num_div_trunc_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh();
let num_var = var_store.fresh();
let unbound_zero_var = var_store.fresh();
@ -4739,7 +4707,7 @@ fn result_map(symbol: Symbol, var_store: &mut VarStore) -> Def {
CalledVia::Space,
);
let tag_name = TagName::Global("Ok".into());
let tag_name = TagName::Tag("Ok".into());
// ok branch
let ok = Tag {
@ -4763,6 +4731,7 @@ fn result_map(symbol: Symbol, var_store: &mut VarStore) -> Def {
patterns: vec![no_region(pattern)],
value: no_region(ok),
guard: None,
redundant: RedundantMark::new(var_store),
};
branches.push(branch);
@ -4770,7 +4739,7 @@ fn result_map(symbol: Symbol, var_store: &mut VarStore) -> Def {
{
// err branch
let tag_name = TagName::Global("Err".into());
let tag_name = TagName::Tag("Err".into());
let err = Tag {
variant_var: var_store.fresh(),
@ -4793,6 +4762,7 @@ fn result_map(symbol: Symbol, var_store: &mut VarStore) -> Def {
patterns: vec![no_region(pattern)],
value: no_region(err),
guard: None,
redundant: RedundantMark::new(var_store),
};
branches.push(branch);
@ -4804,6 +4774,8 @@ fn result_map(symbol: Symbol, var_store: &mut VarStore) -> Def {
region: Region::zero(),
loc_cond: Box::new(no_region(Var(Symbol::ARG_1))),
branches,
branches_cond_var: var_store.fresh(),
exhaustive: ExhaustiveMark::new(var_store),
};
defn(
@ -4836,7 +4808,7 @@ fn result_map_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
CalledVia::Space,
);
let tag_name = TagName::Global("Err".into());
let tag_name = TagName::Tag("Err".into());
// ok branch
let ok = Tag {
@ -4860,6 +4832,7 @@ fn result_map_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
patterns: vec![no_region(pattern)],
value: no_region(ok),
guard: None,
redundant: RedundantMark::new(var_store),
};
branches.push(branch);
@ -4867,7 +4840,7 @@ fn result_map_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
{
// err branch
let tag_name = TagName::Global("Ok".into());
let tag_name = TagName::Tag("Ok".into());
let err = Tag {
variant_var: var_store.fresh(),
@ -4890,6 +4863,7 @@ fn result_map_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
patterns: vec![no_region(pattern)],
value: no_region(err),
guard: None,
redundant: RedundantMark::new(var_store),
};
branches.push(branch);
@ -4901,6 +4875,8 @@ fn result_map_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
region: Region::zero(),
loc_cond: Box::new(no_region(Var(Symbol::ARG_1))),
branches,
branches_cond_var: var_store.fresh(),
exhaustive: ExhaustiveMark::new(var_store),
};
defn(
@ -4920,7 +4896,7 @@ fn result_with_default(symbol: Symbol, var_store: &mut VarStore) -> Def {
{
// ok branch
let tag_name = TagName::Global("Ok".into());
let tag_name = TagName::Tag("Ok".into());
let pattern = Pattern::AppliedTag {
whole_var: result_var,
@ -4933,6 +4909,7 @@ fn result_with_default(symbol: Symbol, var_store: &mut VarStore) -> Def {
patterns: vec![no_region(pattern)],
value: no_region(Var(Symbol::ARG_3)),
guard: None,
redundant: RedundantMark::new(var_store),
};
branches.push(branch);
@ -4940,7 +4917,7 @@ fn result_with_default(symbol: Symbol, var_store: &mut VarStore) -> Def {
{
// err branch
let tag_name = TagName::Global("Err".into());
let tag_name = TagName::Tag("Err".into());
let pattern = Pattern::AppliedTag {
whole_var: result_var,
@ -4953,6 +4930,7 @@ fn result_with_default(symbol: Symbol, var_store: &mut VarStore) -> Def {
patterns: vec![no_region(pattern)],
value: no_region(Var(Symbol::ARG_2)),
guard: None,
redundant: RedundantMark::new(var_store),
};
branches.push(branch);
@ -4964,6 +4942,8 @@ fn result_with_default(symbol: Symbol, var_store: &mut VarStore) -> Def {
region: Region::zero(),
loc_cond: Box::new(no_region(Var(Symbol::ARG_1))),
branches,
branches_cond_var: var_store.fresh(),
exhaustive: ExhaustiveMark::new(var_store),
};
defn(
@ -4983,7 +4963,7 @@ fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
{
// ok branch
let tag_name = TagName::Global("Ok".into());
let tag_name = TagName::Tag("Ok".into());
let pattern = Pattern::AppliedTag {
whole_var: result_var,
@ -4995,7 +4975,7 @@ fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
let false_expr = Tag {
variant_var: var_store.fresh(),
ext_var: var_store.fresh(),
name: TagName::Global("False".into()),
name: TagName::Tag("False".into()),
arguments: vec![],
};
@ -5003,6 +4983,7 @@ fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
patterns: vec![no_region(pattern)],
value: no_region(false_expr),
guard: None,
redundant: RedundantMark::new(var_store),
};
branches.push(branch);
@ -5010,7 +4991,7 @@ fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
{
// err branch
let tag_name = TagName::Global("Err".into());
let tag_name = TagName::Tag("Err".into());
let pattern = Pattern::AppliedTag {
whole_var: result_var,
@ -5022,7 +5003,7 @@ fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
let true_expr = Tag {
variant_var: var_store.fresh(),
ext_var: var_store.fresh(),
name: TagName::Global("True".into()),
name: TagName::Tag("True".into()),
arguments: vec![],
};
@ -5030,6 +5011,7 @@ fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
patterns: vec![no_region(pattern)],
value: no_region(true_expr),
guard: None,
redundant: RedundantMark::new(var_store),
};
branches.push(branch);
@ -5041,6 +5023,8 @@ fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
region: Region::zero(),
loc_cond: Box::new(no_region(Var(Symbol::ARG_1))),
branches,
branches_cond_var: var_store.fresh(),
exhaustive: ExhaustiveMark::new(var_store),
};
defn(
@ -5060,7 +5044,7 @@ fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def {
{
// ok branch
let tag_name = TagName::Global("Ok".into());
let tag_name = TagName::Tag("Ok".into());
let pattern = Pattern::AppliedTag {
whole_var: result_var,
@ -5072,7 +5056,7 @@ fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def {
let true_expr = Tag {
variant_var: var_store.fresh(),
ext_var: var_store.fresh(),
name: TagName::Global("True".into()),
name: TagName::Tag("True".into()),
arguments: vec![],
};
@ -5080,6 +5064,7 @@ fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def {
patterns: vec![no_region(pattern)],
value: no_region(true_expr),
guard: None,
redundant: RedundantMark::new(var_store),
};
branches.push(branch);
@ -5087,7 +5072,7 @@ fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def {
{
// err branch
let tag_name = TagName::Global("Err".into());
let tag_name = TagName::Tag("Err".into());
let pattern = Pattern::AppliedTag {
whole_var: result_var,
@ -5099,7 +5084,7 @@ fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def {
let false_expr = Tag {
variant_var: var_store.fresh(),
ext_var: var_store.fresh(),
name: TagName::Global("False".into()),
name: TagName::Tag("False".into()),
arguments: vec![],
};
@ -5107,6 +5092,7 @@ fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def {
patterns: vec![no_region(pattern)],
value: no_region(false_expr),
guard: None,
redundant: RedundantMark::new(var_store),
};
branches.push(branch);
@ -5118,6 +5104,8 @@ fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def {
region: Region::zero(),
loc_cond: Box::new(no_region(Var(Symbol::ARG_1))),
branches,
branches_cond_var: var_store.fresh(),
exhaustive: ExhaustiveMark::new(var_store),
};
defn(
@ -5150,7 +5138,7 @@ fn result_after(symbol: Symbol, var_store: &mut VarStore) -> Def {
CalledVia::Space,
);
let tag_name = TagName::Global("Ok".into());
let tag_name = TagName::Tag("Ok".into());
// ok branch
let ok = call_func;
@ -5169,6 +5157,7 @@ fn result_after(symbol: Symbol, var_store: &mut VarStore) -> Def {
patterns: vec![no_region(pattern)],
value: no_region(ok),
guard: None,
redundant: RedundantMark::new(var_store),
};
branches.push(branch);
@ -5176,7 +5165,7 @@ fn result_after(symbol: Symbol, var_store: &mut VarStore) -> Def {
{
// err branch
let tag_name = TagName::Global("Err".into());
let tag_name = TagName::Tag("Err".into());
let err = Tag {
variant_var: var_store.fresh(),
@ -5199,6 +5188,7 @@ fn result_after(symbol: Symbol, var_store: &mut VarStore) -> Def {
patterns: vec![no_region(pattern)],
value: no_region(err),
guard: None,
redundant: RedundantMark::new(var_store),
};
branches.push(branch);
@ -5210,6 +5200,8 @@ fn result_after(symbol: Symbol, var_store: &mut VarStore) -> Def {
region: Region::zero(),
loc_cond: Box::new(no_region(Var(Symbol::ARG_1))),
branches,
branches_cond_var: var_store.fresh(),
exhaustive: ExhaustiveMark::new(var_store),
};
defn(
@ -5234,7 +5226,7 @@ fn tag(name: &'static str, args: Vec<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)))
@ -5395,7 +5387,13 @@ fn defn_help(
let closure_args = args
.into_iter()
.map(|(var, symbol)| (var, no_region(Identifier(symbol))))
.map(|(var, symbol)| {
(
var,
AnnotatedMark::new(var_store),
no_region(Identifier(symbol)),
)
})
.collect();
Closure(ClosureData {
@ -5411,36 +5409,6 @@ fn defn_help(
})
}
#[inline(always)]
fn int_min_or_max<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.ident_ids.get_id(&ident) {
Some(ident_id) => {
let symbol = Symbol::new(module_id, ident_id);
@ -97,16 +89,20 @@ impl<'a> Env<'a> {
Ok(symbol)
}
None => Err(RuntimeError::LookupNotInScope(
Loc {
value: ident,
region,
},
self.ident_ids
.idents()
.map(|(_, string)| string.as_ref().into())
.collect(),
)),
None => {
let error = RuntimeError::LookupNotInScope(
Loc {
value: ident,
region,
},
scope
.ident_ids
.ident_strs()
.map(|(_, string)| string.into())
.collect(),
);
Err(error)
}
}
} else {
match self.dep_idents.get(&module_id) {
@ -124,11 +120,11 @@ impl<'a> Env<'a> {
}
None => {
let exposed_values = exposed_ids
.idents()
.ident_strs()
.filter(|(_, ident)| {
ident.as_ref().starts_with(|c: char| c.is_lowercase())
ident.starts_with(|c: char| c.is_lowercase())
})
.map(|(_, ident)| Lowercase::from(ident.as_ref()))
.map(|(_, ident)| Lowercase::from(ident))
.collect();
Err(RuntimeError::ValueNotExposed {
module_name,
@ -165,22 +161,7 @@ impl<'a> Env<'a> {
}
}
/// Generates a unique, new symbol like "$1" or "$5",
/// using the home module as the module_id.
///
/// This is used, for example, during canonicalization of an Expr::Closure
/// to generate a unique symbol to refer to that closure.
pub fn gen_unique_symbol(&mut self) -> Symbol {
let ident_id = self.ident_ids.gen_unique();
Symbol::new(self.home, ident_id)
}
pub fn problem(&mut self, problem: Problem) {
self.problems.push(problem)
}
pub fn register_closure(&mut self, symbol: Symbol, references: References) {
self.closures.insert(symbol, references);
}
}

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

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,
@ -163,8 +170,7 @@ pub enum Expr {
name: TagName,
},
/// A wrapping of an opaque type, like `$Age 21`
// TODO(opaques): $->@ above when opaques land
/// A wrapping of an opaque type, like `@Age 21`
OpaqueRef {
opaque_var: Variable,
name: Symbol,
@ -194,7 +200,74 @@ pub enum Expr {
// Compiles, but will crash if reached
RuntimeError(RuntimeError),
}
#[derive(Clone, Debug, PartialEq)]
impl Expr {
pub fn category(&self) -> Category {
match self {
Self::Num(..) => Category::Num,
Self::Int(..) => Category::Int,
Self::Float(..) => Category::Float,
Self::Str(..) => Category::Str,
Self::SingleQuote(..) => Category::Character,
Self::List { .. } => Category::List,
&Self::Var(sym) => Category::Lookup(sym),
Self::When { .. } => Category::When,
Self::If { .. } => Category::If,
Self::LetRec(_, expr, _) => expr.value.category(),
Self::LetNonRec(_, expr, _) => expr.value.category(),
&Self::Call(_, _, called_via) => Category::CallResult(None, called_via),
&Self::RunLowLevel { op, .. } => Category::LowLevelOpResult(op),
Self::ForeignCall { .. } => Category::ForeignCall,
Self::Closure(..) => Category::Lambda,
Self::Record { .. } => Category::Record,
Self::EmptyRecord => Category::Record,
Self::Access { field, .. } => Category::Access(field.clone()),
Self::Accessor(data) => Category::Accessor(data.field.clone()),
Self::Update { .. } => Category::Record,
Self::Tag {
name, arguments, ..
} => Category::TagApply {
tag_name: name.clone(),
args_count: arguments.len(),
},
Self::ZeroArgumentTag { name, .. } => Category::TagApply {
tag_name: name.clone(),
args_count: 0,
},
&Self::OpaqueRef { name, .. } => Category::OpaqueWrap(name),
Self::Expect(..) => Category::Expect,
Self::RuntimeError(..) => Category::Unknown,
}
}
}
/// Stores exhaustiveness-checking metadata for a closure argument that may
/// have an annotated type.
#[derive(Clone, Copy, Debug)]
pub struct AnnotatedMark {
pub annotation_var: Variable,
pub exhaustive: ExhaustiveMark,
}
impl AnnotatedMark {
pub fn new(var_store: &mut VarStore) -> Self {
Self {
annotation_var: var_store.fresh(),
exhaustive: ExhaustiveMark::new(var_store),
}
}
// NOTE: only ever use this if you *know* a pattern match is surely exhaustive!
// Otherwise you will get unpleasant unification errors.
pub fn known_exhaustive() -> Self {
Self {
annotation_var: Variable::EMPTY_TAG_UNION,
exhaustive: ExhaustiveMark::known_exhaustive(),
}
}
}
#[derive(Clone, Debug)]
pub struct ClosureData {
pub function_type: Variable,
pub closure_type: Variable,
@ -203,7 +276,7 @@ pub struct ClosureData {
pub name: Symbol,
pub captured_symbols: Vec<(Symbol, Variable)>,
pub recursive: Recursive,
pub arguments: Vec<(Variable, Loc<Pattern>)>,
pub arguments: Vec<(Variable, AnnotatedMark, Loc<Pattern>)>,
pub loc_body: Box<Loc<Expr>>,
}
@ -255,7 +328,11 @@ impl AccessorData {
let loc_body = Loc::at_zero(body);
let arguments = vec![(record_var, Loc::at_zero(Pattern::Identifier(record_symbol)))];
let arguments = vec![(
record_var,
AnnotatedMark::known_exhaustive(),
Loc::at_zero(Pattern::Identifier(record_symbol)),
)];
ClosureData {
function_type: function_var,
@ -271,7 +348,7 @@ impl AccessorData {
}
}
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug)]
pub struct Field {
pub var: Variable,
// The region of the full `foo: f bar`, rather than just `f bar`
@ -286,11 +363,30 @@ pub enum Recursive {
TailRecursive = 2,
}
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug)]
pub struct WhenBranch {
pub patterns: Vec<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>(
@ -354,7 +450,7 @@ pub fn canonicalize_expr<'a>(
if let Var(symbol) = &can_update.value {
match canonicalize_fields(env, var_store, scope, region, fields.items) {
Ok((can_fields, mut output)) => {
output.references = output.references.union(update_out.references);
output.references.union_mut(&update_out.references);
let answer = Update {
record_var: var_store.fresh(),
@ -432,7 +528,7 @@ pub fn canonicalize_expr<'a>(
let (can_expr, elem_out) =
canonicalize_expr(env, var_store, scope, loc_elem.region, &loc_elem.value);
references = references.union(elem_out.references);
references.union_mut(&elem_out.references);
can_elems.push(can_expr);
}
@ -466,7 +562,7 @@ pub fn canonicalize_expr<'a>(
canonicalize_expr(env, var_store, scope, loc_arg.region, &loc_arg.value);
args.push((var_store.fresh(), arg_expr));
output.references = output.references.union(arg_out.references);
output.references.union_mut(&arg_out.references);
}
if let ast::Expr::OpaqueRef(name) = loc_fn.value {
@ -487,8 +583,7 @@ pub fn canonicalize_expr<'a>(
}
Ok((name, opaque_def)) => {
let argument = Box::new(args.pop().unwrap());
output.references.referenced_type_defs.insert(name);
output.references.type_lookups.insert(name);
output.references.insert_type_lookup(name);
let (type_arguments, lambda_set_variables, specialized_def_type) =
freshen_opaque_def(var_store, opaque_def);
@ -518,7 +613,7 @@ pub fn canonicalize_expr<'a>(
let expr = match fn_expr.value {
Var(symbol) => {
output.references.calls.insert(symbol);
output.references.insert_call(symbol);
// we're tail-calling a symbol by name, check if it's the tail-callable symbol
output.tail_call = match &env.tailcallable_symbol {
@ -597,146 +692,19 @@ pub fn canonicalize_expr<'a>(
(RuntimeError(problem), Output::default())
}
ast::Expr::Defs(loc_defs, loc_ret) => {
can_defs_with_return(
env,
var_store,
// The body expression gets a new scope for canonicalization,
// so clone it.
scope.clone(),
loc_defs,
loc_ret,
)
// The body expression gets a new scope for canonicalization,
scope.inner_scope(|inner_scope| {
can_defs_with_return(env, var_store, inner_scope, loc_defs, loc_ret)
})
}
ast::Expr::Backpassing(_, _, _) => {
unreachable!("Backpassing should have been desugared by now")
}
ast::Expr::Closure(loc_arg_patterns, loc_body_expr) => {
// The globally unique symbol that will refer to this closure once it gets converted
// into a top-level procedure for code gen.
//
// In the Foo module, this will look something like Foo.$1 or Foo.$2.
let symbol = env
.closure_name_symbol
.unwrap_or_else(|| env.gen_unique_symbol());
env.closure_name_symbol = None;
let (closure_data, output) =
canonicalize_closure(env, var_store, scope, loc_arg_patterns, loc_body_expr, None);
// The body expression gets a new scope for canonicalization.
// Shadow `scope` to make sure we don't accidentally use the original one for the
// rest of this block, but keep the original around for later diffing.
let original_scope = scope;
let mut scope = original_scope.clone();
let mut can_args = Vec::with_capacity(loc_arg_patterns.len());
let mut output = Output::default();
let mut bound_by_argument_patterns = MutSet::default();
for loc_pattern in loc_arg_patterns.iter() {
let (new_output, can_arg) = canonicalize_pattern(
env,
var_store,
&mut scope,
FunctionArg,
&loc_pattern.value,
loc_pattern.region,
);
bound_by_argument_patterns
.extend(new_output.references.bound_symbols.iter().copied());
output.union(new_output);
can_args.push((var_store.fresh(), can_arg));
}
let (loc_body_expr, new_output) = canonicalize_expr(
env,
var_store,
&mut scope,
loc_body_expr.region,
&loc_body_expr.value,
);
let mut captured_symbols: MutSet<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.
@ -750,10 +718,18 @@ pub fn canonicalize_expr<'a>(
let mut can_branches = Vec::with_capacity(branches.len());
for branch in branches.iter() {
let (can_when_branch, branch_references) =
canonicalize_when_branch(env, var_store, scope, region, *branch, &mut output);
let (can_when_branch, branch_references) = scope.inner_scope(|inner_scope| {
canonicalize_when_branch(
env,
var_store,
inner_scope,
region,
*branch,
&mut output,
)
});
output.references = output.references.union(branch_references);
output.references.union_mut(&branch_references);
can_branches.push(can_when_branch);
}
@ -772,6 +748,8 @@ pub fn canonicalize_expr<'a>(
region,
loc_cond: Box::new(can_cond),
branches: can_branches,
branches_cond_var: var_store.fresh(),
exhaustive: ExhaustiveMark::new(var_store),
};
(expr, output)
@ -792,7 +770,7 @@ pub fn canonicalize_expr<'a>(
}
ast::Expr::AccessorFunction(field) => (
Accessor(AccessorData {
name: env.gen_unique_symbol(),
name: scope.gen_unique_symbol(),
function_var: var_store.fresh(),
record_var: var_store.fresh(),
ext_var: var_store.fresh(),
@ -803,15 +781,15 @@ pub fn canonicalize_expr<'a>(
}),
Output::default(),
),
ast::Expr::GlobalTag(tag) => {
ast::Expr::Tag(tag) => {
let variant_var = var_store.fresh();
let ext_var = var_store.fresh();
let symbol = env.gen_unique_symbol();
let symbol = scope.gen_unique_symbol();
(
ZeroArgumentTag {
name: TagName::Global((*tag).into()),
name: TagName::Tag((*tag).into()),
variant_var,
closure_name: symbol,
ext_var,
@ -819,23 +797,6 @@ pub fn canonicalize_expr<'a>(
Output::default(),
)
}
ast::Expr::PrivateTag(tag) => {
let variant_var = var_store.fresh();
let ext_var = var_store.fresh();
let tag_ident = env.ident_ids.get_or_insert(&(*tag).into());
let symbol = Symbol::new(env.home, tag_ident);
let lambda_set_symbol = env.gen_unique_symbol();
(
ZeroArgumentTag {
name: TagName::Private(symbol),
variant_var,
ext_var,
closure_name: lambda_set_symbol,
},
Output::default(),
)
}
ast::Expr::OpaqueRef(opaque_ref) => {
// If we're here, the opaque reference is definitely not wrapping an argument - wrapped
// arguments are handled in the Apply branch.
@ -886,8 +847,8 @@ pub fn canonicalize_expr<'a>(
branches.push((loc_cond, loc_then));
output.references = output.references.union(cond_output.references);
output.references = output.references.union(then_output.references);
output.references.union_mut(&cond_output.references);
output.references.union_mut(&then_output.references);
}
let (loc_else, else_output) = canonicalize_expr(
@ -898,7 +859,7 @@ pub fn canonicalize_expr<'a>(
&final_else_branch.value,
);
output.references = output.references.union(else_output.references);
output.references.union_mut(&else_output.references);
(
If {
@ -1024,6 +985,133 @@ pub fn canonicalize_expr<'a>(
)
}
pub fn canonicalize_closure<'a>(
env: &mut Env<'a>,
var_store: &mut VarStore,
scope: &mut Scope,
loc_arg_patterns: &'a [Loc<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>,
@ -1035,29 +1123,25 @@ fn canonicalize_when_branch<'a>(
) -> (WhenBranch, References) {
let mut patterns = Vec::with_capacity(branch.patterns.len());
let original_scope = scope;
let mut scope = original_scope.clone();
// TODO report symbols not bound in all patterns
for loc_pattern in branch.patterns.iter() {
let (new_output, can_pattern) = canonicalize_pattern(
let can_pattern = canonicalize_pattern(
env,
var_store,
&mut scope,
scope,
output,
WhenBranch,
&loc_pattern.value,
loc_pattern.region,
);
output.union(new_output);
patterns.push(can_pattern);
}
let (value, mut branch_output) = canonicalize_expr(
env,
var_store,
&mut scope,
scope,
branch.value.region,
&branch.value.value,
);
@ -1066,70 +1150,35 @@ fn canonicalize_when_branch<'a>(
None => None,
Some(loc_expr) => {
let (can_guard, guard_branch_output) =
canonicalize_expr(env, var_store, &mut scope, loc_expr.region, &loc_expr.value);
canonicalize_expr(env, var_store, scope, loc_expr.region, &loc_expr.value);
branch_output.union(guard_branch_output);
Some(can_guard)
}
};
// Now that we've collected all the references for this branch, check to see if
// any of the new idents it defined were unused. If any were, report it.
for (symbol, region) in scope.symbols() {
let symbol = *symbol;
if !output.references.has_value_lookup(symbol)
&& !output.references.has_type_lookup(symbol)
&& !branch_output.references.has_value_lookup(symbol)
&& !branch_output.references.has_type_lookup(symbol)
&& !original_scope.contains_symbol(symbol)
&& !scope.abilities_store.is_specialization_name(symbol)
{
env.problem(Problem::UnusedDef(symbol, *region));
}
}
let references = branch_output.references.clone();
output.union(branch_output);
// Now that we've collected all the references for this branch, check to see if
// any of the new idents it defined were unused. If any were, report it.
for (symbol, region) in BindingsFromPattern::new_many(patterns.iter()) {
if !output.references.has_value_lookup(symbol) {
env.problem(Problem::UnusedDef(symbol, region));
}
}
(
WhenBranch {
patterns,
value,
guard,
redundant: RedundantMark::new(var_store),
},
references,
)
}
pub fn local_successors_with_duplicates<'a>(
references: &'a References,
closures: &'a MutMap<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,
@ -1167,7 +1216,7 @@ fn canonicalize_fields<'a>(
});
}
output.references = output.references.union(field_out.references);
output.references.union_mut(&field_out.references);
}
Err(CanonicalizeFieldProblem::InvalidOptionalValue {
field_name,
@ -1255,7 +1304,7 @@ fn canonicalize_var_lookup(
// Look it up in scope!
match scope.lookup(&(*ident).into(), region) {
Ok(symbol) => {
output.references.value_lookups.insert(symbol);
output.references.insert_value_lookup(symbol);
Var(symbol)
}
@ -1268,9 +1317,9 @@ fn canonicalize_var_lookup(
} else {
// Since module_name was nonempty, this is a qualified var.
// Look it up in the env!
match env.qualified_lookup(module_name, ident, region) {
match env.qualified_lookup(scope, module_name, ident, region) {
Ok(symbol) => {
output.references.value_lookups.insert(symbol);
output.references.insert_value_lookup(symbol);
Var(symbol)
}
@ -1336,6 +1385,8 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
region,
loc_cond,
branches,
branches_cond_var,
exhaustive,
} => {
let loc_cond = Box::new(Loc {
region: loc_cond.region,
@ -1360,6 +1411,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
patterns: branch.patterns,
value,
guard,
redundant: RedundantMark::new(var_store),
};
new_branches.push(new_branch);
@ -1371,6 +1423,8 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
region,
loc_cond,
branches: new_branches,
branches_cond_var,
exhaustive,
}
}
If {
@ -1600,7 +1654,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
// Wrap the body in one LetNonRec for each argument,
// such that at the end we have all the arguments in
// scope with the values the caller provided.
for ((_param_var, loc_pattern), (expr_var, loc_expr)) in
for ((_param_var, _exhaustive_mark, loc_pattern), (expr_var, loc_expr)) in
params.iter().cloned().zip(args.into_iter()).rev()
{
// TODO get the correct vars into here.
@ -1726,7 +1780,7 @@ fn flatten_str_lines<'a>(
Interpolated(loc_expr) => {
if is_valid_interpolation(loc_expr.value) {
// Interpolations desugar to Str.concat calls
output.references.calls.insert(Symbol::STR_CONCAT);
output.references.insert_call(Symbol::STR_CONCAT);
if !buf.is_empty() {
segments.push(StrSegment::Plaintext(buf.into()));

View File

@ -8,6 +8,7 @@ pub mod constraint;
pub mod def;
pub mod effect_module;
pub mod env;
pub mod exhaustive;
pub mod expected;
pub mod expr;
pub mod module;
@ -15,5 +16,7 @@ pub mod num;
pub mod operator;
pub mod pattern;
pub mod procedure;
mod reference_matrix;
pub mod scope;
pub mod string;
pub mod traverse;

View File

@ -7,10 +7,10 @@ use crate::operator::desugar_def;
use crate::pattern::Pattern;
use crate::scope::Scope;
use bumpalo::Bump;
use roc_collections::all::{MutMap, MutSet, SendMap};
use roc_collections::{MutMap, SendMap, VecSet};
use roc_module::ident::Ident;
use roc_module::ident::Lowercase;
use roc_module::ident::{Ident, TagName};
use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_module::symbol::{IdentIds, IdentIdsByModule, ModuleId, ModuleIds, Symbol};
use roc_parse::ast;
use roc_parse::header::HeaderFor;
use roc_parse::pattern::PatternType;
@ -23,9 +23,9 @@ use roc_types::types::{Alias, AliasKind, Type};
pub struct Module {
pub module_id: ModuleId,
pub exposed_imports: MutMap<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,
@ -36,7 +36,7 @@ pub struct Module {
pub struct RigidVariables {
pub named: MutMap<Variable, Lowercase>,
pub able: MutMap<Variable, (Lowercase, Symbol)>,
pub wildcards: MutSet<Variable>,
pub wildcards: VecSet<Variable>,
}
#[derive(Debug)]
@ -47,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,
}
@ -77,6 +76,83 @@ fn validate_generate_with<'a>(
(functions, unknown)
}
#[derive(Debug)]
enum GeneratedInfo {
Hosted {
effect_symbol: Symbol,
generated_functions: HostedGeneratedFunctions,
},
Builtin,
NotSpecial,
}
impl GeneratedInfo {
fn from_header_for<'a>(
env: &mut Env,
scope: &mut Scope,
var_store: &mut VarStore,
header_for: &HeaderFor<'a>,
) -> Self {
match header_for {
HeaderFor::Hosted {
generates,
generates_with,
} => {
let name: &str = generates.into();
let (generated_functions, unknown_generated) =
validate_generate_with(generates_with);
for unknown in unknown_generated {
env.problem(Problem::UnknownGeneratesWith(unknown));
}
let effect_symbol = scope.introduce(name.into(), Region::zero()).unwrap();
{
let a_var = var_store.fresh();
let actual =
crate::effect_module::build_effect_actual(Type::Variable(a_var), var_store);
scope.add_alias(
effect_symbol,
Region::zero(),
vec![Loc::at_zero(("a".into(), a_var))],
actual,
AliasKind::Opaque,
);
}
GeneratedInfo::Hosted {
effect_symbol,
generated_functions,
}
}
HeaderFor::Builtin { generates_with } => {
debug_assert!(generates_with.is_empty());
GeneratedInfo::Builtin
}
_ => GeneratedInfo::NotSpecial,
}
}
}
fn has_no_implementation(expr: &Expr) -> bool {
match expr {
Expr::RuntimeError(RuntimeError::NoImplementationNamed { .. }) => true,
Expr::Closure(closure_data)
if matches!(
closure_data.loc_body.value,
Expr::RuntimeError(RuntimeError::NoImplementationNamed { .. })
) =>
{
true
}
_ => false,
}
}
// TODO trim these down
#[allow(clippy::too_many_arguments)]
pub fn canonicalize_module_defs<'a>(
@ -86,15 +162,15 @@ pub fn canonicalize_module_defs<'a>(
home: ModuleId,
module_ids: &ModuleIds,
exposed_ident_ids: IdentIds,
dep_idents: &'a MutMap<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() {
@ -107,59 +183,8 @@ pub fn canonicalize_module_defs<'a>(
);
}
struct Hosted {
effect_symbol: Symbol,
generated_functions: HostedGeneratedFunctions,
}
let hosted_info = if let HeaderFor::Hosted {
generates,
generates_with,
} = header_for
{
let name: &str = generates.into();
let (generated_functions, unknown_generated) = validate_generate_with(generates_with);
for unknown in unknown_generated {
env.problem(Problem::UnknownGeneratesWith(unknown));
}
let effect_symbol = scope
.introduce(
name.into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
Region::zero(),
)
.unwrap();
let effect_tag_name = TagName::Private(effect_symbol);
{
let a_var = var_store.fresh();
let actual = crate::effect_module::build_effect_actual(
effect_tag_name,
Type::Variable(a_var),
var_store,
);
scope.add_alias(
effect_symbol,
Region::zero(),
vec![Loc::at_zero(("a".into(), a_var))],
actual,
AliasKind::Structural,
);
}
Some(Hosted {
effect_symbol,
generated_functions,
})
} else {
None
};
let generated_info =
GeneratedInfo::from_header_for(&mut env, &mut scope, var_store, header_for);
// Desugar operators (convert them to Apply calls, taking into account
// operator precedence and associativity rules), before doing other canonicalization.
@ -215,14 +240,25 @@ pub fn canonicalize_module_defs<'a>(
panic!("TODO gracefully handle shadowing in imports.")
}
}
} else if [
Symbol::LIST_LIST,
Symbol::STR_STR,
Symbol::DICT_DICT,
Symbol::SET_SET,
Symbol::BOX_BOX_TYPE,
]
.contains(&symbol)
{
// These are not aliases but Apply's and we make sure they are always in scope
} else {
// This is a type alias
// the symbol should already be added to the scope when this module is canonicalized
debug_assert!(
scope.contains_alias(symbol),
"apparently, {:?} is not actually a type alias",
symbol
"The {:?} is not a type alias known in {:?}",
symbol,
home
);
// but now we know this symbol by a different identifier, so we still need to add it to
@ -231,18 +267,21 @@ pub fn canonicalize_module_defs<'a>(
Ok(()) => {
// here we do nothing special
}
Err((_shadowed_symbol, _region)) => {
panic!("TODO gracefully handle shadowing in imports.")
Err((shadowed_symbol, _region)) => {
panic!(
"TODO gracefully handle shadowing in imports, {:?} is shadowed.",
shadowed_symbol
)
}
}
}
}
let (defs, mut scope, output, symbols_introduced) = canonicalize_defs(
let (defs, output, symbols_introduced) = canonicalize_defs(
&mut env,
Output::default(),
var_store,
&scope,
&mut scope,
&desugared,
PatternType::TopLevelDef,
);
@ -250,8 +289,7 @@ pub fn canonicalize_module_defs<'a>(
// See if any of the new idents we defined went unused.
// If any were unused and also not exposed, report it.
for (symbol, region) in symbols_introduced {
if !output.references.has_value_lookup(symbol)
&& !output.references.has_type_lookup(symbol)
if !output.references.has_type_or_value_lookup(symbol)
&& !exposed_symbols.contains(&symbol)
&& !scope.abilities_store.is_specialization_name(symbol)
{
@ -273,15 +311,15 @@ pub fn canonicalize_module_defs<'a>(
rigid_variables.wildcards.insert(var.value);
}
let mut referenced_values = MutSet::default();
let mut referenced_types = MutSet::default();
let mut referenced_values = VecSet::default();
let mut referenced_types = VecSet::default();
// Gather up all the symbols that were referenced across all the defs' lookups.
referenced_values.extend(output.references.value_lookups);
referenced_types.extend(output.references.type_lookups);
referenced_values.extend(output.references.value_lookups().copied());
referenced_types.extend(output.references.type_lookups().copied());
// Gather up all the symbols that were referenced across all the defs' calls.
referenced_values.extend(output.references.calls);
referenced_values.extend(output.references.calls().copied());
// Gather up all the symbols that were referenced from other modules.
referenced_values.extend(env.qualified_value_lookups.iter().copied());
@ -314,16 +352,15 @@ pub fn canonicalize_module_defs<'a>(
(Ok(mut declarations), output) => {
use crate::def::Declaration::*;
if let Some(Hosted {
if let GeneratedInfo::Hosted {
effect_symbol,
generated_functions,
}) = hosted_info
} = generated_info
{
let mut exposed_symbols = MutSet::default();
let mut exposed_symbols = VecSet::default();
// NOTE this currently builds all functions, not just the ones that the user requested
crate::effect_module::build_effect_builtins(
&mut env,
&mut scope,
effect_symbol,
var_store,
@ -350,13 +387,25 @@ pub fn canonicalize_module_defs<'a>(
// Temporary hack: we don't know exactly what symbols are hosted symbols,
// and which are meant to be normal definitions without a body. So for now
// we just assume they are hosted functions (meant to be provided by the platform)
if let Some(Hosted { effect_symbol, .. }) = hosted_info {
macro_rules! make_hosted_def {
() => {
if has_no_implementation(&def.loc_expr.value) {
match generated_info {
GeneratedInfo::Builtin => {
let symbol = def.pattern_vars.iter().next().unwrap().0;
match crate::builtins::builtin_defs_map(*symbol, var_store) {
None => {
panic!("A builtin module contains a signature without implementation for {:?}", symbol)
}
Some(mut replacement_def) => {
replacement_def.annotation = def.annotation.take();
*def = replacement_def;
}
}
}
GeneratedInfo::Hosted { effect_symbol, .. } => {
let symbol = def.pattern_vars.iter().next().unwrap().0;
let ident_id = symbol.ident_id();
let ident =
env.ident_ids.get_name(ident_id).unwrap().to_string();
scope.ident_ids.get_name(ident_id).unwrap().to_string();
let def_annotation = def.annotation.clone().unwrap();
let annotation = crate::annotation::Annotation {
typ: def_annotation.signature,
@ -366,37 +415,17 @@ pub fn canonicalize_module_defs<'a>(
};
let hosted_def = crate::effect_module::build_host_exposed_def(
&mut env,
&mut scope,
*symbol,
&ident,
TagName::Private(effect_symbol),
effect_symbol,
var_store,
annotation,
);
*def = hosted_def;
};
}
match &def.loc_expr.value {
Expr::RuntimeError(RuntimeError::NoImplementationNamed {
..
}) => {
make_hosted_def!();
}
Expr::Closure(closure_data)
if matches!(
closure_data.loc_body.value,
Expr::RuntimeError(
RuntimeError::NoImplementationNamed { .. }
)
) =>
{
make_hosted_def!();
}
_ => {}
_ => (),
}
}
}
@ -431,7 +460,7 @@ pub fn canonicalize_module_defs<'a>(
let mut aliases = MutMap::default();
if let Some(Hosted { effect_symbol, .. }) = hosted_info {
if let GeneratedInfo::Hosted { effect_symbol, .. } = generated_info {
// Remove this from exposed_symbols,
// so that at the end of the process,
// we can see if there were any
@ -483,11 +512,11 @@ pub fn canonicalize_module_defs<'a>(
}
// Incorporate any remaining output.lookups entries into references.
referenced_values.extend(output.references.value_lookups);
referenced_types.extend(output.references.type_lookups);
referenced_values.extend(output.references.value_lookups().copied());
referenced_types.extend(output.references.type_lookups().copied());
// Incorporate any remaining output.calls entries into references.
referenced_values.extend(output.references.calls);
referenced_values.extend(output.references.calls().copied());
// Gather up all the symbols that were referenced from other modules.
referenced_values.extend(env.qualified_value_lookups.iter().copied());
@ -495,25 +524,14 @@ pub fn canonicalize_module_defs<'a>(
for declaration in declarations.iter_mut() {
match declaration {
Declare(def) => fix_values_captured_in_closure_def(def, &mut MutSet::default()),
Declare(def) => fix_values_captured_in_closure_def(def, &mut VecSet::default()),
DeclareRec(defs) => {
fix_values_captured_in_closure_defs(defs, &mut MutSet::default())
fix_values_captured_in_closure_defs(defs, &mut VecSet::default())
}
InvalidCycle(_) | Builtin(_) => {}
}
}
// TODO this loops over all symbols in the module, we can speed it up by having an
// iterator over all builtin symbols
for symbol in referenced_values.iter() {
if symbol.is_builtin() {
// this can fail when the symbol is for builtin types, or has no implementation yet
if let Some(def) = crate::builtins::builtin_defs_map(*symbol, var_store) {
declarations.push(Declaration::Builtin(def));
}
}
}
let output = ModuleOutput {
scope,
aliases,
@ -524,7 +542,6 @@ pub fn canonicalize_module_defs<'a>(
exposed_imports: can_exposed_imports,
problems: env.problems,
lookups,
ident_ids: env.ident_ids,
};
Ok(output)
@ -535,7 +552,7 @@ pub fn canonicalize_module_defs<'a>(
fn fix_values_captured_in_closure_def(
def: &mut crate::def::Def,
no_capture_symbols: &mut MutSet<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);
@ -545,7 +562,7 @@ 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>,
no_capture_symbols: &mut VecSet<Symbol>,
) {
// recursive defs cannot capture each other
for def in defs.iter() {
@ -561,7 +578,7 @@ fn fix_values_captured_in_closure_defs(
fn fix_values_captured_in_closure_pattern(
pattern: &mut crate::pattern::Pattern,
no_capture_symbols: &mut MutSet<Symbol>,
no_capture_symbols: &mut VecSet<Symbol>,
) {
use crate::pattern::Pattern::*;
@ -610,7 +627,7 @@ fn fix_values_captured_in_closure_pattern(
fn fix_values_captured_in_closure_expr(
expr: &mut crate::expr::Expr,
no_capture_symbols: &mut MutSet<Symbol>,
no_capture_symbols: &mut VecSet<Symbol>,
) {
use crate::expr::Expr::*;
@ -646,7 +663,7 @@ fn fix_values_captured_in_closure_expr(
}
// patterns can contain default expressions, so much go over them too!
for (_, loc_pat) in arguments.iter_mut() {
for (_, _, loc_pat) in arguments.iter_mut() {
fix_values_captured_in_closure_pattern(&mut loc_pat.value, no_capture_symbols);
}

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

@ -13,11 +13,11 @@ use roc_parse::pattern::PatternType;
use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError, ShadowKind};
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::{LambdaSet, Type};
use roc_types::types::{LambdaSet, PatternCategory, Type};
/// A pattern, including possible problems (e.g. shadowing) so that
/// codegen can generate a runtime error if this pattern is reached.
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug)]
pub enum Pattern {
Identifier(Symbol),
AppliedTag {
@ -82,7 +82,85 @@ pub enum Pattern {
MalformedPattern(MalformedPatternProblem, Region),
}
#[derive(Clone, Debug, PartialEq)]
impl Pattern {
pub fn opt_var(&self) -> Option<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,
@ -90,7 +168,7 @@ pub struct RecordDestruct {
pub typ: DestructType,
}
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug)]
pub enum DestructType {
Required,
Optional(Variable, Loc<Expr>),
@ -156,47 +234,44 @@ pub fn canonicalize_def_header_pattern<'a>(
env: &mut Env<'a>,
var_store: &mut VarStore,
scope: &mut Scope,
output: &mut Output,
pattern_type: PatternType,
pattern: &ast::Pattern<'a>,
region: Region,
) -> (Output, Loc<Pattern>) {
) -> Loc<Pattern> {
use roc_parse::ast::Pattern::*;
let mut output = Output::default();
match pattern {
// Identifiers that shadow ability members may appear (and may only appear) at the header of a def.
Identifier(name) => match scope.introduce_or_shadow_ability_member(
(*name).into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
region,
) {
Ok((symbol, shadowing_ability_member)) => {
output.references.bound_symbols.insert(symbol);
let can_pattern = match shadowing_ability_member {
// A fresh identifier.
None => Pattern::Identifier(symbol),
// Likely a specialization of an ability.
Some(ability_member_name) => Pattern::AbilityMemberSpecialization {
ident: symbol,
specializes: ability_member_name,
},
};
(output, Loc::at(region, can_pattern))
}
Err((original_region, shadow, new_symbol)) => {
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region,
shadow: shadow.clone(),
kind: ShadowKind::Variable,
}));
output.references.bound_symbols.insert(new_symbol);
Identifier(name) => {
match scope.introduce_or_shadow_ability_member((*name).into(), region) {
Ok((symbol, shadowing_ability_member)) => {
output.references.insert_bound(symbol);
let can_pattern = match shadowing_ability_member {
// A fresh identifier.
None => Pattern::Identifier(symbol),
// Likely a specialization of an ability.
Some(ability_member_name) => Pattern::AbilityMemberSpecialization {
ident: symbol,
specializes: ability_member_name,
},
};
Loc::at(region, can_pattern)
}
Err((original_region, shadow, new_symbol)) => {
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region,
shadow: shadow.clone(),
kind: ShadowKind::Variable,
}));
output.references.insert_bound(new_symbol);
let can_pattern = Pattern::Shadowed(original_region, shadow, new_symbol);
(output, Loc::at(region, can_pattern))
let can_pattern = Pattern::Shadowed(original_region, shadow, new_symbol);
Loc::at(region, can_pattern)
}
}
},
_ => canonicalize_pattern(env, var_store, scope, pattern_type, pattern, region),
}
_ => canonicalize_pattern(env, var_store, scope, output, pattern_type, pattern, region),
}
}
@ -204,23 +279,18 @@ pub fn canonicalize_pattern<'a>(
env: &mut Env<'a>,
var_store: &mut VarStore,
scope: &mut Scope,
output: &mut Output,
pattern_type: PatternType,
pattern: &ast::Pattern<'a>,
region: Region,
) -> (Output, Loc<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((*name).into(), region) {
Ok(symbol) => {
output.references.bound_symbols.insert(symbol);
output.references.insert_bound(symbol);
Pattern::Identifier(symbol)
}
@ -230,28 +300,17 @@ pub fn canonicalize_pattern<'a>(
shadow: shadow.clone(),
kind: ShadowKind::Variable,
}));
output.references.bound_symbols.insert(new_symbol);
output.references.insert_bound(new_symbol);
Pattern::Shadowed(original_region, shadow, new_symbol)
}
},
GlobalTag(name) => {
Tag(name) => {
// Canonicalize the tag's name.
Pattern::AppliedTag {
whole_var: var_store.fresh(),
ext_var: var_store.fresh(),
tag_name: TagName::Global((*name).into()),
arguments: vec![],
}
}
PrivateTag(name) => {
let ident_id = env.ident_ids.get_or_insert(&(*name).into());
// Canonicalize the tag's name.
Pattern::AppliedTag {
whole_var: var_store.fresh(),
ext_var: var_store.fresh(),
tag_name: TagName::Private(Symbol::new(env.home, ident_id)),
tag_name: TagName::Tag((*name).into()),
arguments: vec![],
}
}
@ -266,34 +325,22 @@ pub fn canonicalize_pattern<'a>(
Apply(tag, patterns) => {
let mut can_patterns = Vec::with_capacity(patterns.len());
for loc_pattern in *patterns {
let (new_output, can_pattern) = canonicalize_pattern(
let can_pattern = canonicalize_pattern(
env,
var_store,
scope,
output,
pattern_type,
&loc_pattern.value,
loc_pattern.region,
);
output.union(new_output);
can_patterns.push((var_store.fresh(), can_pattern));
}
match tag.value {
GlobalTag(name) => {
let tag_name = TagName::Global(name.into());
Pattern::AppliedTag {
whole_var: var_store.fresh(),
ext_var: var_store.fresh(),
tag_name,
arguments: can_patterns,
}
}
PrivateTag(name) => {
let ident_id = env.ident_ids.get_or_insert(&name.into());
let tag_name = TagName::Private(Symbol::new(env.home, ident_id));
Tag(name) => {
let tag_name = TagName::Tag(name.into());
Pattern::AppliedTag {
whole_var: var_store.fresh(),
ext_var: var_store.fresh(),
@ -318,8 +365,7 @@ pub fn canonicalize_pattern<'a>(
let (type_arguments, lambda_set_variables, specialized_def_type) =
freshen_opaque_def(var_store, opaque_def);
output.references.referenced_type_defs.insert(opaque);
output.references.type_lookups.insert(opaque);
output.references.insert_type_lookup(opaque);
Pattern::UnwrappedOpaque {
whole_var: var_store.fresh(),
@ -443,7 +489,15 @@ pub fn canonicalize_pattern<'a>(
}
SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) => {
return canonicalize_pattern(env, var_store, scope, pattern_type, sub_pattern, region)
return canonicalize_pattern(
env,
var_store,
scope,
output,
pattern_type,
sub_pattern,
region,
)
}
RecordDestructure(patterns) => {
let ext_var = var_store.fresh();
@ -454,14 +508,9 @@ pub fn canonicalize_pattern<'a>(
for loc_pattern in patterns.iter() {
match loc_pattern.value {
Identifier(label) => {
match scope.introduce(
label.into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
region,
) {
match scope.introduce(label.into(), region) {
Ok(symbol) => {
output.references.bound_symbols.insert(symbol);
output.references.insert_bound(symbol);
destructs.push(Loc {
region: loc_pattern.region,
@ -492,18 +541,17 @@ pub fn canonicalize_pattern<'a>(
RequiredField(label, loc_guard) => {
// a guard does not introduce the label into scope!
let symbol = scope.ignore(label.into(), &mut env.ident_ids);
let (new_output, can_guard) = canonicalize_pattern(
let symbol = scope.ignore(&Ident::from(label));
let can_guard = canonicalize_pattern(
env,
var_store,
scope,
output,
pattern_type,
&loc_guard.value,
loc_guard.region,
);
output.union(new_output);
destructs.push(Loc {
region: loc_pattern.region,
value: RecordDestruct {
@ -516,12 +564,7 @@ pub fn canonicalize_pattern<'a>(
}
OptionalField(label, loc_default) => {
// an optional DOES introduce the label into scope!
match scope.introduce(
label.into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
region,
) {
match scope.introduce(label.into(), region) {
Ok(symbol) => {
let (can_default, expr_output) = canonicalize_expr(
env,
@ -532,7 +575,7 @@ pub fn canonicalize_pattern<'a>(
);
// an optional field binds the symbol!
output.references.bound_symbols.insert(symbol);
output.references.insert_bound(symbol);
output.union(expr_output);
@ -598,13 +641,10 @@ pub fn canonicalize_pattern<'a>(
}
};
(
output,
Loc {
region,
value: can_pattern,
},
)
Loc {
region,
value: can_pattern,
}
}
/// When we detect an unsupported pattern type (e.g. 5 = 1 + 2 is unsupported because you can't
@ -639,69 +679,122 @@ fn malformed_pattern(env: &mut Env, problem: MalformedPatternProblem, region: Re
Pattern::MalformedPattern(problem, region)
}
pub fn bindings_from_patterns<'a, I>(loc_patterns: I) -> Vec<(Symbol, Region)>
where
I: Iterator<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)
| AbilityMemberSpecialization {
ident: symbol,
specializes: _,
} => {
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,4 +1,4 @@
use roc_collections::all::{MutSet, SendMap};
use roc_collections::{MutSet, SmallStringInterner, VecMap};
use roc_module::ident::{Ident, Lowercase};
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_problem::can::RuntimeError;
@ -8,18 +8,12 @@ use roc_types::types::{Alias, AliasKind, Type};
use crate::abilities::AbilitiesStore;
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug)]
pub struct Scope {
/// All the identifiers in scope, mapped to were they were defined and
/// the Symbol they resolve to.
idents: SendMap<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>,
idents: IdentStore,
/// 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,
@ -27,101 +21,123 @@ pub struct Scope {
/// The current module being processed. This will be used to turn
/// unqualified idents into Symbols.
home: ModuleId,
pub ident_ids: IdentIds,
/// The first `exposed_ident_count` identifiers are exposed
exposed_ident_count: usize,
}
fn add_aliases(var_store: &mut VarStore) -> VecMap<Symbol, Alias> {
use roc_types::solved_types::{BuiltinAlias, FreeVars};
let solved_aliases = roc_types::builtin_aliases::aliases();
let mut aliases = VecMap::default();
for (symbol, builtin_alias) in solved_aliases {
let BuiltinAlias {
region,
vars,
typ,
kind,
} = builtin_alias;
let mut free_vars = FreeVars::default();
let typ = roc_types::solved_types::to_type(&typ, &mut free_vars, var_store);
let mut variables = Vec::new();
// make sure to sort these variables to make them line up with the type arguments
let mut type_variables: Vec<_> = free_vars.unnamed_vars.into_iter().collect();
type_variables.sort();
for (loc_name, (_, var)) in vars.iter().zip(type_variables) {
variables.push(Loc::at(loc_name.region, (loc_name.value.clone(), var)));
}
let alias = Alias {
region,
typ,
lambda_set_variables: Vec::new(),
recursion_variables: MutSet::default(),
type_variables: variables,
kind,
};
aliases.insert(symbol, alias);
}
aliases
}
impl Scope {
pub fn new(home: ModuleId, var_store: &mut VarStore) -> Scope {
use roc_types::solved_types::{BuiltinAlias, FreeVars};
let solved_aliases = roc_types::builtin_aliases::aliases();
let mut aliases = SendMap::default();
for (symbol, builtin_alias) in solved_aliases {
let BuiltinAlias { region, vars, typ } = builtin_alias;
let mut free_vars = FreeVars::default();
let typ = roc_types::solved_types::to_type(&typ, &mut free_vars, var_store);
let mut variables = Vec::new();
// make sure to sort these variables to make them line up with the type arguments
let mut type_variables: Vec<_> = free_vars.unnamed_vars.into_iter().collect();
type_variables.sort();
for (loc_name, (_, var)) in vars.iter().zip(type_variables) {
variables.push(Loc::at(loc_name.region, (loc_name.value.clone(), var)));
}
let alias = Alias {
region,
typ,
lambda_set_variables: Vec::new(),
recursion_variables: MutSet::default(),
type_variables: variables,
// TODO(opaques): replace when opaques are included in the stdlib
kind: AliasKind::Structural,
};
aliases.insert(symbol, alias);
}
pub fn new(home: ModuleId, initial_ident_ids: IdentIds) -> Scope {
Scope {
home,
idents: Symbol::default_in_scope(),
symbols: SendMap::default(),
aliases,
exposed_ident_count: initial_ident_ids.len(),
ident_ids: initial_ident_ids,
idents: IdentStore::new(),
aliases: VecMap::default(),
// TODO(abilities): default abilities in scope
abilities_store: AbilitiesStore::default(),
}
}
pub fn idents(&self) -> impl Iterator<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 new_with_aliases(
home: ModuleId,
var_store: &mut VarStore,
initial_ident_ids: IdentIds,
) -> Scope {
Scope {
home,
exposed_ident_count: initial_ident_ids.len(),
ident_ids: initial_ident_ids,
idents: IdentStore::new(),
aliases: add_aliases(var_store),
// TODO(abilities): default abilities in scope
abilities_store: AbilitiesStore::default(),
}
}
pub fn lookup(&self, ident: &Ident, region: Region) -> Result<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(),
)),
match self.idents.get_symbol(ident) {
Some(symbol) => Ok(symbol),
None => {
let error = RuntimeError::LookupNotInScope(
Loc {
region,
value: ident.clone(),
},
self.idents
.iter_idents()
.map(|v| v.as_ref().into())
.collect(),
);
Err(error)
}
}
}
#[cfg(test)]
fn idents_in_scope(&self) -> impl Iterator<Item = Ident> + '_ {
self.idents.iter_idents()
}
pub fn lookup_alias(&self, symbol: Symbol) -> Option<&Alias> {
self.aliases.get(&symbol)
}
/// Check if there is an opaque type alias referenced by `opaque_ref` referenced in the
/// current scope. E.g. `$Age` must reference an opaque `Age` declared in this module, not any
/// current scope. E.g. `@Age` must reference an opaque `Age` declared in this module, not any
/// other!
// TODO(opaques): $->@ in the above comment
pub fn lookup_opaque_ref(
&self,
opaque_ref: &str,
lookup_region: Region,
) -> Result<(Symbol, &Alias), RuntimeError> {
debug_assert!(opaque_ref.starts_with('$'));
debug_assert!(opaque_ref.starts_with('@'));
let opaque = opaque_ref[1..].into();
match self.idents.get(&opaque) {
match self.idents.get_symbol_and_region(&opaque) {
// TODO: is it worth caching any of these results?
Some((symbol, decl_region)) => {
if symbol.module_id() != self.home {
@ -131,11 +147,11 @@ impl Scope {
return Err(RuntimeError::OpaqueOutsideScope {
opaque,
referenced_region: lookup_region,
imported_region: *decl_region,
imported_region: decl_region,
});
}
match self.aliases.get(symbol) {
match self.aliases.get(&symbol) {
None => Err(self.opaque_not_defined_error(opaque, lookup_region, None)),
Some(alias) => match alias.kind {
@ -146,7 +162,7 @@ impl Scope {
Some(alias.header_region()),
)),
// All is good
AliasKind::Opaque => Ok((*symbol, alias)),
AliasKind::Opaque => Ok((symbol, alias)),
},
}
}
@ -161,8 +177,9 @@ impl Scope {
opt_defined_alias: Option<Region>,
) -> RuntimeError {
let opaques_in_scope = self
.idents()
.filter(|(_, (sym, _))| {
.idents
.iter_idents_symbols()
.filter(|(_, sym)| {
self.aliases
.get(sym)
.map(|alias| alias.kind)
@ -191,46 +208,34 @@ impl Scope {
pub fn introduce(
&mut self,
ident: Ident,
exposed_ident_ids: &IdentIds,
all_ident_ids: &mut IdentIds,
region: Region,
) -> Result<Symbol, (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());
match self.introduce_without_shadow_symbol(&ident, region) {
Ok(symbol) => Ok(symbol),
Err((original_region, shadow)) => {
let ident_id = self.ident_ids.add_ident(&ident);
let symbol = Symbol::new(self.home, ident_id);
self.symbols.insert(symbol, region);
self.idents.insert(ident, (symbol, region));
Err((original_region, shadow, symbol))
}
None => Ok(self.commit_introduction(ident, exposed_ident_ids, all_ident_ids, region)),
}
}
/// Like [Self::introduce], but does not introduce a new symbol for the shadowing symbol.
pub fn introduce_without_shadow_symbol(
&mut self,
ident: Ident,
exposed_ident_ids: &IdentIds,
all_ident_ids: &mut IdentIds,
ident: &Ident,
region: Region,
) -> Result<Symbol, (Region, Loc<Ident>)> {
match self.idents.get(&ident) {
Some(&(_, original_region)) => {
match self.idents.get_symbol_and_region(ident) {
Some((_, original_region)) => {
let shadow = Loc {
value: ident.clone(),
region,
};
Err((original_region, shadow))
}
None => Ok(self.commit_introduction(ident, exposed_ident_ids, all_ident_ids, region)),
None => Ok(self.commit_introduction(ident, region)),
}
}
@ -244,21 +249,24 @@ impl Scope {
pub fn introduce_or_shadow_ability_member(
&mut self,
ident: Ident,
exposed_ident_ids: &IdentIds,
all_ident_ids: &mut IdentIds,
region: Region,
) -> Result<(Symbol, Option<Symbol>), (Region, Loc<Ident>, Symbol)> {
match self.idents.get(&ident) {
Some(&(original_symbol, original_region)) => {
let shadow_ident_id = all_ident_ids.add(ident.clone());
let shadow_symbol = Symbol::new(self.home, shadow_ident_id);
match self.idents.get_index(&ident) {
Some(index) => {
let original_symbol = self.idents.symbols[index];
let original_region = self.idents.regions[index];
self.symbols.insert(shadow_symbol, region);
let shadow_ident_id = self.ident_ids.add_ident(&ident);
let shadow_symbol = Symbol::new(self.home, shadow_ident_id);
if self.abilities_store.is_ability_member_name(original_symbol) {
self.abilities_store
.register_specializing_symbol(shadow_symbol, original_symbol);
// Add a symbol for the shadow, but don't re-associate the member name.
let dummy = Ident::default();
self.idents.insert_unchecked(&dummy, shadow_symbol, region);
Ok((shadow_symbol, Some(original_symbol)))
} else {
// This is an illegal shadow.
@ -267,38 +275,26 @@ impl Scope {
region,
};
self.idents.insert(ident, (shadow_symbol, region));
Err((original_region, shadow, shadow_symbol))
}
}
None => {
let new_symbol =
self.commit_introduction(ident, exposed_ident_ids, all_ident_ids, region);
let new_symbol = self.commit_introduction(&ident, region);
Ok((new_symbol, None))
}
}
}
fn commit_introduction(
&mut self,
ident: Ident,
exposed_ident_ids: &IdentIds,
all_ident_ids: &mut IdentIds,
region: Region,
) -> Symbol {
// If this IdentId was already added previously
// when the value was exposed in the module header,
// use that existing IdentId. Otherwise, create a fresh one.
let ident_id = match exposed_ident_ids.get_id(&ident) {
Some(ident_id) => ident_id,
None => all_ident_ids.add(ident.clone()),
fn commit_introduction(&mut self, ident: &Ident, region: Region) -> Symbol {
// if the identifier is exposed, use the IdentId we already have for it
let ident_id = match self.ident_ids.get_id(ident) {
Some(ident_id) if ident_id.index() < self.exposed_ident_count => ident_id,
_ => self.ident_ids.add_ident(ident),
};
let symbol = Symbol::new(self.home, ident_id);
self.symbols.insert(symbol, region);
self.idents.insert(ident, (symbol, region));
self.idents.insert_unchecked(ident, symbol, region);
symbol
}
@ -306,8 +302,8 @@ impl Scope {
/// Ignore an identifier.
///
/// Used for record guards like { x: Just _ }
pub fn ignore(&mut self, ident: Ident, all_ident_ids: &mut IdentIds) -> Symbol {
let ident_id = all_ident_ids.add(ident);
pub fn ignore(&mut self, ident: &Ident) -> Symbol {
let ident_id = self.ident_ids.add_ident(ident);
Symbol::new(self.home, ident_id)
}
@ -321,11 +317,10 @@ impl Scope {
symbol: Symbol,
region: Region,
) -> Result<(), (Symbol, Region)> {
match self.idents.get(&ident) {
Some(shadowed) => Err(*shadowed),
match self.idents.get_symbol_and_region(&ident) {
Some(shadowed) => Err(shadowed),
None => {
self.symbols.insert(symbol, region);
self.idents.insert(ident, (symbol, region));
self.idents.insert_unchecked(&ident, symbol, region);
Ok(())
}
@ -347,6 +342,43 @@ impl Scope {
pub fn contains_alias(&mut self, name: Symbol) -> bool {
self.aliases.contains_key(&name)
}
pub fn inner_scope<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
// - ident_ids: identifiers in inner scopes should still be available in the ident_ids
// - idents: we have to clone for now
// - aliases: stored in a VecMap, we just discard anything added in an inner scope
// - exposed_ident_count: unchanged
let idents = self.idents.clone();
let aliases_count = self.aliases.len();
let result = f(self);
self.idents = idents;
self.aliases.truncate(aliases_count);
result
}
pub fn register_debug_idents(&self) {
self.home.register_debug_idents(&self.ident_ids)
}
/// Generates a unique, new symbol like "$1" or "$5",
/// using the home module as the module_id.
///
/// This is used, for example, during canonicalization of an Expr::Closure
/// to generate a unique symbol to refer to that closure.
pub fn gen_unique_symbol(&mut self) -> Symbol {
let ident_id = self.ident_ids.gen_unique();
Symbol::new(self.home, ident_id)
}
}
pub fn create_alias(
@ -393,3 +425,220 @@ pub fn create_alias(
kind,
}
}
#[derive(Clone, Debug)]
struct IdentStore {
interner: SmallStringInterner,
/// A Symbol for each Ident
symbols: Vec<Symbol>,
/// A Region for each Ident
regions: Vec<Region>,
}
impl IdentStore {
fn new() -> Self {
let defaults = Symbol::default_in_scope();
let capacity = defaults.len();
let mut this = Self {
interner: SmallStringInterner::with_capacity(capacity),
symbols: Vec::with_capacity(capacity),
regions: Vec::with_capacity(capacity),
};
for (ident, (symbol, region)) in defaults {
this.insert_unchecked(&ident, symbol, region);
}
this
}
fn iter_idents(&self) -> impl Iterator<Item = Ident> + '_ {
self.interner.iter().filter_map(move |string| {
// empty string is used when ability members are shadowed
if string.is_empty() {
None
} else {
Some(Ident::from(string))
}
})
}
fn iter_idents_symbols(&self) -> impl Iterator<Item = (Ident, Symbol)> + '_ {
self.interner
.iter()
.zip(self.symbols.iter())
.filter_map(move |(string, symbol)| {
// empty slice is used when ability members are shadowed
if string.is_empty() {
None
} else {
Some((Ident::from(string), *symbol))
}
})
}
fn get_index(&self, ident: &Ident) -> Option<usize> {
let ident_str = ident.as_inline_str().as_str();
self.interner.find_index(ident_str)
}
fn get_symbol(&self, ident: &Ident) -> Option<Symbol> {
Some(self.symbols[self.get_index(ident)?])
}
fn get_symbol_and_region(&self, ident: &Ident) -> Option<(Symbol, Region)> {
let index = self.get_index(ident)?;
Some((self.symbols[index], self.regions[index]))
}
/// Does not check that the ident is unique
fn insert_unchecked(&mut self, ident: &Ident, symbol: Symbol, region: Region) {
let ident_str = ident.as_inline_str().as_str();
let index = self.interner.insert(ident_str);
debug_assert_eq!(index, self.symbols.len());
debug_assert_eq!(index, self.regions.len());
self.symbols.push(symbol);
self.regions.push(region);
}
}
#[cfg(test)]
mod test {
use super::*;
use roc_module::symbol::ModuleIds;
use roc_region::all::Position;
use pretty_assertions::{assert_eq, assert_ne};
#[test]
fn scope_contains_introduced() {
let _register_module_debug_names = ModuleIds::default();
let mut scope = Scope::new(ModuleId::ATTR, IdentIds::default());
let region = Region::zero();
let ident = Ident::from("mezolit");
assert!(scope.lookup(&ident, region).is_err());
assert!(scope.introduce(ident.clone(), region).is_ok());
assert!(scope.lookup(&ident, region).is_ok());
}
#[test]
fn second_introduce_shadows() {
let _register_module_debug_names = ModuleIds::default();
let mut scope = Scope::new(ModuleId::ATTR, IdentIds::default());
let region1 = Region::from_pos(Position { offset: 10 });
let region2 = Region::from_pos(Position { offset: 20 });
let ident = Ident::from("mezolit");
assert!(scope.lookup(&ident, Region::zero()).is_err());
let first = scope.introduce(ident.clone(), region1).unwrap();
let (original_region, _ident, shadow_symbol) =
scope.introduce(ident.clone(), region2).unwrap_err();
scope.register_debug_idents();
assert_ne!(first, shadow_symbol);
assert_eq!(original_region, region1);
let lookup = scope.lookup(&ident, Region::zero()).unwrap();
assert_eq!(first, lookup);
}
#[test]
fn inner_scope_does_not_influence_outer() {
let _register_module_debug_names = ModuleIds::default();
let mut scope = Scope::new(ModuleId::ATTR, IdentIds::default());
let region = Region::zero();
let ident = Ident::from("uránia");
assert!(scope.lookup(&ident, region).is_err());
scope.inner_scope(|inner| {
assert!(inner.introduce(ident.clone(), region).is_ok());
});
assert!(scope.lookup(&ident, region).is_err());
}
#[test]
fn idents_with_inner_scope() {
let _register_module_debug_names = ModuleIds::default();
let mut scope = Scope::new(ModuleId::ATTR, IdentIds::default());
let idents: Vec<_> = scope.idents_in_scope().collect();
assert_eq!(
&idents,
&[
Ident::from("Box"),
Ident::from("Set"),
Ident::from("Dict"),
Ident::from("Str"),
Ident::from("Ok"),
Ident::from("False"),
Ident::from("List"),
Ident::from("True"),
Ident::from("Err"),
]
);
let builtin_count = idents.len();
let region = Region::zero();
let ident1 = Ident::from("uránia");
let ident2 = Ident::from("malmok");
let ident3 = Ident::from("Járnak");
scope.introduce(ident1.clone(), region).unwrap();
scope.introduce(ident2.clone(), region).unwrap();
scope.introduce(ident3.clone(), region).unwrap();
let idents: Vec<_> = scope.idents_in_scope().collect();
assert_eq!(
&idents[builtin_count..],
&[ident1.clone(), ident2.clone(), ident3.clone(),]
);
scope.inner_scope(|inner| {
let ident4 = Ident::from("Ångström");
let ident5 = Ident::from("Sirály");
inner.introduce(ident4.clone(), region).unwrap();
inner.introduce(ident5.clone(), region).unwrap();
let idents: Vec<_> = inner.idents_in_scope().collect();
assert_eq!(
&idents[builtin_count..],
&[
ident1.clone(),
ident2.clone(),
ident3.clone(),
ident4,
ident5
]
);
});
let idents: Vec<_> = scope.idents_in_scope().collect();
assert_eq!(&idents[builtin_count..], &[ident1, ident2, ident3,]);
}
}

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.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,170 @@
/// 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<u16>,
offsets: Vec<u32>,
}
impl std::fmt::Debug for SmallStringInterner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let strings: Vec<_> = self.iter().collect();
f.debug_struct("SmallStringInterner")
.field("buffer", &self.buffer)
.field("lengths", &self.lengths)
.field("offsets", &self.offsets)
.field("strings", &strings)
.finish()
}
}
impl SmallStringInterner {
pub fn with_capacity(capacity: usize) -> Self {
Self {
// guess: the average symbol length is 5
buffer: Vec::with_capacity(5 * capacity),
lengths: Vec::with_capacity(capacity),
offsets: Vec::with_capacity(capacity),
}
}
pub const fn from_parts(buffer: Vec<u8>, lengths: Vec<u16>, offsets: Vec<u32>) -> Self {
Self {
buffer,
lengths,
offsets,
}
}
pub fn iter(&self) -> impl Iterator<Item = &str> {
(0..self.offsets.len()).map(move |index| self.get(index))
}
pub fn insert(&mut self, string: &str) -> usize {
let bytes = string.as_bytes();
assert!(bytes.len() < u16::MAX as usize);
let offset = self.buffer.len() as u32;
let length = bytes.len() as u16;
let index = self.lengths.len();
self.lengths.push(length);
self.offsets.push(offset);
self.buffer.extend(bytes);
index
}
/// Insert a string equal to the current length into the interner.
///
/// Assuming that normally you don't insert strings consisting of just digits,
/// this is an easy way to create a unique string name. We use this to create
/// unique variable names: variable names cannot start with a digit in the source,
/// so if we insert the current length of `length` as its digits, that is always unique
pub fn insert_index_str(&mut self) -> usize {
use std::io::Write;
let index = self.lengths.len();
let offset = self.buffer.len();
write!(self.buffer, "{}", index).unwrap();
let length = self.buffer.len() - offset;
self.lengths.push(length as u16);
self.offsets.push(offset as u32);
index
}
#[inline(always)]
pub fn find_index(&self, string: &str) -> Option<usize> {
let target_length = string.len() as u16;
// there can be gaps in the parts of the string that we use (because of updates)
// hence we can't just sum the lengths we've seen so far to get the next offset
for (index, length) in self.lengths.iter().enumerate() {
if *length == target_length {
let offset = self.offsets[index];
let slice = &self.buffer[offset as usize..][..*length as usize];
if string.as_bytes() == slice {
return Some(index);
}
}
}
None
}
fn get(&self, index: usize) -> &str {
let length = self.lengths[index] as usize;
let offset = self.offsets[index] as usize;
let bytes = &self.buffer[offset..][..length];
unsafe { std::str::from_utf8_unchecked(bytes) }
}
pub fn try_get(&self, index: usize) -> Option<&str> {
if index < self.lengths.len() {
Some(self.get(index))
} else {
None
}
}
pub fn update(&mut self, index: usize, new_string: &str) {
let length = new_string.len();
let offset = self.buffer.len();
// future optimization idea: if the current name bytes are at the end of
// `buffer`, we can update them in-place
self.buffer.extend(new_string.bytes());
self.lengths[index] = length as u16;
self.offsets[index] = offset as u32;
}
pub fn find_and_update(&mut self, old_string: &str, new_string: &str) -> Option<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,95 @@
#[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
}
}
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()
}
}
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,13 @@ use arrayvec::ArrayVec;
use roc_can::constraint::{Constraint, Constraints};
use roc_can::expected::Expected::{self, *};
use roc_can::num::{FloatBound, FloatWidth, IntBound, IntWidth, NumericBound, SignDemand};
use roc_module::ident::{Lowercase, TagName};
use roc_module::ident::Lowercase;
use roc_module::symbol::Symbol;
use roc_region::all::Region;
use roc_types::subs::Variable;
use roc_types::types::Reason;
use roc_types::types::Type::{self, *};
use roc_types::types::{AliasKind, Category};
use roc_types::types::{Reason, TypeExtension};
#[must_use]
#[inline(always)]
@ -163,14 +163,14 @@ fn builtin_alias(
symbol: Symbol,
type_arguments: Vec<(Lowercase, Type)>,
actual: Box<Type>,
kind: AliasKind,
) -> Type {
Type::Alias {
symbol,
type_arguments,
actual,
lambda_set_variables: vec![],
// TODO(opaques): revisit later
kind: AliasKind::Structural,
kind,
}
}
@ -180,49 +180,48 @@ pub fn num_float(range: Type) -> Type {
Symbol::NUM_FLOAT,
vec![("range".into(), range.clone())],
Box::new(num_num(num_floatingpoint(range))),
AliasKind::Structural,
)
}
#[inline(always)]
pub fn num_floatingpoint(range: Type) -> Type {
let alias_content = Type::TagUnion(
vec![(
TagName::Private(Symbol::NUM_AT_FLOATINGPOINT),
vec![range.clone()],
)],
TypeExtension::Closed,
);
builtin_alias(
Symbol::NUM_FLOATINGPOINT,
vec![("range".into(), range)],
Box::new(alias_content),
vec![("range".into(), range.clone())],
Box::new(range),
AliasKind::Opaque,
)
}
#[inline(always)]
pub fn num_u32() -> Type {
builtin_alias(Symbol::NUM_U32, vec![], Box::new(num_int(num_unsigned32())))
builtin_alias(
Symbol::NUM_U32,
vec![],
Box::new(num_int(num_unsigned32())),
AliasKind::Structural,
)
}
#[inline(always)]
fn num_unsigned32() -> Type {
let alias_content = Type::TagUnion(
vec![(TagName::Private(Symbol::NUM_AT_UNSIGNED32), vec![])],
TypeExtension::Closed,
);
builtin_alias(Symbol::NUM_UNSIGNED32, vec![], Box::new(alias_content))
builtin_alias(
Symbol::NUM_UNSIGNED32,
vec![],
Box::new(Type::EmptyTagUnion),
AliasKind::Opaque,
)
}
#[inline(always)]
pub fn num_binary64() -> Type {
let alias_content = Type::TagUnion(
vec![(TagName::Private(Symbol::NUM_AT_BINARY64), vec![])],
TypeExtension::Closed,
);
builtin_alias(Symbol::NUM_BINARY64, vec![], Box::new(alias_content))
builtin_alias(
Symbol::NUM_BINARY64,
vec![],
Box::new(Type::EmptyTagUnion),
AliasKind::Opaque,
)
}
#[inline(always)]
@ -231,47 +230,37 @@ pub fn num_int(range: Type) -> Type {
Symbol::NUM_INT,
vec![("range".into(), range.clone())],
Box::new(num_num(num_integer(range))),
AliasKind::Structural,
)
}
#[inline(always)]
pub fn num_signed64() -> Type {
let alias_content = Type::TagUnion(
vec![(TagName::Private(Symbol::NUM_AT_SIGNED64), vec![])],
TypeExtension::Closed,
);
builtin_alias(Symbol::NUM_SIGNED64, vec![], Box::new(alias_content))
builtin_alias(
Symbol::NUM_SIGNED64,
vec![],
Box::new(Type::EmptyTagUnion),
AliasKind::Opaque,
)
}
#[inline(always)]
pub fn num_integer(range: Type) -> Type {
let alias_content = Type::TagUnion(
vec![(
TagName::Private(Symbol::NUM_AT_INTEGER),
vec![range.clone()],
)],
TypeExtension::Closed,
);
builtin_alias(
Symbol::NUM_INTEGER,
vec![("range".into(), range)],
Box::new(alias_content),
vec![("range".into(), range.clone())],
Box::new(range),
AliasKind::Opaque,
)
}
#[inline(always)]
pub fn num_num(typ: Type) -> Type {
let alias_content = Type::TagUnion(
vec![(TagName::Private(Symbol::NUM_AT_NUM), vec![typ.clone()])],
TypeExtension::Closed,
);
builtin_alias(
Symbol::NUM_NUM,
vec![("range".into(), typ)],
Box::new(alias_content),
vec![("range".into(), typ.clone())],
Box::new(typ),
AliasKind::Opaque,
)
}

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,
@ -1087,15 +1152,22 @@ pub fn constrain_expr(
}
}
/// Constrain a when branch, returning (variables in pattern, symbols introduced in pattern, pattern constraint, body constraint).
/// We want to constraint all pattern constraints in a "when" before body constraints.
#[inline(always)]
fn constrain_when_branch(
fn constrain_when_branch_help(
constraints: &mut Constraints,
env: &Env,
region: Region,
when_branch: &WhenBranch,
pattern_expected: PExpected<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,
@ -1106,24 +1178,27 @@ fn constrain_when_branch(
let mut state = PatternState {
headers: SendMap::default(),
vars: Vec::with_capacity(1),
constraints: Vec::with_capacity(1),
vars: Vec::with_capacity(2),
constraints: Vec::with_capacity(2),
delayed_is_open_constraints: Vec::new(),
};
// TODO investigate for error messages, is it better to unify all branches with a variable,
// then unify that variable with the expectation?
for loc_pattern in &when_branch.patterns {
for (i, loc_pattern) in when_branch.patterns.iter().enumerate() {
let pattern_expected = pattern_expected(HumanIndex::zero_based(i), loc_pattern.region);
constrain_pattern(
constraints,
env,
&loc_pattern.value,
loc_pattern.region,
pattern_expected.clone(),
pattern_expected,
&mut state,
);
}
if let Some(loc_guard) = &when_branch.guard {
let (pattern_constraints, body_constraints) = if let Some(loc_guard) = &when_branch.guard {
let guard_constraint = constrain_expr(
constraints,
env,
@ -1137,20 +1212,27 @@ fn constrain_when_branch(
);
// must introduce the headers from the pattern before constraining the guard
state
.constraints
.append(&mut state.delayed_is_open_constraints);
let state_constraints = constraints.and_constraint(state.constraints);
let inner = constraints.let_constraint([], [], [], guard_constraint, ret_constraint);
constraints.let_constraint([], state.vars, state.headers, state_constraints, inner)
(state_constraints, inner)
} else {
state
.constraints
.append(&mut state.delayed_is_open_constraints);
let state_constraints = constraints.and_constraint(state.constraints);
constraints.let_constraint(
[],
state.vars,
state.headers,
state_constraints,
ret_constraint,
)
}
(state_constraints, ret_constraint)
};
(
state.vars,
state.headers,
pattern_constraints,
body_constraints,
)
}
fn constrain_field(
@ -1232,6 +1314,7 @@ fn constrain_def_pattern(
headers: SendMap::default(),
vars: Vec::with_capacity(1),
constraints: Vec::with_capacity(1),
delayed_is_open_constraints: vec![],
};
constrain_pattern(
@ -1329,6 +1412,7 @@ fn constrain_typed_def(
headers: SendMap::default(),
vars: Vec::with_capacity(arguments.len()),
constraints: Vec::with_capacity(1),
delayed_is_open_constraints: vec![],
};
let mut vars = Vec::with_capacity(argument_pattern_state.vars.capacity() + 1);
let ret_var = *ret_var;
@ -1464,7 +1548,7 @@ fn constrain_typed_function_arguments(
def: &Def,
def_pattern_state: &mut PatternState,
argument_pattern_state: &mut PatternState,
arguments: &[(Variable, Loc<Pattern>)],
arguments: &[(Variable, AnnotatedMark, Loc<Pattern>)],
arg_types: &[Type],
) {
// ensure type matches the one in the annotation
@ -1475,38 +1559,113 @@ fn constrain_typed_function_arguments(
};
let it = arguments.iter().zip(arg_types.iter()).enumerate();
for (index, ((pattern_var, loc_pattern), loc_ann)) in it {
let pattern_expected = PExpected::ForReason(
PReason::TypedArg {
index: HumanIndex::zero_based(index),
opt_name: opt_label,
},
loc_ann.clone(),
loc_pattern.region,
);
constrain_pattern(
constraints,
env,
&loc_pattern.value,
loc_pattern.region,
pattern_expected,
argument_pattern_state,
);
{
// NOTE: because we perform an equality with part of the signature
// this constraint must be to the def_pattern_state's constraints
def_pattern_state.vars.push(*pattern_var);
let pattern_con = constraints.equal_types_var(
*pattern_var,
Expected::NoExpectation(loc_ann.clone()),
Category::Storage(std::file!(), std::line!()),
for (index, ((pattern_var, annotated_mark, loc_pattern), ann)) in it {
if loc_pattern.value.surely_exhaustive() {
// OPT: we don't need to perform any type-level exhaustiveness checking.
// Check instead only that the pattern unifies with the annotation type.
let pattern_expected = PExpected::ForReason(
PReason::TypedArg {
index: HumanIndex::zero_based(index),
opt_name: opt_label,
},
ann.clone(),
loc_pattern.region,
);
def_pattern_state.constraints.push(pattern_con);
constrain_pattern(
constraints,
env,
&loc_pattern.value,
loc_pattern.region,
pattern_expected,
argument_pattern_state,
);
{
// NOTE: because we perform an equality with part of the signature
// this constraint must be to the def_pattern_state's constraints
def_pattern_state.vars.push(*pattern_var);
let pattern_con = constraints.equal_types_var(
*pattern_var,
Expected::NoExpectation(ann.clone()),
Category::Storage(std::file!(), std::line!()),
loc_pattern.region,
);
def_pattern_state.constraints.push(pattern_con);
}
} else {
// We need to check the types, and run exhaustiveness checking.
let &AnnotatedMark {
annotation_var,
exhaustive,
} = annotated_mark;
def_pattern_state.vars.push(*pattern_var);
def_pattern_state.vars.push(annotation_var);
{
// First, solve the type that the pattern is expecting to match in this
// position.
let pattern_expected = PExpected::NoExpectation(Type::Variable(*pattern_var));
constrain_pattern(
constraints,
env,
&loc_pattern.value,
loc_pattern.region,
pattern_expected,
argument_pattern_state,
);
}
{
// Store the actual type in a variable.
argument_pattern_state
.constraints
.push(constraints.equal_types_var(
annotation_var,
Expected::NoExpectation(ann.clone()),
Category::Storage(file!(), line!()),
Region::zero(),
));
}
{
// let pattern_expected = PExpected::ForReason(
// PReason::TypedArg {
// index: HumanIndex::zero_based(index),
// opt_name: opt_label,
// },
// ann.clone(),
// loc_pattern.region,
// );
// Exhaustiveness-check the type in the pattern against what the
// annotation wants.
let sketched_rows =
sketch_pattern_to_rows(annotation_var, loc_pattern.region, &loc_pattern.value);
let category = loc_pattern.value.category();
let expected = PExpected::ForReason(
PReason::TypedArg {
index: HumanIndex::zero_based(index),
opt_name: opt_label,
},
Type::Variable(*pattern_var),
loc_pattern.region,
);
let exhaustive_constraint = constraints.exhaustive(
annotation_var,
loc_pattern.region,
Err((category, expected)),
sketched_rows,
ExhaustiveContext::BadArg,
exhaustive,
);
argument_pattern_state
.constraints
.push(exhaustive_constraint)
}
}
}
}
@ -1649,18 +1808,18 @@ fn instantiate_rigids(
let mut new_rigid_variables: Vec<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());
}
}
}
@ -1808,9 +1967,9 @@ pub fn rec_defs_help(
headers: SendMap::default(),
vars: Vec::with_capacity(arguments.len()),
constraints: Vec::with_capacity(1),
delayed_is_open_constraints: vec![],
};
let mut vars = Vec::with_capacity(state.vars.capacity() + 1);
let mut pattern_types = Vec::with_capacity(state.vars.capacity());
let ret_var = *ret_var;
let closure_var = *closure_var;
let closure_ext_var = *closure_ext_var;
@ -1820,53 +1979,16 @@ pub fn rec_defs_help(
vars.push(closure_var);
vars.push(closure_ext_var);
let it = arguments.iter().zip(arg_types.iter()).enumerate();
for (index, ((pattern_var, loc_pattern), loc_ann)) in it {
{
// ensure type matches the one in the annotation
let opt_label =
if let Pattern::Identifier(label) = def.loc_pattern.value {
Some(label)
} else {
None
};
let pattern_type: &Type = loc_ann;
let pattern_expected = PExpected::ForReason(
PReason::TypedArg {
index: HumanIndex::zero_based(index),
opt_name: opt_label,
},
pattern_type.clone(),
loc_pattern.region,
);
constrain_pattern(
constraints,
env,
&loc_pattern.value,
loc_pattern.region,
pattern_expected,
&mut state,
);
}
{
// NOTE: because we perform an equality with part of the signature
// this constraint must be to the def_pattern_state's constraints
def_pattern_state.vars.push(*pattern_var);
pattern_types.push(Type::Variable(*pattern_var));
let pattern_con = constraints.equal_types_var(
*pattern_var,
Expected::NoExpectation(loc_ann.clone()),
Category::Storage(std::file!(), std::line!()),
loc_pattern.region,
);
def_pattern_state.constraints.push(pattern_con);
}
}
constrain_typed_function_arguments(
constraints,
env,
def,
&mut def_pattern_state,
&mut state,
arguments,
arg_types,
);
let pattern_types = arguments.iter().map(|a| Type::Variable(a.0)).collect();
let closure_constraint = constrain_closure_size(
constraints,

View File

@ -2,12 +2,14 @@ use roc_builtins::std::StdLib;
use roc_can::abilities::AbilitiesStore;
use roc_can::constraint::{Constraint, Constraints};
use roc_can::def::Declaration;
use roc_can::expected::Expected;
use roc_collections::all::MutMap;
use roc_error_macros::internal_error;
use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::Loc;
use roc_region::all::{Loc, Region};
use roc_types::solved_types::{FreeVars, SolvedType};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::{Category, Type};
/// The types of all exposed values/functions of a collection of modules
#[derive(Clone, Debug, Default)]
@ -65,17 +67,8 @@ impl ExposedForModule {
let mut imported_values = Vec::new();
for symbol in it {
// Today, builtins are not actually imported,
// but generated in each module that uses them
//
// This will change when we write builtins in roc
if symbol.is_builtin() {
continue;
}
if let Some(ExposedModuleTypes::Valid { .. }) =
exposed_by_module.exposed.get(&symbol.module_id())
{
let module = exposed_by_module.exposed.get(&symbol.module_id());
if let Some(ExposedModuleTypes::Valid { .. }) = module {
imported_values.push(*symbol);
} else {
continue;
@ -105,27 +98,53 @@ pub fn constrain_module(
declarations: &[Declaration],
home: ModuleId,
) -> Constraint {
let mut constraint = crate::expr::constrain_decls(constraints, home, declarations);
let constraint = crate::expr::constrain_decls(constraints, home, declarations);
let constraint = frontload_ability_constraints(constraints, abilities_store, constraint);
// The module constraint should always save the environment at the end.
debug_assert!(constraints.contains_save_the_environment(&constraint));
constraint
}
pub fn frontload_ability_constraints(
constraints: &mut Constraints,
abilities_store: &AbilitiesStore,
mut constraint: Constraint,
) -> Constraint {
for (member_name, member_data) in abilities_store.root_ability_members().iter() {
// 1. Attach the type of member signature to the reserved signature_var. This is
// infallible.
let unify_with_signature_var = constraints.equal_types_var(
member_data.signature_var,
Expected::NoExpectation(member_data.signature.clone()),
Category::Storage(std::file!(), std::column!()),
Region::zero(),
);
// 2. Store the member signature on the member symbol. This makes sure we generalize it on
// the toplevel, as appropriate.
let vars = &member_data.variables;
let rigids = (vars.rigid_vars.iter())
// For our purposes, in the let constraint, able vars are treated like rigids.
.chain(vars.able_vars.iter())
.copied();
let flex = vars.flex_vars.iter().copied();
constraint = constraints.let_constraint(
let let_constr = constraints.let_constraint(
rigids,
flex,
[(*member_name, Loc::at_zero(member_data.signature.clone()))],
[(
*member_name,
Loc::at_zero(Type::Variable(member_data.signature_var)),
)],
Constraint::True,
constraint,
);
constraint = constraints.and_constraint([unify_with_signature_var, let_constr]);
}
// The module constraint should always save the environment at the end.
debug_assert!(constraints.contains_save_the_environment(&constraint));
constraint
}
@ -170,17 +189,6 @@ pub fn constrain_builtin_imports(
}
}
None => {
let is_valid_alias = stdlib.applies.contains(&symbol)
// This wasn't a builtin value or Apply; maybe it was a builtin alias.
|| roc_types::builtin_aliases::aliases().contains_key(&symbol);
if !is_valid_alias {
panic!(
"Could not find {:?} in builtin types {:?} or builtin aliases",
symbol, stdlib.types,
);
}
continue;
}
};

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
@ -180,7 +181,7 @@ pub fn constrain_pattern(
// so, we know that "x" (in this case, a tag union) must be open.
if could_be_a_tag_union(expected.get_type_ref()) {
state
.constraints
.delayed_is_open_constraints
.push(constraints.is_open_type(expected.get_type()));
}
}
@ -191,7 +192,7 @@ pub fn constrain_pattern(
Identifier(symbol) | Shadowed(_, _, symbol) => {
if could_be_a_tag_union(expected.get_type_ref()) {
state
.constraints
.delayed_is_open_constraints
.push(constraints.is_open_type(expected.get_type_ref().clone()));
}
@ -494,6 +495,9 @@ pub fn constrain_pattern(
state.vars.push(*ext_var);
state.constraints.push(whole_con);
state.constraints.push(tag_con);
state
.constraints
.append(&mut state.delayed_is_open_constraints);
}
UnwrappedOpaque {

View File

@ -2,7 +2,10 @@
//! http://moscova.inria.fr/~maranget/papers/warn/warn.pdf
use roc_collections::all::{HumanIndex, MutMap};
use roc_module::ident::{Lowercase, TagIdIntType, TagName};
use roc_module::{
ident::{Lowercase, TagIdIntType, TagName},
symbol::Symbol,
};
use roc_region::all::Region;
use roc_std::RocDec;
@ -15,9 +18,9 @@ pub struct Union {
}
impl Union {
pub fn newtype_wrapper(tag_name: TagName, arity: usize) -> Self {
pub fn newtype_wrapper(name: CtorName, arity: usize) -> Self {
let alternatives = vec![Ctor {
name: tag_name,
name,
tag_id: TagId(0),
arity,
}];
@ -40,9 +43,24 @@ pub enum RenderAs {
#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)]
pub struct TagId(pub TagIdIntType);
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum CtorName {
Tag(TagName),
Opaque(Symbol),
}
impl CtorName {
pub fn is_tag(&self, tag_name: &TagName) -> bool {
match self {
Self::Tag(test) => test == tag_name,
_ => false,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Ctor {
pub name: TagName,
pub name: CtorName,
pub tag_id: TagId,
pub arity: usize,
}
@ -54,12 +72,13 @@ pub enum Pattern {
Ctor(Union, TagId, std::vec::Vec<Pattern>),
}
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Literal {
Int(i128),
U128(u128),
Bit(bool),
Byte(u8),
/// Stores the float bits
Float(u64),
Decimal(RocDec),
Str(Box<str>),
@ -77,14 +96,14 @@ pub enum Error {
},
}
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Context {
BadArg,
BadDestruct,
BadCase,
}
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Guard {
HasGuard,
NoGuard,

View File

@ -468,9 +468,7 @@ impl<'a> Formattable for Tag<'a> {
use self::Tag::*;
match self {
Global { args, .. } | Private { args, .. } => {
args.iter().any(|arg| (&arg.value).is_multiline())
}
Apply { args, .. } => args.iter().any(|arg| (&arg.value).is_multiline()),
Tag::SpaceBefore(_, _) | Tag::SpaceAfter(_, _) => true,
Malformed(text) => text.chars().any(|c| c == '\n'),
}
@ -486,25 +484,7 @@ impl<'a> Formattable for Tag<'a> {
let is_multiline = self.is_multiline();
match self {
Tag::Global { name, args } => {
buf.indent(indent);
buf.push_str(name.value);
if is_multiline {
let arg_indent = indent + INDENT;
for arg in *args {
buf.newline();
arg.format_with_options(buf, Parens::InApply, Newlines::No, arg_indent);
}
} else {
for arg in *args {
buf.spaces(1);
arg.format_with_options(buf, Parens::InApply, Newlines::No, indent);
}
}
}
Tag::Private { name, args } => {
debug_assert!(name.value.starts_with('@'));
Tag::Apply { name, args } => {
buf.indent(indent);
buf.push_str(name.value);
if is_multiline {

View File

@ -191,12 +191,26 @@ pub fn fmt_body<'a, 'buf>(
buf.push_str(" =");
if body.is_multiline() {
match body {
Expr::SpaceBefore(_, _) => {
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT);
}
Expr::Record { .. } | Expr::List { .. } => {
buf.newline();
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT);
Expr::SpaceBefore(sub_def, spaces) => {
let should_outdent = match sub_def {
Expr::Record { .. } | Expr::List { .. } => {
let is_only_newlines = spaces.iter().all(|s| s.is_newline());
is_only_newlines && sub_def.is_multiline()
}
_ => false,
};
if should_outdent {
buf.spaces(1);
sub_def.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
} else {
body.format_with_options(
buf,
Parens::NotNeeded,
Newlines::Yes,
indent + INDENT,
);
}
}
_ => {
buf.spaces(1);

View File

@ -37,8 +37,7 @@ impl<'a> Formattable for Expr<'a> {
| Underscore { .. }
| MalformedIdent(_, _)
| MalformedClosure
| GlobalTag(_)
| PrivateTag(_)
| Tag(_)
| OpaqueRef(_) => false,
// These expressions always have newlines
@ -118,25 +117,16 @@ impl<'a> Formattable for Expr<'a> {
use self::Expr::*;
//dbg!(self);
let format_newlines = newlines == Newlines::Yes;
let apply_needs_parens = parens == Parens::InApply;
match self {
SpaceBefore(sub_expr, spaces) => {
if format_newlines {
fmt_spaces(buf, spaces.iter(), indent);
} else {
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
}
format_spaces(buf, spaces, newlines, indent);
sub_expr.format_with_options(buf, parens, newlines, indent);
}
SpaceAfter(sub_expr, spaces) => {
sub_expr.format_with_options(buf, parens, newlines, indent);
if format_newlines {
fmt_spaces(buf, spaces.iter(), indent);
} else {
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
}
format_spaces(buf, spaces, newlines, indent);
}
ParensAround(sub_expr) => {
if parens == Parens::NotNeeded && !sub_expr_requests_parens(sub_expr) {
@ -187,13 +177,73 @@ impl<'a> Formattable for Expr<'a> {
let multiline_args = loc_args.iter().any(|loc_arg| loc_arg.is_multiline());
if multiline_args {
let mut found_multiline_expr = false;
let mut iter = loc_args.iter().peekable();
while let Some(loc_arg) = iter.next() {
if iter.peek().is_none() {
found_multiline_expr = match loc_arg.value {
SpaceBefore(sub_expr, spaces) => match sub_expr {
Record { .. } | List { .. } => {
let is_only_newlines = spaces.iter().all(|s| s.is_newline());
is_only_newlines
&& !found_multiline_expr
&& sub_expr.is_multiline()
}
_ => false,
},
Record { .. } | List { .. } | Closure { .. } => {
!found_multiline_expr && loc_arg.is_multiline()
}
_ => false,
}
} else {
found_multiline_expr = loc_arg.is_multiline();
}
}
let should_outdent_last_arg = found_multiline_expr;
if multiline_args && !should_outdent_last_arg {
let arg_indent = indent + INDENT;
for loc_arg in loc_args.iter() {
buf.newline();
loc_arg.format_with_options(buf, Parens::InApply, Newlines::No, arg_indent);
}
} else if multiline_args && should_outdent_last_arg {
let mut iter = loc_args.iter().peekable();
while let Some(loc_arg) = iter.next() {
buf.spaces(1);
if iter.peek().is_none() {
match loc_arg.value {
SpaceBefore(sub_expr, _) => {
sub_expr.format_with_options(
buf,
Parens::InApply,
Newlines::Yes,
indent,
);
}
_ => {
loc_arg.format_with_options(
buf,
Parens::InApply,
Newlines::Yes,
indent,
);
}
}
} else {
loc_arg.format_with_options(
buf,
Parens::InApply,
Newlines::Yes,
indent,
);
}
}
} else {
for loc_arg in loc_args.iter() {
buf.spaces(1);
@ -213,7 +263,7 @@ impl<'a> Formattable for Expr<'a> {
buf.indent(indent);
buf.push_str(string);
}
GlobalTag(string) | PrivateTag(string) | OpaqueRef(string) => {
Tag(string) | OpaqueRef(string) => {
buf.indent(indent);
buf.push_str(string)
}
@ -262,15 +312,39 @@ impl<'a> Formattable for Expr<'a> {
fmt_def(buf, &loc_def.value, indent);
}
let empty_line_before_return = empty_line_before_expr(&ret.value);
match &ret.value {
SpaceBefore(sub_expr, spaces) => {
let empty_line_before_return = empty_line_before_expr(&ret.value);
let has_inline_comment = with_inline_comment(&ret.value);
if !empty_line_before_return {
buf.newline();
if has_inline_comment {
buf.spaces(1);
format_spaces(buf, spaces, newlines, indent);
if !empty_line_before_return {
buf.newline();
}
sub_expr.format_with_options(
buf,
Parens::NotNeeded,
Newlines::Yes,
indent,
);
} else {
if !empty_line_before_return {
buf.newline();
}
ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
}
}
_ => {
// Even if there were no defs, which theoretically should never happen,
// still print the return value.
ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
}
}
// Even if there were no defs, which theoretically should never happen,
// still print the return value.
ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
}
Expect(condition, continuation) => {
fmt_expect(buf, condition, continuation, self.is_multiline(), indent);
@ -372,7 +446,6 @@ fn push_op(buf: &mut Buf, op: BinOp) {
called_via::BinOp::Slash => buf.push('/'),
called_via::BinOp::DoubleSlash => buf.push_str("//"),
called_via::BinOp::Percent => buf.push('%'),
called_via::BinOp::DoublePercent => buf.push_str("%%"),
called_via::BinOp::Plus => buf.push('+'),
called_via::BinOp::Minus => buf.push('-'),
called_via::BinOp::Equals => buf.push_str("=="),
@ -483,6 +556,34 @@ fn fmt_bin_ops<'a, 'buf>(
loc_right_side.format_with_options(buf, apply_needs_parens, Newlines::Yes, next_indent);
}
fn format_spaces<'a, 'buf>(
buf: &mut Buf<'buf>,
spaces: &[CommentOrNewline<'a>],
newlines: Newlines,
indent: u16,
) {
let format_newlines = newlines == Newlines::Yes;
if format_newlines {
fmt_spaces(buf, spaces.iter(), indent);
} else {
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
}
}
fn with_inline_comment<'a>(expr: &'a Expr<'a>) -> bool {
use roc_parse::ast::Expr::*;
match expr {
SpaceBefore(_, spaces) => match spaces.iter().next() {
Some(CommentOrNewline::LineComment(_)) => true,
Some(_) => false,
None => false,
},
_ => false,
}
}
fn empty_line_before_expr<'a>(expr: &'a Expr<'a>) -> bool {
use roc_parse::ast::Expr::*;
@ -847,7 +948,34 @@ fn fmt_closure<'a, 'buf>(
}
};
loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, body_indent);
if is_multiline {
match &loc_ret.value {
SpaceBefore(sub_expr, spaces) => {
let should_outdent = match sub_expr {
Record { .. } | List { .. } => {
let is_only_newlines = spaces.iter().all(|s| s.is_newline());
is_only_newlines && sub_expr.is_multiline()
}
_ => false,
};
if should_outdent {
buf.spaces(1);
sub_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
} else {
loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, body_indent);
}
}
Record { .. } | List { .. } => {
loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
}
_ => {
loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, body_indent);
}
}
} else {
loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, body_indent);
}
}
fn fmt_backpassing<'a, 'buf>(
@ -1104,7 +1232,6 @@ fn sub_expr_requests_parens(expr: &Expr<'_>) -> bool {
| BinOp::Slash
| BinOp::DoubleSlash
| BinOp::Percent
| BinOp::DoublePercent
| BinOp::Plus
| BinOp::Minus
| BinOp::Equals

View File

@ -28,8 +28,7 @@ impl<'a> Formattable for Pattern<'a> {
Pattern::OptionalField(_, expr) => expr.is_multiline(),
Pattern::Identifier(_)
| Pattern::GlobalTag(_)
| Pattern::PrivateTag(_)
| Pattern::Tag(_)
| Pattern::OpaqueRef(_)
| Pattern::Apply(_, _)
| Pattern::NumLiteral(..)
@ -58,7 +57,7 @@ impl<'a> Formattable for Pattern<'a> {
buf.indent(indent);
buf.push_str(string)
}
GlobalTag(name) | PrivateTag(name) | OpaqueRef(name) => {
Tag(name) | OpaqueRef(name) => {
buf.indent(indent);
buf.push_str(name);
}

View File

@ -106,26 +106,61 @@ mod test_fmt {
}
#[test]
#[ignore]
fn def_with_comment_on_same_line() {
// TODO(joshuawarner32): make trailing comments format stabily
// This test currently fails because the comment ends up as SpaceBefore for the following `a`
// This works fine when formatted _once_ - but if you format again, the formatter wants to
// insert a newline between `a = "Hello"` and the comment, further muddying the waters.
// Clearly the formatter shouldn't be allowed to migrate a comment around like that.
fn def_with_inline_comment() {
expr_formats_same(indoc!(
r#"
x = 0 # comment
x
"#
));
expr_formats_to(
indoc!(
r#"
a = "Hello" # This variable is for greeting
x = 0# comment
a
x
"#
),
indoc!(
r#"
a = "Hello"
# This variable is for greeting
a
x = 0 # comment
x
"#
),
);
expr_formats_to(
indoc!(
r#"
x = 0# comment
x
"#
),
indoc!(
r#"
x = 0 # comment
x
"#
),
);
expr_formats_to(
indoc!(
r#"
x = 0 # comment
x
"#
),
indoc!(
r#"
x = 0 # comment
x
"#
),
);
@ -626,6 +661,392 @@ mod test_fmt {
));
}
#[test]
fn lambda_returns_record() {
expr_formats_same(indoc!(
r#"
toRecord = \_ -> {
x: 1,
y: 2,
z: 3,
}
toRecord
"#
));
expr_formats_same(indoc!(
r#"
func = \_ ->
{ x: 1, y: 2, z: 3 }
func
"#
));
expr_formats_same(indoc!(
r#"
toRecord = \_ ->
val = 0
{
x: 1,
y: 2,
z: 3,
}
toRecord
"#
));
expr_formats_to(
indoc!(
r#"
toRecord = \_ ->
{
x: 1,
y: 2,
z: 3,
}
toRecord
"#
),
indoc!(
r#"
toRecord = \_ -> {
x: 1,
y: 2,
z: 3,
}
toRecord
"#
),
);
}
#[test]
fn lambda_returns_list() {
expr_formats_same(indoc!(
r#"
toList = \_ -> [
1,
2,
3,
]
toList
"#
));
expr_formats_same(indoc!(
r#"
func = \_ ->
[ 1, 2, 3 ]
func
"#
));
expr_formats_same(indoc!(
r#"
toList = \_ ->
val = 0
[
1,
2,
3,
]
toList
"#
));
expr_formats_to(
indoc!(
r#"
toList = \_ ->
[
1,
2,
3,
]
toList
"#
),
indoc!(
r#"
toList = \_ -> [
1,
2,
3,
]
toList
"#
),
);
}
#[test]
fn multiline_list_func_arg() {
expr_formats_same(indoc!(
r#"
result = func arg [
1,
2,
3,
]
result
"#
));
expr_formats_to(
indoc!(
r#"
result = func arg
[ 1, 2, 3 ]
result
"#
),
indoc!(
r#"
result = func
arg
[ 1, 2, 3 ]
result
"#
),
);
expr_formats_to(
indoc!(
r#"
result = func arg [
1,
2,
3,
]
result
"#
),
indoc!(
r#"
result = func arg [
1,
2,
3,
]
result
"#
),
);
expr_formats_to(
indoc!(
r#"
result = func [
1,
2,
3,
]
arg
result
"#
),
indoc!(
r#"
result = func
[
1,
2,
3,
]
arg
result
"#
),
);
expr_formats_to(
indoc!(
r#"
result = func arg
[
1,
2,
3,
]
result
"#
),
indoc!(
r#"
result = func arg [
1,
2,
3,
]
result
"#
),
);
expr_formats_same(indoc!(
r#"
result = func
arg
[
1,
2,
3,
]
result
"#
));
}
#[test]
fn multiline_record_func_arg() {
expr_formats_same(indoc!(
r#"
result = func arg {
x: 1,
y: 2,
z: 3,
}
result
"#
));
expr_formats_to(
indoc!(
r#"
result = func arg
{ x: 1, y: 2, z: 3 }
result
"#
),
indoc!(
r#"
result = func
arg
{ x: 1, y: 2, z: 3 }
result
"#
),
);
expr_formats_to(
indoc!(
r#"
result = func arg {
x: 1,
y: 2,
z: 3,
}
result
"#
),
indoc!(
r#"
result = func arg {
x: 1,
y: 2,
z: 3,
}
result
"#
),
);
expr_formats_to(
indoc!(
r#"
result = func {
x: 1,
y: 2,
z: 3,
}
arg
result
"#
),
indoc!(
r#"
result = func
{
x: 1,
y: 2,
z: 3,
}
arg
result
"#
),
);
expr_formats_to(
indoc!(
r#"
result = func arg
{
x: 1,
y: 2,
z: 3,
}
result
"#
),
indoc!(
r#"
result = func arg {
x: 1,
y: 2,
z: 3,
}
result
"#
),
);
expr_formats_same(indoc!(
r#"
result = func
arg
{
x: 1,
y: 2,
z: 3,
}
result
"#
));
}
#[test]
fn record_updating() {
expr_formats_same(indoc!(
@ -1301,6 +1722,27 @@ mod test_fmt {
fn multi_line_list_def() {
expr_formats_same(indoc!(
r#"
l = [
1,
2,
]
l
"#
));
expr_formats_same(indoc!(
r#"
l =
[ 1, 2 ]
l
"#
));
expr_formats_to(
indoc!(
r#"
l =
[
1,
@ -1308,8 +1750,19 @@ mod test_fmt {
]
l
"#
));
"#
),
indoc!(
r#"
l = [
1,
2,
]
l
"#
),
);
expr_formats_to(
indoc!(
@ -1324,11 +1777,10 @@ mod test_fmt {
),
indoc!(
r#"
results =
[
Ok 4,
Ok 5,
]
results = [
Ok 4,
Ok 5,
]
allOks results
"#
@ -1417,18 +1869,69 @@ mod test_fmt {
#[test]
fn multi_line_record_def() {
expr_formats_same(indoc!(
r#"
pos = {
x: 4,
y: 11,
z: 16,
}
pos
"#
));
expr_formats_same(indoc!(
r#"
pos =
{ x: 4, y: 11, z: 16 }
pos
"#
));
expr_formats_same(indoc!(
r#"
myDef =
list = [
a,
b,
]
{
c,
d,
}
myDef
"#
));
expr_formats_to(
indoc!(
r#"
pos =
{
x: 4,
y: 11,
z: 16,
}
pos
"#
),
indoc!(
r#"
pos = {
x: 4,
y: 11,
z: 16,
}
pos
"#
));
pos
"#
),
);
expr_formats_to(
indoc!(
@ -1443,11 +1946,10 @@ mod test_fmt {
),
indoc!(
r#"
pos =
{
x: 5,
y: 10,
}
pos = {
x: 5,
y: 10,
}
pos
"#
@ -2537,7 +3039,7 @@ mod test_fmt {
indoc!(
r#"
2 % 3
%% 5
// 5
+ 7
"#
),
@ -2545,7 +3047,7 @@ mod test_fmt {
r#"
2
% 3
%% 5
// 5
+ 7
"#
),
@ -2619,6 +3121,18 @@ mod test_fmt {
));
}
#[test]
fn func_call_trailing_multiline_lambda() {
expr_formats_same(indoc!(
r#"
list = List.map [ 1, 2, 3 ] \x ->
x + 1
list
"#
));
}
// MODULES
#[test]

View File

@ -19,7 +19,7 @@ roc_mono = { path = "../mono" }
roc_target = { path = "../roc_target" }
roc_error_macros = { path = "../../error_macros" }
bumpalo = { version = "3.8.0", features = ["collections"] }
target-lexicon = "0.12.2"
target-lexicon = "0.12.3"
# TODO: Deal with the update of object to 0.27.
# It looks like it breaks linking the generated objects.
# Probably just need to specify an extra field that used to be implicit or something.

View File

@ -913,10 +913,7 @@ trait Backend<'a> {
TagName::Closure(sym) => {
self.set_last_seen(*sym, stmt);
}
TagName::Private(sym) => {
self.set_last_seen(*sym, stmt);
}
TagName::Global(_) => {}
TagName::Tag(_) => {}
}
for sym in *arguments {
self.set_last_seen(*sym, stmt);

View File

@ -18,4 +18,4 @@ roc_std = { path = "../../roc_std", default-features = false }
morphic_lib = { path = "../../vendor/morphic_lib" }
bumpalo = { version = "3.8.0", features = ["collections"] }
inkwell = { path = "../../vendor/inkwell" }
target-lexicon = "0.12.2"
target-lexicon = "0.12.3"

View File

@ -30,6 +30,7 @@ use crate::llvm::refcounting::{
};
use bumpalo::collections::Vec;
use bumpalo::Bump;
use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::basic_block::BasicBlock;
use inkwell::builder::Builder;
use inkwell::context::Context;
@ -40,7 +41,7 @@ use inkwell::memory_buffer::MemoryBuffer;
use inkwell::module::{Linkage, Module};
use inkwell::passes::{PassManager, PassManagerBuilder};
use inkwell::types::{
BasicMetadataTypeEnum, BasicType, BasicTypeEnum, FunctionType, IntType, StructType,
AnyType, BasicMetadataTypeEnum, BasicType, BasicTypeEnum, FunctionType, IntType, StructType,
};
use inkwell::values::BasicValueEnum::{self, *};
use inkwell::values::{
@ -63,7 +64,7 @@ use roc_mono::ir::{
ModifyRc, OptLevel, ProcLayout,
};
use roc_mono::layout::{Builtin, LambdaSet, Layout, LayoutIds, TagIdIntType, UnionLayout};
use roc_target::TargetInfo;
use roc_target::{PtrWidth, TargetInfo};
use target_lexicon::{Architecture, OperatingSystem, Triple};
use super::convert::zig_with_overflow_roc_dec;
@ -469,7 +470,7 @@ fn add_float_intrinsic<'ctx, F>(
if let Some(_) = module.get_function(full_name) {
// zig defined this function already
} else {
add_intrinsic(module, full_name, construct_type($typ));
add_intrinsic(ctx, module, full_name, construct_type($typ));
}
};
}
@ -494,7 +495,7 @@ fn add_int_intrinsic<'ctx, F>(
if let Some(_) = module.get_function(full_name) {
// zig defined this function already
} else {
add_intrinsic(module, full_name, construct_type($typ));
add_intrinsic(ctx, module, full_name, construct_type($typ));
}
};
}
@ -516,12 +517,10 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
// List of all supported LLVM intrinsics:
//
// https://releases.llvm.org/10.0.0/docs/LangRef.html#standard-c-library-intrinsics
let f64_type = ctx.f64_type();
let i1_type = ctx.bool_type();
let i8_type = ctx.i8_type();
let i8_ptr_type = i8_type.ptr_type(AddressSpace::Generic);
let i32_type = ctx.i32_type();
let i64_type = ctx.i64_type();
let void_type = ctx.void_type();
if let Some(func) = module.get_function("__muloti4") {
@ -529,37 +528,31 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
}
add_intrinsic(
ctx,
module,
LLVM_SETJMP,
i32_type.fn_type(&[i8_ptr_type.into()], false),
);
if true {
add_intrinsic(
module,
LLVM_LONGJMP,
void_type.fn_type(&[i8_ptr_type.into()], false),
);
} else {
add_intrinsic(
module,
LLVM_LONGJMP,
void_type.fn_type(&[i8_ptr_type.into(), i32_type.into()], false),
);
}
add_intrinsic(
ctx,
module,
LLVM_LONGJMP,
void_type.fn_type(&[i8_ptr_type.into()], false),
);
add_intrinsic(
ctx,
module,
LLVM_FRAME_ADDRESS,
i8_ptr_type.fn_type(&[i32_type.into()], false),
);
add_intrinsic(module, LLVM_STACK_SAVE, i8_ptr_type.fn_type(&[], false));
add_intrinsic(
ctx,
module,
LLVM_LROUND_I64_F64,
i64_type.fn_type(&[f64_type.into()], false),
LLVM_STACK_SAVE,
i8_ptr_type.fn_type(&[], false),
);
add_float_intrinsic(ctx, module, &LLVM_LOG, |t| t.fn_type(&[t.into()], false));
@ -613,9 +606,7 @@ static LLVM_FLOOR: IntrinsicName = float_intrinsic!("llvm.floor");
static LLVM_MEMSET_I64: &str = "llvm.memset.p0i8.i64";
static LLVM_MEMSET_I32: &str = "llvm.memset.p0i8.i32";
static LLVM_LROUND_I64_F64: &str = "llvm.lround.i64.f64";
// static LLVM_FRAME_ADDRESS: &str = "llvm.frameaddress";
static LLVM_FRAME_ADDRESS: &str = "llvm.frameaddress.p0i8";
static LLVM_STACK_SAVE: &str = "llvm.stacksave";
@ -633,18 +624,17 @@ const LLVM_ADD_SATURATED: IntrinsicName = llvm_int_intrinsic!("llvm.sadd.sat", "
const LLVM_SUB_SATURATED: IntrinsicName = llvm_int_intrinsic!("llvm.ssub.sat", "llvm.usub.sat");
fn add_intrinsic<'ctx>(
context: &Context,
module: &Module<'ctx>,
intrinsic_name: &str,
fn_type: FunctionType<'ctx>,
) -> FunctionValue<'ctx> {
add_func(
context,
module,
intrinsic_name,
fn_type,
FunctionSpec::intrinsic(fn_type),
Linkage::External,
// LLVM intrinsics always use the C calling convention, because
// they are implemented in C libraries
C_CALL_CONV,
)
}
@ -3256,32 +3246,29 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>(
// let mut argument_types = roc_function.get_type().get_param_types();
let mut argument_types = cc_argument_types;
let c_function_type = match roc_function.get_type().get_return_type() {
match roc_function.get_type().get_return_type() {
None => {
// this function already returns by-pointer
let output_type = roc_function.get_type().get_param_types().pop().unwrap();
argument_types.insert(0, output_type);
env.context
.void_type()
.fn_type(&function_arguments(env, &argument_types), false)
}
Some(return_type) => {
let output_type = return_type.ptr_type(AddressSpace::Generic);
argument_types.insert(0, output_type.into());
env.context
.void_type()
.fn_type(&function_arguments(env, &argument_types), false)
}
};
}
// This is not actually a function that returns a value but then became
// return-by-pointer do to the calling convention. Instead, here we
// explicitly are forcing the passing of values via the first parameter
// pointer, since they are generic and hence opaque to anything outside roc.
let c_function_spec = FunctionSpec::cconv(env, CCReturn::Void, None, &argument_types);
let c_function = add_func(
env.context,
env.module,
c_function_name,
c_function_type,
c_function_spec,
Linkage::External,
C_CALL_CONV,
);
let subprogram = env.new_subprogram(c_function_name);
@ -3394,20 +3381,18 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>(
let mut argument_types = cc_argument_types;
let return_type = wrapper_return_type;
let c_function_type = {
let c_function_spec = {
let output_type = return_type.ptr_type(AddressSpace::Generic);
argument_types.push(output_type.into());
env.context
.void_type()
.fn_type(&function_arguments(env, &argument_types), false)
FunctionSpec::cconv(env, CCReturn::Void, None, &argument_types)
};
let c_function = add_func(
env.context,
env.module,
c_function_name,
c_function_type,
c_function_spec,
Linkage::External,
C_CALL_CONV,
);
let subprogram = env.new_subprogram(c_function_name);
@ -3472,15 +3457,20 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>(
builder.build_return(None);
// STEP 3: build a {} -> u64 function that gives the size of the return type
let size_function_type = env.context.i64_type().fn_type(&[], false);
let size_function_spec = FunctionSpec::cconv(
env,
CCReturn::Return,
Some(env.context.i64_type().as_basic_type_enum()),
&[],
);
let size_function_name: String = format!("roc__{}_size", ident_string);
let size_function = add_func(
env.context,
env.module,
size_function_name.as_str(),
size_function_type,
size_function_spec,
Linkage::External,
C_CALL_CONV,
);
let subprogram = env.new_subprogram(&size_function_name);
@ -3512,14 +3502,14 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
let cc_return = to_cc_return(env, &return_layout);
let roc_return = RocReturn::from_layout(env, &return_layout);
let c_function_type = cc_return.to_signature(env, return_type, argument_types.as_slice());
let c_function_spec = FunctionSpec::cconv(env, cc_return, Some(return_type), &argument_types);
let c_function = add_func(
env.context,
env.module,
c_function_name,
c_function_type,
c_function_spec,
Linkage::External,
C_CALL_CONV,
);
let subprogram = env.new_subprogram(c_function_name);
@ -3630,15 +3620,20 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
);
// STEP 3: build a {} -> u64 function that gives the size of the return type
let size_function_type = env.context.i64_type().fn_type(&[], false);
let size_function_spec = FunctionSpec::cconv(
env,
CCReturn::Return,
Some(env.context.i64_type().as_basic_type_enum()),
&[],
);
let size_function_name: String = format!("roc__{}_size", ident_string);
let size_function = add_func(
env.context,
env.module,
size_function_name.as_str(),
size_function_type,
size_function_spec,
Linkage::External,
C_CALL_CONV,
);
let subprogram = env.new_subprogram(&size_function_name);
@ -3664,10 +3659,22 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
}
pub fn get_sjlj_buffer<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValue<'ctx> {
let type_ = env
.context
.i8_type()
.array_type(5 * env.target_info.ptr_width() as u32);
// The size of jump_buf is platform-dependent.
// - AArch64 needs 3 machine-sized words
// - LLVM says the following about the SJLJ intrinsic:
//
// [It is] a five word buffer in which the calling context is saved.
// The front end places the frame pointer in the first word, and the
// target implementation of this intrinsic should place the destination
// address for a llvm.eh.sjlj.longjmp in the second word.
// The following three words are available for use in a target-specific manner.
//
// So, let's create a 5-word buffer.
let word_type = match env.target_info.ptr_width() {
PtrWidth::Bytes4 => env.context.i32_type(),
PtrWidth::Bytes8 => env.context.i64_type(),
};
let type_ = word_type.array_type(5);
let global = match env.module.get_global("roc_sjlj_buffer") {
Some(global) => global,
@ -3679,12 +3686,85 @@ pub fn get_sjlj_buffer<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValu
env.builder
.build_bitcast(
global.as_pointer_value(),
env.context.i8_type().ptr_type(AddressSpace::Generic),
env.context.i32_type().ptr_type(AddressSpace::Generic),
"cast_sjlj_buffer",
)
.into_pointer_value()
}
pub fn build_setjmp_call<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValueEnum<'ctx> {
let jmp_buf = get_sjlj_buffer(env);
if cfg!(target_arch = "aarch64") {
// Due to https://github.com/rtfeldman/roc/issues/2965, we use a setjmp we linked in from Zig
call_bitcode_fn(env, &[jmp_buf.into()], bitcode::UTILS_SETJMP)
} else {
// Anywhere else, use the LLVM intrinsic.
// https://llvm.org/docs/ExceptionHandling.html#llvm-eh-sjlj-setjmp
let jmp_buf_i8p_arr = env
.builder
.build_bitcast(
jmp_buf,
env.context
.i8_type()
.ptr_type(AddressSpace::Generic)
.array_type(5)
.ptr_type(AddressSpace::Generic),
"jmp_buf [5 x i8*]",
)
.into_pointer_value();
// LLVM asks us to please store the frame pointer in the first word.
let frame_address = env.call_intrinsic(
LLVM_FRAME_ADDRESS,
&[env.context.i32_type().const_zero().into()],
);
let zero = env.context.i32_type().const_zero();
let fa_index = env.context.i32_type().const_zero();
let fa = unsafe {
env.builder.build_in_bounds_gep(
jmp_buf_i8p_arr,
&[zero, fa_index],
"frame address index",
)
};
env.builder.build_store(fa, frame_address);
// LLVM says that the target implementation of the setjmp intrinsic will put the
// destination address at index 1, and that the remaining three words are for ad-hoc target
// usage. But for whatever reason, on x86, it appears we need a stacksave in those words.
let ss_index = env.context.i32_type().const_int(2, false);
let ss = unsafe {
env.builder
.build_in_bounds_gep(jmp_buf_i8p_arr, &[zero, ss_index], "name")
};
let stack_save = env.call_intrinsic(LLVM_STACK_SAVE, &[]);
env.builder.build_store(ss, stack_save);
let jmp_buf_i8p = env.builder.build_bitcast(
jmp_buf,
env.context.i8_type().ptr_type(AddressSpace::Generic),
"jmp_buf i8*",
);
env.call_intrinsic(LLVM_SETJMP, &[jmp_buf_i8p])
}
}
/// Pointer to pointer of the panic message.
pub fn get_panic_msg_ptr<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValue<'ctx> {
let ptr_to_u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic);
let global_name = "roc_panic_msg_ptr";
let global = env.module.get_global(global_name).unwrap_or_else(|| {
let global = env.module.add_global(ptr_to_u8_ptr, None, global_name);
global.set_initializer(&ptr_to_u8_ptr.const_zero());
global
});
global.as_pointer_value()
}
fn set_jump_and_catch_long_jump<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
@ -3703,53 +3783,7 @@ fn set_jump_and_catch_long_jump<'a, 'ctx, 'env>(
let catch_block = context.append_basic_block(parent, "catch_block");
let cont_block = context.append_basic_block(parent, "cont_block");
let buffer = get_sjlj_buffer(env);
let cast = env
.builder
.build_bitcast(
buffer,
env.context
.i8_type()
.ptr_type(AddressSpace::Generic)
.array_type(5)
.ptr_type(AddressSpace::Generic),
"to [5 x i8*]",
)
.into_pointer_value();
let zero = env.context.i32_type().const_zero();
let index = env.context.i32_type().const_zero();
let fa = unsafe {
env.builder
.build_in_bounds_gep(cast, &[zero, index], "name")
};
let index = env.context.i32_type().const_int(2, false);
let ss = unsafe {
env.builder
.build_in_bounds_gep(cast, &[zero, index], "name")
};
let index = env.context.i32_type().const_int(3, false);
let error_msg = unsafe {
env.builder
.build_in_bounds_gep(cast, &[zero, index], "name")
};
let frame_address = env.call_intrinsic(
LLVM_FRAME_ADDRESS,
&[env.context.i32_type().const_zero().into()],
);
env.builder.build_store(fa, frame_address);
let stack_save = env.call_intrinsic(LLVM_STACK_SAVE, &[]);
env.builder.build_store(ss, stack_save);
let panicked_u32 = env.call_intrinsic(LLVM_SETJMP, &[buffer.into()]);
let panicked_u32 = build_setjmp_call(env);
let panicked_bool = env.builder.build_int_compare(
IntPredicate::NE,
panicked_u32.into_int_value(),
@ -3779,19 +3813,10 @@ fn set_jump_and_catch_long_jump<'a, 'ctx, 'env>(
let error_msg = {
// u8**
let ptr_int_ptr = builder.build_bitcast(
error_msg,
env.context
.i8_type()
.ptr_type(AddressSpace::Generic)
.ptr_type(AddressSpace::Generic),
"cast",
);
let ptr_int_ptr = get_panic_msg_ptr(env);
// u8* again
let ptr_int = builder.build_load(ptr_int_ptr.into_pointer_value(), "ptr_int");
ptr_int
builder.build_load(ptr_int_ptr, "ptr_int")
};
let return_value = {
@ -3919,16 +3944,20 @@ fn make_exception_catching_wrapper<'a, 'ctx, 'env>(
// argument_types.push(wrapper_return_type.ptr_type(AddressSpace::Generic).into());
// let wrapper_function_type = env.context.void_type().fn_type(&argument_types, false);
let wrapper_function_type =
wrapper_return_type.fn_type(&function_arguments(env, &argument_types), false);
let wrapper_function_spec = FunctionSpec::cconv(
env,
CCReturn::Return,
Some(wrapper_return_type.as_basic_type_enum()),
&argument_types,
);
// Add main to the module.
let wrapper_function = add_func(
env.context,
env.module,
wrapper_function_name,
wrapper_function_type,
wrapper_function_spec,
Linkage::External,
C_CALL_CONV,
);
let subprogram = env.new_subprogram(wrapper_function_name);
@ -4157,23 +4186,15 @@ fn build_proc_header<'a, 'ctx, 'env>(
arg_basic_types.push(arg_type);
}
let fn_type = match RocReturn::from_layout(env, &proc.ret_layout) {
RocReturn::Return => ret_type.fn_type(&function_arguments(env, &arg_basic_types), false),
RocReturn::ByPointer => {
// println!( "{:?} will return void instead of {:?}", symbol, proc.ret_layout);
arg_basic_types.push(ret_type.ptr_type(AddressSpace::Generic).into());
env.context
.void_type()
.fn_type(&function_arguments(env, &arg_basic_types), false)
}
};
let roc_return = RocReturn::from_layout(env, &proc.ret_layout);
let fn_spec = FunctionSpec::fastcc(env, roc_return, ret_type, arg_basic_types);
let fn_val = add_func(
env.context,
env.module,
fn_name.as_str(),
fn_type,
fn_spec,
Linkage::Internal,
FAST_CALL_CONV,
);
let subprogram = env.new_subprogram(&fn_name);
@ -4191,8 +4212,6 @@ fn build_proc_header<'a, 'ctx, 'env>(
}
if false {
use inkwell::attributes::{Attribute, AttributeLoc};
let kind_id = Attribute::get_named_enum_kind_id("alwaysinline");
debug_assert!(kind_id > 0);
let enum_attr = env.context.create_enum_attribute(kind_id, 1);
@ -4200,8 +4219,6 @@ fn build_proc_header<'a, 'ctx, 'env>(
}
if false {
use inkwell::attributes::{Attribute, AttributeLoc};
let kind_id = Attribute::get_named_enum_kind_id("noinline");
debug_assert!(kind_id > 0);
let enum_attr = env.context.create_enum_attribute(kind_id, 1);
@ -4255,14 +4272,14 @@ pub fn build_closure_caller<'a, 'ctx, 'env>(
alias_symbol.as_str(&env.interns)
);
let function_type = context.void_type().fn_type(&argument_types, false);
let function_spec = FunctionSpec::cconv(env, CCReturn::Void, None, &argument_types);
let function_value = add_func(
env.context,
env.module,
function_name.as_str(),
function_type,
function_spec,
Linkage::External,
C_CALL_CONV,
);
// STEP 2: build function body
@ -4361,7 +4378,8 @@ fn build_host_exposed_alias_size_help<'a, 'ctx, 'env>(
let builder = env.builder;
let context = env.context;
let size_function_type = env.context.i64_type().fn_type(&[], false);
let i64 = env.context.i64_type().as_basic_type_enum();
let size_function_spec = FunctionSpec::cconv(env, CCReturn::Return, Some(i64), &[]);
let size_function_name: String = if let Some(label) = opt_label {
format!(
"roc__{}_{}_{}_size",
@ -4378,11 +4396,11 @@ fn build_host_exposed_alias_size_help<'a, 'ctx, 'env>(
};
let size_function = add_func(
env.context,
env.module,
size_function_name.as_str(),
size_function_type,
size_function_spec,
Linkage::External,
C_CALL_CONV,
);
let entry = context.append_basic_block(size_function, "entry");
@ -6083,6 +6101,13 @@ fn run_low_level<'a, 'ctx, 'env>(
let key_layout = list_element_layout!(list_layout);
set_from_list(env, layout_ids, list, key_layout)
}
SetToDict => {
debug_assert_eq!(args.len(), 1);
let (set, _set_layout) = load_symbol_and_layout(scope, &args[0]);
set
}
ExpectTrue => {
debug_assert_eq!(args.len(), 1);
@ -6205,6 +6230,7 @@ fn to_cc_type_builtin<'a, 'ctx, 'env>(
}
}
#[derive(Clone, Copy)]
enum RocReturn {
/// Return as normal
Return,
@ -6245,7 +6271,7 @@ impl RocReturn {
}
}
#[derive(Debug)]
#[derive(Debug, Clone, Copy)]
pub enum CCReturn {
/// Return as normal
Return,
@ -6257,32 +6283,113 @@ pub enum CCReturn {
Void,
}
impl CCReturn {
fn to_signature<'a, 'ctx, 'env>(
&self,
#[derive(Debug, Clone, Copy)]
pub struct FunctionSpec<'ctx> {
/// The function type
pub typ: FunctionType<'ctx>,
call_conv: u32,
/// Index (0-based) of return-by-pointer parameter, if it exists.
/// We only care about this for C-call-conv functions, because this may take
/// ownership of a register due to the convention. For example, on AArch64,
/// values returned-by-pointer use the x8 register.
/// But for internal functions we don't need to worry about that and we don't
/// want the convention, since it might eat a register and cause a spill!
cconv_sret_parameter: Option<u32>,
}
impl<'ctx> FunctionSpec<'ctx> {
fn attach_attributes(&self, ctx: &Context, fn_val: FunctionValue<'ctx>) {
fn_val.set_call_conventions(self.call_conv);
if let Some(param_index) = self.cconv_sret_parameter {
// Indicate to LLVM that this argument holds the return value of the function.
let sret_attribute_id = Attribute::get_named_enum_kind_id("sret");
debug_assert!(sret_attribute_id > 0);
let ret_typ = self.typ.get_param_types()[param_index as usize];
let sret_attribute =
ctx.create_type_attribute(sret_attribute_id, ret_typ.as_any_type_enum());
fn_val.add_attribute(AttributeLoc::Param(0), sret_attribute);
}
}
/// C-calling convention
pub fn cconv<'a, 'env>(
env: &Env<'a, 'ctx, 'env>,
return_type: BasicTypeEnum<'ctx>,
cc_return: CCReturn,
return_type: Option<BasicTypeEnum<'ctx>>,
argument_types: &[BasicTypeEnum<'ctx>],
) -> FunctionType<'ctx> {
match self {
) -> FunctionSpec<'ctx> {
let (typ, opt_sret_parameter) = match cc_return {
CCReturn::ByPointer => {
// turn the output type into a pointer type. Make it the first argument to the function
let output_type = return_type.ptr_type(AddressSpace::Generic);
let output_type = return_type.unwrap().ptr_type(AddressSpace::Generic);
let mut arguments: Vec<'_, BasicTypeEnum> =
bumpalo::vec![in env.arena; output_type.into()];
arguments.extend(argument_types);
let arguments = function_arguments(env, &arguments);
env.context.void_type().fn_type(&arguments, false)
(env.context.void_type().fn_type(&arguments, false), Some(0))
}
CCReturn::Return => {
let arguments = function_arguments(env, argument_types);
return_type.fn_type(&arguments, false)
(return_type.unwrap().fn_type(&arguments, false), None)
}
CCReturn::Void => {
let arguments = function_arguments(env, argument_types);
env.context.void_type().fn_type(&arguments, false)
(env.context.void_type().fn_type(&arguments, false), None)
}
};
Self {
typ,
call_conv: C_CALL_CONV,
cconv_sret_parameter: opt_sret_parameter,
}
}
/// Fastcc calling convention
fn fastcc<'a, 'env>(
env: &Env<'a, 'ctx, 'env>,
roc_return: RocReturn,
return_type: BasicTypeEnum<'ctx>,
mut argument_types: Vec<BasicTypeEnum<'ctx>>,
) -> FunctionSpec<'ctx> {
let typ = match roc_return {
RocReturn::Return => {
return_type.fn_type(&function_arguments(env, &argument_types), false)
}
RocReturn::ByPointer => {
argument_types.push(return_type.ptr_type(AddressSpace::Generic).into());
env.context
.void_type()
.fn_type(&function_arguments(env, &argument_types), false)
}
};
Self {
typ,
call_conv: FAST_CALL_CONV,
cconv_sret_parameter: None,
}
}
pub fn known_fastcc(fn_type: FunctionType<'ctx>) -> FunctionSpec<'ctx> {
Self {
typ: fn_type,
call_conv: FAST_CALL_CONV,
cconv_sret_parameter: None,
}
}
pub fn intrinsic(fn_type: FunctionType<'ctx>) -> Self {
// LLVM intrinsics always use the C calling convention, because
// they are implemented in C libraries
Self {
typ: fn_type,
call_conv: C_CALL_CONV,
cconv_sret_parameter: None,
}
}
}
@ -6363,27 +6470,19 @@ fn build_foreign_symbol<'a, 'ctx, 'env>(
arguments.push(value);
}
let cc_type = cc_return.to_signature(env, return_type, cc_argument_types.as_slice());
let cc_type =
FunctionSpec::cconv(env, cc_return, Some(return_type), &cc_argument_types);
let cc_function = get_foreign_symbol(env, foreign.clone(), cc_type);
let fastcc_type = match roc_return {
RocReturn::Return => {
return_type.fn_type(&function_arguments(env, &fastcc_argument_types), false)
}
RocReturn::ByPointer => {
fastcc_argument_types.push(return_type.ptr_type(AddressSpace::Generic).into());
env.context
.void_type()
.fn_type(&function_arguments(env, &fastcc_argument_types), false)
}
};
let fastcc_type =
FunctionSpec::fastcc(env, roc_return, return_type, fastcc_argument_types);
let fastcc_function = add_func(
env.context,
env.module,
&fastcc_function_name,
fastcc_type,
Linkage::Internal,
FAST_CALL_CONV,
);
let old = builder.get_insert_block().unwrap();
@ -6921,7 +7020,6 @@ fn build_float_binop<'a, 'ctx, 'env>(
NumGte => bd.build_float_compare(OGE, lhs, rhs, "float_gte").into(),
NumLt => bd.build_float_compare(OLT, lhs, rhs, "float_lt").into(),
NumLte => bd.build_float_compare(OLE, lhs, rhs, "float_lte").into(),
NumRemUnchecked => bd.build_float_rem(lhs, rhs, "rem_float").into(),
NumDivUnchecked => bd.build_float_div(lhs, rhs, "div_float").into(),
NumPow => env.call_intrinsic(&LLVM_POW[float_width], &[lhs.into(), rhs.into()]),
_ => {
@ -7510,7 +7608,7 @@ fn throw_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, message: &str) {
fn get_foreign_symbol<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
foreign_symbol: roc_module::ident::ForeignSymbol,
function_type: FunctionType<'ctx>,
function_spec: FunctionSpec<'ctx>,
) -> FunctionValue<'ctx> {
let module = env.module;
@ -7518,11 +7616,11 @@ fn get_foreign_symbol<'a, 'ctx, 'env>(
Some(gvalue) => gvalue,
None => {
let foreign_function = add_func(
env.context,
module,
foreign_symbol.as_str(),
function_type,
function_spec,
Linkage::External,
C_CALL_CONV,
);
foreign_function
@ -7534,11 +7632,11 @@ fn get_foreign_symbol<'a, 'ctx, 'env>(
/// We never want to define the same function twice in the same module!
/// The result can be bugs that are difficult to track down.
pub fn add_func<'ctx>(
ctx: &Context,
module: &Module<'ctx>,
name: &str,
typ: FunctionType<'ctx>,
spec: FunctionSpec<'ctx>,
linkage: Linkage,
call_conv: u32,
) -> FunctionValue<'ctx> {
if cfg!(debug_assertions) {
if let Some(func) = module.get_function(name) {
@ -7546,9 +7644,9 @@ pub fn add_func<'ctx>(
}
}
let fn_val = module.add_function(name, typ, Some(linkage));
let fn_val = module.add_function(name, spec.typ, Some(linkage));
fn_val.set_call_conventions(call_conv);
spec.attach_attributes(ctx, fn_val);
fn_val
}

View File

@ -1,8 +1,13 @@
use crate::llvm::build::Env;
use crate::llvm::build::{add_func, C_CALL_CONV};
use crate::llvm::bitcode::call_void_bitcode_fn;
use crate::llvm::build::{add_func, get_panic_msg_ptr, C_CALL_CONV};
use crate::llvm::build::{CCReturn, Env, FunctionSpec};
use inkwell::module::Linkage;
use inkwell::types::BasicType;
use inkwell::values::BasicValue;
use inkwell::AddressSpace;
use roc_builtins::bitcode;
use super::build::{get_sjlj_buffer, LLVM_LONGJMP};
/// Define functions for roc_alloc, roc_realloc, and roc_dealloc
/// which use libc implementations (malloc, realloc, and free)
@ -82,21 +87,18 @@ pub fn add_default_roc_externs(env: &Env<'_, '_, '_>) {
// roc_realloc
{
let libc_realloc_val = {
let fn_val = add_func(
module,
"realloc",
i8_ptr_type.fn_type(
&[
// ptr: *void
i8_ptr_type.into(),
// size: usize
usize_type.into(),
],
false,
),
Linkage::External,
C_CALL_CONV,
let fn_spec = FunctionSpec::cconv(
env,
CCReturn::Return,
Some(i8_ptr_type.as_basic_type_enum()),
&[
// ptr: *void
i8_ptr_type.into(),
// size: usize
usize_type.into(),
],
);
let fn_val = add_func(env.context, module, "realloc", fn_spec, Linkage::External);
let mut params = fn_val.get_param_iter();
let ptr_arg = params.next().unwrap();
@ -186,8 +188,6 @@ pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) {
// roc_panic
{
use crate::llvm::build::LLVM_LONGJMP;
// The type of this function (but not the implementation) should have
// already been defined by the builtins, which rely on it.
let fn_val = module.get_function("roc_panic").unwrap();
@ -209,32 +209,10 @@ pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) {
builder.position_at_end(entry);
let buffer = crate::llvm::build::get_sjlj_buffer(env);
// write our error message pointer
let index = env
.ptr_int()
.const_int(3 * env.target_info.ptr_width() as u64, false);
let message_buffer_raw =
unsafe { builder.build_gep(buffer, &[index], "raw_msg_buffer_ptr") };
let message_buffer = builder.build_bitcast(
message_buffer_raw,
env.context
.i8_type()
.ptr_type(AddressSpace::Generic)
.ptr_type(AddressSpace::Generic),
"to **u8",
);
env.builder.build_store(get_panic_msg_ptr(env), ptr_arg);
env.builder
.build_store(message_buffer.into_pointer_value(), ptr_arg);
let tag = env.context.i32_type().const_int(1, false);
if true {
let _call = env.build_intrinsic_call(LLVM_LONGJMP, &[buffer.into()]);
} else {
let _call = env.build_intrinsic_call(LLVM_LONGJMP, &[buffer.into(), tag.into()]);
}
build_longjmp_call(env);
builder.build_unreachable();
@ -243,3 +221,21 @@ pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) {
}
}
}
pub fn build_longjmp_call(env: &Env) {
let jmp_buf = get_sjlj_buffer(env);
if cfg!(target_arch = "aarch64") {
// Call the Zig-linked longjmp: `void longjmp(i32*, i32)`
let tag = env.context.i32_type().const_int(1, false);
let _call =
call_void_bitcode_fn(env, &[jmp_buf.into(), tag.into()], bitcode::UTILS_LONGJMP);
} else {
// Call the LLVM-intrinsic longjmp: `void @llvm.eh.sjlj.longjmp(i8* %setjmp_buf)`
let jmp_buf_i8p = env.builder.build_bitcast(
jmp_buf,
env.context.i8_type().ptr_type(AddressSpace::Generic),
"jmp_buf i8*",
);
let _call = env.build_intrinsic_call(LLVM_LONGJMP, &[jmp_buf_i8p]);
}
}

View File

@ -18,7 +18,7 @@ use roc_module::symbol::Interns;
use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout};
use super::build::load_roc_value;
use super::build::{load_roc_value, FunctionSpec};
use super::convert::{argument_type_from_layout, argument_type_from_union_layout};
pub struct PointerToRefcount<'ctx> {
@ -143,11 +143,11 @@ impl<'ctx> PointerToRefcount<'ctx> {
);
let function_value = add_func(
env.context,
env.module,
fn_name,
fn_type,
FunctionSpec::known_fastcc(fn_type),
Linkage::Internal,
FAST_CALL_CONV, // Because it's an internal-only function, it should use the fast calling convention.
);
let subprogram = env.new_subprogram(fn_name);
@ -1138,11 +1138,11 @@ pub fn build_header_help<'a, 'ctx, 'env>(
};
let fn_val = add_func(
env.context,
env.module,
fn_name,
fn_type,
FunctionSpec::known_fastcc(fn_type),
Linkage::Private,
FAST_CALL_CONV, // Because it's an internal-only function, it should use the fast calling convention.
);
let subprogram = env.new_subprogram(fn_name);
@ -1809,7 +1809,39 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
for (i, field_layout) in field_layouts.iter().enumerate() {
if let Layout::RecursivePointer = field_layout {
panic!("non-recursive tag unions cannot contain naked recursion pointers!");
let recursive_union_layout = match when_recursive {
WhenRecursive::Unreachable => {
panic!("non-recursive tag unions cannot contain naked recursion pointers!");
}
WhenRecursive::Loop(recursive_union_layout) => recursive_union_layout,
};
// This field is a pointer to the recursive pointer.
let field_ptr = env
.builder
.build_struct_gep(cast_tag_data_pointer, i as u32, "modify_tag_field")
.unwrap();
// This is the actual pointer to the recursive data.
let field_value = env.builder.build_load(field_ptr, "load_recursive_pointer");
debug_assert!(field_value.is_pointer_value());
// therefore we must cast it to our desired type
let union_type =
basic_type_from_layout(env, &Layout::Union(*recursive_union_layout));
let recursive_ptr_field_value =
cast_basic_basic(env.builder, field_value, union_type);
modify_refcount_layout_help(
env,
parent,
layout_ids,
mode.to_call_mode(fn_val),
when_recursive,
recursive_ptr_field_value,
&Layout::RecursivePointer,
)
} else if field_layout.contains_refcounted() {
let field_ptr = env
.builder

View File

@ -7,7 +7,7 @@ use roc_collections::all::MutMap;
use roc_module::ident::Ident;
use roc_module::low_level::{LowLevel, LowLevelWrapperType};
use roc_module::symbol::{Interns, Symbol};
use roc_mono::code_gen_help::{CodeGenHelp, REFCOUNT_MAX};
use roc_mono::code_gen_help::{CodeGenHelp, HelperOp, REFCOUNT_MAX};
use roc_mono::ir::{
BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, ModifyRc, Param, Proc,
ProcLayout, Stmt,
@ -192,7 +192,7 @@ impl<'a> WasmBackend<'a> {
.get_mut(&self.env.module_id)
.unwrap();
let ident_id = ident_ids.add(Ident::from(debug_name));
let ident_id = ident_ids.add_ident(&Ident::from(debug_name));
Symbol::new(self.env.module_id, ident_id)
}
@ -283,6 +283,13 @@ impl<'a> WasmBackend<'a> {
self.storage.stack_frame_size,
self.storage.stack_frame_pointer,
);
if DEBUG_LOG_SETTINGS.storage_map {
println!("\nStorage:");
for (sym, storage) in self.storage.symbol_storage_map.iter() {
println!("{:?} => {:?}", sym, storage);
}
}
}
fn append_proc_debug_name(&mut self, sym: Symbol) {
@ -1623,8 +1630,9 @@ impl<'a> WasmBackend<'a> {
);
}
/// Generate a refcount increment procedure and return its Wasm function index
pub fn gen_refcount_inc_for_zig(&mut self, layout: Layout<'a>) -> u32 {
/// Generate a refcount helper procedure and return a pointer (table index) to it
/// This allows it to be indirectly called from Zig code
pub fn get_refcount_fn_ptr(&mut self, layout: Layout<'a>, op: HelperOp) -> i32 {
let ident_ids = self
.interns
.all_ident_ids
@ -1633,7 +1641,7 @@ impl<'a> WasmBackend<'a> {
let (proc_symbol, new_specializations) = self
.helper_proc_gen
.gen_refcount_inc_proc(ident_ids, layout);
.gen_refcount_proc(ident_ids, layout, op);
// If any new specializations were created, register their symbol data
for (spec_sym, spec_layout) in new_specializations.into_iter() {
@ -1646,6 +1654,7 @@ impl<'a> WasmBackend<'a> {
.position(|lookup| lookup.name == proc_symbol && lookup.layout.arguments[0] == layout)
.unwrap();
self.fn_index_offset + proc_index as u32
let wasm_fn_index = self.fn_index_offset + proc_index as u32;
self.get_fn_table_index(wasm_fn_index)
}
}

View File

@ -253,6 +253,7 @@ pub struct WasmDebugLogSettings {
helper_procs_ir: bool,
let_stmt_ir: bool,
instructions: bool,
storage_map: bool,
pub keep_test_binary: bool,
}
@ -262,5 +263,6 @@ pub const DEBUG_LOG_SETTINGS: WasmDebugLogSettings = WasmDebugLogSettings {
helper_procs_ir: false && cfg!(debug_assertions),
let_stmt_ir: false && cfg!(debug_assertions),
instructions: false && cfg!(debug_assertions),
storage_map: false && cfg!(debug_assertions),
keep_test_binary: false && cfg!(debug_assertions),
};

View File

@ -3,6 +3,7 @@ use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
use roc_error_macros::internal_error;
use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol;
use roc_mono::code_gen_help::HelperOp;
use roc_mono::ir::{HigherOrderLowLevel, PassedFunction, ProcLayout};
use roc_mono::layout::{Builtin, Layout, UnionLayout};
use roc_mono::low_level::HigherOrder;
@ -304,7 +305,7 @@ impl<'a> LowLevelCall<'a> {
DictSize | DictEmpty | DictInsert | DictRemove | DictContains | DictGetUnsafe
| DictKeys | DictValues | DictUnion | DictIntersection | DictDifference
| SetFromList => {
| SetFromList | SetToDict => {
todo!("{:?}", self.lowlevel);
}
@ -1035,58 +1036,73 @@ pub fn call_higher_order_lowlevel<'a>(
};
let wrapper_fn_idx = backend.register_helper_proc(wrapper_sym, wrapper_layout, source);
let inc_fn_idx = backend.gen_refcount_inc_for_zig(closure_data_layout);
let wrapper_fn_ptr = backend.get_fn_table_index(wrapper_fn_idx);
let inc_fn_ptr = backend.get_fn_table_index(inc_fn_idx);
let inc_fn_ptr = match closure_data_layout {
Layout::Struct {
field_layouts: &[], ..
} => {
// Our code gen would ignore the Unit arg, but the Zig builtin passes a pointer for it!
// That results in an exception (type signature mismatch in indirect call).
// The workaround is to use I32 layout, treating the (ignored) pointer as an integer.
backend.get_refcount_fn_ptr(Layout::Builtin(Builtin::Int(IntWidth::I32)), HelperOp::Inc)
}
_ => backend.get_refcount_fn_ptr(closure_data_layout, HelperOp::Inc),
};
match op {
// List.map : List elem_x, (elem_x -> elem_ret) -> List elem_ret
ListMap { xs } => {
let list_layout_in = backend.storage.symbol_layouts[xs];
ListMap { xs } => list_map_n(
bitcode::LIST_MAP,
backend,
&[*xs],
return_sym,
*return_layout,
wrapper_fn_ptr,
inc_fn_ptr,
closure_data_exists,
*captured_environment,
*owns_captured_environment,
),
let (elem_x, elem_ret) = match (list_layout_in, return_layout) {
(
Layout::Builtin(Builtin::List(elem_x)),
Layout::Builtin(Builtin::List(elem_ret)),
) => (elem_x, elem_ret),
_ => unreachable!("invalid layout for List.map arguments"),
};
let elem_x_size = elem_x.stack_size(TARGET_INFO);
let (elem_ret_size, elem_ret_align) = elem_ret.stack_size_and_alignment(TARGET_INFO);
ListMap2 { xs, ys } => list_map_n(
bitcode::LIST_MAP2,
backend,
&[*xs, *ys],
return_sym,
*return_layout,
wrapper_fn_ptr,
inc_fn_ptr,
closure_data_exists,
*captured_environment,
*owns_captured_environment,
),
let cb = &mut backend.code_builder;
ListMap3 { xs, ys, zs } => list_map_n(
bitcode::LIST_MAP3,
backend,
&[*xs, *ys, *zs],
return_sym,
*return_layout,
wrapper_fn_ptr,
inc_fn_ptr,
closure_data_exists,
*captured_environment,
*owns_captured_environment,
),
// Load return pointer & argument values
// Wasm signature: (i32, i64, i64, i32, i32, i32, i32, i32, i32, i32) -> nil
backend.storage.load_symbols(cb, &[return_sym]);
backend.storage.load_symbol_zig(cb, *xs); // list with capacity = 2 x i64 args
cb.i32_const(wrapper_fn_ptr);
if closure_data_exists {
backend.storage.load_symbols(cb, &[*captured_environment]);
} else {
// Normally, a zero-size arg would be eliminated in code gen, but Zig expects one!
cb.i32_const(0); // null pointer
}
cb.i32_const(inc_fn_ptr);
cb.i32_const(*owns_captured_environment as i32);
cb.i32_const(elem_ret_align as i32); // used for allocating the new list
cb.i32_const(elem_x_size as i32);
cb.i32_const(elem_ret_size as i32);
ListMap4 { xs, ys, zs, ws } => list_map_n(
bitcode::LIST_MAP4,
backend,
&[*xs, *ys, *zs, *ws],
return_sym,
*return_layout,
wrapper_fn_ptr,
inc_fn_ptr,
closure_data_exists,
*captured_environment,
*owns_captured_environment,
),
let num_wasm_args = 10; // 1 return pointer + 8 Zig args + list 2nd i64
let has_return_val = false;
backend.call_zig_builtin_after_loading_args(
bitcode::LIST_MAP,
num_wasm_args,
has_return_val,
);
}
ListMap2 { .. }
| ListMap3 { .. }
| ListMap4 { .. }
| ListMapWithIndex { .. }
ListMapWithIndex { .. }
| ListKeepIf { .. }
| ListWalk { .. }
| ListWalkUntil { .. }
@ -1100,3 +1116,71 @@ pub fn call_higher_order_lowlevel<'a>(
| DictWalk { .. } => todo!("{:?}", op),
}
}
fn unwrap_list_elem_layout(list_layout: Layout<'_>) -> &Layout<'_> {
match list_layout {
Layout::Builtin(Builtin::List(x)) => x,
e => internal_error!("expected List layout, got {:?}", e),
}
}
#[allow(clippy::too_many_arguments)]
fn list_map_n<'a>(
zig_fn_name: &'static str,
backend: &mut WasmBackend<'a>,
arg_symbols: &[Symbol],
return_sym: Symbol,
return_layout: Layout<'a>,
wrapper_fn_ptr: i32,
inc_fn_ptr: i32,
closure_data_exists: bool,
captured_environment: Symbol,
owns_captured_environment: bool,
) {
let arg_elem_layouts = Vec::from_iter_in(
arg_symbols
.iter()
.map(|sym| *unwrap_list_elem_layout(backend.storage.symbol_layouts[sym])),
backend.env.arena,
);
let elem_ret = unwrap_list_elem_layout(return_layout);
let (elem_ret_size, elem_ret_align) = elem_ret.stack_size_and_alignment(TARGET_INFO);
let cb = &mut backend.code_builder;
backend.storage.load_symbols(cb, &[return_sym]);
for s in arg_symbols {
backend.storage.load_symbol_zig(cb, *s);
}
cb.i32_const(wrapper_fn_ptr);
if closure_data_exists {
backend.storage.load_symbols(cb, &[captured_environment]);
} else {
// load_symbols assumes that a zero-size arg should be eliminated in code gen,
// but that's a specialization that our Zig code doesn't have! Pass a null pointer.
cb.i32_const(0);
}
cb.i32_const(inc_fn_ptr);
cb.i32_const(owns_captured_environment as i32);
cb.i32_const(elem_ret_align as i32);
for el in arg_elem_layouts.iter() {
cb.i32_const(el.stack_size(TARGET_INFO) as i32);
}
cb.i32_const(elem_ret_size as i32);
// If we have lists of different lengths, we may need to decrement
let num_wasm_args = if arg_elem_layouts.len() > 1 {
for el in arg_elem_layouts.iter() {
let ptr = backend.get_refcount_fn_ptr(*el, HelperOp::Dec);
backend.code_builder.i32_const(ptr);
}
7 + arg_elem_layouts.len() * 4
} else {
7 + arg_elem_layouts.len() * 3
};
let has_return_val = false;
backend.call_zig_builtin_after_loading_args(zig_fn_name, num_wasm_args, has_return_val);
}

View File

@ -62,7 +62,7 @@ struct VmBlock<'a> {
impl std::fmt::Debug for VmBlock<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("{:?}", self.opcode))
f.write_fmt(format_args!("{:?} {:?}", self.opcode, self.value_stack))
}
}
@ -608,7 +608,7 @@ impl<'a> CodeBuilder<'a> {
log_instruction!(
"{:10}\t\t{:?}",
format!("{:?}", opcode),
self.current_stack()
self.vm_block_stack
);
}
@ -635,7 +635,7 @@ impl<'a> CodeBuilder<'a> {
"{:10}\t{}\t{:?}",
format!("{:?}", opcode),
immediate,
self.current_stack()
self.vm_block_stack
);
}
@ -648,7 +648,7 @@ impl<'a> CodeBuilder<'a> {
format!("{:?}", opcode),
align,
offset,
self.current_stack()
self.vm_block_stack
);
}
@ -752,7 +752,7 @@ impl<'a> CodeBuilder<'a> {
"{:10}\t{}\t{:?}",
format!("{:?}", CALL),
function_index,
self.current_stack()
self.vm_block_stack
);
}
@ -823,7 +823,7 @@ impl<'a> CodeBuilder<'a> {
"{:10}\t{}\t{:?}",
format!("{:?}", opcode),
x,
self.current_stack()
self.vm_block_stack
);
}
pub fn i32_const(&mut self, x: i32) {

View File

@ -30,7 +30,8 @@ impl IdentStr {
// Reserve 1 byte for the discriminant
const SMALL_STR_BYTES: usize = std::mem::size_of::<Self>() - 1;
pub fn len(&self) -> usize {
#[inline(always)]
pub const fn len(&self) -> usize {
let bytes = self.length.to_ne_bytes();
let last_byte = bytes[mem::size_of::<usize>() - 1];
@ -55,11 +56,11 @@ impl IdentStr {
}
}
pub fn is_empty(&self) -> bool {
pub const fn is_empty(&self) -> bool {
self.length == 0
}
pub fn is_small_str(&self) -> bool {
pub const fn is_small_str(&self) -> bool {
let bytes = self.length.to_ne_bytes();
let last_byte = bytes[mem::size_of::<usize>() - 1];
@ -144,6 +145,7 @@ impl IdentStr {
}
}
#[inline(always)]
pub fn as_slice(&self) -> &[u8] {
use core::slice::from_raw_parts;
@ -156,6 +158,7 @@ impl IdentStr {
}
}
#[inline(always)]
pub fn as_str(&self) -> &str {
let slice = self.as_slice();
@ -201,8 +204,19 @@ impl From<&str> for IdentStr {
}
impl From<String> for IdentStr {
fn from(str: String) -> Self {
Self::from_str(&str)
fn from(string: String) -> Self {
if string.len() <= Self::SMALL_STR_BYTES {
Self::from_str(string.as_str())
} else {
// Take over the string's heap allocation
let length = string.len();
let elements = string.as_ptr();
// Make sure the existing string doesn't get dropped.
std::mem::forget(string);
Self { elements, length }
}
}
}

View File

@ -5,13 +5,13 @@ use roc_module::symbol::ModuleId;
const MODULES: &[(ModuleId, &str)] = &[
(ModuleId::BOOL, "Bool.roc"),
// (ModuleId::RESULT, "Result.roc"),
// (ModuleId::LIST, "List.roc"),
// (ModuleId::STR, "Str.roc"),
// (ModuleId::DICT, "Dict.roc"),
// (ModuleId::SET, "Set.roc"),
// (ModuleId::BOX, "Box.roc"),
// (ModuleId::NUM, "Num.roc"),
(ModuleId::RESULT, "Result.roc"),
(ModuleId::NUM, "Num.roc"),
(ModuleId::LIST, "List.roc"),
(ModuleId::STR, "Str.roc"),
(ModuleId::DICT, "Dict.roc"),
(ModuleId::SET, "Set.roc"),
(ModuleId::BOX, "Box.roc"),
];
fn main() {

View File

@ -35,6 +35,30 @@ fn load<'a>(
)
}
/// Load using only a single thread; used when compiling to webassembly
pub fn load_single_threaded<'a>(
arena: &'a Bump,
load_start: LoadStart<'a>,
src_dir: &Path,
exposed_types: ExposedByModule,
goal_phase: Phase,
target_info: TargetInfo,
render: RenderTarget,
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
let cached_subs = read_cached_subs();
roc_load_internal::file::load_single_threaded(
arena,
load_start,
src_dir,
exposed_types,
goal_phase,
target_info,
cached_subs,
render,
)
}
pub fn load_and_monomorphize_from_str<'a>(
arena: &'a Bump,
filename: PathBuf,
@ -114,14 +138,44 @@ pub fn load_and_typecheck<'a>(
}
}
pub fn load_and_typecheck_str<'a>(
arena: &'a Bump,
filename: PathBuf,
source: &'a str,
src_dir: &Path,
exposed_types: ExposedByModule,
target_info: TargetInfo,
render: RenderTarget,
) -> Result<LoadedModule, LoadingProblem<'a>> {
use LoadResult::*;
let load_start = LoadStart::from_str(arena, filename, source)?;
// NOTE: this function is meant for tests, and so we use single-threaded
// solving so we don't use too many threads per-test. That gives higher
// throughput for the test run overall
match load_single_threaded(
arena,
load_start,
src_dir,
exposed_types,
Phase::SolveTypes,
target_info,
render,
)? {
Monomorphized(_) => unreachable!(""),
TypeChecked(module) => Ok(module),
}
}
const BOOL: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Bool.dat")) as &[_];
// const RESULT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Result.dat")) as &[_];
// const LIST: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/List.dat")) as &[_];
// const STR: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Str.dat")) as &[_];
// const DICT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Dict.dat")) as &[_];
// const SET: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Set.dat")) as &[_];
// const BOX: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Box.dat")) as &[_];
// const NUM: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Num.dat")) as &[_];
const RESULT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Result.dat")) as &[_];
const LIST: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/List.dat")) as &[_];
const STR: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Str.dat")) as &[_];
const DICT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Dict.dat")) as &[_];
const SET: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Set.dat")) as &[_];
const BOX: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Box.dat")) as &[_];
const NUM: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Num.dat")) as &[_];
fn deserialize_help(bytes: &[u8]) -> (Subs, Vec<(Symbol, Variable)>) {
let (subs, slice) = Subs::deserialize(bytes);
@ -132,14 +186,20 @@ fn deserialize_help(bytes: &[u8]) -> (Subs, Vec<(Symbol, Variable)>) {
fn read_cached_subs() -> MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)> {
let mut output = MutMap::default();
output.insert(ModuleId::BOOL, deserialize_help(BOOL));
// output.insert(ModuleId::RESULT, deserialize_help(RESULT));
// output.insert(ModuleId::LIST, deserialize_help(LIST));
// output.insert(ModuleId::STR, deserialize_help(STR));
// output.insert(ModuleId::DICT, deserialize_help(DICT));
// output.insert(ModuleId::SET, deserialize_help(SET));
// output.insert(ModuleId::BOX, deserialize_help(BOX));
// output.insert(ModuleId::NUM, deserialize_help(NUM));
// Wasm seems to re-order definitions between build time and runtime, but only in release mode.
// That is very strange, but we can solve it separately
if !cfg!(target_family = "wasm") {
output.insert(ModuleId::BOOL, deserialize_help(BOOL));
output.insert(ModuleId::RESULT, deserialize_help(RESULT));
output.insert(ModuleId::NUM, deserialize_help(NUM));
output.insert(ModuleId::LIST, deserialize_help(LIST));
output.insert(ModuleId::STR, deserialize_help(STR));
output.insert(ModuleId::DICT, deserialize_help(DICT));
output.insert(ModuleId::SET, deserialize_help(SET));
output.insert(ModuleId::BOX, deserialize_help(BOX));
}
output
}

1
compiler/load_internal/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/tmp

View File

@ -33,3 +33,4 @@ tempfile = "3.2.0"
pretty_assertions = "1.0.0"
maplit = "1.0.2"
indoc = "1.0.3"
roc_test_utils = { path = "../../test_utils" }

View File

@ -1,7 +1,5 @@
use crate::docs::DocEntry::DetachedDoc;
use crate::docs::TypeAnnotation::{
Apply, BoundVariable, Function, NoTypeAnn, ObscuredRecord, ObscuredTagUnion, Record, TagUnion,
};
use crate::docs::TypeAnnotation::{Apply, BoundVariable, Function, NoTypeAnn, Record, TagUnion};
use crate::file::LoadedModule;
use roc_can::scope::Scope;
use roc_error_macros::todo_abilities;
@ -91,14 +89,13 @@ pub struct Tag {
pub fn generate_module_docs<'a>(
scope: Scope,
module_name: ModuleName,
ident_ids: &'a IdentIds,
parsed_defs: &'a [Loc<ast::Def<'a>>],
) -> ModuleDocumentation {
let (entries, _) =
parsed_defs
.iter()
.fold((vec![], None), |(acc, maybe_comments_after), def| {
generate_entry_doc(ident_ids, acc, maybe_comments_after, &def.value)
generate_entry_doc(&scope.ident_ids, acc, maybe_comments_after, &def.value)
});
ModuleDocumentation {
@ -274,36 +271,20 @@ fn type_to_docs(in_func_type_ann: bool, type_annotation: ast::TypeAnnotation) ->
ast::TypeAnnotation::TagUnion { tags, ext } => {
let mut tags_to_render: Vec<Tag> = Vec::new();
let mut any_tags_are_private = false;
for tag in tags.iter() {
match tag_to_doc(in_func_type_ann, tag.value) {
None => {
any_tags_are_private = true;
break;
}
Some(tag_ann) => {
tags_to_render.push(tag_ann);
}
if let Some(tag_ann) = tag_to_doc(in_func_type_ann, tag.value) {
tags_to_render.push(tag_ann);
}
}
if any_tags_are_private {
if in_func_type_ann {
ObscuredTagUnion
} else {
NoTypeAnn
}
} else {
let extension = match ext {
None => NoTypeAnn,
Some(ext_type_ann) => type_to_docs(in_func_type_ann, ext_type_ann.value),
};
let extension = match ext {
None => NoTypeAnn,
Some(ext_type_ann) => type_to_docs(in_func_type_ann, ext_type_ann.value),
};
TagUnion {
tags: tags_to_render,
extension: Box::new(extension),
}
TagUnion {
tags: tags_to_render,
extension: Box::new(extension),
}
}
ast::TypeAnnotation::BoundVariable(var_name) => BoundVariable(var_name.to_string()),
@ -328,35 +309,19 @@ fn type_to_docs(in_func_type_ann: bool, type_annotation: ast::TypeAnnotation) ->
ast::TypeAnnotation::Record { fields, ext } => {
let mut doc_fields = Vec::new();
let mut any_fields_include_private_tags = false;
for field in fields.items {
match record_field_to_doc(in_func_type_ann, field.value) {
None => {
any_fields_include_private_tags = true;
break;
}
Some(doc_field) => {
doc_fields.push(doc_field);
}
if let Some(doc_field) = record_field_to_doc(in_func_type_ann, field.value) {
doc_fields.push(doc_field);
}
}
if any_fields_include_private_tags {
if in_func_type_ann {
ObscuredRecord
} else {
NoTypeAnn
}
} else {
let extension = match ext {
None => NoTypeAnn,
Some(ext_type_ann) => type_to_docs(in_func_type_ann, ext_type_ann.value),
};
let extension = match ext {
None => NoTypeAnn,
Some(ext_type_ann) => type_to_docs(in_func_type_ann, ext_type_ann.value),
};
Record {
fields: doc_fields,
extension: Box::new(extension),
}
Record {
fields: doc_fields,
extension: Box::new(extension),
}
}
ast::TypeAnnotation::SpaceBefore(&sub_type_ann, _) => {
@ -404,11 +369,10 @@ fn record_field_to_doc(
}
}
// The Option here represents if it is private. Private tags
// evaluate to `None`.
// The Option here represents if it is malformed.
fn tag_to_doc(in_func_ann: bool, tag: ast::Tag) -> Option<Tag> {
match tag {
ast::Tag::Global { name, args } => Some(Tag {
ast::Tag::Apply { name, args } => Some(Tag {
name: name.value.to_string(),
values: {
let mut type_vars = Vec::new();
@ -420,7 +384,6 @@ fn tag_to_doc(in_func_ann: bool, tag: ast::Tag) -> Option<Tag> {
type_vars
},
}),
ast::Tag::Private { .. } => None,
ast::Tag::SpaceBefore(&sub_tag, _) => tag_to_doc(in_func_ann, sub_tag),
ast::Tag::SpaceAfter(&sub_tag, _) => tag_to_doc(in_func_ann, sub_tag),
ast::Tag::Malformed(_) => None,

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