mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-03 00:24:34 +00:00
Merge branch 'trunk' of github.com:rtfeldman/roc into str-split
This commit is contained in:
commit
d41e940b7f
108 changed files with 3509 additions and 863 deletions
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
|
@ -51,11 +51,6 @@ jobs:
|
|||
command: clippy
|
||||
args: -- -D warnings
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
name: cargo test
|
||||
with:
|
||||
command: test
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
name: cargo test -- --ignored
|
||||
continue-on-error: true
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
# Building the Roc compiler from source
|
||||
|
||||
|
||||
## Installing LLVM, Zig, valgrind, libunwind, and libc++-dev
|
||||
## Installing LLVM, Python, Zig, valgrind, libunwind, and libc++-dev
|
||||
|
||||
To build the compiler, you need these installed:
|
||||
|
||||
* `libunwind` (macOS should already have this one installed)
|
||||
* `libc++-dev`
|
||||
* Python 2.7 (Windows only), `python-is-python3` (Ubuntu)
|
||||
* a particular version of Zig (see below)
|
||||
* a particular version of LLVM (see below)
|
||||
|
||||
|
@ -33,7 +34,7 @@ We use a specific version of Zig, a build off the the commit `0088efc4b`. The la
|
|||
tar xvf zig-linux-x86_64-0.6.0+0088efc4b.tar
|
||||
# move the files into /opt:
|
||||
sudo mkdir -p /opt/zig
|
||||
sudo mv tar xvf zig-linux-x86_64-0.6.0+0088efc4b.tar/* /opt/zig/
|
||||
sudo mv zig-linux-x86_64-0.6.0+0088efc4b/* /opt/zig/
|
||||
```
|
||||
Then add `/opt/zig/` to your `PATH` (e.g. in `~/.bashrc`).
|
||||
|
||||
|
|
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -644,6 +644,8 @@ dependencies = [
|
|||
"bumpalo",
|
||||
"fs_extra",
|
||||
"handlebars",
|
||||
"maplit",
|
||||
"pretty_assertions",
|
||||
"pulldown-cmark",
|
||||
"roc_builtins",
|
||||
"roc_collections",
|
||||
|
@ -661,9 +663,9 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
|
|||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.6.0"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd56b59865bce947ac5958779cfa508f6c3b9497cc762b7e24a12d11ccde2c4f"
|
||||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
|
@ -2463,6 +2465,7 @@ name = "roc_gen"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"either",
|
||||
"im",
|
||||
"im-rc",
|
||||
"indoc",
|
||||
|
|
|
@ -194,42 +194,40 @@ fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fa
|
|||
exposed_types,
|
||||
);
|
||||
|
||||
let loaded = loaded.expect("failed to load module");
|
||||
let mut loaded = loaded.expect("failed to load module");
|
||||
|
||||
use roc_load::file::MonomorphizedModule;
|
||||
let MonomorphizedModule {
|
||||
can_problems,
|
||||
type_problems,
|
||||
mono_problems,
|
||||
mut procedures,
|
||||
interns,
|
||||
exposed_to_host,
|
||||
mut subs,
|
||||
module_id: home,
|
||||
sources,
|
||||
..
|
||||
} = loaded;
|
||||
|
||||
let mut lines = Vec::new();
|
||||
|
||||
for (home, (module_path, src)) in sources {
|
||||
let can_problems = loaded.can_problems.remove(&home).unwrap_or_default();
|
||||
let type_problems = loaded.type_problems.remove(&home).unwrap_or_default();
|
||||
let mono_problems = loaded.mono_problems.remove(&home).unwrap_or_default();
|
||||
|
||||
let error_count = can_problems.len() + type_problems.len() + mono_problems.len();
|
||||
|
||||
if error_count > 0 {
|
||||
// There were problems; report them and return.
|
||||
let src_lines: Vec<&str> = module_src.split('\n').collect();
|
||||
if error_count == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Used for reporting where an error came from.
|
||||
//
|
||||
// TODO: maybe Reporting should have this be an Option?
|
||||
let path = PathBuf::new();
|
||||
|
||||
// Report problems
|
||||
let src_lines: Vec<&str> = src.split('\n').collect();
|
||||
let palette = DEFAULT_PALETTE;
|
||||
|
||||
// Report parsing and canonicalization problems
|
||||
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
||||
|
||||
let mut lines = Vec::with_capacity(error_count);
|
||||
|
||||
for problem in can_problems.into_iter() {
|
||||
let report = can_problem(&alloc, path.clone(), problem);
|
||||
let report = can_problem(&alloc, module_path.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
@ -237,8 +235,8 @@ fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fa
|
|||
lines.push(buf);
|
||||
}
|
||||
|
||||
for problem in type_problems.into_iter() {
|
||||
let report = type_problem(&alloc, path.clone(), problem);
|
||||
for problem in type_problems {
|
||||
let report = type_problem(&alloc, module_path.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
@ -246,15 +244,17 @@ fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fa
|
|||
lines.push(buf);
|
||||
}
|
||||
|
||||
for problem in mono_problems.into_iter() {
|
||||
let report = mono_problem(&alloc, path.clone(), problem);
|
||||
for problem in mono_problems {
|
||||
let report = mono_problem(&alloc, module_path.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
||||
lines.push(buf);
|
||||
}
|
||||
}
|
||||
|
||||
if !lines.is_empty() {
|
||||
Ok(ReplOutput::Problems(lines))
|
||||
} else {
|
||||
let context = Context::create();
|
||||
|
|
|
@ -59,6 +59,11 @@ fn jit_to_ast_help<'a>(
|
|||
content: &Content,
|
||||
) -> Expr<'a> {
|
||||
match layout {
|
||||
Layout::Builtin(Builtin::Int1) => {
|
||||
// TODO this will not handle the case where this bool was really
|
||||
// a 1-element recored, e.g. { x: True }, correctly
|
||||
run_jit_function!(lib, main_fn_name, bool, |num| i1_to_ast(num))
|
||||
}
|
||||
Layout::Builtin(Builtin::Int64) => {
|
||||
run_jit_function!(lib, main_fn_name, i64, |num| num_to_ast(
|
||||
env,
|
||||
|
@ -433,6 +438,15 @@ fn i64_to_ast(arena: &Bump, num: i64) -> Expr<'_> {
|
|||
Expr::Num(arena.alloc(format!("{}", num)))
|
||||
}
|
||||
|
||||
/// This is centralized in case we want to format it differently later,
|
||||
/// e.g. adding underscores for large numbers
|
||||
fn i1_to_ast<'a>(num: bool) -> Expr<'a> {
|
||||
match num {
|
||||
true => Expr::GlobalTag(&"True"),
|
||||
false => Expr::GlobalTag(&"False"),
|
||||
}
|
||||
}
|
||||
|
||||
/// This is centralized in case we want to format it differently later,
|
||||
/// e.g. adding underscores for large numbers
|
||||
fn f64_to_ast(arena: &Bump, num: f64) -> Expr<'_> {
|
||||
|
|
|
@ -12,24 +12,13 @@ mod helpers;
|
|||
#[cfg(test)]
|
||||
mod cli_run {
|
||||
use crate::helpers::{
|
||||
example_file, extract_valgrind_errors, run_cmd, run_roc, run_with_valgrind,
|
||||
example_file, extract_valgrind_errors, fixture_file, run_cmd, run_roc, run_with_valgrind,
|
||||
};
|
||||
use serial_test::serial;
|
||||
use std::path::Path;
|
||||
|
||||
fn check_output(
|
||||
folder: &str,
|
||||
file: &str,
|
||||
flags: &[&str],
|
||||
expected_ending: &str,
|
||||
use_valgrind: bool,
|
||||
) {
|
||||
let compile_out = run_roc(
|
||||
&[
|
||||
&["build", example_file(folder, file).to_str().unwrap()],
|
||||
flags,
|
||||
]
|
||||
.concat(),
|
||||
);
|
||||
fn check_output(file: &Path, flags: &[&str], expected_ending: &str, use_valgrind: bool) {
|
||||
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags].concat());
|
||||
if !compile_out.stderr.is_empty() {
|
||||
panic!(compile_out.stderr);
|
||||
}
|
||||
|
@ -37,14 +26,14 @@ mod cli_run {
|
|||
|
||||
let out = if use_valgrind {
|
||||
let (valgrind_out, raw_xml) =
|
||||
run_with_valgrind(&[example_file(folder, "app").to_str().unwrap()]);
|
||||
run_with_valgrind(&[file.with_file_name("app").to_str().unwrap()]);
|
||||
let memory_errors = extract_valgrind_errors(&raw_xml);
|
||||
if !memory_errors.is_empty() {
|
||||
panic!("{:?}", memory_errors);
|
||||
}
|
||||
valgrind_out
|
||||
} else {
|
||||
run_cmd(example_file(folder, "app").to_str().unwrap(), &[])
|
||||
run_cmd(file.with_file_name("app").to_str().unwrap(), &[])
|
||||
};
|
||||
if !&out.stdout.ends_with(expected_ending) {
|
||||
panic!(
|
||||
|
@ -59,8 +48,7 @@ mod cli_run {
|
|||
#[serial(hello_world)]
|
||||
fn run_hello_world() {
|
||||
check_output(
|
||||
"hello-world",
|
||||
"Hello.roc",
|
||||
&example_file("hello-world", "Hello.roc"),
|
||||
&[],
|
||||
"Hello, World!!!!!!!!!!!!!\n",
|
||||
true,
|
||||
|
@ -71,8 +59,7 @@ mod cli_run {
|
|||
#[serial(hello_world)]
|
||||
fn run_hello_world_optimized() {
|
||||
check_output(
|
||||
"hello-world",
|
||||
"Hello.roc",
|
||||
&example_file("hello-world", "Hello.roc"),
|
||||
&[],
|
||||
"Hello, World!!!!!!!!!!!!!\n",
|
||||
true,
|
||||
|
@ -83,8 +70,7 @@ mod cli_run {
|
|||
#[serial(quicksort)]
|
||||
fn run_quicksort_not_optimized() {
|
||||
check_output(
|
||||
"quicksort",
|
||||
"Quicksort.roc",
|
||||
&example_file("quicksort", "Quicksort.roc"),
|
||||
&[],
|
||||
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
|
||||
false,
|
||||
|
@ -95,8 +81,7 @@ mod cli_run {
|
|||
#[serial(quicksort)]
|
||||
fn run_quicksort_optimized() {
|
||||
check_output(
|
||||
"quicksort",
|
||||
"Quicksort.roc",
|
||||
&example_file("quicksort", "Quicksort.roc"),
|
||||
&["--optimize"],
|
||||
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
|
||||
false,
|
||||
|
@ -109,8 +94,7 @@ mod cli_run {
|
|||
#[ignore]
|
||||
fn run_quicksort_valgrind() {
|
||||
check_output(
|
||||
"quicksort",
|
||||
"Quicksort.roc",
|
||||
&example_file("quicksort", "Quicksort.roc"),
|
||||
&[],
|
||||
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
|
||||
true,
|
||||
|
@ -123,8 +107,7 @@ mod cli_run {
|
|||
#[ignore]
|
||||
fn run_quicksort_optimized_valgrind() {
|
||||
check_output(
|
||||
"quicksort",
|
||||
"Quicksort.roc",
|
||||
&example_file("quicksort", "Quicksort.roc"),
|
||||
&["--optimize"],
|
||||
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
|
||||
true,
|
||||
|
@ -135,8 +118,7 @@ mod cli_run {
|
|||
#[serial(multi_module)]
|
||||
fn run_multi_module() {
|
||||
check_output(
|
||||
"multi-module",
|
||||
"Quicksort.roc",
|
||||
&example_file("multi-module", "Quicksort.roc"),
|
||||
&[],
|
||||
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
|
||||
false,
|
||||
|
@ -147,8 +129,7 @@ mod cli_run {
|
|||
#[serial(multi_module)]
|
||||
fn run_multi_module_optimized() {
|
||||
check_output(
|
||||
"multi-module",
|
||||
"Quicksort.roc",
|
||||
&example_file("multi-module", "Quicksort.roc"),
|
||||
&["--optimize"],
|
||||
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
|
||||
false,
|
||||
|
@ -161,8 +142,7 @@ mod cli_run {
|
|||
#[ignore]
|
||||
fn run_multi_module_valgrind() {
|
||||
check_output(
|
||||
"multi-module",
|
||||
"Quicksort.roc",
|
||||
&example_file("multi-module", "Quicksort.roc"),
|
||||
&[],
|
||||
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
|
||||
true,
|
||||
|
@ -175,11 +155,60 @@ mod cli_run {
|
|||
#[ignore]
|
||||
fn run_multi_module_optimized_valgrind() {
|
||||
check_output(
|
||||
"multi-module",
|
||||
"Quicksort.roc",
|
||||
&example_file("multi-module", "Quicksort.roc"),
|
||||
&["--optimize"],
|
||||
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial(multi_dep_str)]
|
||||
fn run_multi_dep_str_unoptimized() {
|
||||
// if true {
|
||||
// todo!(
|
||||
// "fix this test so it no longer deadlocks and hangs during monomorphization! The test never shows the error; to see the panic error, run this: cargo run run cli/tests/fixtures/multi-dep-str/Main.roc"
|
||||
// );
|
||||
// }
|
||||
|
||||
check_output(
|
||||
&fixture_file("multi-dep-str", "Main.roc"),
|
||||
&[],
|
||||
"I am Dep2.str2\n",
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial(multi_dep_str)]
|
||||
fn run_multi_dep_str_optimized() {
|
||||
check_output(
|
||||
&fixture_file("multi-dep-str", "Main.roc"),
|
||||
&[],
|
||||
"I am Dep2.str2\n",
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial(multi_dep_thunk)]
|
||||
fn run_multi_dep_thunk_unoptimized() {
|
||||
check_output(
|
||||
&fixture_file("multi-dep-thunk", "Main.roc"),
|
||||
&[],
|
||||
"I am Dep2.value2\n",
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial(multi_dep_str)]
|
||||
fn run_multi_dep_thunk_optimized() {
|
||||
check_output(
|
||||
&fixture_file("multi-dep-thunk", "Main.roc"),
|
||||
&[],
|
||||
"I am Dep2.value2\n",
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
4
cli/tests/fixtures/.gitignore
vendored
Normal file
4
cli/tests/fixtures/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
app
|
||||
host.o
|
||||
c_host.o
|
||||
app.dSYM
|
4
cli/tests/fixtures/multi-dep-str/Dep1.roc
vendored
Normal file
4
cli/tests/fixtures/multi-dep-str/Dep1.roc
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
interface Dep1 exposes [ str1 ] imports [ Dep2 ]
|
||||
|
||||
str1 : Str
|
||||
str1 = Dep2.str2
|
4
cli/tests/fixtures/multi-dep-str/Dep2.roc
vendored
Normal file
4
cli/tests/fixtures/multi-dep-str/Dep2.roc
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
interface Dep2 exposes [ str2 ] imports []
|
||||
|
||||
str2 : Str
|
||||
str2 = "I am Dep2.str2"
|
4
cli/tests/fixtures/multi-dep-str/Main.roc
vendored
Normal file
4
cli/tests/fixtures/multi-dep-str/Main.roc
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
app Main provides [ main ] imports [ Dep1 ]
|
||||
|
||||
main : Str
|
||||
main = Dep1.str1
|
23
cli/tests/fixtures/multi-dep-str/platform/Cargo.lock
generated
vendored
Normal file
23
cli/tests/fixtures/multi-dep-str/platform/Cargo.lock
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "host"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"roc_std 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "roc_std"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[metadata]
|
||||
"checksum libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)" = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743"
|
13
cli/tests/fixtures/multi-dep-str/platform/Cargo.toml
vendored
Normal file
13
cli/tests/fixtures/multi-dep-str/platform/Cargo.toml
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "host"
|
||||
version = "0.1.0"
|
||||
authors = ["Richard Feldman <oss@rtfeldman.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib"]
|
||||
|
||||
[dependencies]
|
||||
roc_std = { path = "../../../../../roc_std" }
|
||||
|
||||
[workspace]
|
8
cli/tests/fixtures/multi-dep-str/platform/README.md
vendored
Normal file
8
cli/tests/fixtures/multi-dep-str/platform/README.md
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Rebuilding the host from source
|
||||
|
||||
Run `build.sh` to manually rebuild this platform's host.
|
||||
|
||||
Note that the compiler currently has its own logic for rebuilding these hosts
|
||||
(in `link.rs`). It's hardcoded for now, but the long-term goal is that
|
||||
hosts will be precompiled by platform authors and distributed in packages,
|
||||
at which point only package authors will need to think about rebuilding hosts.
|
7
cli/tests/fixtures/multi-dep-str/platform/host.c
vendored
Normal file
7
cli/tests/fixtures/multi-dep-str/platform/host.c
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
#include <stdio.h>
|
||||
|
||||
extern int rust_main();
|
||||
|
||||
int main() {
|
||||
return rust_main();
|
||||
}
|
28
cli/tests/fixtures/multi-dep-str/platform/src/lib.rs
vendored
Normal file
28
cli/tests/fixtures/multi-dep-str/platform/src/lib.rs
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
use roc_std::RocCallResult;
|
||||
use roc_std::RocStr;
|
||||
use std::str;
|
||||
|
||||
extern "C" {
|
||||
#[link_name = "main_1_exposed"]
|
||||
fn say_hello(output: &mut RocCallResult<RocStr>) -> ();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn rust_main() -> isize {
|
||||
let answer = unsafe {
|
||||
use std::mem::MaybeUninit;
|
||||
let mut output: MaybeUninit<RocCallResult<RocStr>> = MaybeUninit::uninit();
|
||||
|
||||
say_hello(&mut *output.as_mut_ptr());
|
||||
|
||||
match output.assume_init().into() {
|
||||
Ok(value) => value,
|
||||
Err(msg) => panic!("roc failed with message {}", msg),
|
||||
}
|
||||
};
|
||||
|
||||
println!("Roc says: {}", str::from_utf8(answer.as_slice()).unwrap());
|
||||
|
||||
// Exit code
|
||||
0
|
||||
}
|
4
cli/tests/fixtures/multi-dep-thunk/Dep1.roc
vendored
Normal file
4
cli/tests/fixtures/multi-dep-thunk/Dep1.roc
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
interface Dep1 exposes [ value1 ] imports [ Dep2 ]
|
||||
|
||||
value1 : {} -> Str
|
||||
value1 = \_ -> Dep2.value2 {}
|
4
cli/tests/fixtures/multi-dep-thunk/Dep2.roc
vendored
Normal file
4
cli/tests/fixtures/multi-dep-thunk/Dep2.roc
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
interface Dep2 exposes [ value2 ] imports []
|
||||
|
||||
value2 : {} -> Str
|
||||
value2 = \_ -> "I am Dep2.value2"
|
4
cli/tests/fixtures/multi-dep-thunk/Main.roc
vendored
Normal file
4
cli/tests/fixtures/multi-dep-thunk/Main.roc
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
app Main provides [ main ] imports [ Dep1 ]
|
||||
|
||||
main : Str
|
||||
main = Dep1.value1 {}
|
23
cli/tests/fixtures/multi-dep-thunk/platform/Cargo.lock
generated
vendored
Normal file
23
cli/tests/fixtures/multi-dep-thunk/platform/Cargo.lock
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "host"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"roc_std 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "roc_std"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[metadata]
|
||||
"checksum libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)" = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743"
|
13
cli/tests/fixtures/multi-dep-thunk/platform/Cargo.toml
vendored
Normal file
13
cli/tests/fixtures/multi-dep-thunk/platform/Cargo.toml
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "host"
|
||||
version = "0.1.0"
|
||||
authors = ["Richard Feldman <oss@rtfeldman.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib"]
|
||||
|
||||
[dependencies]
|
||||
roc_std = { path = "../../../../../roc_std" }
|
||||
|
||||
[workspace]
|
8
cli/tests/fixtures/multi-dep-thunk/platform/README.md
vendored
Normal file
8
cli/tests/fixtures/multi-dep-thunk/platform/README.md
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Rebuilding the host from source
|
||||
|
||||
Run `build.sh` to manually rebuild this platform's host.
|
||||
|
||||
Note that the compiler currently has its own logic for rebuilding these hosts
|
||||
(in `link.rs`). It's hardcoded for now, but the long-term goal is that
|
||||
hosts will be precompiled by platform authors and distributed in packages,
|
||||
at which point only package authors will need to think about rebuilding hosts.
|
12
cli/tests/fixtures/multi-dep-thunk/platform/build.sh
vendored
Executable file
12
cli/tests/fixtures/multi-dep-thunk/platform/build.sh
vendored
Executable file
|
@ -0,0 +1,12 @@
|
|||
#!/bin/bash
|
||||
|
||||
# compile c_host.o and rust_host.o
|
||||
clang -c host.c -o c_host.o
|
||||
rustc host.rs -o rust_host.o
|
||||
|
||||
# link them together into host.o
|
||||
ld -r c_host.o rust_host.o -o host.o
|
||||
|
||||
# clean up
|
||||
rm -f c_host.o
|
||||
rm -f rust_host.o
|
7
cli/tests/fixtures/multi-dep-thunk/platform/host.c
vendored
Normal file
7
cli/tests/fixtures/multi-dep-thunk/platform/host.c
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
#include <stdio.h>
|
||||
|
||||
extern int rust_main();
|
||||
|
||||
int main() {
|
||||
return rust_main();
|
||||
}
|
28
cli/tests/fixtures/multi-dep-thunk/platform/src/lib.rs
vendored
Normal file
28
cli/tests/fixtures/multi-dep-thunk/platform/src/lib.rs
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
use roc_std::RocCallResult;
|
||||
use roc_std::RocStr;
|
||||
use std::str;
|
||||
|
||||
extern "C" {
|
||||
#[link_name = "main_1_exposed"]
|
||||
fn say_hello(output: &mut RocCallResult<RocStr>) -> ();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn rust_main() -> isize {
|
||||
let answer = unsafe {
|
||||
use std::mem::MaybeUninit;
|
||||
let mut output: MaybeUninit<RocCallResult<RocStr>> = MaybeUninit::uninit();
|
||||
|
||||
say_hello(&mut *output.as_mut_ptr());
|
||||
|
||||
match output.assume_init().into() {
|
||||
Ok(value) => value,
|
||||
Err(msg) => panic!("roc failed with message {}", msg),
|
||||
}
|
||||
};
|
||||
|
||||
println!("Roc says: {}", str::from_utf8(answer.as_slice()).unwrap());
|
||||
|
||||
// Exit code
|
||||
0
|
||||
}
|
|
@ -207,6 +207,40 @@ pub fn example_file(dir_name: &str, file_name: &str) -> PathBuf {
|
|||
path
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn fixtures_dir(dir_name: &str) -> PathBuf {
|
||||
let mut path = env::current_exe().ok().unwrap();
|
||||
|
||||
// Get rid of the filename in target/debug/deps/cli_run-99c65e4e9a1fbd06
|
||||
path.pop();
|
||||
|
||||
// If we're in deps/ get rid of deps/ in target/debug/deps/
|
||||
if path.ends_with("deps") {
|
||||
path.pop();
|
||||
}
|
||||
|
||||
// Get rid of target/debug/ so we're back at the project root
|
||||
path.pop();
|
||||
path.pop();
|
||||
|
||||
// Descend into cli/tests/fixtures/{dir_name}
|
||||
path.push("cli");
|
||||
path.push("tests");
|
||||
path.push("fixtures");
|
||||
path.push(dir_name);
|
||||
|
||||
path
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn fixture_file(dir_name: &str, file_name: &str) -> PathBuf {
|
||||
let mut path = fixtures_dir(dir_name);
|
||||
|
||||
path.push(file_name);
|
||||
|
||||
path
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn repl_eval(input: &str) -> Out {
|
||||
let mut cmd = Command::new(path_to_roc_binary());
|
||||
|
|
|
@ -175,6 +175,13 @@ mod repl_eval {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_contains() {
|
||||
expect_success("List.contains [] 0", "False : Bool");
|
||||
expect_success("List.contains [ 1, 2, 3 ] 2", "True : Bool");
|
||||
expect_success("List.contains [ 1, 2, 3 ] 4", "False : Bool");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_1_field_i64_record() {
|
||||
// Even though this gets unwrapped at runtime, the repl should still
|
||||
|
|
|
@ -6,7 +6,7 @@ use libloading::{Error, Library};
|
|||
use roc_gen::llvm::build::OptLevel;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Child, Command};
|
||||
use std::process::{Child, Command, Output};
|
||||
use target_lexicon::{Architecture, OperatingSystem, Triple};
|
||||
use tempfile::tempdir;
|
||||
|
||||
|
@ -47,7 +47,7 @@ pub fn rebuild_host(host_input_path: &Path) {
|
|||
let host_dest = host_input_path.with_file_name("host.o");
|
||||
|
||||
// Compile host.c
|
||||
Command::new("clang")
|
||||
let output = Command::new("clang")
|
||||
.env_clear()
|
||||
.args(&[
|
||||
"-c",
|
||||
|
@ -58,18 +58,22 @@ pub fn rebuild_host(host_input_path: &Path) {
|
|||
.output()
|
||||
.unwrap();
|
||||
|
||||
validate_output("host.c", "clang", output);
|
||||
|
||||
if cargo_host_src.exists() {
|
||||
// Compile and link Cargo.toml, if it exists
|
||||
let cargo_dir = host_input_path.parent().unwrap();
|
||||
let libhost_dir = cargo_dir.join("target").join("release");
|
||||
|
||||
Command::new("cargo")
|
||||
let output = Command::new("cargo")
|
||||
.args(&["build", "--release"])
|
||||
.current_dir(cargo_dir)
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
Command::new("ld")
|
||||
validate_output("host.rs", "cargo build --release", output);
|
||||
|
||||
let output = Command::new("ld")
|
||||
.env_clear()
|
||||
.args(&[
|
||||
"-r",
|
||||
|
@ -82,9 +86,11 @@ pub fn rebuild_host(host_input_path: &Path) {
|
|||
])
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
validate_output("c_host.o", "ld", output);
|
||||
} else if rust_host_src.exists() {
|
||||
// Compile and link host.rs, if it exists
|
||||
Command::new("rustc")
|
||||
let output = Command::new("rustc")
|
||||
.args(&[
|
||||
rust_host_src.to_str().unwrap(),
|
||||
"-o",
|
||||
|
@ -93,7 +99,9 @@ pub fn rebuild_host(host_input_path: &Path) {
|
|||
.output()
|
||||
.unwrap();
|
||||
|
||||
Command::new("ld")
|
||||
validate_output("host.rs", "rustc", output);
|
||||
|
||||
let output = Command::new("ld")
|
||||
.env_clear()
|
||||
.args(&[
|
||||
"-r",
|
||||
|
@ -105,8 +113,10 @@ pub fn rebuild_host(host_input_path: &Path) {
|
|||
.output()
|
||||
.unwrap();
|
||||
|
||||
validate_output("rust_host.o", "ld", output);
|
||||
|
||||
// Clean up rust_host.o
|
||||
Command::new("rm")
|
||||
let output = Command::new("rm")
|
||||
.env_clear()
|
||||
.args(&[
|
||||
"-f",
|
||||
|
@ -115,13 +125,17 @@ pub fn rebuild_host(host_input_path: &Path) {
|
|||
])
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
validate_output("rust_host.o", "rm", output);
|
||||
} else {
|
||||
// Clean up rust_host.o
|
||||
Command::new("mv")
|
||||
let output = Command::new("mv")
|
||||
.env_clear()
|
||||
.args(&[c_host_dest, host_dest])
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
validate_output("rust_host.o", "mv", output);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -303,3 +317,18 @@ pub fn module_to_dylib(
|
|||
|
||||
Library::new(path)
|
||||
}
|
||||
|
||||
fn validate_output(file_name: &str, cmd_name: &str, output: Output) {
|
||||
if !output.status.success() {
|
||||
match std::str::from_utf8(&output.stderr) {
|
||||
Ok(stderr) => panic!(
|
||||
"Failed to rebuild {} - stderr of the `{}` command was:\n{}",
|
||||
file_name, cmd_name, stderr
|
||||
),
|
||||
Err(utf8_err) => panic!(
|
||||
"Failed to rebuild {} - stderr of the `{}` command was invalid utf8 ({:?})",
|
||||
file_name, cmd_name, utf8_err
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,24 +14,26 @@ use target_lexicon::Triple;
|
|||
#[allow(clippy::cognitive_complexity)]
|
||||
pub fn gen_from_mono_module(
|
||||
arena: &Bump,
|
||||
loaded: MonomorphizedModule,
|
||||
file_path: PathBuf,
|
||||
mut loaded: MonomorphizedModule,
|
||||
_file_path: PathBuf,
|
||||
target: Triple,
|
||||
app_o_file: &Path,
|
||||
opt_level: OptLevel,
|
||||
) {
|
||||
use roc_reporting::report::{can_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE};
|
||||
use roc_reporting::report::{
|
||||
can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE,
|
||||
};
|
||||
|
||||
let src = loaded.src;
|
||||
let home = loaded.module_id;
|
||||
for (home, (module_path, src)) in loaded.sources {
|
||||
let src_lines: Vec<&str> = src.split('\n').collect();
|
||||
let palette = DEFAULT_PALETTE;
|
||||
|
||||
// Report parsing and canonicalization problems
|
||||
let alloc = RocDocAllocator::new(&src_lines, home, &loaded.interns);
|
||||
|
||||
for problem in loaded.can_problems.into_iter() {
|
||||
let report = can_problem(&alloc, file_path.clone(), problem);
|
||||
let problems = loaded.can_problems.remove(&home).unwrap_or_default();
|
||||
for problem in problems.into_iter() {
|
||||
let report = can_problem(&alloc, module_path.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
@ -39,8 +41,9 @@ pub fn gen_from_mono_module(
|
|||
println!("\n{}\n", buf);
|
||||
}
|
||||
|
||||
for problem in loaded.type_problems.into_iter() {
|
||||
let report = type_problem(&alloc, file_path.clone(), problem);
|
||||
let problems = loaded.type_problems.remove(&home).unwrap_or_default();
|
||||
for problem in problems {
|
||||
let report = type_problem(&alloc, module_path.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
@ -48,6 +51,17 @@ pub fn gen_from_mono_module(
|
|||
println!("\n{}\n", buf);
|
||||
}
|
||||
|
||||
let problems = loaded.mono_problems.remove(&home).unwrap_or_default();
|
||||
for problem in problems {
|
||||
let report = mono_problem(&alloc, module_path.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
||||
println!("\n{}\n", buf);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate the binary
|
||||
|
||||
let context = Context::create();
|
||||
|
|
|
@ -459,6 +459,15 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
|||
),
|
||||
);
|
||||
|
||||
// contains : List elem, elem -> Bool
|
||||
add_type(
|
||||
Symbol::LIST_CONTAINS,
|
||||
top_level_function(
|
||||
vec![list_type(flex(TVAR1)), flex(TVAR1)],
|
||||
Box::new(bool_type()),
|
||||
),
|
||||
);
|
||||
|
||||
// walkRight : List elem, (elem -> accum -> accum), accum -> accum
|
||||
add_type(
|
||||
Symbol::LIST_WALK_RIGHT,
|
||||
|
|
|
@ -674,6 +674,15 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
|||
)
|
||||
});
|
||||
|
||||
// contains : Attr * (List a)
|
||||
// , a
|
||||
// -> Attr * Bool
|
||||
add_type(Symbol::LIST_CONTAINS, {
|
||||
let_tvars! { a, star1, star2 };
|
||||
|
||||
unique_function(vec![list_type(star1, a), flex(a)], bool_type(star2))
|
||||
});
|
||||
|
||||
// join : Attr * (List (Attr * (List a)))
|
||||
// -> Attr * (List a)
|
||||
add_type(Symbol::LIST_JOIN, {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::env::Env;
|
||||
use crate::scope::Scope;
|
||||
use roc_collections::all::{ImMap, MutSet, SendMap};
|
||||
use roc_collections::all::{ImMap, MutMap, MutSet, SendMap};
|
||||
use roc_module::ident::{Ident, Lowercase, TagName};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_parse::ast::{AssignedField, Tag, TypeAnnotation};
|
||||
|
@ -31,6 +31,7 @@ pub struct IntroducedVariables {
|
|||
pub wildcards: Vec<Variable>,
|
||||
pub var_by_name: SendMap<Lowercase, Variable>,
|
||||
pub name_by_var: SendMap<Variable, Lowercase>,
|
||||
pub host_exposed_aliases: MutMap<Symbol, Variable>,
|
||||
}
|
||||
|
||||
impl IntroducedVariables {
|
||||
|
@ -43,10 +44,16 @@ impl IntroducedVariables {
|
|||
self.wildcards.push(var);
|
||||
}
|
||||
|
||||
pub fn insert_host_exposed_alias(&mut self, symbol: Symbol, var: Variable) {
|
||||
self.host_exposed_aliases.insert(symbol, var);
|
||||
}
|
||||
|
||||
pub fn union(&mut self, other: &Self) {
|
||||
self.wildcards.extend(other.wildcards.iter().cloned());
|
||||
self.var_by_name.extend(other.var_by_name.clone());
|
||||
self.name_by_var.extend(other.name_by_var.clone());
|
||||
self.host_exposed_aliases
|
||||
.extend(other.host_exposed_aliases.clone());
|
||||
}
|
||||
|
||||
pub fn var_by_name(&self, name: &Lowercase) -> Option<&Variable> {
|
||||
|
@ -220,7 +227,15 @@ fn can_annotation_help(
|
|||
// instantiate variables
|
||||
actual.substitute(&substitutions);
|
||||
|
||||
Type::Alias(symbol, vars, Box::new(actual))
|
||||
// Type::Alias(symbol, vars, Box::new(actual))
|
||||
let actual_var = var_store.fresh();
|
||||
introduced_variables.insert_host_exposed_alias(symbol, actual_var);
|
||||
Type::HostExposedAlias {
|
||||
name: symbol,
|
||||
arguments: vars,
|
||||
actual: Box::new(actual),
|
||||
actual_var,
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let mut args = Vec::new();
|
||||
|
@ -352,7 +367,16 @@ fn can_annotation_help(
|
|||
let alias = scope.lookup_alias(symbol).unwrap();
|
||||
local_aliases.insert(symbol, alias.clone());
|
||||
|
||||
Type::Alias(symbol, vars, Box::new(alias.typ.clone()))
|
||||
// Type::Alias(symbol, vars, Box::new(alias.typ.clone()))
|
||||
|
||||
let actual_var = var_store.fresh();
|
||||
introduced_variables.insert_host_exposed_alias(symbol, actual_var);
|
||||
Type::HostExposedAlias {
|
||||
name: symbol,
|
||||
arguments: vars,
|
||||
actual: Box::new(alias.typ.clone()),
|
||||
actual_var,
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// This is a syntactically invalid type alias.
|
||||
|
|
|
@ -63,6 +63,7 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
|
|||
Symbol::LIST_REPEAT => list_repeat,
|
||||
Symbol::LIST_REVERSE => list_reverse,
|
||||
Symbol::LIST_CONCAT => list_concat,
|
||||
Symbol::LIST_CONTAINS => list_contains,
|
||||
Symbol::LIST_PREPEND => list_prepend,
|
||||
Symbol::LIST_JOIN => list_join,
|
||||
Symbol::LIST_MAP => list_map,
|
||||
|
@ -101,6 +102,36 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
|
|||
Symbol::NUM_ATAN => num_atan,
|
||||
Symbol::NUM_ACOS => num_acos,
|
||||
Symbol::NUM_ASIN => num_asin,
|
||||
Symbol::NUM_MAX_INT => num_max_int,
|
||||
Symbol::NUM_MIN_INT => num_min_int,
|
||||
}
|
||||
}
|
||||
|
||||
/// Num.maxInt : Int
|
||||
fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let int_var = var_store.fresh();
|
||||
let body = Int(int_var, i64::MAX);
|
||||
|
||||
Def {
|
||||
annotation: None,
|
||||
expr_var: int_var,
|
||||
loc_expr: Located::at_zero(body),
|
||||
loc_pattern: Located::at_zero(Pattern::Identifier(symbol)),
|
||||
pattern_vars: SendMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Num.minInt : Int
|
||||
fn num_min_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let int_var = var_store.fresh();
|
||||
let body = Int(int_var, i64::MIN);
|
||||
|
||||
Def {
|
||||
annotation: None,
|
||||
expr_var: int_var,
|
||||
loc_expr: Located::at_zero(body),
|
||||
loc_pattern: Located::at_zero(Pattern::Identifier(symbol)),
|
||||
pattern_vars: SendMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1291,6 +1322,30 @@ fn list_keep_if(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
)
|
||||
}
|
||||
|
||||
// List.contains : List elem, elem, -> Bool
|
||||
fn list_contains(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let list_var = var_store.fresh();
|
||||
let elem_var = var_store.fresh();
|
||||
let bool_var = var_store.fresh();
|
||||
|
||||
let body = RunLowLevel {
|
||||
op: LowLevel::ListContains,
|
||||
args: vec![
|
||||
(list_var, Var(Symbol::ARG_1)),
|
||||
(elem_var, Var(Symbol::ARG_2)),
|
||||
],
|
||||
ret_var: bool_var,
|
||||
};
|
||||
|
||||
defn(
|
||||
symbol,
|
||||
vec![(list_var, Symbol::ARG_1), (elem_var, Symbol::ARG_2)],
|
||||
var_store,
|
||||
body,
|
||||
bool_var,
|
||||
)
|
||||
}
|
||||
|
||||
/// List.map : List before, (before -> after) -> List after
|
||||
fn list_map(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let list_var = var_store.fresh();
|
||||
|
|
|
@ -1443,6 +1443,8 @@ fn to_pending_def<'a>(
|
|||
SpaceBefore(sub_def, _) | SpaceAfter(sub_def, _) | Nested(sub_def) => {
|
||||
to_pending_def(env, var_store, sub_def, scope, pattern_type)
|
||||
}
|
||||
|
||||
NotYetImplemented(s) => todo!("{}", s),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -207,9 +207,8 @@ pub fn canonicalize_expr<'a>(
|
|||
let (can_update, update_out) =
|
||||
canonicalize_expr(env, var_store, scope, loc_update.region, &loc_update.value);
|
||||
if let Var(symbol) = &can_update.value {
|
||||
let (can_fields, mut output) =
|
||||
canonicalize_fields(env, var_store, scope, region, fields);
|
||||
|
||||
match canonicalize_fields(env, var_store, scope, region, fields) {
|
||||
Ok((can_fields, mut output)) => {
|
||||
output.references = output.references.union(update_out.references);
|
||||
|
||||
let answer = Update {
|
||||
|
@ -220,6 +219,20 @@ pub fn canonicalize_expr<'a>(
|
|||
};
|
||||
|
||||
(answer, output)
|
||||
}
|
||||
Err(CanonicalizeRecordProblem::InvalidOptionalValue {
|
||||
field_name,
|
||||
field_region,
|
||||
record_region,
|
||||
}) => (
|
||||
Expr::RuntimeError(roc_problem::can::RuntimeError::InvalidOptionalValue {
|
||||
field_name,
|
||||
field_region,
|
||||
record_region,
|
||||
}),
|
||||
Output::default(),
|
||||
),
|
||||
}
|
||||
} else {
|
||||
// only (optionally qualified) variables can be updated, not arbitrary expressions
|
||||
|
||||
|
@ -241,16 +254,27 @@ pub fn canonicalize_expr<'a>(
|
|||
if fields.is_empty() {
|
||||
(EmptyRecord, Output::default())
|
||||
} else {
|
||||
let (can_fields, output) =
|
||||
canonicalize_fields(env, var_store, scope, region, fields);
|
||||
|
||||
(
|
||||
match canonicalize_fields(env, var_store, scope, region, fields) {
|
||||
Ok((can_fields, output)) => (
|
||||
Record {
|
||||
record_var: var_store.fresh(),
|
||||
fields: can_fields,
|
||||
},
|
||||
output,
|
||||
)
|
||||
),
|
||||
Err(CanonicalizeRecordProblem::InvalidOptionalValue {
|
||||
field_name,
|
||||
field_region,
|
||||
record_region,
|
||||
}) => (
|
||||
Expr::RuntimeError(roc_problem::can::RuntimeError::InvalidOptionalValue {
|
||||
field_name,
|
||||
field_region,
|
||||
record_region,
|
||||
}),
|
||||
Output::default(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
ast::Expr::Str(literal) => flatten_str_literal(env, var_store, scope, literal),
|
||||
|
@ -971,20 +995,26 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
enum CanonicalizeRecordProblem {
|
||||
InvalidOptionalValue {
|
||||
field_name: Lowercase,
|
||||
field_region: Region,
|
||||
record_region: Region,
|
||||
},
|
||||
}
|
||||
fn canonicalize_fields<'a>(
|
||||
env: &mut Env<'a>,
|
||||
var_store: &mut VarStore,
|
||||
scope: &mut Scope,
|
||||
region: Region,
|
||||
fields: &'a [Located<ast::AssignedField<'a, ast::Expr<'a>>>],
|
||||
) -> (SendMap<Lowercase, Field>, Output) {
|
||||
) -> Result<(SendMap<Lowercase, Field>, Output), CanonicalizeRecordProblem> {
|
||||
let mut can_fields = SendMap::default();
|
||||
let mut output = Output::default();
|
||||
|
||||
for loc_field in fields.iter() {
|
||||
let (label, field_expr, field_out, field_var) =
|
||||
canonicalize_field(env, var_store, scope, &loc_field.value, loc_field.region);
|
||||
|
||||
match canonicalize_field(env, var_store, scope, &loc_field.value, loc_field.region) {
|
||||
Ok((label, field_expr, field_out, field_var)) => {
|
||||
let field = Field {
|
||||
var: field_var,
|
||||
region: loc_field.region,
|
||||
|
@ -1004,17 +1034,40 @@ fn canonicalize_fields<'a>(
|
|||
|
||||
output.references = output.references.union(field_out.references);
|
||||
}
|
||||
|
||||
(can_fields, output)
|
||||
Err(CanonicalizeFieldProblem::InvalidOptionalValue {
|
||||
field_name,
|
||||
field_region,
|
||||
}) => {
|
||||
env.problems.push(Problem::InvalidOptionalValue {
|
||||
field_name: field_name.clone(),
|
||||
field_region,
|
||||
record_region: region,
|
||||
});
|
||||
return Err(CanonicalizeRecordProblem::InvalidOptionalValue {
|
||||
field_name,
|
||||
field_region,
|
||||
record_region: region,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((can_fields, output))
|
||||
}
|
||||
|
||||
enum CanonicalizeFieldProblem {
|
||||
InvalidOptionalValue {
|
||||
field_name: Lowercase,
|
||||
field_region: Region,
|
||||
},
|
||||
}
|
||||
fn canonicalize_field<'a>(
|
||||
env: &mut Env<'a>,
|
||||
var_store: &mut VarStore,
|
||||
scope: &mut Scope,
|
||||
field: &'a ast::AssignedField<'a, ast::Expr<'a>>,
|
||||
region: Region,
|
||||
) -> (Lowercase, Located<Expr>, Output, Variable) {
|
||||
) -> Result<(Lowercase, Located<Expr>, Output, Variable), CanonicalizeFieldProblem> {
|
||||
use roc_parse::ast::AssignedField::*;
|
||||
|
||||
match field {
|
||||
|
@ -1024,17 +1077,18 @@ fn canonicalize_field<'a>(
|
|||
let (loc_can_expr, output) =
|
||||
canonicalize_expr(env, var_store, scope, loc_expr.region, &loc_expr.value);
|
||||
|
||||
(
|
||||
Ok((
|
||||
Lowercase::from(label.value),
|
||||
loc_can_expr,
|
||||
output,
|
||||
field_var,
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
OptionalValue(_, _, _) => {
|
||||
todo!("TODO gracefully handle an optional field being used in an Expr");
|
||||
}
|
||||
OptionalValue(label, _, loc_expr) => Err(CanonicalizeFieldProblem::InvalidOptionalValue {
|
||||
field_name: Lowercase::from(label.value),
|
||||
field_region: Region::span_across(&label.region, &loc_expr.region),
|
||||
}),
|
||||
|
||||
// A label with no value, e.g. `{ name }` (this is sugar for { name: name })
|
||||
LabelOnly(_) => {
|
||||
|
|
|
@ -47,6 +47,8 @@ pub fn desugar_def<'a>(arena: &'a Bump, def: &'a Def<'a>) -> Def<'a> {
|
|||
Nested(alias @ Alias { .. }) => Nested(alias),
|
||||
ann @ Annotation(_, _) => Nested(ann),
|
||||
Nested(ann @ Annotation(_, _)) => Nested(ann),
|
||||
Nested(NotYetImplemented(s)) => todo!("{}", s),
|
||||
NotYetImplemented(s) => todo!("{}", s),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -305,6 +305,32 @@ mod test_can {
|
|||
// );
|
||||
// }
|
||||
|
||||
// OPTIONAL RECORDS
|
||||
#[test]
|
||||
fn incorrect_optional_value() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
{ x ? 42 }
|
||||
"#
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut {
|
||||
problems, loc_expr, ..
|
||||
} = can_expr_with(&arena, test_home(), src);
|
||||
|
||||
assert_eq!(problems.len(), 1);
|
||||
assert!(problems.iter().all(|problem| match problem {
|
||||
Problem::InvalidOptionalValue { .. } => true,
|
||||
_ => false,
|
||||
}));
|
||||
|
||||
assert!(match loc_expr.value {
|
||||
Expr::RuntimeError(roc_problem::can::RuntimeError::InvalidOptionalValue { .. }) => true,
|
||||
_ => false,
|
||||
});
|
||||
}
|
||||
|
||||
// TAIL CALLS
|
||||
fn get_closure(expr: &Expr, i: usize) -> roc_can::expr::Recursive {
|
||||
match expr {
|
||||
LetRec(assignments, body, _) => {
|
||||
|
|
|
@ -2105,6 +2105,46 @@ fn annotation_to_attr_type(
|
|||
|
||||
let alias = Type::Alias(*symbol, new_fields, Box::new(actual_type));
|
||||
|
||||
(
|
||||
actual_vars,
|
||||
crate::builtins::builtin_type(Symbol::ATTR_ATTR, vec![uniq_type, alias]),
|
||||
)
|
||||
} else {
|
||||
panic!("lifted type is not Attr")
|
||||
}
|
||||
}
|
||||
HostExposedAlias {
|
||||
name: symbol,
|
||||
arguments: fields,
|
||||
actual_var,
|
||||
actual,
|
||||
} => {
|
||||
let (mut actual_vars, lifted_actual) =
|
||||
annotation_to_attr_type(var_store, actual, rigids, change_var_kind);
|
||||
|
||||
if let Type::Apply(attr_symbol, args) = lifted_actual {
|
||||
debug_assert!(attr_symbol == Symbol::ATTR_ATTR);
|
||||
|
||||
let uniq_type = args[0].clone();
|
||||
let actual_type = args[1].clone();
|
||||
|
||||
let mut new_fields = Vec::with_capacity(fields.len());
|
||||
for (name, tipe) in fields {
|
||||
let (lifted_vars, lifted) =
|
||||
annotation_to_attr_type(var_store, tipe, rigids, change_var_kind);
|
||||
|
||||
actual_vars.extend(lifted_vars);
|
||||
|
||||
new_fields.push((name.clone(), lifted));
|
||||
}
|
||||
|
||||
let alias = Type::HostExposedAlias {
|
||||
name: *symbol,
|
||||
arguments: new_fields,
|
||||
actual_var: *actual_var,
|
||||
actual: Box::new(actual_type),
|
||||
};
|
||||
|
||||
(
|
||||
actual_vars,
|
||||
crate::builtins::builtin_type(Symbol::ATTR_ATTR, vec![uniq_type, alias]),
|
||||
|
|
|
@ -20,6 +20,7 @@ impl<'a> Formattable<'a> for Def<'a> {
|
|||
spaces.iter().any(|s| is_comment(s)) || sub_def.is_multiline()
|
||||
}
|
||||
Nested(def) => def.is_multiline(),
|
||||
NotYetImplemented(s) => todo!("{}", s),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,6 +67,7 @@ impl<'a> Formattable<'a> for Def<'a> {
|
|||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
}
|
||||
Nested(def) => def.format(buf, indent),
|
||||
NotYetImplemented(s) => todo!("{}", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ im = "14" # im and im-rc should always have the same version!
|
|||
im-rc = "14" # im and im-rc should always have the same version!
|
||||
bumpalo = { version = "3.2", features = ["collections"] }
|
||||
inlinable_string = "0.1"
|
||||
either = "1.6.1"
|
||||
# NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything.
|
||||
#
|
||||
# The reason for this fork is that the way Inkwell is designed, you have to use
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use crate::layout_id::LayoutIds;
|
||||
use crate::llvm::build_list::{
|
||||
allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_get_unsafe,
|
||||
list_join, list_keep_if, list_len, list_map, list_prepend, list_repeat, list_reverse, list_set,
|
||||
list_single, list_walk_right,
|
||||
allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_contains,
|
||||
list_get_unsafe, list_join, list_keep_if, list_len, list_map, list_prepend, list_repeat,
|
||||
list_reverse, list_set, list_single, list_walk_right,
|
||||
};
|
||||
use crate::llvm::build_str::{str_concat, str_len, str_split, CHAR_LAYOUT};
|
||||
use crate::llvm::compare::{build_eq, build_neq};
|
||||
|
@ -34,7 +34,7 @@ use roc_collections::all::{ImMap, MutSet};
|
|||
use roc_module::low_level::LowLevel;
|
||||
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||
use roc_mono::ir::{JoinPointId, Wrapped};
|
||||
use roc_mono::layout::{Builtin, Layout, MemoryMode};
|
||||
use roc_mono::layout::{Builtin, ClosureLayout, Layout, MemoryMode};
|
||||
use target_lexicon::CallingConvention;
|
||||
|
||||
/// This is for Inkwell's FunctionValue::verify - we want to know the verification
|
||||
|
@ -1842,6 +1842,286 @@ pub fn create_entry_block_alloca<'a, 'ctx>(
|
|||
builder.build_alloca(basic_type, name)
|
||||
}
|
||||
|
||||
fn expose_function_to_host<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
roc_function: FunctionValue<'ctx>,
|
||||
) {
|
||||
use inkwell::types::BasicType;
|
||||
|
||||
let roc_wrapper_function = make_exception_catching_wrapper(env, roc_function);
|
||||
|
||||
let roc_function_type = roc_wrapper_function.get_type();
|
||||
|
||||
// STEP 1: turn `f : a,b,c -> d` into `f : a,b,c, &d -> {}`
|
||||
let mut argument_types = roc_function_type.get_param_types();
|
||||
let return_type = roc_function_type.get_return_type().unwrap();
|
||||
let output_type = return_type.ptr_type(AddressSpace::Generic);
|
||||
argument_types.push(output_type.into());
|
||||
|
||||
let c_function_type = env.context.void_type().fn_type(&argument_types, false);
|
||||
let c_function_name: String = format!("{}_exposed", roc_function.get_name().to_str().unwrap());
|
||||
|
||||
let c_function = env.module.add_function(
|
||||
c_function_name.as_str(),
|
||||
c_function_type,
|
||||
Some(Linkage::External),
|
||||
);
|
||||
|
||||
// STEP 2: build the exposed function's body
|
||||
let builder = env.builder;
|
||||
let context = env.context;
|
||||
|
||||
let entry = context.append_basic_block(c_function, "entry");
|
||||
|
||||
builder.position_at_end(entry);
|
||||
|
||||
// drop the final argument, which is the pointer we write the result into
|
||||
let args = c_function.get_params();
|
||||
let output_arg_index = args.len() - 1;
|
||||
let args = &args[..args.len() - 1];
|
||||
|
||||
debug_assert_eq!(args.len(), roc_function.get_params().len());
|
||||
debug_assert_eq!(args.len(), roc_wrapper_function.get_params().len());
|
||||
|
||||
let call_wrapped = builder.build_call(roc_wrapper_function, args, "call_wrapped_function");
|
||||
call_wrapped.set_call_convention(FAST_CALL_CONV);
|
||||
|
||||
let call_result = call_wrapped.try_as_basic_value().left().unwrap();
|
||||
|
||||
let output_arg = c_function
|
||||
.get_nth_param(output_arg_index as u32)
|
||||
.unwrap()
|
||||
.into_pointer_value();
|
||||
|
||||
builder.build_store(output_arg, call_result);
|
||||
|
||||
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_name: String = format!("{}_size", roc_function.get_name().to_str().unwrap());
|
||||
|
||||
let size_function = env.module.add_function(
|
||||
size_function_name.as_str(),
|
||||
size_function_type,
|
||||
Some(Linkage::External),
|
||||
);
|
||||
|
||||
let entry = context.append_basic_block(size_function, "entry");
|
||||
|
||||
builder.position_at_end(entry);
|
||||
|
||||
let size: BasicValueEnum = return_type.size_of().unwrap().into();
|
||||
builder.build_return(Some(&size));
|
||||
}
|
||||
|
||||
fn invoke_and_catch<'a, 'ctx, 'env, F, T>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
function: F,
|
||||
arguments: &[BasicValueEnum<'ctx>],
|
||||
return_type: T,
|
||||
) -> BasicValueEnum<'ctx>
|
||||
where
|
||||
F: Into<either::Either<FunctionValue<'ctx>, PointerValue<'ctx>>>,
|
||||
T: inkwell::types::BasicType<'ctx>,
|
||||
{
|
||||
let context = env.context;
|
||||
let builder = env.builder;
|
||||
|
||||
let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic);
|
||||
|
||||
let call_result_type = context.struct_type(
|
||||
&[context.i64_type().into(), return_type.as_basic_type_enum()],
|
||||
false,
|
||||
);
|
||||
|
||||
let then_block = context.append_basic_block(parent, "then_block");
|
||||
let catch_block = context.append_basic_block(parent, "catch_block");
|
||||
let cont_block = context.append_basic_block(parent, "cont_block");
|
||||
|
||||
let result_alloca = builder.build_alloca(call_result_type, "result");
|
||||
|
||||
// invoke instead of call, so that we can catch any exeptions thrown in Roc code
|
||||
let call_result = {
|
||||
let call = builder.build_invoke(
|
||||
function,
|
||||
&arguments,
|
||||
then_block,
|
||||
catch_block,
|
||||
"call_roc_function",
|
||||
);
|
||||
call.set_call_convention(FAST_CALL_CONV);
|
||||
call.try_as_basic_value().left().unwrap()
|
||||
};
|
||||
|
||||
// exception handling
|
||||
{
|
||||
builder.position_at_end(catch_block);
|
||||
|
||||
let landing_pad_type = {
|
||||
let exception_ptr = context.i8_type().ptr_type(AddressSpace::Generic).into();
|
||||
let selector_value = context.i32_type().into();
|
||||
|
||||
context.struct_type(&[exception_ptr, selector_value], false)
|
||||
};
|
||||
|
||||
let info = builder
|
||||
.build_catch_all_landing_pad(
|
||||
&landing_pad_type,
|
||||
&BasicValueEnum::IntValue(context.i8_type().const_zero()),
|
||||
context.i8_type().ptr_type(AddressSpace::Generic),
|
||||
"main_landing_pad",
|
||||
)
|
||||
.into_struct_value();
|
||||
|
||||
let exception_ptr = builder
|
||||
.build_extract_value(info, 0, "exception_ptr")
|
||||
.unwrap();
|
||||
|
||||
let thrown = cxa_begin_catch(env, exception_ptr);
|
||||
|
||||
let error_msg = {
|
||||
let exception_type = u8_ptr;
|
||||
let ptr = builder.build_bitcast(
|
||||
thrown,
|
||||
exception_type.ptr_type(AddressSpace::Generic),
|
||||
"cast",
|
||||
);
|
||||
|
||||
builder.build_load(ptr.into_pointer_value(), "error_msg")
|
||||
};
|
||||
|
||||
let return_type = context.struct_type(&[context.i64_type().into(), u8_ptr.into()], false);
|
||||
|
||||
let return_value = {
|
||||
let v1 = return_type.const_zero();
|
||||
|
||||
// flag is non-zero, indicating failure
|
||||
let flag = context.i64_type().const_int(1, false);
|
||||
|
||||
let v2 = builder
|
||||
.build_insert_value(v1, flag, 0, "set_error")
|
||||
.unwrap();
|
||||
|
||||
let v3 = builder
|
||||
.build_insert_value(v2, error_msg, 1, "set_exception")
|
||||
.unwrap();
|
||||
|
||||
v3
|
||||
};
|
||||
|
||||
// bitcast result alloca so we can store our concrete type { flag, error_msg } in there
|
||||
let result_alloca_bitcast = builder
|
||||
.build_bitcast(
|
||||
result_alloca,
|
||||
return_type.ptr_type(AddressSpace::Generic),
|
||||
"result_alloca_bitcast",
|
||||
)
|
||||
.into_pointer_value();
|
||||
|
||||
// store our return value
|
||||
builder.build_store(result_alloca_bitcast, return_value);
|
||||
|
||||
cxa_end_catch(env);
|
||||
|
||||
builder.build_unconditional_branch(cont_block);
|
||||
}
|
||||
|
||||
{
|
||||
builder.position_at_end(then_block);
|
||||
|
||||
let return_value = {
|
||||
let v1 = call_result_type.const_zero();
|
||||
|
||||
let v2 = builder
|
||||
.build_insert_value(v1, context.i64_type().const_zero(), 0, "set_no_error")
|
||||
.unwrap();
|
||||
let v3 = builder
|
||||
.build_insert_value(v2, call_result, 1, "set_call_result")
|
||||
.unwrap();
|
||||
|
||||
v3
|
||||
};
|
||||
|
||||
let ptr = builder.build_bitcast(
|
||||
result_alloca,
|
||||
call_result_type.ptr_type(AddressSpace::Generic),
|
||||
"name",
|
||||
);
|
||||
builder.build_store(ptr.into_pointer_value(), return_value);
|
||||
|
||||
builder.build_unconditional_branch(cont_block);
|
||||
}
|
||||
|
||||
builder.position_at_end(cont_block);
|
||||
|
||||
let result = builder.build_load(result_alloca, "result");
|
||||
|
||||
// MUST set the personality at the very end;
|
||||
// doing it earlier can cause the personality to be ignored
|
||||
let personality_func = get_gxx_personality_v0(env);
|
||||
parent.set_personality_function(personality_func);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn make_exception_catching_wrapper<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
roc_function: FunctionValue<'ctx>,
|
||||
) -> FunctionValue<'ctx> {
|
||||
// build the C calling convention wrapper
|
||||
|
||||
let context = env.context;
|
||||
let builder = env.builder;
|
||||
|
||||
let roc_function_type = roc_function.get_type();
|
||||
let argument_types = roc_function_type.get_param_types();
|
||||
|
||||
let wrapper_function_name = format!("{}_catcher", roc_function.get_name().to_str().unwrap());
|
||||
|
||||
let wrapper_return_type = context.struct_type(
|
||||
&[
|
||||
context.i64_type().into(),
|
||||
roc_function_type.get_return_type().unwrap(),
|
||||
],
|
||||
false,
|
||||
);
|
||||
|
||||
let wrapper_function_type = wrapper_return_type.fn_type(&argument_types, false);
|
||||
|
||||
// Add main to the module.
|
||||
let wrapper_function =
|
||||
env.module
|
||||
.add_function(&wrapper_function_name, wrapper_function_type, None);
|
||||
|
||||
// our exposed main function adheres to the C calling convention
|
||||
wrapper_function.set_call_conventions(FAST_CALL_CONV);
|
||||
|
||||
// invoke instead of call, so that we can catch any exeptions thrown in Roc code
|
||||
let arguments = wrapper_function.get_params();
|
||||
|
||||
let basic_block = context.append_basic_block(wrapper_function, "entry");
|
||||
builder.position_at_end(basic_block);
|
||||
|
||||
let result = invoke_and_catch(
|
||||
env,
|
||||
wrapper_function,
|
||||
roc_function,
|
||||
&arguments,
|
||||
roc_function_type.get_return_type().unwrap(),
|
||||
);
|
||||
|
||||
builder.build_return(Some(&result));
|
||||
|
||||
// MUST set the personality at the very end;
|
||||
// doing it earlier can cause the personality to be ignored
|
||||
let personality_func = get_gxx_personality_v0(env);
|
||||
wrapper_function.set_personality_function(personality_func);
|
||||
|
||||
wrapper_function
|
||||
}
|
||||
|
||||
pub fn build_proc_header<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
|
@ -1853,6 +2133,32 @@ pub fn build_proc_header<'a, 'ctx, 'env>(
|
|||
let arena = env.arena;
|
||||
let context = &env.context;
|
||||
|
||||
let fn_name = layout_ids
|
||||
.get(symbol, layout)
|
||||
.to_symbol_string(symbol, &env.interns);
|
||||
|
||||
use roc_mono::ir::HostExposedLayouts;
|
||||
match &proc.host_exposed_layouts {
|
||||
HostExposedLayouts::NotHostExposed => {}
|
||||
HostExposedLayouts::HostExposed { rigids: _, aliases } => {
|
||||
for (name, layout) in aliases {
|
||||
match layout {
|
||||
Layout::Closure(arguments, closure, result) => {
|
||||
build_closure_caller(env, &fn_name, *name, arguments, closure, result)
|
||||
}
|
||||
Layout::FunctionPointer(_arguments, _result) => {
|
||||
// TODO should this be considered a closure of size 0?
|
||||
// or do we let the host call it directly?
|
||||
// then we have no RocCallResult wrapping though
|
||||
}
|
||||
_ => {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ret_type = basic_type_from_layout(arena, context, &proc.ret_layout, env.ptr_bytes);
|
||||
let mut arg_basic_types = Vec::with_capacity_in(args.len(), arena);
|
||||
|
||||
|
@ -1864,26 +2170,199 @@ pub fn build_proc_header<'a, 'ctx, 'env>(
|
|||
|
||||
let fn_type = get_fn_type(&ret_type, &arg_basic_types);
|
||||
|
||||
let fn_name = layout_ids
|
||||
.get(symbol, layout)
|
||||
.to_symbol_string(symbol, &env.interns);
|
||||
let fn_val = env
|
||||
.module
|
||||
.add_function(fn_name.as_str(), fn_type, Some(Linkage::Private));
|
||||
|
||||
if env.exposed_to_host.contains(&symbol) {
|
||||
// If this is an external-facing function, it'll use the C calling convention
|
||||
// and external linkage.
|
||||
fn_val.set_linkage(Linkage::External);
|
||||
fn_val.set_call_conventions(C_CALL_CONV);
|
||||
} else {
|
||||
// If it's an internal-only function, it should use the fast calling conention.
|
||||
fn_val.set_call_conventions(FAST_CALL_CONV);
|
||||
|
||||
if env.exposed_to_host.contains(&symbol) {
|
||||
expose_function_to_host(env, fn_val);
|
||||
}
|
||||
|
||||
fn_val
|
||||
}
|
||||
|
||||
pub fn build_closure_caller<'a, 'ctx, 'env>(
|
||||
env: &'a Env<'a, 'ctx, 'env>,
|
||||
def_name: &str,
|
||||
alias_symbol: Symbol,
|
||||
arguments: &[Layout<'a>],
|
||||
closure: &ClosureLayout<'a>,
|
||||
result: &Layout<'a>,
|
||||
) {
|
||||
use inkwell::types::BasicType;
|
||||
|
||||
let arena = env.arena;
|
||||
let context = &env.context;
|
||||
let builder = env.builder;
|
||||
|
||||
// STEP 1: build function header
|
||||
|
||||
let function_name = format!(
|
||||
"{}_{}_caller",
|
||||
def_name,
|
||||
alias_symbol.ident_string(&env.interns)
|
||||
);
|
||||
|
||||
let mut argument_types = Vec::with_capacity_in(arguments.len() + 3, env.arena);
|
||||
|
||||
for layout in arguments {
|
||||
argument_types.push(basic_type_from_layout(
|
||||
arena,
|
||||
context,
|
||||
layout,
|
||||
env.ptr_bytes,
|
||||
));
|
||||
}
|
||||
|
||||
let function_pointer_type = {
|
||||
let function_layout =
|
||||
ClosureLayout::extend_function_layout(arena, arguments, closure.clone(), result);
|
||||
|
||||
// this is already a (function) pointer type
|
||||
basic_type_from_layout(arena, context, &function_layout, env.ptr_bytes)
|
||||
};
|
||||
argument_types.push(function_pointer_type);
|
||||
|
||||
let closure_argument_type = {
|
||||
let basic_type = basic_type_from_layout(
|
||||
arena,
|
||||
context,
|
||||
&closure.as_block_of_memory_layout(),
|
||||
env.ptr_bytes,
|
||||
);
|
||||
|
||||
basic_type.ptr_type(AddressSpace::Generic)
|
||||
};
|
||||
argument_types.push(closure_argument_type.into());
|
||||
|
||||
let result_type = basic_type_from_layout(arena, context, result, env.ptr_bytes);
|
||||
|
||||
let roc_call_result_type =
|
||||
context.struct_type(&[context.i64_type().into(), result_type], false);
|
||||
|
||||
let output_type = { roc_call_result_type.ptr_type(AddressSpace::Generic) };
|
||||
argument_types.push(output_type.into());
|
||||
|
||||
let function_type = context.void_type().fn_type(&argument_types, false);
|
||||
|
||||
let function_value = env.module.add_function(
|
||||
function_name.as_str(),
|
||||
function_type,
|
||||
Some(Linkage::External),
|
||||
);
|
||||
|
||||
function_value.set_call_conventions(C_CALL_CONV);
|
||||
|
||||
// STEP 2: build function body
|
||||
|
||||
let entry = context.append_basic_block(function_value, "entry");
|
||||
|
||||
builder.position_at_end(entry);
|
||||
|
||||
let mut parameters = function_value.get_params();
|
||||
let output = parameters.pop().unwrap().into_pointer_value();
|
||||
let closure_data_ptr = parameters.pop().unwrap().into_pointer_value();
|
||||
let function_ptr = parameters.pop().unwrap().into_pointer_value();
|
||||
|
||||
let closure_data = builder.build_load(closure_data_ptr, "load_closure_data");
|
||||
|
||||
let mut arguments = parameters;
|
||||
arguments.push(closure_data);
|
||||
|
||||
let result = invoke_and_catch(env, function_value, function_ptr, &arguments, result_type);
|
||||
|
||||
builder.build_store(output, result);
|
||||
|
||||
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_name: String = format!(
|
||||
"{}_{}_size",
|
||||
def_name,
|
||||
alias_symbol.ident_string(&env.interns)
|
||||
);
|
||||
|
||||
let size_function = env.module.add_function(
|
||||
size_function_name.as_str(),
|
||||
size_function_type,
|
||||
Some(Linkage::External),
|
||||
);
|
||||
|
||||
let entry = context.append_basic_block(size_function, "entry");
|
||||
|
||||
builder.position_at_end(entry);
|
||||
|
||||
let size: BasicValueEnum = roc_call_result_type.size_of().unwrap().into();
|
||||
builder.build_return(Some(&size));
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn build_closure_caller_old<'a, 'ctx, 'env>(
|
||||
env: &'a Env<'a, 'ctx, 'env>,
|
||||
closure_function: FunctionValue<'ctx>,
|
||||
) {
|
||||
let context = env.context;
|
||||
let builder = env.builder;
|
||||
// asuming the closure has type `a, b, closure_data -> c`
|
||||
// change that into `a, b, *const closure_data, *mut output -> ()`
|
||||
|
||||
// a function `a, b, closure_data -> RocCallResult<c>`
|
||||
let wrapped_function = make_exception_catching_wrapper(env, closure_function);
|
||||
|
||||
let closure_function_type = closure_function.get_type();
|
||||
let wrapped_function_type = wrapped_function.get_type();
|
||||
|
||||
let mut arguments = closure_function_type.get_param_types();
|
||||
|
||||
// require that the closure data is passed by reference
|
||||
let closure_data_type = arguments.pop().unwrap();
|
||||
let closure_data_ptr_type = get_ptr_type(&closure_data_type, AddressSpace::Generic);
|
||||
arguments.push(closure_data_ptr_type.into());
|
||||
|
||||
// require that a pointer is passed in to write the result into
|
||||
let output_type = get_ptr_type(
|
||||
&wrapped_function_type.get_return_type().unwrap(),
|
||||
AddressSpace::Generic,
|
||||
);
|
||||
arguments.push(output_type.into());
|
||||
|
||||
let caller_function_type = env.context.void_type().fn_type(&arguments, false);
|
||||
let caller_function_name: String =
|
||||
format!("{}_caller", closure_function.get_name().to_str().unwrap());
|
||||
|
||||
let caller_function = env.module.add_function(
|
||||
caller_function_name.as_str(),
|
||||
caller_function_type,
|
||||
Some(Linkage::External),
|
||||
);
|
||||
|
||||
caller_function.set_call_conventions(C_CALL_CONV);
|
||||
|
||||
let entry = context.append_basic_block(caller_function, "entry");
|
||||
|
||||
builder.position_at_end(entry);
|
||||
|
||||
let mut parameters = caller_function.get_params();
|
||||
let output = parameters.pop().unwrap();
|
||||
let closure_data_ptr = parameters.pop().unwrap();
|
||||
|
||||
let closure_data =
|
||||
builder.build_load(closure_data_ptr.into_pointer_value(), "load_closure_data");
|
||||
parameters.push(closure_data);
|
||||
|
||||
let call = builder.build_call(wrapped_function, ¶meters, "call_wrapped_function");
|
||||
call.set_call_convention(FAST_CALL_CONV);
|
||||
|
||||
let result = call.try_as_basic_value().left().unwrap();
|
||||
|
||||
builder.build_store(output.into_pointer_value(), result);
|
||||
|
||||
builder.build_return(None);
|
||||
}
|
||||
|
||||
pub fn build_proc<'a, 'ctx, 'env>(
|
||||
env: &'a Env<'a, 'ctx, 'env>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
|
@ -1907,6 +2386,10 @@ pub fn build_proc<'a, 'ctx, 'env>(
|
|||
// the closure argument (if any) comes in as an opaque sequence of bytes.
|
||||
// we need to cast that to the specific closure data layout that the body expects
|
||||
let value = if let Symbol::ARG_CLOSURE = *arg_symbol {
|
||||
// generate a caller function (to be used by the host)
|
||||
// build_closure_caller(env, fn_val);
|
||||
// builder.position_at_end(entry);
|
||||
|
||||
// blindly trust that there is a layout available for the closure data
|
||||
let layout = proc.closure_data_layout.clone().unwrap();
|
||||
|
||||
|
@ -1971,8 +2454,8 @@ fn call_with_args<'a, 'ctx, 'env>(
|
|||
panic!("Unrecognized builtin function: {:?}", fn_name)
|
||||
} else {
|
||||
panic!(
|
||||
"Unrecognized non-builtin function: {:?} {:?}",
|
||||
fn_name, layout
|
||||
"Unrecognized non-builtin function: {:?} (symbol: {:?}, layout: {:?})",
|
||||
fn_name, symbol, layout
|
||||
)
|
||||
}
|
||||
});
|
||||
|
@ -2126,6 +2609,16 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
|
||||
list_keep_if(env, inplace, parent, func, func_layout, list, list_layout)
|
||||
}
|
||||
ListContains => {
|
||||
// List.contains : List elem, elem -> Bool
|
||||
debug_assert_eq!(args.len(), 2);
|
||||
|
||||
let (list, list_layout) = load_symbol_and_layout(env, scope, &args[0]);
|
||||
|
||||
let (elem, elem_layout) = load_symbol_and_layout(env, scope, &args[1]);
|
||||
|
||||
list_contains(env, parent, elem, elem_layout, list, list_layout)
|
||||
}
|
||||
ListWalkRight => {
|
||||
// List.walkRight : List elem, (elem -> accum -> accum), accum -> accum
|
||||
debug_assert_eq!(args.len(), 3);
|
||||
|
@ -2920,7 +3413,7 @@ fn cxa_rethrow_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValu
|
|||
}
|
||||
|
||||
fn get_gxx_personality_v0<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> FunctionValue<'ctx> {
|
||||
let name = "__cxa_rethrow";
|
||||
let name = "__gxx_personality_v0";
|
||||
|
||||
let module = env.module;
|
||||
let context = env.context;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::llvm::build::{Env, InPlace};
|
||||
use crate::llvm::compare::build_eq;
|
||||
use crate::llvm::convert::{basic_type_from_layout, collection, get_ptr_type, ptr_int};
|
||||
use inkwell::builder::Builder;
|
||||
use inkwell::context::Context;
|
||||
|
@ -819,6 +820,116 @@ pub fn list_walk_right<'a, 'ctx, 'env>(
|
|||
builder.build_load(accum_alloca, "load_final_acum")
|
||||
}
|
||||
|
||||
/// List.contains : List elem, elem -> Bool
|
||||
pub fn list_contains<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
elem: BasicValueEnum<'ctx>,
|
||||
elem_layout: &Layout<'a>,
|
||||
list: BasicValueEnum<'ctx>,
|
||||
list_layout: &Layout<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
use inkwell::types::BasicType;
|
||||
|
||||
let builder = env.builder;
|
||||
|
||||
let wrapper_struct = list.into_struct_value();
|
||||
let list_elem_layout = match &list_layout {
|
||||
// this pointer will never actually be dereferenced
|
||||
Layout::Builtin(Builtin::EmptyList) => &Layout::Builtin(Builtin::Int64),
|
||||
Layout::Builtin(Builtin::List(_, element_layout)) => element_layout,
|
||||
_ => unreachable!("Invalid layout {:?} in List.contains", list_layout),
|
||||
};
|
||||
|
||||
let list_elem_type =
|
||||
basic_type_from_layout(env.arena, env.context, list_elem_layout, env.ptr_bytes);
|
||||
|
||||
let list_ptr = load_list_ptr(
|
||||
builder,
|
||||
wrapper_struct,
|
||||
list_elem_type.ptr_type(AddressSpace::Generic),
|
||||
);
|
||||
|
||||
let length = list_len(builder, list.into_struct_value());
|
||||
|
||||
list_contains_help(
|
||||
env,
|
||||
parent,
|
||||
length,
|
||||
list_ptr,
|
||||
list_elem_layout,
|
||||
elem,
|
||||
elem_layout,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn list_contains_help<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
length: IntValue<'ctx>,
|
||||
source_ptr: PointerValue<'ctx>,
|
||||
list_elem_layout: &Layout<'a>,
|
||||
elem: BasicValueEnum<'ctx>,
|
||||
elem_layout: &Layout<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let builder = env.builder;
|
||||
let ctx = env.context;
|
||||
|
||||
let bool_alloca = builder.build_alloca(ctx.bool_type(), "bool_alloca");
|
||||
let index_alloca = builder.build_alloca(ctx.i64_type(), "index_alloca");
|
||||
let next_free_index_alloca = builder.build_alloca(ctx.i64_type(), "next_free_index_alloca");
|
||||
|
||||
builder.build_store(bool_alloca, ctx.bool_type().const_zero());
|
||||
builder.build_store(index_alloca, ctx.i64_type().const_zero());
|
||||
builder.build_store(next_free_index_alloca, ctx.i64_type().const_zero());
|
||||
|
||||
let condition_bb = ctx.append_basic_block(parent, "condition");
|
||||
builder.build_unconditional_branch(condition_bb);
|
||||
builder.position_at_end(condition_bb);
|
||||
|
||||
let index = builder.build_load(index_alloca, "index").into_int_value();
|
||||
|
||||
let condition = builder.build_int_compare(IntPredicate::SGT, length, index, "loopcond");
|
||||
|
||||
let body_bb = ctx.append_basic_block(parent, "body");
|
||||
let cont_bb = ctx.append_basic_block(parent, "cont");
|
||||
builder.build_conditional_branch(condition, body_bb, cont_bb);
|
||||
|
||||
// loop body
|
||||
builder.position_at_end(body_bb);
|
||||
|
||||
let current_elem_ptr = unsafe { builder.build_in_bounds_gep(source_ptr, &[index], "elem_ptr") };
|
||||
|
||||
let current_elem = builder.build_load(current_elem_ptr, "load_elem");
|
||||
|
||||
let has_found = build_eq(env, current_elem, elem, list_elem_layout, elem_layout);
|
||||
|
||||
builder.build_store(bool_alloca, has_found.into_int_value());
|
||||
|
||||
let one = ctx.i64_type().const_int(1, false);
|
||||
|
||||
let next_free_index = builder
|
||||
.build_load(next_free_index_alloca, "load_next_free")
|
||||
.into_int_value();
|
||||
|
||||
builder.build_store(
|
||||
next_free_index_alloca,
|
||||
builder.build_int_add(next_free_index, one, "incremented_next_free_index"),
|
||||
);
|
||||
|
||||
builder.build_store(
|
||||
index_alloca,
|
||||
builder.build_int_add(index, one, "incremented_index"),
|
||||
);
|
||||
|
||||
builder.build_conditional_branch(has_found.into_int_value(), cont_bb, condition_bb);
|
||||
|
||||
// continuation
|
||||
builder.position_at_end(cont_bb);
|
||||
|
||||
builder.build_load(bool_alloca, "answer")
|
||||
}
|
||||
|
||||
/// List.keepIf : List elem, (elem -> Bool) -> List elem
|
||||
pub fn list_keep_if<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
|
@ -849,7 +960,7 @@ pub fn list_keep_if<'a, 'ctx, 'env>(
|
|||
elem_layout.clone(),
|
||||
),
|
||||
|
||||
_ => unreachable!("Invalid layout {:?} in List.reverse", list_layout),
|
||||
_ => unreachable!("Invalid layout {:?} in List.keepIf", list_layout),
|
||||
};
|
||||
|
||||
let list_type = basic_type_from_layout(env.arena, env.context, &list_layout, env.ptr_bytes);
|
||||
|
|
|
@ -285,6 +285,7 @@ mod gen_list {
|
|||
r#"
|
||||
Bit : [ Zero, One ]
|
||||
|
||||
byte : List Bit
|
||||
byte = [ Zero, One, Zero, One, Zero, Zero, One, Zero ]
|
||||
|
||||
initialCounts = { zeroes: 0, ones: 0 }
|
||||
|
@ -313,7 +314,7 @@ mod gen_list {
|
|||
empty =
|
||||
[]
|
||||
|
||||
List.keepIf empty (\x -> True)
|
||||
List.keepIf empty (\_ -> True)
|
||||
"#
|
||||
),
|
||||
RocList::from_slice(&[]),
|
||||
|
@ -345,7 +346,7 @@ mod gen_list {
|
|||
indoc!(
|
||||
r#"
|
||||
alwaysTrue : Int -> Bool
|
||||
alwaysTrue = \i ->
|
||||
alwaysTrue = \_ ->
|
||||
True
|
||||
|
||||
oneThroughEight : List Int
|
||||
|
@ -366,7 +367,7 @@ mod gen_list {
|
|||
indoc!(
|
||||
r#"
|
||||
alwaysFalse : Int -> Bool
|
||||
alwaysFalse = \i ->
|
||||
alwaysFalse = \_ ->
|
||||
False
|
||||
|
||||
List.keepIf [1,2,3,4,5,6,7,8] alwaysFalse
|
||||
|
@ -1602,4 +1603,13 @@ mod gen_list {
|
|||
RocList<i64>
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_contains() {
|
||||
assert_evals_to!(indoc!("List.contains [1,2,3] 1"), true, bool);
|
||||
|
||||
assert_evals_to!(indoc!("List.contains [1,2,3] 4"), false, bool);
|
||||
|
||||
assert_evals_to!(indoc!("List.contains [] 4"), false, bool);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -557,7 +557,7 @@ mod gen_num {
|
|||
indoc!(
|
||||
r#"
|
||||
always42 : Num.Num Num.Integer -> Num.Num Num.Integer
|
||||
always42 = \num -> 42
|
||||
always42 = \_ -> 42
|
||||
|
||||
always42 5
|
||||
"#
|
||||
|
@ -788,4 +788,30 @@ mod gen_num {
|
|||
// f64
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn num_max_int() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
Num.maxInt
|
||||
"#
|
||||
),
|
||||
i64::MAX,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_min_int() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
Num.minInt
|
||||
"#
|
||||
),
|
||||
i64::MIN,
|
||||
i64
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -294,7 +294,7 @@ mod gen_primitives {
|
|||
r#"
|
||||
wrapper = \{} ->
|
||||
alwaysFloatIdentity : Int -> (Float -> Float)
|
||||
alwaysFloatIdentity = \num ->
|
||||
alwaysFloatIdentity = \_ ->
|
||||
(\a -> a)
|
||||
|
||||
(alwaysFloatIdentity 2) 3.14
|
||||
|
@ -362,7 +362,7 @@ mod gen_primitives {
|
|||
|
||||
pi = 3.14
|
||||
|
||||
answer
|
||||
if pi > 3 then answer else answer
|
||||
"#
|
||||
),
|
||||
42,
|
||||
|
@ -376,7 +376,7 @@ mod gen_primitives {
|
|||
|
||||
pi = 3.14
|
||||
|
||||
pi
|
||||
if answer > 3 then pi else pi
|
||||
"#
|
||||
),
|
||||
3.14,
|
||||
|
@ -384,87 +384,89 @@ mod gen_primitives {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_chained_defs() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x = i1
|
||||
i3 = i2
|
||||
i1 = 1337
|
||||
i2 = i1
|
||||
y = 12.4
|
||||
|
||||
i3
|
||||
"#
|
||||
),
|
||||
1337,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_nested_defs_old() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x = 5
|
||||
|
||||
answer =
|
||||
i3 = i2
|
||||
|
||||
nested =
|
||||
a = 1.0
|
||||
b = 5
|
||||
|
||||
i1
|
||||
|
||||
i1 = 1337
|
||||
i2 = i1
|
||||
|
||||
|
||||
nested
|
||||
|
||||
# None of this should affect anything, even though names
|
||||
# overlap with the previous nested defs
|
||||
unused =
|
||||
nested = 17
|
||||
|
||||
i1 = 84.2
|
||||
|
||||
nested
|
||||
|
||||
y = 12.4
|
||||
|
||||
answer
|
||||
"#
|
||||
),
|
||||
1337,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_x_in_x() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x = 5
|
||||
|
||||
answer =
|
||||
1337
|
||||
|
||||
unused =
|
||||
nested = 17
|
||||
nested
|
||||
|
||||
answer
|
||||
"#
|
||||
),
|
||||
1337,
|
||||
i64
|
||||
);
|
||||
}
|
||||
// These tests caught a bug in how Defs are converted to the mono IR
|
||||
// but they have UnusedDef or UnusedArgument problems, and don't run any more
|
||||
// #[test]
|
||||
// fn gen_chained_defs() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// x = i1
|
||||
// i3 = i2
|
||||
// i1 = 1337
|
||||
// i2 = i1
|
||||
// y = 12.4
|
||||
//
|
||||
// i3
|
||||
// "#
|
||||
// ),
|
||||
// 1337,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn gen_nested_defs_old() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// x = 5
|
||||
//
|
||||
// answer =
|
||||
// i3 = i2
|
||||
//
|
||||
// nested =
|
||||
// a = 1.0
|
||||
// b = 5
|
||||
//
|
||||
// i1
|
||||
//
|
||||
// i1 = 1337
|
||||
// i2 = i1
|
||||
//
|
||||
//
|
||||
// nested
|
||||
//
|
||||
// # None of this should affect anything, even though names
|
||||
// # overlap with the previous nested defs
|
||||
// unused =
|
||||
// nested = 17
|
||||
//
|
||||
// i1 = 84.2
|
||||
//
|
||||
// nested
|
||||
//
|
||||
// y = 12.4
|
||||
//
|
||||
// answer
|
||||
// "#
|
||||
// ),
|
||||
// 1337,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn let_x_in_x() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// x = 5
|
||||
//
|
||||
// answer =
|
||||
// 1337
|
||||
//
|
||||
// unused =
|
||||
// nested = 17
|
||||
// nested
|
||||
//
|
||||
// answer
|
||||
// "#
|
||||
// ),
|
||||
// 1337,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn factorial() {
|
||||
|
|
|
@ -254,11 +254,11 @@ mod gen_records {
|
|||
r#"
|
||||
v = {}
|
||||
|
||||
1
|
||||
v
|
||||
"#
|
||||
),
|
||||
1,
|
||||
i64
|
||||
(),
|
||||
()
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
|
|
|
@ -23,11 +23,12 @@ mod gen_tags {
|
|||
x : Maybe Int
|
||||
x = Nothing
|
||||
|
||||
0x1
|
||||
x
|
||||
"#
|
||||
),
|
||||
1,
|
||||
i64
|
||||
(i64, i64),
|
||||
|(tag, _)| tag
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -41,11 +42,12 @@ mod gen_tags {
|
|||
x : Maybe Int
|
||||
x = Nothing
|
||||
|
||||
0x1
|
||||
x
|
||||
"#
|
||||
),
|
||||
1,
|
||||
i64
|
||||
(i64, i64),
|
||||
|(tag, _)| tag
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -59,11 +61,11 @@ mod gen_tags {
|
|||
y : Maybe Int
|
||||
y = Just 0x4
|
||||
|
||||
0x1
|
||||
y
|
||||
"#
|
||||
),
|
||||
1,
|
||||
i64
|
||||
(0, 0x4),
|
||||
(i64, i64)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -77,11 +79,11 @@ mod gen_tags {
|
|||
y : Maybe Int
|
||||
y = Just 0x4
|
||||
|
||||
0x1
|
||||
y
|
||||
"#
|
||||
),
|
||||
1,
|
||||
i64
|
||||
(0, 0x4),
|
||||
(i64, i64)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -99,11 +101,11 @@ mod gen_tags {
|
|||
y : Maybe Fruit
|
||||
y = Just orange
|
||||
|
||||
0x1
|
||||
y
|
||||
"#
|
||||
),
|
||||
1,
|
||||
i64
|
||||
(0, 2),
|
||||
(i64, i64)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -350,7 +352,7 @@ mod gen_tags {
|
|||
|
||||
when x is
|
||||
These a b -> a + b
|
||||
That v -> 8
|
||||
That v -> v
|
||||
This v -> v
|
||||
"#
|
||||
),
|
||||
|
@ -616,10 +618,10 @@ mod gen_tags {
|
|||
x : [ Pair Int ]
|
||||
x = Pair 2
|
||||
|
||||
0x3
|
||||
x
|
||||
"#
|
||||
),
|
||||
3,
|
||||
2,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
@ -637,11 +639,11 @@ mod gen_tags {
|
|||
x = Just (Just 41)
|
||||
|
||||
main =
|
||||
5
|
||||
x
|
||||
"#
|
||||
),
|
||||
5,
|
||||
i64
|
||||
(0, (0, 41)),
|
||||
(i64, (i64, i64))
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
|
@ -654,11 +656,11 @@ mod gen_tags {
|
|||
v : Unit
|
||||
v = Unit
|
||||
|
||||
1
|
||||
v
|
||||
"#
|
||||
),
|
||||
1,
|
||||
i64
|
||||
(),
|
||||
()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -667,8 +669,6 @@ mod gen_tags {
|
|||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
Maybe a : [ Nothing, Just a ]
|
||||
|
||||
x = { a : { b : 0x5 } }
|
||||
|
||||
y = x.a
|
||||
|
|
|
@ -50,14 +50,10 @@ pub fn helper<'a>(
|
|||
exposed_types,
|
||||
);
|
||||
|
||||
let loaded = loaded.expect("failed to load module");
|
||||
let mut loaded = loaded.expect("failed to load module");
|
||||
|
||||
use roc_load::file::MonomorphizedModule;
|
||||
let MonomorphizedModule {
|
||||
module_id: home,
|
||||
can_problems,
|
||||
type_problems,
|
||||
mono_problems,
|
||||
mut procedures,
|
||||
interns,
|
||||
exposed_to_host,
|
||||
|
@ -76,47 +72,52 @@ pub fn helper<'a>(
|
|||
let target = target_lexicon::Triple::host();
|
||||
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
|
||||
|
||||
// don't panic based on the errors here, so we can test that RuntimeError generates the correct code
|
||||
let errors = can_problems
|
||||
.into_iter()
|
||||
.filter(|problem| {
|
||||
use roc_problem::can::Problem::*;
|
||||
|
||||
// Ignore "unused" problems
|
||||
match problem {
|
||||
UnusedDef(_, _) | UnusedArgument(_, _, _) | UnusedImport(_, _) => false,
|
||||
_ => true,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<roc_problem::can::Problem>>();
|
||||
let mut lines = Vec::new();
|
||||
// errors whose reporting we delay (so we can see that code gen generates runtime errors)
|
||||
let mut delayed_errors = Vec::new();
|
||||
|
||||
for (home, (module_path, src)) in loaded.sources {
|
||||
use roc_reporting::report::{
|
||||
can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE,
|
||||
};
|
||||
|
||||
let error_count = errors.len() + type_problems.len() + mono_problems.len();
|
||||
let fatal_error_count = type_problems.len() + mono_problems.len();
|
||||
let can_problems = loaded.can_problems.remove(&home).unwrap_or_default();
|
||||
let type_problems = loaded.type_problems.remove(&home).unwrap_or_default();
|
||||
let mono_problems = loaded.mono_problems.remove(&home).unwrap_or_default();
|
||||
|
||||
if error_count > 0 {
|
||||
// There were problems; report them and return.
|
||||
let src_lines: Vec<&str> = module_src.split('\n').collect();
|
||||
let error_count = can_problems.len() + type_problems.len() + mono_problems.len();
|
||||
|
||||
// Used for reporting where an error came from.
|
||||
//
|
||||
// TODO: maybe Reporting should have this be an Option?
|
||||
let path = PathBuf::new();
|
||||
if error_count == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Report problems
|
||||
let src_lines: Vec<&str> = src.split('\n').collect();
|
||||
let palette = DEFAULT_PALETTE;
|
||||
|
||||
// Report parsing and canonicalization problems
|
||||
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
||||
|
||||
let mut lines = Vec::with_capacity(error_count);
|
||||
|
||||
let can_problems = errors.clone();
|
||||
use roc_problem::can::Problem::*;
|
||||
for problem in can_problems.into_iter() {
|
||||
let report = can_problem(&alloc, path.clone(), problem);
|
||||
// Ignore "unused" problems
|
||||
match problem {
|
||||
UnusedDef(_, _) | UnusedArgument(_, _, _) | UnusedImport(_, _) => {
|
||||
delayed_errors.push(problem);
|
||||
continue;
|
||||
}
|
||||
_ => {
|
||||
let report = can_problem(&alloc, module_path.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
||||
lines.push(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for problem in type_problems {
|
||||
let report = type_problem(&alloc, module_path.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
@ -124,31 +125,19 @@ pub fn helper<'a>(
|
|||
lines.push(buf);
|
||||
}
|
||||
|
||||
for problem in type_problems.into_iter() {
|
||||
let report = type_problem(&alloc, path.clone(), problem);
|
||||
for problem in mono_problems {
|
||||
let report = mono_problem(&alloc, module_path.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
||||
lines.push(buf);
|
||||
}
|
||||
|
||||
for problem in mono_problems.into_iter() {
|
||||
let report = mono_problem(&alloc, path.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
||||
lines.push(buf);
|
||||
}
|
||||
|
||||
println!("{}", (&lines).join("\n"));
|
||||
|
||||
// we want to continue onward only for canonical problems at the moment,
|
||||
// to check that they codegen into runtime exceptions
|
||||
if fatal_error_count > 0 {
|
||||
assert_eq!(0, 1, "problems occured");
|
||||
}
|
||||
if !lines.is_empty() {
|
||||
println!("{}", lines.join("\n"));
|
||||
assert_eq!(0, 1, "Mistakes were made");
|
||||
}
|
||||
|
||||
let module = roc_gen::llvm::build::module_from_builtins(context, "app");
|
||||
|
@ -261,7 +250,7 @@ pub fn helper<'a>(
|
|||
let lib = module_to_dylib(&env.module, &target, opt_level)
|
||||
.expect("Error loading compiled dylib for test");
|
||||
|
||||
(main_fn_name, errors, lib)
|
||||
(main_fn_name, delayed_errors, lib)
|
||||
}
|
||||
|
||||
// TODO this is almost all code duplication with assert_llvm_evals_to
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// This file was copied from file.rs and modified to expose information
|
||||
// required to auto-generate documentation
|
||||
use inlinable_string::InlinableString;
|
||||
use roc_module::ident::ModuleName;
|
||||
use roc_module::symbol::IdentIds;
|
||||
|
@ -71,7 +69,7 @@ fn generate_module_doc<'a>(
|
|||
// If there are comments before, attach to this definition
|
||||
generate_module_doc(exposed_ident_ids, acc, before_comments_or_new_lines, sub_def);
|
||||
|
||||
// Comments after a definition are attached to the next defition
|
||||
// Comments after a definition are attached to the next definition
|
||||
(new_acc, Some(comments_or_new_lines))
|
||||
}
|
||||
|
||||
|
@ -98,9 +96,15 @@ fn generate_module_doc<'a>(
|
|||
name: _,
|
||||
vars: _,
|
||||
ann: _,
|
||||
} => (acc, None),
|
||||
} =>
|
||||
// TODO
|
||||
{
|
||||
(acc, None)
|
||||
}
|
||||
|
||||
Body(_, _) | Nested(_) => (acc, None),
|
||||
|
||||
NotYetImplemented(s) => todo!("{}", s),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,9 +117,11 @@ fn comments_or_new_lines_to_docs<'a>(
|
|||
|
||||
for comment_or_new_line in comments_or_new_lines.iter() {
|
||||
match comment_or_new_line {
|
||||
Newline => {}
|
||||
LineComment(_) => {}
|
||||
DocComment(doc_str) => docs.push_str(doc_str),
|
||||
DocComment(doc_str) => {
|
||||
docs.push_str(doc_str);
|
||||
docs.push_str("\n");
|
||||
}
|
||||
Newline | LineComment(_) => {}
|
||||
}
|
||||
}
|
||||
if docs.is_empty() {
|
||||
|
|
|
@ -13,11 +13,10 @@ use roc_constrain::module::{
|
|||
constrain_imports, pre_constrain_imports, ConstrainableImports, Import,
|
||||
};
|
||||
use roc_constrain::module::{constrain_module, ExposedModuleTypes, SubsByModule};
|
||||
use roc_module::ident::{Ident, ModuleName};
|
||||
use roc_module::ident::{Ident, Lowercase, ModuleName};
|
||||
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
|
||||
use roc_mono::ir::{
|
||||
CapturedSymbols, ExternalSpecializations, MonoProblem, PartialProc, PendingSpecialization,
|
||||
Proc, Procs,
|
||||
CapturedSymbols, ExternalSpecializations, PartialProc, PendingSpecialization, Proc, Procs,
|
||||
};
|
||||
use roc_mono::layout::{Layout, LayoutCache};
|
||||
use roc_parse::ast::{self, Attempting, ExposesEntry, ImportsEntry};
|
||||
|
@ -201,15 +200,24 @@ impl Dependencies {
|
|||
#[derive(Debug, Default)]
|
||||
struct ModuleCache<'a> {
|
||||
module_names: MutMap<ModuleId, ModuleName>,
|
||||
|
||||
/// Phases
|
||||
headers: MutMap<ModuleId, ModuleHeader<'a>>,
|
||||
parsed: MutMap<ModuleId, ParsedModule<'a>>,
|
||||
canonicalized: MutMap<ModuleId, CanonicalizedModule<'a>>,
|
||||
aliases: MutMap<ModuleId, MutMap<Symbol, Alias>>,
|
||||
constrained: MutMap<ModuleId, ConstrainedModule<'a>>,
|
||||
constrained: MutMap<ModuleId, ConstrainedModule>,
|
||||
typechecked: MutMap<ModuleId, TypeCheckedModule<'a>>,
|
||||
found_specializations: MutMap<ModuleId, FoundSpecializationsModule<'a>>,
|
||||
external_specializations_requested: MutMap<ModuleId, ExternalSpecializations>,
|
||||
|
||||
/// Various information
|
||||
documentation: MutMap<ModuleId, ModuleDocumentation>,
|
||||
can_problems: MutMap<ModuleId, Vec<roc_problem::can::Problem>>,
|
||||
type_problems: MutMap<ModuleId, Vec<solve::TypeError>>,
|
||||
mono_problems: MutMap<ModuleId, Vec<roc_mono::ir::MonoProblem>>,
|
||||
|
||||
sources: MutMap<ModuleId, (PathBuf, &'a str)>,
|
||||
}
|
||||
|
||||
fn start_phase<'a>(module_id: ModuleId, phase: Phase, state: &mut State<'a>) -> BuildTask<'a> {
|
||||
|
@ -314,7 +322,6 @@ fn start_phase<'a>(module_id: ModuleId, phase: Phase, state: &mut State<'a>) ->
|
|||
module,
|
||||
ident_ids,
|
||||
module_timing,
|
||||
src,
|
||||
constraint,
|
||||
var_store,
|
||||
imported_modules,
|
||||
|
@ -326,7 +333,6 @@ fn start_phase<'a>(module_id: ModuleId, phase: Phase, state: &mut State<'a>) ->
|
|||
module,
|
||||
ident_ids,
|
||||
module_timing,
|
||||
src,
|
||||
constraint,
|
||||
var_store,
|
||||
imported_modules,
|
||||
|
@ -344,7 +350,6 @@ fn start_phase<'a>(module_id: ModuleId, phase: Phase, state: &mut State<'a>) ->
|
|||
module_timing,
|
||||
solved_subs,
|
||||
decls,
|
||||
finished_info,
|
||||
ident_ids,
|
||||
} = typechecked;
|
||||
|
||||
|
@ -354,7 +359,6 @@ fn start_phase<'a>(module_id: ModuleId, phase: Phase, state: &mut State<'a>) ->
|
|||
module_timing,
|
||||
solved_subs,
|
||||
decls,
|
||||
finished_info,
|
||||
ident_ids,
|
||||
exposed_to_host: state.exposed_to_host.clone(),
|
||||
}
|
||||
|
@ -378,7 +382,6 @@ fn start_phase<'a>(module_id: ModuleId, phase: Phase, state: &mut State<'a>) ->
|
|||
subs,
|
||||
procs,
|
||||
layout_cache,
|
||||
finished_info,
|
||||
} = found_specializations;
|
||||
|
||||
BuildTask::MakeSpecializations {
|
||||
|
@ -388,7 +391,6 @@ fn start_phase<'a>(module_id: ModuleId, phase: Phase, state: &mut State<'a>) ->
|
|||
procs,
|
||||
layout_cache,
|
||||
specializations_we_must_make,
|
||||
finished_info,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -399,11 +401,11 @@ pub struct LoadedModule {
|
|||
pub module_id: ModuleId,
|
||||
pub interns: Interns,
|
||||
pub solved: Solved<Subs>,
|
||||
pub can_problems: Vec<roc_problem::can::Problem>,
|
||||
pub type_problems: Vec<solve::TypeError>,
|
||||
pub can_problems: MutMap<ModuleId, Vec<roc_problem::can::Problem>>,
|
||||
pub type_problems: MutMap<ModuleId, Vec<solve::TypeError>>,
|
||||
pub declarations_by_id: MutMap<ModuleId, Vec<Declaration>>,
|
||||
pub exposed_to_host: MutMap<Symbol, Variable>,
|
||||
pub src: Box<str>,
|
||||
pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
|
||||
pub timings: MutMap<ModuleId, ModuleTiming>,
|
||||
pub documentation: MutMap<ModuleId, ModuleDocumentation>,
|
||||
}
|
||||
|
@ -417,6 +419,7 @@ pub enum BuildProblem<'a> {
|
|||
struct ModuleHeader<'a> {
|
||||
module_id: ModuleId,
|
||||
module_name: ModuleName,
|
||||
module_path: PathBuf,
|
||||
exposed_ident_ids: IdentIds,
|
||||
deps_by_name: MutMap<ModuleName, ModuleId>,
|
||||
imported_modules: MutSet<ModuleId>,
|
||||
|
@ -427,11 +430,10 @@ struct ModuleHeader<'a> {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ConstrainedModule<'a> {
|
||||
struct ConstrainedModule {
|
||||
module: Module,
|
||||
declarations: Vec<Declaration>,
|
||||
imported_modules: MutSet<ModuleId>,
|
||||
src: &'a str,
|
||||
constraint: Constraint,
|
||||
ident_ids: IdentIds,
|
||||
var_store: VarStore,
|
||||
|
@ -446,7 +448,6 @@ pub struct TypeCheckedModule<'a> {
|
|||
pub solved_subs: Solved<Subs>,
|
||||
pub decls: Vec<Declaration>,
|
||||
pub ident_ids: IdentIds,
|
||||
pub finished_info: FinishedInfo<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -456,7 +457,6 @@ pub struct FoundSpecializationsModule<'a> {
|
|||
pub layout_cache: LayoutCache<'a>,
|
||||
pub procs: Procs<'a>,
|
||||
pub subs: Subs,
|
||||
pub finished_info: FinishedInfo<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -464,19 +464,26 @@ pub struct MonomorphizedModule<'a> {
|
|||
pub module_id: ModuleId,
|
||||
pub interns: Interns,
|
||||
pub subs: Subs,
|
||||
pub can_problems: Vec<roc_problem::can::Problem>,
|
||||
pub type_problems: Vec<solve::TypeError>,
|
||||
pub mono_problems: Vec<roc_mono::ir::MonoProblem>,
|
||||
pub can_problems: MutMap<ModuleId, Vec<roc_problem::can::Problem>>,
|
||||
pub type_problems: MutMap<ModuleId, Vec<solve::TypeError>>,
|
||||
pub mono_problems: MutMap<ModuleId, Vec<roc_mono::ir::MonoProblem>>,
|
||||
pub procedures: MutMap<(Symbol, Layout<'a>), Proc<'a>>,
|
||||
pub exposed_to_host: MutMap<Symbol, Variable>,
|
||||
pub src: Box<str>,
|
||||
pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
|
||||
pub timings: MutMap<ModuleId, ModuleTiming>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct VariablySizedLayouts<'a> {
|
||||
rigids: MutMap<Lowercase, Layout<'a>>,
|
||||
aliases: MutMap<Symbol, Layout<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ParsedModule<'a> {
|
||||
module_id: ModuleId,
|
||||
module_name: ModuleName,
|
||||
module_path: PathBuf,
|
||||
src: &'a str,
|
||||
module_timing: ModuleTiming,
|
||||
deps_by_name: MutMap<ModuleName, ModuleId>,
|
||||
|
@ -498,12 +505,11 @@ enum Msg<'a> {
|
|||
Header(ModuleHeader<'a>),
|
||||
Parsed(ParsedModule<'a>),
|
||||
CanonicalizedAndConstrained {
|
||||
constrained_module: ConstrainedModule<'a>,
|
||||
constrained_module: ConstrainedModule,
|
||||
canonicalization_problems: Vec<roc_problem::can::Problem>,
|
||||
module_docs: ModuleDocumentation,
|
||||
},
|
||||
SolvedTypes {
|
||||
src: &'a str,
|
||||
module_id: ModuleId,
|
||||
ident_ids: IdentIds,
|
||||
solved_module: SolvedModule,
|
||||
|
@ -515,7 +521,6 @@ enum Msg<'a> {
|
|||
solved_subs: Solved<Subs>,
|
||||
exposed_vars_by_symbol: Vec<(Symbol, Variable)>,
|
||||
documentation: MutMap<ModuleId, ModuleDocumentation>,
|
||||
src: &'a str,
|
||||
},
|
||||
FoundSpecializations {
|
||||
module_id: ModuleId,
|
||||
|
@ -524,7 +529,6 @@ enum Msg<'a> {
|
|||
procs: Procs<'a>,
|
||||
problems: Vec<roc_mono::ir::MonoProblem>,
|
||||
solved_subs: Solved<Subs>,
|
||||
finished_info: FinishedInfo<'a>,
|
||||
},
|
||||
MadeSpecializations {
|
||||
module_id: ModuleId,
|
||||
|
@ -534,7 +538,6 @@ enum Msg<'a> {
|
|||
procedures: MutMap<(Symbol, Layout<'a>), Proc<'a>>,
|
||||
problems: Vec<roc_mono::ir::MonoProblem>,
|
||||
subs: Subs,
|
||||
finished_info: FinishedInfo<'a>,
|
||||
},
|
||||
|
||||
/// The task is to only typecheck AND monomorphize modules
|
||||
|
@ -542,16 +545,9 @@ enum Msg<'a> {
|
|||
FinishedAllSpecialization {
|
||||
subs: Subs,
|
||||
exposed_to_host: MutMap<Symbol, Variable>,
|
||||
src: &'a str,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FinishedInfo<'a> {
|
||||
exposed_vars_by_symbol: Vec<(Symbol, Variable)>,
|
||||
src: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct State<'a> {
|
||||
pub root_id: ModuleId,
|
||||
|
@ -559,10 +555,7 @@ struct State<'a> {
|
|||
pub stdlib: StdLib,
|
||||
pub exposed_types: SubsByModule,
|
||||
|
||||
pub can_problems: std::vec::Vec<roc_problem::can::Problem>,
|
||||
pub mono_problems: std::vec::Vec<MonoProblem>,
|
||||
pub headers_parsed: MutSet<ModuleId>,
|
||||
pub type_problems: std::vec::Vec<solve::TypeError>,
|
||||
|
||||
pub module_cache: ModuleCache<'a>,
|
||||
pub dependencies: Dependencies,
|
||||
|
@ -702,7 +695,6 @@ enum BuildTask<'a> {
|
|||
constraint: Constraint,
|
||||
var_store: VarStore,
|
||||
declarations: Vec<Declaration>,
|
||||
src: &'a str,
|
||||
},
|
||||
BuildPendingSpecializations {
|
||||
module_timing: ModuleTiming,
|
||||
|
@ -711,7 +703,6 @@ enum BuildTask<'a> {
|
|||
module_id: ModuleId,
|
||||
ident_ids: IdentIds,
|
||||
decls: Vec<Declaration>,
|
||||
finished_info: FinishedInfo<'a>,
|
||||
exposed_to_host: MutMap<Symbol, Variable>,
|
||||
},
|
||||
MakeSpecializations {
|
||||
|
@ -720,7 +711,6 @@ enum BuildTask<'a> {
|
|||
subs: Subs,
|
||||
procs: Procs<'a>,
|
||||
layout_cache: LayoutCache<'a>,
|
||||
finished_info: FinishedInfo<'a>,
|
||||
specializations_we_must_make: ExternalSpecializations,
|
||||
},
|
||||
}
|
||||
|
@ -1105,9 +1095,6 @@ where
|
|||
exposed_types,
|
||||
headers_parsed,
|
||||
loading_started,
|
||||
can_problems: std::vec::Vec::new(),
|
||||
type_problems: std::vec::Vec::new(),
|
||||
mono_problems: std::vec::Vec::new(),
|
||||
arc_modules,
|
||||
constrained_ident_ids: IdentIds::exposed_builtins(0),
|
||||
ident_ids_by_module,
|
||||
|
@ -1140,7 +1127,6 @@ where
|
|||
solved_subs,
|
||||
exposed_vars_by_symbol,
|
||||
documentation,
|
||||
src,
|
||||
} => {
|
||||
// We're done! There should be no more messages pending.
|
||||
debug_assert!(msg_rx.is_empty());
|
||||
|
@ -1157,13 +1143,11 @@ where
|
|||
solved_subs,
|
||||
exposed_vars_by_symbol,
|
||||
documentation,
|
||||
src,
|
||||
)));
|
||||
}
|
||||
Msg::FinishedAllSpecialization {
|
||||
subs,
|
||||
exposed_to_host,
|
||||
src,
|
||||
} => {
|
||||
// We're done! There should be no more messages pending.
|
||||
debug_assert!(msg_rx.is_empty());
|
||||
|
@ -1179,7 +1163,6 @@ where
|
|||
state,
|
||||
subs,
|
||||
exposed_to_host,
|
||||
src,
|
||||
)));
|
||||
}
|
||||
msg => {
|
||||
|
@ -1267,6 +1250,11 @@ fn update<'a>(
|
|||
Ok(state)
|
||||
}
|
||||
Parsed(parsed) => {
|
||||
state
|
||||
.module_cache
|
||||
.sources
|
||||
.insert(parsed.module_id, (parsed.module_path.clone(), parsed.src));
|
||||
|
||||
let module_id = parsed.module_id;
|
||||
|
||||
state.module_cache.parsed.insert(parsed.module_id, parsed);
|
||||
|
@ -1288,7 +1276,10 @@ fn update<'a>(
|
|||
} => {
|
||||
let module_id = constrained_module.module.module_id;
|
||||
log!("generated constraints for {:?}", module_id);
|
||||
state.can_problems.extend(canonicalization_problems);
|
||||
state
|
||||
.module_cache
|
||||
.can_problems
|
||||
.insert(module_id, canonicalization_problems);
|
||||
|
||||
state
|
||||
.module_cache
|
||||
|
@ -1318,7 +1309,6 @@ fn update<'a>(
|
|||
Ok(state)
|
||||
}
|
||||
SolvedTypes {
|
||||
src,
|
||||
module_id,
|
||||
ident_ids,
|
||||
solved_module,
|
||||
|
@ -1329,7 +1319,10 @@ fn update<'a>(
|
|||
log!("solved types for {:?}", module_id);
|
||||
module_timing.end_time = SystemTime::now();
|
||||
|
||||
state.type_problems.extend(solved_module.problems);
|
||||
state
|
||||
.module_cache
|
||||
.type_problems
|
||||
.insert(module_id, solved_module.problems);
|
||||
|
||||
let work = state.dependencies.notify(module_id, Phase::SolveTypes);
|
||||
|
||||
|
@ -1357,7 +1350,6 @@ fn update<'a>(
|
|||
solved_subs,
|
||||
exposed_vars_by_symbol: solved_module.exposed_vars_by_symbol,
|
||||
documentation,
|
||||
src,
|
||||
})
|
||||
.map_err(|_| LoadingProblem::MsgChannelDied)?;
|
||||
|
||||
|
@ -1382,11 +1374,6 @@ fn update<'a>(
|
|||
if state.goal_phase > Phase::SolveTypes {
|
||||
let layout_cache = state.layout_caches.pop().unwrap_or_default();
|
||||
|
||||
let finished_info = FinishedInfo {
|
||||
src,
|
||||
exposed_vars_by_symbol: solved_module.exposed_vars_by_symbol,
|
||||
};
|
||||
|
||||
let typechecked = TypeCheckedModule {
|
||||
module_id,
|
||||
decls,
|
||||
|
@ -1394,7 +1381,6 @@ fn update<'a>(
|
|||
ident_ids,
|
||||
module_timing,
|
||||
layout_cache,
|
||||
finished_info,
|
||||
};
|
||||
|
||||
state
|
||||
|
@ -1417,7 +1403,6 @@ fn update<'a>(
|
|||
FoundSpecializations {
|
||||
module_id,
|
||||
procs,
|
||||
finished_info,
|
||||
solved_subs,
|
||||
ident_ids,
|
||||
layout_cache,
|
||||
|
@ -1443,7 +1428,6 @@ fn update<'a>(
|
|||
layout_cache,
|
||||
module_id,
|
||||
procs,
|
||||
finished_info,
|
||||
ident_ids,
|
||||
subs,
|
||||
};
|
||||
|
@ -1468,7 +1452,6 @@ fn update<'a>(
|
|||
module_id,
|
||||
ident_ids,
|
||||
subs,
|
||||
finished_info,
|
||||
procedures,
|
||||
external_specializations_requested,
|
||||
problems,
|
||||
|
@ -1476,7 +1459,7 @@ fn update<'a>(
|
|||
} => {
|
||||
log!("made specializations for {:?}", module_id);
|
||||
|
||||
state.mono_problems.extend(problems);
|
||||
state.module_cache.mono_problems.insert(module_id, problems);
|
||||
|
||||
for (module_id, requested) in external_specializations_requested {
|
||||
let existing = match state
|
||||
|
@ -1512,7 +1495,6 @@ fn update<'a>(
|
|||
subs,
|
||||
// TODO thread through mono problems
|
||||
exposed_to_host: state.exposed_to_host.clone(),
|
||||
src: finished_info.src,
|
||||
})
|
||||
.map_err(|_| LoadingProblem::MsgChannelDied)?;
|
||||
|
||||
|
@ -1542,7 +1524,6 @@ fn finish_specialization<'a>(
|
|||
state: State<'a>,
|
||||
subs: Subs,
|
||||
exposed_to_host: MutMap<Symbol, Variable>,
|
||||
src: &'a str,
|
||||
) -> MonomorphizedModule<'a> {
|
||||
let module_ids = Arc::try_unwrap(state.arc_modules)
|
||||
.unwrap_or_else(|_| panic!("There were still outstanding Arc references to module_ids"))
|
||||
|
@ -1554,12 +1535,23 @@ fn finish_specialization<'a>(
|
|||
};
|
||||
|
||||
let State {
|
||||
procedures,
|
||||
module_cache,
|
||||
..
|
||||
} = state;
|
||||
|
||||
let ModuleCache {
|
||||
mono_problems,
|
||||
type_problems,
|
||||
can_problems,
|
||||
procedures,
|
||||
sources,
|
||||
..
|
||||
} = state;
|
||||
} = module_cache;
|
||||
|
||||
let sources = sources
|
||||
.into_iter()
|
||||
.map(|(id, (path, src))| (id, (path, src.into())))
|
||||
.collect();
|
||||
|
||||
MonomorphizedModule {
|
||||
can_problems,
|
||||
|
@ -1570,7 +1562,7 @@ fn finish_specialization<'a>(
|
|||
subs,
|
||||
interns,
|
||||
procedures,
|
||||
src: src.into(),
|
||||
sources,
|
||||
timings: state.timings,
|
||||
}
|
||||
}
|
||||
|
@ -1580,7 +1572,6 @@ fn finish<'a>(
|
|||
solved: Solved<Subs>,
|
||||
exposed_vars_by_symbol: Vec<(Symbol, Variable)>,
|
||||
documentation: MutMap<ModuleId, ModuleDocumentation>,
|
||||
src: &'a str,
|
||||
) -> LoadedModule {
|
||||
let module_ids = Arc::try_unwrap(state.arc_modules)
|
||||
.unwrap_or_else(|_| panic!("There were still outstanding Arc references to module_ids"))
|
||||
|
@ -1591,15 +1582,22 @@ fn finish<'a>(
|
|||
all_ident_ids: state.constrained_ident_ids,
|
||||
};
|
||||
|
||||
let sources = state
|
||||
.module_cache
|
||||
.sources
|
||||
.into_iter()
|
||||
.map(|(id, (path, src))| (id, (path, src.into())))
|
||||
.collect();
|
||||
|
||||
LoadedModule {
|
||||
module_id: state.root_id,
|
||||
interns,
|
||||
solved,
|
||||
can_problems: state.can_problems,
|
||||
type_problems: state.type_problems,
|
||||
can_problems: state.module_cache.can_problems,
|
||||
type_problems: state.module_cache.type_problems,
|
||||
declarations_by_id: state.declarations_by_id,
|
||||
exposed_to_host: exposed_vars_by_symbol.into_iter().collect(),
|
||||
src: src.into(),
|
||||
sources,
|
||||
timings: state.timings,
|
||||
documentation,
|
||||
}
|
||||
|
@ -1693,6 +1691,7 @@ fn parse_header<'a>(
|
|||
match parsed {
|
||||
Ok((ast::Module::Interface { header }, parse_state)) => Ok(send_header(
|
||||
header.name,
|
||||
filename,
|
||||
header.exposes.into_bump_slice(),
|
||||
header.imports.into_bump_slice(),
|
||||
parse_state,
|
||||
|
@ -1702,6 +1701,7 @@ fn parse_header<'a>(
|
|||
)),
|
||||
Ok((ast::Module::App { header }, parse_state)) => Ok(send_header(
|
||||
header.name,
|
||||
filename,
|
||||
header.provides.into_bump_slice(),
|
||||
header.imports.into_bump_slice(),
|
||||
parse_state,
|
||||
|
@ -1776,6 +1776,7 @@ fn load_from_str<'a>(
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
fn send_header<'a>(
|
||||
name: Located<roc_parse::header::ModuleName<'a>>,
|
||||
filename: PathBuf,
|
||||
exposes: &'a [Located<ExposesEntry<'a>>],
|
||||
imports: &'a [Located<ImportsEntry<'a>>],
|
||||
parse_state: parser::State<'a>,
|
||||
|
@ -1895,6 +1896,7 @@ fn send_header<'a>(
|
|||
home,
|
||||
Msg::Header(ModuleHeader {
|
||||
module_id: home,
|
||||
module_path: filename,
|
||||
exposed_ident_ids: ident_ids,
|
||||
module_name: declared_name,
|
||||
imported_modules,
|
||||
|
@ -1914,7 +1916,6 @@ impl<'a> BuildTask<'a> {
|
|||
module: Module,
|
||||
ident_ids: IdentIds,
|
||||
module_timing: ModuleTiming,
|
||||
src: &'a str,
|
||||
constraint: Constraint,
|
||||
var_store: VarStore,
|
||||
imported_modules: MutSet<ModuleId>,
|
||||
|
@ -1954,7 +1955,6 @@ impl<'a> BuildTask<'a> {
|
|||
imported_symbols,
|
||||
constraint,
|
||||
var_store,
|
||||
src,
|
||||
declarations,
|
||||
module_timing,
|
||||
}
|
||||
|
@ -1970,7 +1970,6 @@ fn run_solve<'a>(
|
|||
constraint: Constraint,
|
||||
mut var_store: VarStore,
|
||||
decls: Vec<Declaration>,
|
||||
src: &'a str,
|
||||
) -> Msg<'a> {
|
||||
// We have more constraining work to do now, so we'll add it to our timings.
|
||||
let constrain_start = SystemTime::now();
|
||||
|
@ -2012,7 +2011,6 @@ fn run_solve<'a>(
|
|||
|
||||
// Send the subs to the main thread for processing,
|
||||
Msg::SolvedTypes {
|
||||
src,
|
||||
module_id,
|
||||
solved_subs,
|
||||
ident_ids,
|
||||
|
@ -2041,7 +2039,6 @@ fn canonicalize_and_constrain<'a>(
|
|||
exposed_imports,
|
||||
imported_modules,
|
||||
mut module_timing,
|
||||
src,
|
||||
..
|
||||
} = parsed;
|
||||
|
||||
|
@ -2094,7 +2091,6 @@ fn canonicalize_and_constrain<'a>(
|
|||
module,
|
||||
declarations: module_output.declarations,
|
||||
imported_modules,
|
||||
src,
|
||||
var_store,
|
||||
constraint,
|
||||
ident_ids: module_output.ident_ids,
|
||||
|
@ -2145,12 +2141,14 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result<Msg<'a>, Loadi
|
|||
deps_by_name,
|
||||
exposed_ident_ids,
|
||||
exposed_imports,
|
||||
module_path,
|
||||
..
|
||||
} = header;
|
||||
|
||||
let parsed = ParsedModule {
|
||||
module_id,
|
||||
module_name,
|
||||
module_path,
|
||||
deps_by_name,
|
||||
exposed_ident_ids,
|
||||
exposed_imports,
|
||||
|
@ -2202,7 +2200,6 @@ fn make_specializations<'a>(
|
|||
mut procs: Procs<'a>,
|
||||
mut layout_cache: LayoutCache<'a>,
|
||||
specializations_we_must_make: ExternalSpecializations,
|
||||
finished_info: FinishedInfo<'a>,
|
||||
) -> Msg<'a> {
|
||||
let mut mono_problems = Vec::new();
|
||||
// do the thing
|
||||
|
@ -2238,7 +2235,6 @@ fn make_specializations<'a>(
|
|||
procedures,
|
||||
problems: mono_problems,
|
||||
subs,
|
||||
finished_info,
|
||||
external_specializations_requested,
|
||||
}
|
||||
}
|
||||
|
@ -2255,7 +2251,6 @@ fn build_pending_specializations<'a>(
|
|||
mut layout_cache: LayoutCache<'a>,
|
||||
// TODO remove
|
||||
exposed_to_host: MutMap<Symbol, Variable>,
|
||||
finished_info: FinishedInfo<'a>,
|
||||
) -> Msg<'a> {
|
||||
let mut procs = Procs::default();
|
||||
|
||||
|
@ -2309,7 +2304,6 @@ fn build_pending_specializations<'a>(
|
|||
layout_cache,
|
||||
procs,
|
||||
problems,
|
||||
finished_info,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2366,7 +2360,13 @@ fn add_def_to_module<'a>(
|
|||
}
|
||||
};
|
||||
|
||||
procs.insert_exposed(symbol, layout, mono_env.subs, annotation);
|
||||
procs.insert_exposed(
|
||||
symbol,
|
||||
layout,
|
||||
mono_env.subs,
|
||||
def.annotation,
|
||||
annotation,
|
||||
);
|
||||
}
|
||||
|
||||
procs.insert_named(
|
||||
|
@ -2392,7 +2392,13 @@ fn add_def_to_module<'a>(
|
|||
todo!("TODO gracefully handle the situation where we expose a function to the host which doesn't have a valid layout (e.g. maybe the function wasn't monomorphic): {:?}", err)
|
||||
);
|
||||
|
||||
procs.insert_exposed(symbol, layout, mono_env.subs, annotation);
|
||||
procs.insert_exposed(
|
||||
symbol,
|
||||
layout,
|
||||
mono_env.subs,
|
||||
def.annotation,
|
||||
annotation,
|
||||
);
|
||||
}
|
||||
|
||||
let proc = PartialProc {
|
||||
|
@ -2457,7 +2463,6 @@ fn run_task<'a>(
|
|||
var_store,
|
||||
ident_ids,
|
||||
declarations,
|
||||
src,
|
||||
} => Ok(run_solve(
|
||||
module,
|
||||
ident_ids,
|
||||
|
@ -2466,7 +2471,6 @@ fn run_task<'a>(
|
|||
constraint,
|
||||
var_store,
|
||||
declarations,
|
||||
src,
|
||||
)),
|
||||
BuildPendingSpecializations {
|
||||
module_id,
|
||||
|
@ -2475,7 +2479,6 @@ fn run_task<'a>(
|
|||
module_timing,
|
||||
layout_cache,
|
||||
solved_subs,
|
||||
finished_info,
|
||||
exposed_to_host,
|
||||
} => Ok(build_pending_specializations(
|
||||
arena,
|
||||
|
@ -2486,7 +2489,6 @@ fn run_task<'a>(
|
|||
module_timing,
|
||||
layout_cache,
|
||||
exposed_to_host,
|
||||
finished_info,
|
||||
)),
|
||||
MakeSpecializations {
|
||||
module_id,
|
||||
|
@ -2495,7 +2497,6 @@ fn run_task<'a>(
|
|||
procs,
|
||||
layout_cache,
|
||||
specializations_we_must_make,
|
||||
finished_info,
|
||||
} => Ok(make_specializations(
|
||||
arena,
|
||||
module_id,
|
||||
|
@ -2504,7 +2505,6 @@ fn run_task<'a>(
|
|||
procs,
|
||||
layout_cache,
|
||||
specializations_we_must_make,
|
||||
finished_info,
|
||||
)),
|
||||
}?;
|
||||
|
||||
|
|
|
@ -43,10 +43,21 @@ mod test_load {
|
|||
src_dir.as_path(),
|
||||
subs_by_module,
|
||||
);
|
||||
let loaded_module = loaded.expect("Test module failed to load");
|
||||
let mut loaded_module = loaded.expect("Test module failed to load");
|
||||
|
||||
assert_eq!(loaded_module.can_problems, Vec::new());
|
||||
assert_eq!(loaded_module.type_problems, Vec::new());
|
||||
let home = loaded_module.module_id;
|
||||
|
||||
assert_eq!(
|
||||
loaded_module.can_problems.remove(&home).unwrap_or_default(),
|
||||
Vec::new()
|
||||
);
|
||||
assert_eq!(
|
||||
loaded_module
|
||||
.type_problems
|
||||
.remove(&home)
|
||||
.unwrap_or_default(),
|
||||
Vec::new()
|
||||
);
|
||||
|
||||
let expected_name = loaded_module
|
||||
.interns
|
||||
|
@ -87,8 +98,17 @@ mod test_load {
|
|||
let home = loaded_module.module_id;
|
||||
let mut subs = loaded_module.solved.into_inner();
|
||||
|
||||
assert_eq!(loaded_module.can_problems, Vec::new());
|
||||
assert_eq!(loaded_module.type_problems, Vec::new());
|
||||
assert_eq!(
|
||||
loaded_module.can_problems.remove(&home).unwrap_or_default(),
|
||||
Vec::new()
|
||||
);
|
||||
assert_eq!(
|
||||
loaded_module
|
||||
.type_problems
|
||||
.remove(&home)
|
||||
.unwrap_or_default(),
|
||||
Vec::new()
|
||||
);
|
||||
|
||||
for decl in loaded_module.declarations_by_id.remove(&home).unwrap() {
|
||||
match decl {
|
||||
|
@ -141,9 +161,19 @@ mod test_load {
|
|||
);
|
||||
|
||||
let mut loaded_module = loaded.expect("Test module failed to load");
|
||||
let home = loaded_module.module_id;
|
||||
|
||||
assert_eq!(loaded_module.can_problems, Vec::new());
|
||||
assert_eq!(loaded_module.type_problems, Vec::new());
|
||||
assert_eq!(
|
||||
loaded_module.can_problems.remove(&home).unwrap_or_default(),
|
||||
Vec::new()
|
||||
);
|
||||
assert_eq!(
|
||||
loaded_module
|
||||
.type_problems
|
||||
.remove(&home)
|
||||
.unwrap_or_default(),
|
||||
Vec::new()
|
||||
);
|
||||
|
||||
let def_count: usize = loaded_module
|
||||
.declarations_by_id
|
||||
|
|
|
@ -44,10 +44,21 @@ mod test_uniq_load {
|
|||
src_dir.as_path(),
|
||||
subs_by_module,
|
||||
);
|
||||
let loaded_module = loaded.expect("Test module failed to load");
|
||||
let mut loaded_module = loaded.expect("Test module failed to load");
|
||||
|
||||
assert_eq!(loaded_module.can_problems, Vec::new());
|
||||
assert_eq!(loaded_module.type_problems, Vec::new());
|
||||
let home = loaded_module.module_id;
|
||||
|
||||
assert_eq!(
|
||||
loaded_module.can_problems.remove(&home).unwrap_or_default(),
|
||||
Vec::new()
|
||||
);
|
||||
assert_eq!(
|
||||
loaded_module
|
||||
.type_problems
|
||||
.remove(&home)
|
||||
.unwrap_or_default(),
|
||||
Vec::new()
|
||||
);
|
||||
|
||||
let expected_name = loaded_module
|
||||
.interns
|
||||
|
@ -88,8 +99,17 @@ mod test_uniq_load {
|
|||
let home = loaded_module.module_id;
|
||||
let mut subs = loaded_module.solved.into_inner();
|
||||
|
||||
assert_eq!(loaded_module.can_problems, Vec::new());
|
||||
assert_eq!(loaded_module.type_problems, Vec::new());
|
||||
assert_eq!(
|
||||
loaded_module.can_problems.remove(&home).unwrap_or_default(),
|
||||
Vec::new()
|
||||
);
|
||||
assert_eq!(
|
||||
loaded_module
|
||||
.type_problems
|
||||
.remove(&home)
|
||||
.unwrap_or_default(),
|
||||
Vec::new()
|
||||
);
|
||||
|
||||
for decl in loaded_module.declarations_by_id.remove(&home).unwrap() {
|
||||
match decl {
|
||||
|
@ -142,9 +162,19 @@ mod test_uniq_load {
|
|||
);
|
||||
|
||||
let mut loaded_module = loaded.expect("Test module failed to load");
|
||||
let home = loaded_module.module_id;
|
||||
|
||||
assert_eq!(loaded_module.can_problems, Vec::new());
|
||||
assert_eq!(loaded_module.type_problems, Vec::new());
|
||||
assert_eq!(
|
||||
loaded_module.can_problems.remove(&home).unwrap_or_default(),
|
||||
Vec::new()
|
||||
);
|
||||
assert_eq!(
|
||||
loaded_module
|
||||
.type_problems
|
||||
.remove(&home)
|
||||
.unwrap_or_default(),
|
||||
Vec::new()
|
||||
);
|
||||
|
||||
let def_count: usize = loaded_module
|
||||
.declarations_by_id
|
||||
|
|
|
@ -14,6 +14,7 @@ pub enum LowLevel {
|
|||
ListRepeat,
|
||||
ListReverse,
|
||||
ListConcat,
|
||||
ListContains,
|
||||
ListAppend,
|
||||
ListPrepend,
|
||||
ListJoin,
|
||||
|
|
|
@ -691,6 +691,7 @@ define_builtins! {
|
|||
15 LIST_PREPEND: "prepend"
|
||||
16 LIST_JOIN: "join"
|
||||
17 LIST_KEEP_IF: "keepIf"
|
||||
18 LIST_CONTAINS: "contains"
|
||||
}
|
||||
5 RESULT: "Result" => {
|
||||
0 RESULT_RESULT: "Result" imported // the Result.Result type alias
|
||||
|
|
|
@ -520,6 +520,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
|
|||
ListJoin => arena.alloc_slice_copy(&[irrelevant]),
|
||||
ListMap => arena.alloc_slice_copy(&[owned, irrelevant]),
|
||||
ListKeepIf => arena.alloc_slice_copy(&[owned, irrelevant]),
|
||||
ListContains => arena.alloc_slice_copy(&[owned, irrelevant]),
|
||||
ListWalkRight => arena.alloc_slice_copy(&[borrowed, irrelevant, owned]),
|
||||
|
||||
Eq | NotEq | And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumMul | NumGt
|
||||
|
|
|
@ -147,7 +147,7 @@ type LiveVarSet = MutSet<Symbol>;
|
|||
type JPLiveVarMap = MutMap<JoinPointId, LiveVarSet>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Context<'a> {
|
||||
struct Context<'a> {
|
||||
arena: &'a Bump,
|
||||
vars: VarMap,
|
||||
jp_live_vars: JPLiveVarMap, // map: join point => live variables
|
||||
|
|
|
@ -28,6 +28,12 @@ pub struct PartialProc<'a> {
|
|||
pub is_self_recursive: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default)]
|
||||
pub struct HostExposedVariables {
|
||||
rigids: MutMap<Lowercase, Variable>,
|
||||
aliases: MutMap<Symbol, Variable>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum CapturedSymbols<'a> {
|
||||
None,
|
||||
|
@ -46,12 +52,34 @@ impl<'a> CapturedSymbols<'a> {
|
|||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct PendingSpecialization {
|
||||
solved_type: SolvedType,
|
||||
host_exposed_aliases: MutMap<Symbol, SolvedType>,
|
||||
}
|
||||
|
||||
impl PendingSpecialization {
|
||||
pub fn from_var(subs: &Subs, var: Variable) -> Self {
|
||||
let solved_type = SolvedType::from_var(subs, var);
|
||||
PendingSpecialization { solved_type }
|
||||
PendingSpecialization {
|
||||
solved_type,
|
||||
host_exposed_aliases: MutMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_var_host_exposed(
|
||||
subs: &Subs,
|
||||
var: Variable,
|
||||
host_exposed_aliases: &MutMap<Symbol, Variable>,
|
||||
) -> Self {
|
||||
let solved_type = SolvedType::from_var(subs, var);
|
||||
|
||||
let host_exposed_aliases = host_exposed_aliases
|
||||
.iter()
|
||||
.map(|(symbol, variable)| (*symbol, SolvedType::from_var(subs, *variable)))
|
||||
.collect();
|
||||
|
||||
PendingSpecialization {
|
||||
solved_type,
|
||||
host_exposed_aliases,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,6 +91,16 @@ pub struct Proc<'a> {
|
|||
pub closure_data_layout: Option<Layout<'a>>,
|
||||
pub ret_layout: Layout<'a>,
|
||||
pub is_self_recursive: SelfRecursive,
|
||||
pub host_exposed_layouts: HostExposedLayouts<'a>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum HostExposedLayouts<'a> {
|
||||
NotHostExposed,
|
||||
HostExposed {
|
||||
rigids: MutMap<Lowercase, Layout<'a>>,
|
||||
aliases: MutMap<Symbol, Layout<'a>>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
@ -398,8 +436,7 @@ impl<'a> Procs<'a> {
|
|||
// Changing it to use .entry() would necessarily make it incorrect.
|
||||
#[allow(clippy::map_entry)]
|
||||
if !already_specialized {
|
||||
let solved_type = SolvedType::from_var(env.subs, annotation);
|
||||
let pending = PendingSpecialization { solved_type };
|
||||
let pending = PendingSpecialization::from_var(env.subs, annotation);
|
||||
|
||||
let pattern_symbols = pattern_symbols.into_bump_slice();
|
||||
match &mut self.pending_specializations {
|
||||
|
@ -478,6 +515,7 @@ impl<'a> Procs<'a> {
|
|||
name: Symbol,
|
||||
layout: Layout<'a>,
|
||||
subs: &Subs,
|
||||
opt_annotation: Option<roc_can::def::Annotation>,
|
||||
fn_var: Variable,
|
||||
) {
|
||||
let tuple = (name, layout);
|
||||
|
@ -489,7 +527,14 @@ impl<'a> Procs<'a> {
|
|||
|
||||
// We're done with that tuple, so move layout back out to avoid cloning it.
|
||||
let (name, layout) = tuple;
|
||||
let pending = PendingSpecialization::from_var(subs, fn_var);
|
||||
let pending = match opt_annotation {
|
||||
None => PendingSpecialization::from_var(subs, fn_var),
|
||||
Some(annotation) => PendingSpecialization::from_var_host_exposed(
|
||||
subs,
|
||||
fn_var,
|
||||
&annotation.introduced_variables.host_exposed_aliases,
|
||||
),
|
||||
};
|
||||
|
||||
// This should only be called when pending_specializations is Some.
|
||||
// Otherwise, it's being called in the wrong pass!
|
||||
|
@ -971,10 +1016,11 @@ impl<'a> Stmt<'a> {
|
|||
pub fn new(
|
||||
env: &mut Env<'a, '_>,
|
||||
can_expr: roc_can::expr::Expr,
|
||||
var: Variable,
|
||||
procs: &mut Procs<'a>,
|
||||
layout_cache: &mut LayoutCache<'a>,
|
||||
) -> Self {
|
||||
from_can(env, can_expr, procs, layout_cache)
|
||||
from_can(env, var, can_expr, procs, layout_cache)
|
||||
}
|
||||
|
||||
pub fn to_doc<'b, D, A>(&'b self, alloc: &'b D) -> DocBuilder<'b, D, A>
|
||||
|
@ -1301,6 +1347,7 @@ pub fn specialize_all<'a>(
|
|||
name,
|
||||
layout_cache,
|
||||
solved_type,
|
||||
MutMap::default(),
|
||||
partial_proc,
|
||||
) {
|
||||
Ok((proc, layout)) => {
|
||||
|
@ -1386,6 +1433,7 @@ fn specialize_external<'a>(
|
|||
proc_name: Symbol,
|
||||
layout_cache: &mut LayoutCache<'a>,
|
||||
fn_var: Variable,
|
||||
host_exposed_variables: &[(Symbol, Variable)],
|
||||
partial_proc: PartialProc<'a>,
|
||||
) -> Result<Proc<'a>, LayoutProblem> {
|
||||
let PartialProc {
|
||||
|
@ -1405,7 +1453,7 @@ fn specialize_external<'a>(
|
|||
let is_valid = matches!(unified, roc_unify::unify::Unified::Success(_));
|
||||
debug_assert!(is_valid);
|
||||
|
||||
let mut specialized_body = from_can(env, body, procs, layout_cache);
|
||||
let mut specialized_body = from_can(env, fn_var, body, procs, layout_cache);
|
||||
|
||||
// if this is a closure, add the closure record argument
|
||||
let pattern_symbols = if let CapturedSymbols::Captured(_) = captured_symbols {
|
||||
|
@ -1450,6 +1498,25 @@ fn specialize_external<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
// determine the layout of aliases/rigids exposed to the host
|
||||
let host_exposed_layouts = if host_exposed_variables.is_empty() {
|
||||
HostExposedLayouts::NotHostExposed
|
||||
} else {
|
||||
let mut aliases = MutMap::default();
|
||||
|
||||
for (symbol, variable) in host_exposed_variables {
|
||||
let layout = layout_cache
|
||||
.from_var(env.arena, *variable, env.subs)
|
||||
.unwrap();
|
||||
aliases.insert(*symbol, layout);
|
||||
}
|
||||
|
||||
HostExposedLayouts::HostExposed {
|
||||
rigids: MutMap::default(),
|
||||
aliases,
|
||||
}
|
||||
};
|
||||
|
||||
// reset subs, so we don't get type errors when specializing for a different signature
|
||||
layout_cache.rollback_to(cache_snapshot);
|
||||
env.subs.rollback_to(snapshot);
|
||||
|
@ -1472,6 +1539,7 @@ fn specialize_external<'a>(
|
|||
closure_data_layout,
|
||||
ret_layout,
|
||||
is_self_recursive: recursivity,
|
||||
host_exposed_layouts,
|
||||
};
|
||||
|
||||
Ok(proc)
|
||||
|
@ -1692,51 +1760,80 @@ fn specialize<'a>(
|
|||
pending: PendingSpecialization,
|
||||
partial_proc: PartialProc<'a>,
|
||||
) -> Result<(Proc<'a>, Layout<'a>), LayoutProblem> {
|
||||
let PendingSpecialization { solved_type } = pending;
|
||||
let PendingSpecialization {
|
||||
solved_type,
|
||||
host_exposed_aliases,
|
||||
} = pending;
|
||||
|
||||
specialize_solved_type(
|
||||
env,
|
||||
procs,
|
||||
proc_name,
|
||||
layout_cache,
|
||||
solved_type,
|
||||
host_exposed_aliases,
|
||||
partial_proc,
|
||||
)
|
||||
}
|
||||
|
||||
fn introduce_solved_type_to_subs<'a>(env: &mut Env<'a, '_>, solved_type: &SolvedType) -> Variable {
|
||||
use roc_solve::solve::insert_type_into_subs;
|
||||
use roc_types::solved_types::{to_type, FreeVars};
|
||||
use roc_types::subs::VarStore;
|
||||
let mut free_vars = FreeVars::default();
|
||||
let mut var_store = VarStore::new_from_subs(env.subs);
|
||||
|
||||
let before = var_store.peek();
|
||||
|
||||
let normal_type = to_type(solved_type, &mut free_vars, &mut var_store);
|
||||
|
||||
let after = var_store.peek();
|
||||
let variables_introduced = after - before;
|
||||
|
||||
env.subs.extend_by(variables_introduced as usize);
|
||||
|
||||
insert_type_into_subs(env.subs, &normal_type)
|
||||
}
|
||||
|
||||
fn specialize_solved_type<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
procs: &mut Procs<'a>,
|
||||
proc_name: Symbol,
|
||||
layout_cache: &mut LayoutCache<'a>,
|
||||
solved_type: SolvedType,
|
||||
host_exposed_aliases: MutMap<Symbol, SolvedType>,
|
||||
partial_proc: PartialProc<'a>,
|
||||
) -> Result<(Proc<'a>, Layout<'a>), LayoutProblem> {
|
||||
// add the specializations that other modules require of us
|
||||
use roc_solve::solve::{insert_type_into_subs, instantiate_rigids};
|
||||
use roc_types::solved_types::{to_type, FreeVars};
|
||||
use roc_types::subs::VarStore;
|
||||
use roc_solve::solve::instantiate_rigids;
|
||||
|
||||
let snapshot = env.subs.snapshot();
|
||||
let cache_snapshot = layout_cache.snapshot();
|
||||
|
||||
let mut free_vars = FreeVars::default();
|
||||
let mut var_store = VarStore::new_from_subs(env.subs);
|
||||
|
||||
let before = var_store.peek();
|
||||
|
||||
let normal_type = to_type(&solved_type, &mut free_vars, &mut var_store);
|
||||
|
||||
let after = var_store.peek();
|
||||
let variables_introduced = after - before;
|
||||
|
||||
env.subs.extend_by(variables_introduced as usize);
|
||||
|
||||
let fn_var = insert_type_into_subs(env.subs, &normal_type);
|
||||
let fn_var = introduce_solved_type_to_subs(env, &solved_type);
|
||||
|
||||
// make sure rigid variables in the annotation are converted to flex variables
|
||||
instantiate_rigids(env.subs, partial_proc.annotation);
|
||||
|
||||
match specialize_external(env, procs, proc_name, layout_cache, fn_var, partial_proc) {
|
||||
let mut host_exposed_variables = Vec::with_capacity_in(host_exposed_aliases.len(), env.arena);
|
||||
|
||||
for (symbol, solved_type) in host_exposed_aliases {
|
||||
let alias_var = introduce_solved_type_to_subs(env, &solved_type);
|
||||
|
||||
host_exposed_variables.push((symbol, alias_var));
|
||||
}
|
||||
|
||||
let specialized = specialize_external(
|
||||
env,
|
||||
procs,
|
||||
proc_name,
|
||||
layout_cache,
|
||||
fn_var,
|
||||
&host_exposed_variables,
|
||||
partial_proc,
|
||||
);
|
||||
|
||||
match specialized {
|
||||
Ok(proc) => {
|
||||
let layout = layout_cache
|
||||
.from_var(&env.arena, fn_var, env.subs)
|
||||
|
@ -1789,6 +1886,7 @@ impl<'a> FunctionLayouts<'a> {
|
|||
pub fn with_hole<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
can_expr: roc_can::expr::Expr,
|
||||
variable: Variable,
|
||||
procs: &mut Procs<'a>,
|
||||
layout_cache: &mut LayoutCache<'a>,
|
||||
assigned: Symbol,
|
||||
|
@ -1865,7 +1963,15 @@ pub fn with_hole<'a>(
|
|||
return_type,
|
||||
);
|
||||
|
||||
return with_hole(env, cont.value, procs, layout_cache, assigned, hole);
|
||||
return with_hole(
|
||||
env,
|
||||
cont.value,
|
||||
variable,
|
||||
procs,
|
||||
layout_cache,
|
||||
assigned,
|
||||
hole,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1877,6 +1983,7 @@ pub fn with_hole<'a>(
|
|||
return with_hole(
|
||||
env,
|
||||
def.loc_expr.value,
|
||||
def.expr_var,
|
||||
procs,
|
||||
layout_cache,
|
||||
assigned,
|
||||
|
@ -1887,7 +1994,15 @@ pub fn with_hole<'a>(
|
|||
}
|
||||
|
||||
// continue with the default path
|
||||
let mut stmt = with_hole(env, cont.value, procs, layout_cache, assigned, hole);
|
||||
let mut stmt = with_hole(
|
||||
env,
|
||||
cont.value,
|
||||
variable,
|
||||
procs,
|
||||
layout_cache,
|
||||
assigned,
|
||||
hole,
|
||||
);
|
||||
|
||||
// a variable is aliased
|
||||
if let roc_can::expr::Expr::Var(original) = def.loc_expr.value {
|
||||
|
@ -1898,6 +2013,7 @@ pub fn with_hole<'a>(
|
|||
with_hole(
|
||||
env,
|
||||
def.loc_expr.value,
|
||||
def.expr_var,
|
||||
procs,
|
||||
layout_cache,
|
||||
symbol,
|
||||
|
@ -1927,7 +2043,15 @@ pub fn with_hole<'a>(
|
|||
}
|
||||
|
||||
// convert the continuation
|
||||
let mut stmt = with_hole(env, cont.value, procs, layout_cache, assigned, hole);
|
||||
let mut stmt = with_hole(
|
||||
env,
|
||||
cont.value,
|
||||
variable,
|
||||
procs,
|
||||
layout_cache,
|
||||
assigned,
|
||||
hole,
|
||||
);
|
||||
|
||||
let outer_symbol = env.unique_symbol();
|
||||
stmt = store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt)
|
||||
|
@ -1937,6 +2061,7 @@ pub fn with_hole<'a>(
|
|||
with_hole(
|
||||
env,
|
||||
def.loc_expr.value,
|
||||
def.expr_var,
|
||||
procs,
|
||||
layout_cache,
|
||||
outer_symbol,
|
||||
|
@ -1983,7 +2108,15 @@ pub fn with_hole<'a>(
|
|||
unreachable!("recursive value does not have Identifier pattern")
|
||||
}
|
||||
|
||||
with_hole(env, cont.value, procs, layout_cache, assigned, hole)
|
||||
with_hole(
|
||||
env,
|
||||
cont.value,
|
||||
variable,
|
||||
procs,
|
||||
layout_cache,
|
||||
assigned,
|
||||
hole,
|
||||
)
|
||||
}
|
||||
Var(symbol) => {
|
||||
if procs.module_thunks.contains(&symbol) {
|
||||
|
@ -2003,6 +2136,27 @@ pub fn with_hole<'a>(
|
|||
);
|
||||
|
||||
return result;
|
||||
} else if symbol.module_id() != env.home && symbol.module_id() != ModuleId::ATTR {
|
||||
match layout_cache.from_var(env.arena, variable, env.subs) {
|
||||
Err(e) => panic!("invalid layout {:?}", e),
|
||||
Ok(Layout::FunctionPointer(_, _)) => {
|
||||
add_needed_external(procs, env, variable, symbol);
|
||||
}
|
||||
Ok(_) => {
|
||||
// this is a 0-arity thunk
|
||||
let result = call_by_name(
|
||||
env,
|
||||
procs,
|
||||
variable,
|
||||
symbol,
|
||||
std::vec::Vec::new(),
|
||||
layout_cache,
|
||||
assigned,
|
||||
env.arena.alloc(Stmt::Ret(assigned)),
|
||||
);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A bit ugly, but it does the job
|
||||
|
@ -2031,9 +2185,6 @@ pub fn with_hole<'a>(
|
|||
}
|
||||
}
|
||||
}
|
||||
// Var(symbol) => panic!("reached Var {}", symbol),
|
||||
// assigned,
|
||||
// Stmt::Ret(symbol),
|
||||
Tag {
|
||||
variant_var,
|
||||
name: tag_name,
|
||||
|
@ -2151,12 +2302,9 @@ pub fn with_hole<'a>(
|
|||
let sorted_fields = crate::layout::sort_record_fields(env.arena, record_var, env.subs);
|
||||
|
||||
let mut field_symbols = Vec::with_capacity_in(fields.len(), env.arena);
|
||||
let mut field_layouts = Vec::with_capacity_in(fields.len(), env.arena);
|
||||
let mut can_fields = Vec::with_capacity_in(fields.len(), env.arena);
|
||||
|
||||
for (label, layout) in sorted_fields.into_iter() {
|
||||
field_layouts.push(layout);
|
||||
|
||||
for (label, _, _) in sorted_fields.into_iter() {
|
||||
// TODO how should function pointers be handled here?
|
||||
match fields.remove(&label) {
|
||||
Some(field) => match can_reuse_symbol(procs, &field.loc_expr.value) {
|
||||
|
@ -2190,6 +2338,7 @@ pub fn with_hole<'a>(
|
|||
stmt = with_hole(
|
||||
env,
|
||||
field.loc_expr.value,
|
||||
field.var,
|
||||
procs,
|
||||
layout_cache,
|
||||
*symbol,
|
||||
|
@ -2226,6 +2375,7 @@ pub fn with_hole<'a>(
|
|||
let mut stmt = with_hole(
|
||||
env,
|
||||
final_else.value,
|
||||
branch_var,
|
||||
procs,
|
||||
layout_cache,
|
||||
assigned,
|
||||
|
@ -2237,6 +2387,7 @@ pub fn with_hole<'a>(
|
|||
let then = with_hole(
|
||||
env,
|
||||
loc_then.value,
|
||||
branch_var,
|
||||
procs,
|
||||
layout_cache,
|
||||
assigned,
|
||||
|
@ -2257,6 +2408,7 @@ pub fn with_hole<'a>(
|
|||
stmt = with_hole(
|
||||
env,
|
||||
loc_cond.value,
|
||||
cond_var,
|
||||
procs,
|
||||
layout_cache,
|
||||
branching_symbol,
|
||||
|
@ -2275,6 +2427,7 @@ pub fn with_hole<'a>(
|
|||
let mut stmt = with_hole(
|
||||
env,
|
||||
final_else.value,
|
||||
branch_var,
|
||||
procs,
|
||||
layout_cache,
|
||||
assigned_in_jump,
|
||||
|
@ -2286,6 +2439,7 @@ pub fn with_hole<'a>(
|
|||
let then = with_hole(
|
||||
env,
|
||||
loc_then.value,
|
||||
branch_var,
|
||||
procs,
|
||||
layout_cache,
|
||||
assigned_in_jump,
|
||||
|
@ -2306,6 +2460,7 @@ pub fn with_hole<'a>(
|
|||
stmt = with_hole(
|
||||
env,
|
||||
loc_cond.value,
|
||||
cond_var,
|
||||
procs,
|
||||
layout_cache,
|
||||
branching_symbol,
|
||||
|
@ -2441,7 +2596,7 @@ pub fn with_hole<'a>(
|
|||
let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena);
|
||||
|
||||
let mut current = 0;
|
||||
for (label, opt_field_layout) in sorted_fields.into_iter() {
|
||||
for (label, _, opt_field_layout) in sorted_fields.into_iter() {
|
||||
match opt_field_layout {
|
||||
Err(_) => {
|
||||
// this was an optional field, and now does not exist!
|
||||
|
@ -2582,7 +2737,7 @@ pub fn with_hole<'a>(
|
|||
let mut fields = Vec::with_capacity_in(sorted_fields.len(), env.arena);
|
||||
|
||||
let mut current = 0;
|
||||
for (label, opt_field_layout) in sorted_fields.into_iter() {
|
||||
for (label, _, opt_field_layout) in sorted_fields.into_iter() {
|
||||
match opt_field_layout {
|
||||
Err(_) => {
|
||||
debug_assert!(!updates.contains_key(&label));
|
||||
|
@ -2957,6 +3112,7 @@ pub fn with_hole<'a>(
|
|||
result = with_hole(
|
||||
env,
|
||||
loc_expr.value,
|
||||
fn_var,
|
||||
procs,
|
||||
layout_cache,
|
||||
function_symbol,
|
||||
|
@ -3004,6 +3160,7 @@ pub fn with_hole<'a>(
|
|||
|
||||
pub fn from_can<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
variable: Variable,
|
||||
can_expr: roc_can::expr::Expr,
|
||||
procs: &mut Procs<'a>,
|
||||
layout_cache: &mut LayoutCache<'a>,
|
||||
|
@ -3056,11 +3213,11 @@ pub fn from_can<'a>(
|
|||
.from_var(env.arena, cond_var, env.subs)
|
||||
.expect("invalid cond_layout");
|
||||
|
||||
let mut stmt = from_can(env, final_else.value, procs, layout_cache);
|
||||
let mut stmt = from_can(env, branch_var, final_else.value, procs, layout_cache);
|
||||
|
||||
for (loc_cond, loc_then) in branches.into_iter().rev() {
|
||||
let branching_symbol = env.unique_symbol();
|
||||
let then = from_can(env, loc_then.value, procs, layout_cache);
|
||||
let then = from_can(env, branch_var, loc_then.value, procs, layout_cache);
|
||||
|
||||
stmt = Stmt::Cond {
|
||||
cond_symbol: branching_symbol,
|
||||
|
@ -3076,6 +3233,7 @@ pub fn from_can<'a>(
|
|||
stmt = with_hole(
|
||||
env,
|
||||
loc_cond.value,
|
||||
cond_var,
|
||||
procs,
|
||||
layout_cache,
|
||||
branching_symbol,
|
||||
|
@ -3128,7 +3286,7 @@ pub fn from_can<'a>(
|
|||
unreachable!("recursive value does not have Identifier pattern")
|
||||
}
|
||||
|
||||
from_can(env, cont.value, procs, layout_cache)
|
||||
from_can(env, variable, cont.value, procs, layout_cache)
|
||||
}
|
||||
LetNonRec(def, cont, outer_annotation) => {
|
||||
if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value {
|
||||
|
@ -3178,7 +3336,7 @@ pub fn from_can<'a>(
|
|||
return_type,
|
||||
);
|
||||
|
||||
return from_can(env, cont.value, procs, layout_cache);
|
||||
return from_can(env, variable, cont.value, procs, layout_cache);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
@ -3186,7 +3344,7 @@ pub fn from_can<'a>(
|
|||
|
||||
match def.loc_expr.value {
|
||||
roc_can::expr::Expr::Var(original) => {
|
||||
let mut rest = from_can(env, cont.value, procs, layout_cache);
|
||||
let mut rest = from_can(env, def.expr_var, cont.value, procs, layout_cache);
|
||||
// a variable is aliased
|
||||
substitute_in_exprs(env.arena, &mut rest, *symbol, original);
|
||||
|
||||
|
@ -3231,7 +3389,7 @@ pub fn from_can<'a>(
|
|||
nested_annotation,
|
||||
);
|
||||
|
||||
return from_can(env, new_outer, procs, layout_cache);
|
||||
return from_can(env, variable, new_outer, procs, layout_cache);
|
||||
}
|
||||
roc_can::expr::Expr::LetRec(nested_defs, nested_cont, nested_annotation) => {
|
||||
use roc_can::expr::Expr::*;
|
||||
|
@ -3272,13 +3430,14 @@ pub fn from_can<'a>(
|
|||
nested_annotation,
|
||||
);
|
||||
|
||||
return from_can(env, new_outer, procs, layout_cache);
|
||||
return from_can(env, variable, new_outer, procs, layout_cache);
|
||||
}
|
||||
_ => {
|
||||
let rest = from_can(env, cont.value, procs, layout_cache);
|
||||
let rest = from_can(env, variable, cont.value, procs, layout_cache);
|
||||
return with_hole(
|
||||
env,
|
||||
def.loc_expr.value,
|
||||
def.expr_var,
|
||||
procs,
|
||||
layout_cache,
|
||||
*symbol,
|
||||
|
@ -3292,11 +3451,19 @@ pub fn from_can<'a>(
|
|||
let mono_pattern = from_can_pattern(env, layout_cache, &def.loc_pattern.value);
|
||||
|
||||
if let Pattern::Identifier(symbol) = mono_pattern {
|
||||
let hole = env
|
||||
.arena
|
||||
.alloc(from_can(env, cont.value, procs, layout_cache));
|
||||
let hole =
|
||||
env.arena
|
||||
.alloc(from_can(env, variable, cont.value, procs, layout_cache));
|
||||
|
||||
with_hole(env, def.loc_expr.value, procs, layout_cache, symbol, hole)
|
||||
with_hole(
|
||||
env,
|
||||
def.loc_expr.value,
|
||||
def.expr_var,
|
||||
procs,
|
||||
layout_cache,
|
||||
symbol,
|
||||
hole,
|
||||
)
|
||||
} else {
|
||||
let context = crate::exhaustive::Context::BadDestruct;
|
||||
match crate::exhaustive::check(
|
||||
|
@ -3317,7 +3484,7 @@ pub fn from_can<'a>(
|
|||
}
|
||||
|
||||
// convert the continuation
|
||||
let mut stmt = from_can(env, cont.value, procs, layout_cache);
|
||||
let mut stmt = from_can(env, variable, cont.value, procs, layout_cache);
|
||||
|
||||
if let roc_can::expr::Expr::Var(outer_symbol) = def.loc_expr.value {
|
||||
store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt)
|
||||
|
@ -3332,6 +3499,7 @@ pub fn from_can<'a>(
|
|||
with_hole(
|
||||
env,
|
||||
def.loc_expr.value,
|
||||
def.expr_var,
|
||||
procs,
|
||||
layout_cache,
|
||||
outer_symbol,
|
||||
|
@ -3344,7 +3512,7 @@ pub fn from_can<'a>(
|
|||
_ => {
|
||||
let symbol = env.unique_symbol();
|
||||
let hole = env.arena.alloc(Stmt::Ret(symbol));
|
||||
with_hole(env, can_expr, procs, layout_cache, symbol, hole)
|
||||
with_hole(env, can_expr, variable, procs, layout_cache, symbol, hole)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3464,13 +3632,13 @@ fn from_can_when<'a>(
|
|||
.into_iter()
|
||||
.map(|(pattern, opt_guard, can_expr)| {
|
||||
let branch_stmt = match join_point {
|
||||
None => from_can(env, can_expr, procs, layout_cache),
|
||||
None => from_can(env, expr_var, can_expr, procs, layout_cache),
|
||||
Some(id) => {
|
||||
let symbol = env.unique_symbol();
|
||||
let arguments = bumpalo::vec![in env.arena; symbol].into_bump_slice();
|
||||
let jump = env.arena.alloc(Stmt::Jump(id, arguments));
|
||||
|
||||
with_hole(env, can_expr, procs, layout_cache, symbol, jump)
|
||||
with_hole(env, can_expr, expr_var, procs, layout_cache, symbol, jump)
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -3480,7 +3648,15 @@ fn from_can_when<'a>(
|
|||
let symbol = env.unique_symbol();
|
||||
let jump = env.arena.alloc(Stmt::Jump(id, env.arena.alloc([symbol])));
|
||||
|
||||
let guard_stmt = with_hole(env, loc_expr.value, procs, layout_cache, symbol, jump);
|
||||
let guard_stmt = with_hole(
|
||||
env,
|
||||
loc_expr.value,
|
||||
cond_var,
|
||||
procs,
|
||||
layout_cache,
|
||||
symbol,
|
||||
jump,
|
||||
);
|
||||
|
||||
match store_pattern(env, procs, layout_cache, &pattern, cond_symbol, guard_stmt) {
|
||||
Ok(new_guard_stmt) => (
|
||||
|
@ -4026,6 +4202,7 @@ fn store_record_destruct<'a>(
|
|||
stmt = with_hole(
|
||||
env,
|
||||
expr.clone(),
|
||||
destruct.variable,
|
||||
procs,
|
||||
layout_cache,
|
||||
destruct.symbol,
|
||||
|
@ -4250,6 +4427,7 @@ fn assign_to_symbol<'a>(
|
|||
with_hole(
|
||||
env,
|
||||
loc_arg.value,
|
||||
arg_var,
|
||||
procs,
|
||||
layout_cache,
|
||||
symbol,
|
||||
|
@ -4275,6 +4453,24 @@ where
|
|||
result
|
||||
}
|
||||
|
||||
fn add_needed_external<'a>(
|
||||
procs: &mut Procs<'a>,
|
||||
env: &mut Env<'a, '_>,
|
||||
fn_var: Variable,
|
||||
name: Symbol,
|
||||
) {
|
||||
// call of a function that is not in this module
|
||||
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
||||
|
||||
let existing = match procs.externals_we_need.entry(name.module_id()) {
|
||||
Vacant(entry) => entry.insert(ExternalSpecializations::default()),
|
||||
Occupied(entry) => entry.into_mut(),
|
||||
};
|
||||
|
||||
let solved_type = SolvedType::from_var(env.subs, fn_var);
|
||||
existing.insert(name, solved_type);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn call_by_name<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
|
@ -4358,6 +4554,12 @@ fn call_by_name<'a>(
|
|||
// exactly once.
|
||||
match &mut procs.pending_specializations {
|
||||
Some(pending_specializations) => {
|
||||
let is_imported = assigned.module_id() != proc_name.module_id();
|
||||
// builtins are currently (re)defined in each module, so not really imported
|
||||
let is_builtin = proc_name.is_builtin();
|
||||
if is_imported && !is_builtin {
|
||||
add_needed_external(procs, env, original_fn_var, proc_name);
|
||||
} else {
|
||||
// register the pending specialization, so this gets code genned later
|
||||
add_pending(
|
||||
pending_specializations,
|
||||
|
@ -4365,6 +4567,7 @@ fn call_by_name<'a>(
|
|||
full_layout.clone(),
|
||||
pending,
|
||||
);
|
||||
}
|
||||
|
||||
let call = Expr::FunctionCall {
|
||||
call_type: CallType::ByName(proc_name),
|
||||
|
@ -4447,21 +4650,7 @@ fn call_by_name<'a>(
|
|||
}
|
||||
|
||||
None if assigned.module_id() != proc_name.module_id() => {
|
||||
let fn_var = original_fn_var;
|
||||
|
||||
// call of a function that is not not in this module
|
||||
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
||||
|
||||
let existing =
|
||||
match procs.externals_we_need.entry(proc_name.module_id()) {
|
||||
Vacant(entry) => {
|
||||
entry.insert(ExternalSpecializations::default())
|
||||
}
|
||||
Occupied(entry) => entry.into_mut(),
|
||||
};
|
||||
|
||||
let solved_type = SolvedType::from_var(env.subs, fn_var);
|
||||
existing.insert(proc_name, solved_type);
|
||||
add_needed_external(procs, env, original_fn_var, proc_name);
|
||||
|
||||
let call = Expr::FunctionCall {
|
||||
call_type: CallType::ByName(proc_name),
|
||||
|
@ -4548,6 +4737,7 @@ pub enum Pattern<'a> {
|
|||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct RecordDestruct<'a> {
|
||||
pub label: Lowercase,
|
||||
pub variable: Variable,
|
||||
pub layout: Layout<'a>,
|
||||
pub symbol: Symbol,
|
||||
pub typ: DestructType<'a>,
|
||||
|
@ -4774,7 +4964,7 @@ pub fn from_can_pattern<'a>(
|
|||
|
||||
loop {
|
||||
match (opt_sorted, opt_destruct) {
|
||||
(Some((label, Ok(field_layout))), Some(destruct)) => {
|
||||
(Some((label, variable, Ok(field_layout))), Some(destruct)) => {
|
||||
if destruct.value.label == label {
|
||||
mono_destructs.push(from_can_record_destruct(
|
||||
env,
|
||||
|
@ -4790,6 +4980,7 @@ pub fn from_can_pattern<'a>(
|
|||
mono_destructs.push(RecordDestruct {
|
||||
label: label.clone(),
|
||||
symbol: env.unique_symbol(),
|
||||
variable,
|
||||
layout: field_layout.clone(),
|
||||
typ: DestructType::Guard(Pattern::Underscore),
|
||||
});
|
||||
|
@ -4798,7 +4989,7 @@ pub fn from_can_pattern<'a>(
|
|||
}
|
||||
field_layouts.push(field_layout);
|
||||
}
|
||||
(Some((label, Err(field_layout))), Some(destruct)) => {
|
||||
(Some((label, variable, Err(field_layout))), Some(destruct)) => {
|
||||
if destruct.value.label == label {
|
||||
opt_destruct = it2.next();
|
||||
|
||||
|
@ -4806,6 +4997,7 @@ pub fn from_can_pattern<'a>(
|
|||
label: destruct.value.label.clone(),
|
||||
symbol: destruct.value.symbol,
|
||||
layout: field_layout,
|
||||
variable,
|
||||
typ: match &destruct.value.typ {
|
||||
roc_can::pattern::DestructType::Optional(_, loc_expr) => {
|
||||
// if we reach this stage, the optional field is not present
|
||||
|
@ -4821,12 +5013,13 @@ pub fn from_can_pattern<'a>(
|
|||
opt_sorted = it1.next();
|
||||
}
|
||||
|
||||
(Some((label, Err(field_layout))), None) => {
|
||||
(Some((label, variable, Err(field_layout))), None) => {
|
||||
// the remainder of the fields (from the type) is not matched on in
|
||||
// this pattern; to fill it out, we put underscores
|
||||
mono_destructs.push(RecordDestruct {
|
||||
label: label.clone(),
|
||||
symbol: env.unique_symbol(),
|
||||
variable,
|
||||
layout: field_layout.clone(),
|
||||
typ: DestructType::Guard(Pattern::Underscore),
|
||||
});
|
||||
|
@ -4834,12 +5027,13 @@ pub fn from_can_pattern<'a>(
|
|||
opt_sorted = it1.next();
|
||||
}
|
||||
|
||||
(Some((label, Ok(field_layout))), None) => {
|
||||
(Some((label, variable, Ok(field_layout))), None) => {
|
||||
// the remainder of the fields (from the type) is not matched on in
|
||||
// this pattern; to fill it out, we put underscores
|
||||
mono_destructs.push(RecordDestruct {
|
||||
label: label.clone(),
|
||||
symbol: env.unique_symbol(),
|
||||
variable,
|
||||
layout: field_layout.clone(),
|
||||
typ: DestructType::Guard(Pattern::Underscore),
|
||||
});
|
||||
|
@ -4861,6 +5055,7 @@ pub fn from_can_pattern<'a>(
|
|||
mono_destructs.push(RecordDestruct {
|
||||
label: destruct.value.label.clone(),
|
||||
symbol: destruct.value.symbol,
|
||||
variable: destruct.value.var,
|
||||
layout: field_layout,
|
||||
typ: DestructType::Optional(loc_expr.value.clone()),
|
||||
})
|
||||
|
@ -4894,6 +5089,7 @@ fn from_can_record_destruct<'a>(
|
|||
RecordDestruct {
|
||||
label: can_rd.label.clone(),
|
||||
symbol: can_rd.symbol,
|
||||
variable: can_rd.var,
|
||||
layout: field_layout,
|
||||
typ: match &can_rd.typ {
|
||||
roc_can::pattern::DestructType::Required => DestructType::Required,
|
||||
|
|
|
@ -822,7 +822,7 @@ pub fn sort_record_fields<'a>(
|
|||
arena: &'a Bump,
|
||||
var: Variable,
|
||||
subs: &Subs,
|
||||
) -> Vec<'a, (Lowercase, Result<Layout<'a>, Layout<'a>>)> {
|
||||
) -> Vec<'a, (Lowercase, Variable, Result<Layout<'a>, Layout<'a>>)> {
|
||||
let mut fields_map = MutMap::default();
|
||||
|
||||
let mut env = Env {
|
||||
|
@ -844,7 +844,7 @@ pub fn sort_record_fields<'a>(
|
|||
RecordField::Optional(v) => {
|
||||
let layout =
|
||||
Layout::from_var(&mut env, v).expect("invalid layout from var");
|
||||
sorted_fields.push((label, Err(layout)));
|
||||
sorted_fields.push((label, v, Err(layout)));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
@ -853,11 +853,11 @@ pub fn sort_record_fields<'a>(
|
|||
|
||||
// Drop any zero-sized fields like {}
|
||||
if !layout.is_zero_sized() {
|
||||
sorted_fields.push((label, Ok(layout)));
|
||||
sorted_fields.push((label, var, Ok(layout)));
|
||||
}
|
||||
}
|
||||
|
||||
sorted_fields.sort_by(|(label1, _), (label2, _)| label1.cmp(label2));
|
||||
sorted_fields.sort_by(|(label1, _, _), (label2, _, _)| label1.cmp(label2));
|
||||
|
||||
sorted_fields
|
||||
}
|
||||
|
|
|
@ -62,18 +62,20 @@ mod test_mono {
|
|||
exposed_types,
|
||||
);
|
||||
|
||||
let loaded = loaded.expect("failed to load module");
|
||||
let mut loaded = loaded.expect("failed to load module");
|
||||
|
||||
use roc_load::file::MonomorphizedModule;
|
||||
let MonomorphizedModule {
|
||||
can_problems,
|
||||
type_problems,
|
||||
mono_problems,
|
||||
module_id: home,
|
||||
procedures,
|
||||
exposed_to_host,
|
||||
..
|
||||
} = loaded;
|
||||
|
||||
let can_problems = loaded.can_problems.remove(&home).unwrap_or_default();
|
||||
let type_problems = loaded.type_problems.remove(&home).unwrap_or_default();
|
||||
let mono_problems = loaded.mono_problems.remove(&home).unwrap_or_default();
|
||||
|
||||
if !can_problems.is_empty() {
|
||||
println!("Ignoring {} canonicalization problems", can_problems.len());
|
||||
}
|
||||
|
|
4
compiler/parse/fuzz/.gitignore
vendored
Normal file
4
compiler/parse/fuzz/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
|
||||
target
|
||||
corpus
|
||||
artifacts
|
182
compiler/parse/fuzz/Cargo.lock
generated
Normal file
182
compiler/parse/fuzz/Cargo.lock
generated
Normal file
|
@ -0,0 +1,182 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db55d72333851e17d572bec876e390cd3b11eb1ef53ae821dd9f3b653d2b4569"
|
||||
|
||||
[[package]]
|
||||
name = "bitmaps"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d"
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
|
||||
|
||||
[[package]]
|
||||
name = "im"
|
||||
version = "14.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "696059c87b83c5a258817ecd67c3af915e3ed141891fc35a1e79908801cf0ce7"
|
||||
dependencies = [
|
||||
"bitmaps",
|
||||
"rand_core 0.5.1",
|
||||
"rand_xoshiro",
|
||||
"sized-chunks",
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "im-rc"
|
||||
version = "14.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "303f7e6256d546e01979071417432425f15c1891fb309a5f2d724ee908fabd6e"
|
||||
dependencies = [
|
||||
"bitmaps",
|
||||
"rand_core 0.5.1",
|
||||
"rand_xoshiro",
|
||||
"sized-chunks",
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inlinable_string"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb6ee2a7da03bfc3b66ca47c92c2e392fcc053ea040a85561749b026f7aad09a"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee8c42ab62f43795ed77a965ed07994c5584cdc94fd0ebf14b22ac1524077acc"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
|
||||
|
||||
[[package]]
|
||||
name = "rand_xoshiro"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9fcdd2e881d02f1d9390ae47ad8e5696a9e4be7b547a1da2afbc61973217004"
|
||||
dependencies = [
|
||||
"rand_core 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_collections"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"im",
|
||||
"im-rc",
|
||||
"wyhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_module"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"inlinable_string",
|
||||
"lazy_static",
|
||||
"roc_collections",
|
||||
"roc_region",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_parse"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"encode_unicode",
|
||||
"inlinable_string",
|
||||
"roc_collections",
|
||||
"roc_module",
|
||||
"roc_region",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_parse-fuzz"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"libfuzzer-sys",
|
||||
"roc_parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_region"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "sized-chunks"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d59044ea371ad781ff976f7b06480b9f0180e834eda94114f2afb4afc12b7718"
|
||||
dependencies = [
|
||||
"bitmaps",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
|
||||
|
||||
[[package]]
|
||||
name = "wyhash"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "782a50f48ac4336916227cd199c61c7b42f38d0ad705421b49eb12c74c53ae00"
|
||||
dependencies = [
|
||||
"rand_core 0.4.2",
|
||||
]
|
39
compiler/parse/fuzz/Cargo.toml
Normal file
39
compiler/parse/fuzz/Cargo.toml
Normal file
|
@ -0,0 +1,39 @@
|
|||
|
||||
[package]
|
||||
name = "roc_parse-fuzz"
|
||||
version = "0.0.0"
|
||||
authors = ["Automatically generated"]
|
||||
publish = false
|
||||
edition = "2018"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.3"
|
||||
bumpalo = { version = "3.2", features = ["collections"] }
|
||||
|
||||
[dependencies.roc_parse]
|
||||
path = ".."
|
||||
|
||||
# Prevent this from interfering with workspaces
|
||||
[workspace]
|
||||
members = ["."]
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_expr"
|
||||
path = "fuzz_targets/fuzz_expr.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_defs"
|
||||
path = "fuzz_targets/fuzz_defs.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_header"
|
||||
path = "fuzz_targets/fuzz_header.rs"
|
||||
test = false
|
||||
doc = false
|
11
compiler/parse/fuzz/README.md
Normal file
11
compiler/parse/fuzz/README.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
To setup fuzzing you will need to install cargo-fuzz and run with rust nightly:
|
||||
|
||||
```
|
||||
$ cargo install cargo-fuzz
|
||||
$ cargo +nightly fuzz run -j<cores> <target> -- -dict=dict.txt
|
||||
```
|
||||
|
||||
The different targets can be found by running `cargo fuzz list`.
|
||||
|
||||
When a bug is found, it will be reported with commands to run it again and look for a minimized version.
|
||||
If you are going to file a bug, please minimize the input before filing the bug.
|
36
compiler/parse/fuzz/dict.txt
Normal file
36
compiler/parse/fuzz/dict.txt
Normal file
|
@ -0,0 +1,36 @@
|
|||
"if"
|
||||
"then"
|
||||
"else"
|
||||
"when"
|
||||
"as"
|
||||
"is"
|
||||
"expect"
|
||||
|
||||
"app"
|
||||
"platform"
|
||||
"provides"
|
||||
"requires"
|
||||
"exposes"
|
||||
"imports"
|
||||
"effects"
|
||||
"interface"
|
||||
|
||||
"|>"
|
||||
"=="
|
||||
"!="
|
||||
"&&"
|
||||
"||"
|
||||
"+"
|
||||
"*"
|
||||
"-"
|
||||
"//"
|
||||
"/"
|
||||
"<="
|
||||
"<"
|
||||
">="
|
||||
">"
|
||||
"^"
|
||||
"%%"
|
||||
"%"
|
||||
|
||||
"->"
|
11
compiler/parse/fuzz/fuzz_targets/fuzz_defs.rs
Normal file
11
compiler/parse/fuzz/fuzz_targets/fuzz_defs.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
#![no_main]
|
||||
use bumpalo::Bump;
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use roc_parse::test_helpers::parse_defs_with;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
if let Ok(input) = std::str::from_utf8(data) {
|
||||
let arena = Bump::new();
|
||||
let _actual = parse_defs_with(&arena, input.trim());
|
||||
}
|
||||
});
|
11
compiler/parse/fuzz/fuzz_targets/fuzz_expr.rs
Normal file
11
compiler/parse/fuzz/fuzz_targets/fuzz_expr.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
#![no_main]
|
||||
use bumpalo::Bump;
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use roc_parse::test_helpers::parse_expr_with;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
if let Ok(input) = std::str::from_utf8(data) {
|
||||
let arena = Bump::new();
|
||||
let _actual = parse_expr_with(&arena, input.trim());
|
||||
}
|
||||
});
|
11
compiler/parse/fuzz/fuzz_targets/fuzz_header.rs
Normal file
11
compiler/parse/fuzz/fuzz_targets/fuzz_header.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
#![no_main]
|
||||
use bumpalo::Bump;
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use roc_parse::test_helpers::parse_header_with;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
if let Ok(input) = std::str::from_utf8(data) {
|
||||
let arena = Bump::new();
|
||||
let _actual = parse_header_with(&arena, input.trim());
|
||||
}
|
||||
});
|
|
@ -277,6 +277,8 @@ pub enum Def<'a> {
|
|||
/// This is used only to avoid cloning when reordering expressions (e.g. in desugar()).
|
||||
/// It lets us take a (&Def) and create a plain (Def) from it.
|
||||
Nested(&'a Def<'a>),
|
||||
|
||||
NotYetImplemented(&'static str),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
|
|
|
@ -317,12 +317,27 @@ fn spaces<'a>(
|
|||
'\n' => {
|
||||
state = state.newline()?;
|
||||
|
||||
// This was a newline, so end this line comment.
|
||||
space_list.push(LineComment(comment_line_buf.into_bump_str()));
|
||||
match (comment_line_buf.len(), comment_line_buf.chars().next())
|
||||
{
|
||||
(1, Some('#')) => {
|
||||
// This is a line with `##` - that is,
|
||||
// a doc comment new line.
|
||||
space_list.push(DocComment(""));
|
||||
comment_line_buf = String::new_in(arena);
|
||||
|
||||
line_state = LineState::Normal;
|
||||
}
|
||||
_ => {
|
||||
// This was a newline, so end this line comment.
|
||||
space_list.push(LineComment(
|
||||
comment_line_buf.into_bump_str(),
|
||||
));
|
||||
comment_line_buf = String::new_in(arena);
|
||||
|
||||
line_state = LineState::Normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
nonblank => {
|
||||
// Chars can have btye lengths of more than 1!
|
||||
state = state.advance_without_indenting(nonblank.len_utf8())?;
|
||||
|
|
|
@ -561,7 +561,7 @@ fn annotation_or_alias<'a>(
|
|||
ann: loc_ann,
|
||||
},
|
||||
Apply(_, _) => {
|
||||
panic!("TODO gracefully handle invalid Apply in type annotation");
|
||||
Def::NotYetImplemented("TODO gracefully handle invalid Apply in type annotation")
|
||||
}
|
||||
SpaceAfter(value, spaces_before) => Def::SpaceAfter(
|
||||
arena.alloc(annotation_or_alias(arena, value, region, loc_ann)),
|
||||
|
@ -574,19 +574,19 @@ fn annotation_or_alias<'a>(
|
|||
Nested(value) => annotation_or_alias(arena, value, region, loc_ann),
|
||||
|
||||
PrivateTag(_) => {
|
||||
panic!("TODO gracefully handle trying to use a private tag as an annotation.");
|
||||
Def::NotYetImplemented("TODO gracefully handle trying to use a private tag as an annotation.")
|
||||
}
|
||||
QualifiedIdentifier { .. } => {
|
||||
panic!("TODO gracefully handle trying to annotate a qualified identifier, e.g. `Foo.bar : ...`");
|
||||
Def::NotYetImplemented("TODO gracefully handle trying to annotate a qualified identifier, e.g. `Foo.bar : ...`")
|
||||
}
|
||||
NumLiteral(_) | NonBase10Literal { .. } | FloatLiteral(_) | StrLiteral(_) => {
|
||||
panic!("TODO gracefully handle trying to annotate a litera");
|
||||
Def::NotYetImplemented("TODO gracefully handle trying to annotate a litera")
|
||||
}
|
||||
Underscore => {
|
||||
panic!("TODO gracefully handle trying to give a type annotation to an undrscore");
|
||||
Def::NotYetImplemented("TODO gracefully handle trying to give a type annotation to an undrscore")
|
||||
}
|
||||
Malformed(_) => {
|
||||
panic!("TODO translate a malformed pattern into a malformed annotation");
|
||||
Def::NotYetImplemented("TODO translate a malformed pattern into a malformed annotation")
|
||||
}
|
||||
Identifier(ident) => {
|
||||
// This is a regular Annotation
|
||||
|
@ -633,7 +633,13 @@ fn parse_def_expr<'a>(
|
|||
))
|
||||
// `<` because '=' should be same indent (or greater) as the entire def-expr
|
||||
} else if equals_sign_indent < def_start_col {
|
||||
todo!("TODO the = in this declaration seems outdented. equals_sign_indent was {} and def_start_col was {}", equals_sign_indent, def_start_col);
|
||||
Err((
|
||||
Fail {
|
||||
attempting: state.attempting,
|
||||
reason: FailReason::NotYetImplemented(format!("TODO the = in this declaration seems outdented. equals_sign_indent was {} and def_start_col was {}", equals_sign_indent, def_start_col)),
|
||||
},
|
||||
state,
|
||||
))
|
||||
} else {
|
||||
// Indented more beyond the original indent of the entire def-expr.
|
||||
let indented_more = def_start_col + 1;
|
||||
|
@ -720,7 +726,15 @@ fn parse_def_signature<'a>(
|
|||
))
|
||||
// `<` because ':' should be same indent or greater
|
||||
} else if colon_indent < original_indent {
|
||||
panic!("TODO the : in this declaration seems outdented");
|
||||
Err((
|
||||
Fail {
|
||||
attempting: state.attempting,
|
||||
reason: FailReason::NotYetImplemented(
|
||||
"TODO the : in this declaration seems outdented".to_string(),
|
||||
),
|
||||
},
|
||||
state,
|
||||
))
|
||||
} else {
|
||||
// Indented more beyond the original indent.
|
||||
let indented_more = original_indent + 1;
|
||||
|
@ -1069,11 +1083,12 @@ fn loc_ident_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>
|
|||
} else {
|
||||
format!("{}.{}", module_name, parts.join("."))
|
||||
};
|
||||
|
||||
Ok((
|
||||
Located {
|
||||
region: loc_ident.region,
|
||||
value: Pattern::Malformed(arena.alloc(malformed_str)),
|
||||
value: Pattern::Malformed(
|
||||
String::from_str_in(&malformed_str, &arena).into_bump_str(),
|
||||
),
|
||||
},
|
||||
state,
|
||||
))
|
||||
|
@ -1120,7 +1135,15 @@ mod when {
|
|||
),
|
||||
move |arena, state, (case_indent, loc_condition)| {
|
||||
if case_indent < min_indent {
|
||||
panic!("TODO case wasn't indented enough");
|
||||
return Err((
|
||||
Fail {
|
||||
attempting: state.attempting,
|
||||
reason: FailReason::NotYetImplemented(
|
||||
"TODO case wasn't indented enough".to_string(),
|
||||
),
|
||||
},
|
||||
state,
|
||||
));
|
||||
}
|
||||
|
||||
// Everything in the branches must be indented at least as much as the case itself.
|
||||
|
@ -1178,9 +1201,15 @@ mod when {
|
|||
if alternatives_indented_correctly(&loc_patterns, original_indent) {
|
||||
Ok(((loc_patterns, loc_guard), state))
|
||||
} else {
|
||||
panic!(
|
||||
"TODO additional branch didn't have same indentation as first branch"
|
||||
);
|
||||
Err((
|
||||
Fail {
|
||||
attempting: state.attempting,
|
||||
reason: FailReason::NotYetImplemented(
|
||||
"TODO additional branch didn't have same indentation as first branch".to_string(),
|
||||
),
|
||||
},
|
||||
state,
|
||||
))
|
||||
}
|
||||
},
|
||||
),
|
||||
|
@ -1490,10 +1519,16 @@ fn ident_etc<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
|
|||
});
|
||||
}
|
||||
Err(malformed) => {
|
||||
panic!(
|
||||
return Err((
|
||||
Fail {
|
||||
attempting: state.attempting,
|
||||
reason: FailReason::NotYetImplemented(format!(
|
||||
"TODO early return malformed pattern {:?}",
|
||||
malformed
|
||||
);
|
||||
)),
|
||||
},
|
||||
state,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,4 +24,5 @@ pub mod number_literal;
|
|||
pub mod pattern;
|
||||
pub mod problems;
|
||||
pub mod string_literal;
|
||||
pub mod test_helpers;
|
||||
pub mod type_annotation;
|
||||
|
|
|
@ -224,6 +224,7 @@ pub enum FailReason {
|
|||
BadUtf8,
|
||||
ReservedKeyword(Region),
|
||||
ArgumentsBeforeEquals(Region),
|
||||
NotYetImplemented(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use crate::ast::{Attempting, EscapedChar, StrLiteral, StrSegment};
|
||||
use crate::expr;
|
||||
use crate::parser::{
|
||||
allocated, ascii_char, ascii_hex_digits, loc, parse_utf8, unexpected, unexpected_eof,
|
||||
ParseResult, Parser, State,
|
||||
allocated, ascii_char, ascii_hex_digits, loc, parse_utf8, unexpected, unexpected_eof, Fail,
|
||||
FailReason, ParseResult, Parser, State,
|
||||
};
|
||||
use bumpalo::collections::vec::Vec;
|
||||
use bumpalo::Bump;
|
||||
|
@ -279,7 +279,16 @@ where
|
|||
// lines.push(line);
|
||||
|
||||
// Ok((StrLiteral::Block(lines.into_bump_slice()), state))
|
||||
todo!("TODO parse this line in a block string: {:?}", line);
|
||||
Err((
|
||||
Fail {
|
||||
attempting: state.attempting,
|
||||
reason: FailReason::NotYetImplemented(format!(
|
||||
"TODO parse this line in a block string: {:?}",
|
||||
line
|
||||
)),
|
||||
},
|
||||
state,
|
||||
))
|
||||
}
|
||||
Err(reason) => state.fail(reason),
|
||||
};
|
||||
|
|
45
compiler/parse/src/test_helpers.rs
Normal file
45
compiler/parse/src/test_helpers.rs
Normal file
|
@ -0,0 +1,45 @@
|
|||
use crate::ast::{self, Attempting};
|
||||
use crate::blankspace::space0_before;
|
||||
use crate::expr::expr;
|
||||
use crate::module::{header, module_defs};
|
||||
use crate::parser::{loc, Fail, Parser, State};
|
||||
use bumpalo::collections::Vec;
|
||||
use bumpalo::Bump;
|
||||
use roc_region::all::Located;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn parse_expr_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, Fail> {
|
||||
parse_loc_with(arena, input).map(|loc_expr| loc_expr.value)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn parse_header_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Module<'a>, Fail> {
|
||||
let state = State::new(input.trim().as_bytes(), Attempting::Module);
|
||||
let answer = header().parse(arena, state);
|
||||
answer
|
||||
.map(|(loc_expr, _)| loc_expr)
|
||||
.map_err(|(fail, _)| fail)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn parse_defs_with<'a>(
|
||||
arena: &'a Bump,
|
||||
input: &'a str,
|
||||
) -> Result<Vec<'a, Located<ast::Def<'a>>>, Fail> {
|
||||
let state = State::new(input.trim().as_bytes(), Attempting::Module);
|
||||
let answer = module_defs().parse(arena, state);
|
||||
answer
|
||||
.map(|(loc_expr, _)| loc_expr)
|
||||
.map_err(|(fail, _)| fail)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> {
|
||||
let state = State::new(input.trim().as_bytes(), Attempting::Module);
|
||||
let parser = space0_before(loc(expr(0)), 0);
|
||||
let answer = parser.parse(&arena, state);
|
||||
|
||||
answer
|
||||
.map(|(loc_expr, _)| loc_expr)
|
||||
.map_err(|(fail, _)| fail)
|
||||
}
|
|
@ -4,8 +4,8 @@ use crate::expr::{global_tag, private_tag};
|
|||
use crate::ident::join_module_parts;
|
||||
use crate::keyword;
|
||||
use crate::parser::{
|
||||
allocated, ascii_char, ascii_string, not, optional, peek_utf8_char, unexpected, Either,
|
||||
ParseResult, Parser, State,
|
||||
allocated, ascii_char, ascii_string, not, optional, peek_utf8_char, unexpected, Either, Fail,
|
||||
FailReason, ParseResult, Parser, State,
|
||||
};
|
||||
use bumpalo::collections::string::String;
|
||||
use bumpalo::collections::vec::Vec;
|
||||
|
@ -239,7 +239,13 @@ fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>
|
|||
Ok((first, state))
|
||||
} else {
|
||||
// e.g. `Int,Int` without an arrow and return type
|
||||
panic!("Invalid function signature")
|
||||
Err((
|
||||
Fail {
|
||||
attempting: state.attempting,
|
||||
reason: FailReason::NotYetImplemented("TODO: Decide the correct error to return for 'Invalid function signature'".to_string()),
|
||||
},
|
||||
state,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
extern crate bumpalo;
|
||||
|
||||
use self::bumpalo::Bump;
|
||||
use roc_parse::ast::{self, Attempting};
|
||||
use roc_parse::blankspace::space0_before;
|
||||
use roc_parse::parser::{loc, Fail, Parser, State};
|
||||
use roc_region::all::Located;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, Fail> {
|
||||
parse_loc_with(arena, input).map(|loc_expr| loc_expr.value)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> {
|
||||
let state = State::new(input.trim().as_bytes(), Attempting::Module);
|
||||
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
|
||||
let answer = parser.parse(&arena, state);
|
||||
|
||||
answer
|
||||
.map(|(loc_expr, _)| loc_expr)
|
||||
.map_err(|(fail, _)| fail)
|
||||
}
|
|
@ -11,11 +11,8 @@ extern crate quickcheck_macros;
|
|||
extern crate roc_module;
|
||||
extern crate roc_parse;
|
||||
|
||||
mod helpers;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_parse {
|
||||
use crate::helpers::parse_with;
|
||||
use bumpalo::collections::vec::Vec;
|
||||
use bumpalo::{self, Bump};
|
||||
use roc_module::operator::BinOp::*;
|
||||
|
@ -33,19 +30,20 @@ mod test_parse {
|
|||
use roc_parse::header::ModuleName;
|
||||
use roc_parse::module::{interface_header, module_defs};
|
||||
use roc_parse::parser::{Fail, FailReason, Parser, State};
|
||||
use roc_parse::test_helpers::parse_expr_with;
|
||||
use roc_region::all::{Located, Region};
|
||||
use std::{f64, i64};
|
||||
|
||||
fn assert_parses_to<'a>(input: &'a str, expected_expr: Expr<'a>) {
|
||||
let arena = Bump::new();
|
||||
let actual = parse_with(&arena, input.trim());
|
||||
let actual = parse_expr_with(&arena, input.trim());
|
||||
|
||||
assert_eq!(Ok(expected_expr), actual);
|
||||
}
|
||||
|
||||
fn assert_parsing_fails<'a>(input: &'a str, reason: FailReason, attempting: Attempting) {
|
||||
let arena = Bump::new();
|
||||
let actual = parse_with(&arena, input);
|
||||
let actual = parse_expr_with(&arena, input);
|
||||
let expected_fail = Fail { reason, attempting };
|
||||
|
||||
assert_eq!(Err(expected_fail), actual);
|
||||
|
@ -53,7 +51,7 @@ mod test_parse {
|
|||
|
||||
fn assert_segments<E: Fn(&Bump) -> Vec<'_, ast::StrSegment<'_>>>(input: &str, to_expected: E) {
|
||||
let arena = Bump::new();
|
||||
let actual = parse_with(&arena, arena.alloc(input));
|
||||
let actual = parse_expr_with(&arena, arena.alloc(input));
|
||||
let expected_slice = to_expected(&arena);
|
||||
let expected_expr = Expr::Str(Line(&expected_slice));
|
||||
|
||||
|
@ -77,7 +75,7 @@ mod test_parse {
|
|||
("\\t", EscapedChar::Tab),
|
||||
("\\\"", EscapedChar::Quote),
|
||||
] {
|
||||
let actual = parse_with(&arena, arena.alloc(to_input(string)));
|
||||
let actual = parse_expr_with(&arena, arena.alloc(to_input(string)));
|
||||
let expected_slice = to_expected(*escaped, &arena);
|
||||
let expected_expr = Expr::Str(Line(&expected_slice));
|
||||
|
||||
|
@ -423,7 +421,7 @@ mod test_parse {
|
|||
fields: &[],
|
||||
update: None,
|
||||
};
|
||||
let actual = parse_with(&arena, "{}");
|
||||
let actual = parse_expr_with(&arena, "{}");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -455,7 +453,7 @@ mod test_parse {
|
|||
fields,
|
||||
};
|
||||
|
||||
let actual = parse_with(&arena, "{ Foo.Bar.baz & x: 5, y: 0 }");
|
||||
let actual = parse_expr_with(&arena, "{ Foo.Bar.baz & x: 5, y: 0 }");
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
||||
|
@ -470,7 +468,7 @@ mod test_parse {
|
|||
Located::new(0, 0, 2, 3, Num("2")),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
let actual = parse_with(&arena, "1+2");
|
||||
let actual = parse_expr_with(&arena, "1+2");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -484,7 +482,7 @@ mod test_parse {
|
|||
Located::new(0, 0, 2, 3, Num("2")),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
let actual = parse_with(&arena, "1-2");
|
||||
let actual = parse_expr_with(&arena, "1-2");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -498,7 +496,7 @@ mod test_parse {
|
|||
Located::new(0, 0, 7, 8, Num("2")),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
let actual = parse_with(&arena, "1 + 2");
|
||||
let actual = parse_expr_with(&arena, "1 + 2");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -512,7 +510,7 @@ mod test_parse {
|
|||
Located::new(0, 0, 7, 8, Num("2")),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
let actual = parse_with(&arena, "1 - 2");
|
||||
let actual = parse_expr_with(&arena, "1 - 2");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -535,7 +533,7 @@ mod test_parse {
|
|||
Located::new(0, 0, 4, 5, Num("2")),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
let actual = parse_with(&arena, "x + 2");
|
||||
let actual = parse_expr_with(&arena, "x + 2");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -557,7 +555,7 @@ mod test_parse {
|
|||
Located::new(0, 0, 4, 5, Num("2")),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
let actual = parse_with(&arena, "x - 2");
|
||||
let actual = parse_expr_with(&arena, "x - 2");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -572,7 +570,7 @@ mod test_parse {
|
|||
Located::new(1, 1, 2, 3, Num("4")),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
let actual = parse_with(&arena, "3 \n+ 4");
|
||||
let actual = parse_expr_with(&arena, "3 \n+ 4");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -587,7 +585,7 @@ mod test_parse {
|
|||
Located::new(1, 1, 2, 3, Num("4")),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
let actual = parse_with(&arena, "3 \n- 4");
|
||||
let actual = parse_expr_with(&arena, "3 \n- 4");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -602,7 +600,7 @@ mod test_parse {
|
|||
Located::new(1, 1, 2, 3, spaced_int),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
let actual = parse_with(&arena, "3 *\n 4");
|
||||
let actual = parse_expr_with(&arena, "3 *\n 4");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -617,7 +615,7 @@ mod test_parse {
|
|||
Located::new(1, 1, 2, 3, spaced_int),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
let actual = parse_with(&arena, "3 -\n 4");
|
||||
let actual = parse_expr_with(&arena, "3 -\n 4");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -632,7 +630,7 @@ mod test_parse {
|
|||
Located::new(1, 1, 2, 3, Num("4")),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
let actual = parse_with(&arena, "3 # 2 × 2\n+ 4");
|
||||
let actual = parse_expr_with(&arena, "3 # 2 × 2\n+ 4");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -647,7 +645,7 @@ mod test_parse {
|
|||
Located::new(1, 1, 2, 3, Num("4")),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
let actual = parse_with(&arena, "3 # test!\n+ 4");
|
||||
let actual = parse_expr_with(&arena, "3 # test!\n+ 4");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -662,7 +660,7 @@ mod test_parse {
|
|||
Located::new(1, 1, 1, 3, spaced_int),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
let actual = parse_with(&arena, "12 * # test!\n 92");
|
||||
let actual = parse_expr_with(&arena, "12 * # test!\n 92");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -678,7 +676,7 @@ mod test_parse {
|
|||
Located::new(3, 3, 2, 3, spaced_int2),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
let actual = parse_with(&arena, "3 \n+ \n\n 4");
|
||||
let actual = parse_expr_with(&arena, "3 \n+ \n\n 4");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -702,7 +700,7 @@ mod test_parse {
|
|||
Located::new(0, 0, 3, 4, var2),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
let actual = parse_with(&arena, "x- y");
|
||||
let actual = parse_expr_with(&arena, "x- y");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -716,7 +714,7 @@ mod test_parse {
|
|||
Located::new(0, 0, 4, 5, Num("5")),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
let actual = parse_with(&arena, "-12-5");
|
||||
let actual = parse_expr_with(&arena, "-12-5");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -730,7 +728,7 @@ mod test_parse {
|
|||
Located::new(0, 0, 3, 5, Num("11")),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
let actual = parse_with(&arena, "10*11");
|
||||
let actual = parse_expr_with(&arena, "10*11");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -749,7 +747,7 @@ mod test_parse {
|
|||
Located::new(0, 0, 3, 9, BinOp(inner)),
|
||||
));
|
||||
let expected = BinOp(outer);
|
||||
let actual = parse_with(&arena, "31*42+534");
|
||||
let actual = parse_expr_with(&arena, "31*42+534");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -771,7 +769,7 @@ mod test_parse {
|
|||
Located::new(0, 0, 3, 4, var2),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
let actual = parse_with(&arena, "x==y");
|
||||
let actual = parse_expr_with(&arena, "x==y");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -793,7 +791,7 @@ mod test_parse {
|
|||
Located::new(0, 0, 5, 6, var2),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
let actual = parse_with(&arena, "x == y");
|
||||
let actual = parse_expr_with(&arena, "x == y");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -807,7 +805,7 @@ mod test_parse {
|
|||
module_name: "",
|
||||
ident: "whee",
|
||||
};
|
||||
let actual = parse_with(&arena, "whee");
|
||||
let actual = parse_expr_with(&arena, "whee");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -819,7 +817,7 @@ mod test_parse {
|
|||
module_name: "",
|
||||
ident: "whee",
|
||||
}));
|
||||
let actual = parse_with(&arena, "(whee)");
|
||||
let actual = parse_expr_with(&arena, "(whee)");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -831,7 +829,7 @@ mod test_parse {
|
|||
module_name: "One.Two",
|
||||
ident: "whee",
|
||||
};
|
||||
let actual = parse_with(&arena, "One.Two.whee");
|
||||
let actual = parse_expr_with(&arena, "One.Two.whee");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -842,7 +840,7 @@ mod test_parse {
|
|||
fn basic_global_tag() {
|
||||
let arena = Bump::new();
|
||||
let expected = Expr::GlobalTag("Whee");
|
||||
let actual = parse_with(&arena, "Whee");
|
||||
let actual = parse_expr_with(&arena, "Whee");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -851,7 +849,7 @@ mod test_parse {
|
|||
fn basic_private_tag() {
|
||||
let arena = Bump::new();
|
||||
let expected = Expr::PrivateTag("@Whee");
|
||||
let actual = parse_with(&arena, "@Whee");
|
||||
let actual = parse_expr_with(&arena, "@Whee");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -867,7 +865,7 @@ mod test_parse {
|
|||
args,
|
||||
CalledVia::Space,
|
||||
);
|
||||
let actual = parse_with(&arena, "@Whee 12 34");
|
||||
let actual = parse_expr_with(&arena, "@Whee 12 34");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -883,7 +881,7 @@ mod test_parse {
|
|||
args,
|
||||
CalledVia::Space,
|
||||
);
|
||||
let actual = parse_with(&arena, "Whee 12 34");
|
||||
let actual = parse_expr_with(&arena, "Whee 12 34");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -901,7 +899,7 @@ mod test_parse {
|
|||
args,
|
||||
CalledVia::Space,
|
||||
);
|
||||
let actual = parse_with(&arena, "Whee (12) (34)");
|
||||
let actual = parse_expr_with(&arena, "Whee (12) (34)");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -910,7 +908,7 @@ mod test_parse {
|
|||
fn qualified_global_tag() {
|
||||
let arena = Bump::new();
|
||||
let expected = Expr::MalformedIdent("One.Two.Whee");
|
||||
let actual = parse_with(&arena, "One.Two.Whee");
|
||||
let actual = parse_expr_with(&arena, "One.Two.Whee");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -920,7 +918,7 @@ mod test_parse {
|
|||
// fn qualified_private_tag() {
|
||||
// let arena = Bump::new();
|
||||
// let expected = Expr::MalformedIdent("One.Two.@Whee");
|
||||
// let actual = parse_with(&arena, "One.Two.@Whee");
|
||||
// let actual = parse_expr_with(&arena, "One.Two.@Whee");
|
||||
|
||||
// assert_eq!(Ok(expected), actual);
|
||||
// }
|
||||
|
@ -931,7 +929,7 @@ mod test_parse {
|
|||
let pattern = Located::new(0, 0, 1, 6, Pattern::GlobalTag("Thing"));
|
||||
let patterns = &[pattern];
|
||||
let expected = Closure(patterns, arena.alloc(Located::new(0, 0, 10, 12, Num("42"))));
|
||||
let actual = parse_with(&arena, "\\Thing -> 42");
|
||||
let actual = parse_expr_with(&arena, "\\Thing -> 42");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -940,7 +938,7 @@ mod test_parse {
|
|||
fn private_qualified_tag() {
|
||||
let arena = Bump::new();
|
||||
let expected = Expr::MalformedIdent("@One.Two.Whee");
|
||||
let actual = parse_with(&arena, "@One.Two.Whee");
|
||||
let actual = parse_expr_with(&arena, "@One.Two.Whee");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -952,7 +950,7 @@ mod test_parse {
|
|||
let arena = Bump::new();
|
||||
let elems = &[];
|
||||
let expected = List(elems);
|
||||
let actual = parse_with(&arena, "[]");
|
||||
let actual = parse_expr_with(&arena, "[]");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -963,7 +961,7 @@ mod test_parse {
|
|||
let arena = Bump::new();
|
||||
let elems = &[];
|
||||
let expected = List(elems);
|
||||
let actual = parse_with(&arena, "[ ]");
|
||||
let actual = parse_expr_with(&arena, "[ ]");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -973,7 +971,7 @@ mod test_parse {
|
|||
let arena = Bump::new();
|
||||
let elems = &[&*arena.alloc(Located::new(0, 0, 1, 2, Num("1")))];
|
||||
let expected = List(elems);
|
||||
let actual = parse_with(&arena, "[1]");
|
||||
let actual = parse_expr_with(&arena, "[1]");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -983,7 +981,7 @@ mod test_parse {
|
|||
let arena = Bump::new();
|
||||
let elems = &[&*arena.alloc(Located::new(0, 0, 2, 3, Num("1")))];
|
||||
let expected = List(elems);
|
||||
let actual = parse_with(&arena, "[ 1 ]");
|
||||
let actual = parse_expr_with(&arena, "[ 1 ]");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -998,7 +996,7 @@ mod test_parse {
|
|||
ident: "rec",
|
||||
};
|
||||
let expected = Access(arena.alloc(var), "field");
|
||||
let actual = parse_with(&arena, "rec.field");
|
||||
let actual = parse_expr_with(&arena, "rec.field");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -1011,7 +1009,7 @@ mod test_parse {
|
|||
ident: "rec",
|
||||
}));
|
||||
let expected = Access(arena.alloc(paren_var), "field");
|
||||
let actual = parse_with(&arena, "(rec).field");
|
||||
let actual = parse_expr_with(&arena, "(rec).field");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -1024,7 +1022,7 @@ mod test_parse {
|
|||
ident: "rec",
|
||||
}));
|
||||
let expected = Access(arena.alloc(paren_var), "field");
|
||||
let actual = parse_with(&arena, "(One.Two.rec).field");
|
||||
let actual = parse_expr_with(&arena, "(One.Two.rec).field");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -1040,7 +1038,7 @@ mod test_parse {
|
|||
arena.alloc(Access(arena.alloc(Access(arena.alloc(var), "abc")), "def")),
|
||||
"ghi",
|
||||
);
|
||||
let actual = parse_with(&arena, "rec.abc.def.ghi");
|
||||
let actual = parse_expr_with(&arena, "rec.abc.def.ghi");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -1056,7 +1054,7 @@ mod test_parse {
|
|||
arena.alloc(Access(arena.alloc(Access(arena.alloc(var), "abc")), "def")),
|
||||
"ghi",
|
||||
);
|
||||
let actual = parse_with(&arena, "One.Two.rec.abc.def.ghi");
|
||||
let actual = parse_expr_with(&arena, "One.Two.rec.abc.def.ghi");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -1077,7 +1075,7 @@ mod test_parse {
|
|||
args,
|
||||
CalledVia::Space,
|
||||
);
|
||||
let actual = parse_with(&arena, "whee 1");
|
||||
let actual = parse_expr_with(&arena, "whee 1");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -1102,7 +1100,7 @@ mod test_parse {
|
|||
args,
|
||||
CalledVia::Space,
|
||||
);
|
||||
let actual = parse_with(&arena, "whee 12 34");
|
||||
let actual = parse_expr_with(&arena, "whee 12 34");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -1155,7 +1153,7 @@ mod test_parse {
|
|||
args,
|
||||
CalledVia::Space,
|
||||
);
|
||||
let actual = parse_with(&arena, "a b c d");
|
||||
let actual = parse_expr_with(&arena, "a b c d");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -1174,7 +1172,7 @@ mod test_parse {
|
|||
args,
|
||||
CalledVia::Space,
|
||||
);
|
||||
let actual = parse_with(&arena, "(whee) 1");
|
||||
let actual = parse_expr_with(&arena, "(whee) 1");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -1191,7 +1189,7 @@ mod test_parse {
|
|||
};
|
||||
let loc_arg1_expr = Located::new(0, 0, 1, 4, arg1_expr);
|
||||
let expected = UnaryOp(arena.alloc(loc_arg1_expr), loc_op);
|
||||
let actual = parse_with(&arena, "-foo");
|
||||
let actual = parse_expr_with(&arena, "-foo");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -1206,7 +1204,7 @@ mod test_parse {
|
|||
};
|
||||
let loc_arg1_expr = Located::new(0, 0, 1, 5, arg1_expr);
|
||||
let expected = UnaryOp(arena.alloc(loc_arg1_expr), loc_op);
|
||||
let actual = parse_with(&arena, "!blah");
|
||||
let actual = parse_expr_with(&arena, "!blah");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -1242,7 +1240,7 @@ mod test_parse {
|
|||
CalledVia::Space,
|
||||
);
|
||||
let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 13, apply_expr)), loc_op);
|
||||
let actual = parse_with(&arena, "-whee 12 foo");
|
||||
let actual = parse_expr_with(&arena, "-whee 12 foo");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -1278,7 +1276,7 @@ mod test_parse {
|
|||
CalledVia::Space,
|
||||
);
|
||||
let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 13, apply_expr)), loc_op);
|
||||
let actual = parse_with(&arena, "!whee 12 foo");
|
||||
let actual = parse_expr_with(&arena, "!whee 12 foo");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -1314,7 +1312,7 @@ mod test_parse {
|
|||
CalledVia::Space,
|
||||
)));
|
||||
let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 15, apply_expr)), loc_op);
|
||||
let actual = parse_with(&arena, "-(whee 12 foo)");
|
||||
let actual = parse_expr_with(&arena, "-(whee 12 foo)");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -1350,7 +1348,7 @@ mod test_parse {
|
|||
CalledVia::Space,
|
||||
)));
|
||||
let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 15, apply_expr)), loc_op);
|
||||
let actual = parse_with(&arena, "!(whee 12 foo)");
|
||||
let actual = parse_expr_with(&arena, "!(whee 12 foo)");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -1377,7 +1375,7 @@ mod test_parse {
|
|||
args,
|
||||
CalledVia::Space,
|
||||
);
|
||||
let actual = parse_with(&arena, "whee 12 -foo");
|
||||
let actual = parse_expr_with(&arena, "whee 12 -foo");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -1394,7 +1392,7 @@ mod test_parse {
|
|||
let access = Access(arena.alloc(var), "field");
|
||||
let loc_access = Located::new(0, 0, 1, 11, access);
|
||||
let expected = UnaryOp(arena.alloc(loc_access), loc_op);
|
||||
let actual = parse_with(&arena, "-rec1.field");
|
||||
let actual = parse_expr_with(&arena, "-rec1.field");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -1407,7 +1405,7 @@ mod test_parse {
|
|||
let pattern = Located::new(0, 0, 1, 2, Identifier("a"));
|
||||
let patterns = &[pattern];
|
||||
let expected = Closure(patterns, arena.alloc(Located::new(0, 0, 6, 8, Num("42"))));
|
||||
let actual = parse_with(&arena, "\\a -> 42");
|
||||
let actual = parse_expr_with(&arena, "\\a -> 42");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -1418,7 +1416,7 @@ mod test_parse {
|
|||
let pattern = Located::new(0, 0, 1, 2, Underscore);
|
||||
let patterns = &[pattern];
|
||||
let expected = Closure(patterns, arena.alloc(Located::new(0, 0, 6, 8, Num("42"))));
|
||||
let actual = parse_with(&arena, "\\_ -> 42");
|
||||
let actual = parse_expr_with(&arena, "\\_ -> 42");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -1429,7 +1427,7 @@ mod test_parse {
|
|||
// underscore in an argument name, it would parse as three arguments
|
||||
// (and would ignore the underscore as if it had been blank space).
|
||||
let arena = Bump::new();
|
||||
let actual = parse_with(&arena, "\\the_answer -> 42");
|
||||
let actual = parse_expr_with(&arena, "\\the_answer -> 42");
|
||||
|
||||
assert_eq!(Ok(MalformedClosure), actual);
|
||||
}
|
||||
|
@ -1441,7 +1439,7 @@ mod test_parse {
|
|||
let arg2 = Located::new(0, 0, 4, 5, Identifier("b"));
|
||||
let patterns = &[arg1, arg2];
|
||||
let expected = Closure(patterns, arena.alloc(Located::new(0, 0, 9, 11, Num("42"))));
|
||||
let actual = parse_with(&arena, "\\a, b -> 42");
|
||||
let actual = parse_expr_with(&arena, "\\a, b -> 42");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -1457,7 +1455,7 @@ mod test_parse {
|
|||
arena.alloc(patterns),
|
||||
arena.alloc(Located::new(0, 0, 12, 14, Num("42"))),
|
||||
);
|
||||
let actual = parse_with(&arena, "\\a, b, c -> 42");
|
||||
let actual = parse_expr_with(&arena, "\\a, b, c -> 42");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -1472,7 +1470,7 @@ mod test_parse {
|
|||
arena.alloc(patterns),
|
||||
arena.alloc(Located::new(0, 0, 9, 11, Num("42"))),
|
||||
);
|
||||
let actual = parse_with(&arena, "\\_, _ -> 42");
|
||||
let actual = parse_expr_with(&arena, "\\_, _ -> 42");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
@ -2040,7 +2038,7 @@ mod test_parse {
|
|||
};
|
||||
let loc_cond = Located::new(0, 0, 5, 6, var);
|
||||
let expected = Expr::When(arena.alloc(loc_cond), branches);
|
||||
let actual = parse_with(
|
||||
let actual = parse_expr_with(
|
||||
&arena,
|
||||
indoc!(
|
||||
r#"
|
||||
|
@ -2084,7 +2082,7 @@ mod test_parse {
|
|||
};
|
||||
let loc_cond = Located::new(0, 0, 5, 6, var);
|
||||
let expected = Expr::When(arena.alloc(loc_cond), branches);
|
||||
let actual = parse_with(
|
||||
let actual = parse_expr_with(
|
||||
&arena,
|
||||
indoc!(
|
||||
r#"
|
||||
|
@ -2133,7 +2131,7 @@ mod test_parse {
|
|||
};
|
||||
let loc_cond = Located::new(0, 0, 5, 6, var);
|
||||
let expected = Expr::When(arena.alloc(loc_cond), branches);
|
||||
let actual = parse_with(
|
||||
let actual = parse_expr_with(
|
||||
&arena,
|
||||
indoc!(
|
||||
r#"
|
||||
|
@ -2183,7 +2181,7 @@ mod test_parse {
|
|||
};
|
||||
let loc_cond = Located::new(0, 0, 5, 6, var);
|
||||
let expected = Expr::When(arena.alloc(loc_cond), branches);
|
||||
let actual = parse_with(
|
||||
let actual = parse_expr_with(
|
||||
&arena,
|
||||
indoc!(
|
||||
r#"
|
||||
|
@ -2347,18 +2345,20 @@ mod test_parse {
|
|||
let arena = Bump::new();
|
||||
let newlines = &[Newline, Newline];
|
||||
let def = Def::Body(
|
||||
arena.alloc(Located::new(4, 4, 0, 1, Identifier("x"))),
|
||||
arena.alloc(Located::new(4, 4, 4, 5, Num("5"))),
|
||||
arena.alloc(Located::new(6, 6, 0, 1, Identifier("x"))),
|
||||
arena.alloc(Located::new(6, 6, 4, 5, Num("5"))),
|
||||
);
|
||||
let loc_def = &*arena.alloc(Located::new(4, 4, 0, 1, def));
|
||||
let loc_def = &*arena.alloc(Located::new(6, 6, 0, 1, def));
|
||||
let defs = &[loc_def];
|
||||
let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines);
|
||||
let loc_ret = Located::new(6, 6, 0, 2, ret);
|
||||
let loc_ret = Located::new(8, 8, 0, 2, ret);
|
||||
let reset_indentation = &[
|
||||
DocComment("first line of docs"),
|
||||
DocComment(" second line"),
|
||||
DocComment(" third line"),
|
||||
DocComment("fourth line"),
|
||||
DocComment(""),
|
||||
DocComment("sixth line after doc new line"),
|
||||
];
|
||||
let expected = Expr::SpaceBefore(
|
||||
arena.alloc(Defs(defs, arena.alloc(loc_ret))),
|
||||
|
@ -2372,6 +2372,8 @@ mod test_parse {
|
|||
## second line
|
||||
## third line
|
||||
## fourth line
|
||||
##
|
||||
## sixth line after doc new line
|
||||
x = 5
|
||||
|
||||
42
|
||||
|
@ -2490,7 +2492,7 @@ mod test_parse {
|
|||
};
|
||||
let loc_cond = Located::new(0, 0, 5, 6, var);
|
||||
let expected = Expr::When(arena.alloc(loc_cond), branches);
|
||||
let actual = parse_with(
|
||||
let actual = parse_expr_with(
|
||||
&arena,
|
||||
indoc!(
|
||||
r#"
|
||||
|
@ -2535,7 +2537,7 @@ mod test_parse {
|
|||
};
|
||||
let loc_cond = Located::new(0, 0, 5, 6, var);
|
||||
let expected = Expr::When(arena.alloc(loc_cond), branches);
|
||||
let actual = parse_with(
|
||||
let actual = parse_expr_with(
|
||||
&arena,
|
||||
indoc!(
|
||||
r#"
|
||||
|
|
|
@ -40,6 +40,12 @@ pub enum Problem {
|
|||
field_region: Region,
|
||||
replaced_region: Region,
|
||||
},
|
||||
InvalidOptionalValue {
|
||||
field_name: Lowercase,
|
||||
record_region: Region,
|
||||
field_region: Region,
|
||||
},
|
||||
|
||||
DuplicateTag {
|
||||
tag_name: TagName,
|
||||
tag_union_region: Region,
|
||||
|
@ -101,6 +107,11 @@ pub enum RuntimeError {
|
|||
original_region: Region,
|
||||
shadow: Located<Ident>,
|
||||
},
|
||||
InvalidOptionalValue {
|
||||
field_name: Lowercase,
|
||||
record_region: Region,
|
||||
field_region: Region,
|
||||
},
|
||||
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
|
||||
UnsupportedPattern(Region),
|
||||
// Example: when 1 is 1.X -> 32
|
||||
|
|
|
@ -179,6 +179,25 @@ pub fn can_problem<'b>(
|
|||
alloc.reflow(" definitions from this record."),
|
||||
]),
|
||||
]),
|
||||
Problem::InvalidOptionalValue {
|
||||
field_name,
|
||||
field_region,
|
||||
record_region,
|
||||
} => alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("This record uses an optional value for the "),
|
||||
alloc.record_field(field_name),
|
||||
alloc.reflow(" field in an incorrect context!"),
|
||||
]),
|
||||
alloc.region_all_the_things(
|
||||
record_region,
|
||||
field_region,
|
||||
field_region,
|
||||
Annotation::Error,
|
||||
),
|
||||
alloc.reflow("You can only use optional values in record destructuring, for example in affectation:"),
|
||||
alloc.reflow("{ answer ? 42, otherField } = myRecord").indent(4),
|
||||
]),
|
||||
Problem::DuplicateRecordFieldType {
|
||||
field_name,
|
||||
field_region,
|
||||
|
@ -534,6 +553,25 @@ fn pretty_runtime_error<'b>(
|
|||
tip,
|
||||
])
|
||||
}
|
||||
RuntimeError::InvalidOptionalValue {
|
||||
field_name,
|
||||
field_region,
|
||||
record_region,
|
||||
} => alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("This record uses an optional value for the "),
|
||||
alloc.record_field(field_name),
|
||||
alloc.reflow(" field in an incorrect context!"),
|
||||
]),
|
||||
alloc.region_all_the_things(
|
||||
record_region,
|
||||
field_region,
|
||||
field_region,
|
||||
Annotation::Error,
|
||||
),
|
||||
alloc.reflow("You can only use optional values in record destructuring, for exemple in affectation:"),
|
||||
alloc.reflow("{ answer ? 42, otherField } = myRecord"),
|
||||
]),
|
||||
RuntimeError::InvalidRecordUpdate { region } => alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("This expression cannot be updated"),
|
||||
|
|
|
@ -95,8 +95,13 @@ mod test_reporting {
|
|||
home,
|
||||
ident_ids: &mut ident_ids,
|
||||
};
|
||||
let _mono_expr =
|
||||
Stmt::new(&mut mono_env, loc_expr.value, &mut procs, &mut layout_cache);
|
||||
let _mono_expr = Stmt::new(
|
||||
&mut mono_env,
|
||||
loc_expr.value,
|
||||
var,
|
||||
&mut procs,
|
||||
&mut layout_cache,
|
||||
);
|
||||
}
|
||||
|
||||
Ok((unify_problems, can_problems, mono_problems, home, interns))
|
||||
|
@ -3806,6 +3811,32 @@ mod test_reporting {
|
|||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn incorrect_optional_field() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
{ x: 5, y ? 42 }
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||||
|
||||
This record uses an optional value for the `.y` field in an incorrect
|
||||
context!
|
||||
|
||||
1│ { x: 5, y ? 42 }
|
||||
^^^^^^
|
||||
|
||||
You can only use optional values in record destructuring, for example
|
||||
in affectation:
|
||||
|
||||
{ answer ? 42, otherField } = myRecord
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
#[test]
|
||||
fn first_wildcard_is_required() {
|
||||
report_problem_as(
|
||||
|
|
|
@ -813,6 +813,45 @@ fn type_to_variable(
|
|||
|
||||
result
|
||||
}
|
||||
HostExposedAlias {
|
||||
name: symbol,
|
||||
arguments: args,
|
||||
actual: alias_type,
|
||||
actual_var,
|
||||
..
|
||||
} => {
|
||||
let mut arg_vars = Vec::with_capacity(args.len());
|
||||
let mut new_aliases = ImMap::default();
|
||||
|
||||
for (arg, arg_type) in args {
|
||||
let arg_var = type_to_variable(subs, rank, pools, cached, arg_type);
|
||||
|
||||
arg_vars.push((arg.clone(), arg_var));
|
||||
new_aliases.insert(arg.clone(), arg_var);
|
||||
}
|
||||
|
||||
let alias_var = type_to_variable(subs, rank, pools, cached, alias_type);
|
||||
|
||||
// unify the actual_var with the result var
|
||||
// this can be used to access the type of the actual_var
|
||||
// to determine its layout later
|
||||
// subs.set_content(*actual_var, descriptor.content);
|
||||
|
||||
//subs.set(*actual_var, descriptor.clone());
|
||||
let content = Content::Alias(*symbol, arg_vars, alias_var);
|
||||
|
||||
let result = register(subs, rank, pools, content);
|
||||
|
||||
// We only want to unify the actual_var with the alias once
|
||||
// if it's already redirected (and therefore, redundant)
|
||||
// don't do it again
|
||||
if !subs.redundant(*actual_var) {
|
||||
let descriptor = subs.get(result);
|
||||
subs.union(result, *actual_var, descriptor);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
Erroneous(problem) => {
|
||||
let content = Content::Structure(FlatType::Erroneous(problem.clone()));
|
||||
|
||||
|
|
|
@ -75,13 +75,16 @@ mod solve_expr {
|
|||
let LoadedModule {
|
||||
module_id: home,
|
||||
mut can_problems,
|
||||
type_problems,
|
||||
mut type_problems,
|
||||
interns,
|
||||
mut solved,
|
||||
exposed_to_host,
|
||||
..
|
||||
} = loaded;
|
||||
|
||||
let mut can_problems = can_problems.remove(&home).unwrap_or_default();
|
||||
let type_problems = type_problems.remove(&home).unwrap_or_default();
|
||||
|
||||
let mut subs = solved.inner_mut();
|
||||
|
||||
// assert!(can_problems.is_empty());
|
||||
|
|
|
@ -53,6 +53,13 @@ pub enum SolvedType {
|
|||
/// A type alias
|
||||
Alias(Symbol, Vec<(Lowercase, SolvedType)>, Box<SolvedType>),
|
||||
|
||||
HostExposedAlias {
|
||||
name: Symbol,
|
||||
arguments: Vec<(Lowercase, SolvedType)>,
|
||||
actual_var: VarId,
|
||||
actual: Box<SolvedType>,
|
||||
},
|
||||
|
||||
/// a boolean algebra Bool
|
||||
Boolean(SolvedBool),
|
||||
|
||||
|
@ -194,6 +201,26 @@ impl SolvedType {
|
|||
|
||||
SolvedType::Alias(*symbol, solved_args, Box::new(solved_type))
|
||||
}
|
||||
HostExposedAlias {
|
||||
name,
|
||||
arguments,
|
||||
actual_var,
|
||||
actual,
|
||||
} => {
|
||||
let solved_type = Self::from_type(solved_subs, actual);
|
||||
let mut solved_args = Vec::with_capacity(arguments.len());
|
||||
|
||||
for (name, var) in arguments {
|
||||
solved_args.push((name.clone(), Self::from_type(solved_subs, var)));
|
||||
}
|
||||
|
||||
SolvedType::HostExposedAlias {
|
||||
name: *name,
|
||||
arguments: solved_args,
|
||||
actual_var: VarId::from_var(*actual_var, solved_subs.inner()),
|
||||
actual: Box::new(solved_type),
|
||||
}
|
||||
}
|
||||
Boolean(val) => SolvedType::Boolean(SolvedBool::from_bool(&val, solved_subs.inner())),
|
||||
Variable(var) => Self::from_var(solved_subs.inner(), *var),
|
||||
}
|
||||
|
@ -486,6 +513,27 @@ pub fn to_type(
|
|||
|
||||
Type::Alias(*symbol, type_variables, Box::new(actual))
|
||||
}
|
||||
HostExposedAlias {
|
||||
name,
|
||||
arguments: solved_type_variables,
|
||||
actual_var,
|
||||
actual: solved_actual,
|
||||
} => {
|
||||
let mut type_variables = Vec::with_capacity(solved_type_variables.len());
|
||||
|
||||
for (lowercase, solved_arg) in solved_type_variables {
|
||||
type_variables.push((lowercase.clone(), to_type(solved_arg, free_vars, var_store)));
|
||||
}
|
||||
|
||||
let actual = to_type(solved_actual, free_vars, var_store);
|
||||
|
||||
Type::HostExposedAlias {
|
||||
name: *name,
|
||||
arguments: type_variables,
|
||||
actual_var: var_id_to_flex_var(*actual_var, free_vars, var_store),
|
||||
actual: Box::new(actual),
|
||||
}
|
||||
}
|
||||
Error => Type::Erroneous(Problem::SolvedTypeError),
|
||||
Erroneous(problem) => Type::Erroneous(problem.clone()),
|
||||
}
|
||||
|
|
|
@ -143,6 +143,12 @@ pub enum Type {
|
|||
Record(SendMap<Lowercase, RecordField<Type>>, Box<Type>),
|
||||
TagUnion(Vec<(TagName, Vec<Type>)>, Box<Type>),
|
||||
Alias(Symbol, Vec<(Lowercase, Type)>, Box<Type>),
|
||||
HostExposedAlias {
|
||||
name: Symbol,
|
||||
arguments: Vec<(Lowercase, Type)>,
|
||||
actual_var: Variable,
|
||||
actual: Box<Type>,
|
||||
},
|
||||
RecursiveTagUnion(Variable, Vec<(TagName, Vec<Type>)>, Box<Type>),
|
||||
/// Applying a type to some arguments (e.g. Map.Map String Int)
|
||||
Apply(Symbol, Vec<Type>),
|
||||
|
@ -206,6 +212,20 @@ impl fmt::Debug for Type {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
Type::HostExposedAlias {
|
||||
name, arguments, ..
|
||||
} => {
|
||||
write!(f, "HostExposedAlias {:?}", name)?;
|
||||
|
||||
for (_, arg) in arguments {
|
||||
write!(f, " {:?}", arg)?;
|
||||
}
|
||||
|
||||
// Sometimes it's useful to see the expansion of the alias
|
||||
// write!(f, "[ but actually {:?} ]", _actual)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Type::Record(fields, ext) => {
|
||||
write!(f, "{{")?;
|
||||
|
||||
|
@ -405,6 +425,16 @@ impl Type {
|
|||
}
|
||||
actual_type.substitute(substitutions);
|
||||
}
|
||||
HostExposedAlias {
|
||||
arguments,
|
||||
actual: actual_type,
|
||||
..
|
||||
} => {
|
||||
for (_, value) in arguments.iter_mut() {
|
||||
value.substitute(substitutions);
|
||||
}
|
||||
actual_type.substitute(substitutions);
|
||||
}
|
||||
Apply(_, args) => {
|
||||
for arg in args {
|
||||
arg.substitute(substitutions);
|
||||
|
@ -453,6 +483,12 @@ impl Type {
|
|||
Alias(_, _, actual_type) => {
|
||||
actual_type.substitute_alias(rep_symbol, actual);
|
||||
}
|
||||
HostExposedAlias {
|
||||
actual: actual_type,
|
||||
..
|
||||
} => {
|
||||
actual_type.substitute_alias(rep_symbol, actual);
|
||||
}
|
||||
Apply(symbol, _) if *symbol == rep_symbol => {
|
||||
*self = actual.clone();
|
||||
|
||||
|
@ -496,6 +532,9 @@ impl Type {
|
|||
Alias(alias_symbol, _, actual_type) => {
|
||||
alias_symbol == &rep_symbol || actual_type.contains_symbol(rep_symbol)
|
||||
}
|
||||
HostExposedAlias { name, actual, .. } => {
|
||||
name == &rep_symbol || actual.contains_symbol(rep_symbol)
|
||||
}
|
||||
Apply(symbol, _) if *symbol == rep_symbol => true,
|
||||
Apply(_, args) => args.iter().any(|arg| arg.contains_symbol(rep_symbol)),
|
||||
EmptyRec | EmptyTagUnion | Erroneous(_) | Variable(_) | Boolean(_) => false,
|
||||
|
@ -528,6 +567,7 @@ impl Type {
|
|||
.any(|arg| arg.contains_variable(rep_variable))
|
||||
}
|
||||
Alias(_, _, actual_type) => actual_type.contains_variable(rep_variable),
|
||||
HostExposedAlias { actual, .. } => actual.contains_variable(rep_variable),
|
||||
Apply(_, args) => args.iter().any(|arg| arg.contains_variable(rep_variable)),
|
||||
EmptyRec | EmptyTagUnion | Erroneous(_) | Boolean(_) => false,
|
||||
}
|
||||
|
@ -579,7 +619,12 @@ impl Type {
|
|||
}
|
||||
ext.instantiate_aliases(region, aliases, var_store, introduced);
|
||||
}
|
||||
Alias(_, type_args, actual_type) => {
|
||||
HostExposedAlias {
|
||||
arguments: type_args,
|
||||
actual: actual_type,
|
||||
..
|
||||
}
|
||||
| Alias(_, type_args, actual_type) => {
|
||||
for arg in type_args {
|
||||
arg.1
|
||||
.instantiate_aliases(region, aliases, var_store, introduced);
|
||||
|
@ -761,6 +806,10 @@ fn symbols_help(tipe: &Type, accum: &mut ImSet<Symbol>) {
|
|||
accum.insert(*alias_symbol);
|
||||
symbols_help(&actual_type, accum);
|
||||
}
|
||||
HostExposedAlias { name, actual, .. } => {
|
||||
accum.insert(*name);
|
||||
symbols_help(&actual, accum);
|
||||
}
|
||||
Apply(symbol, args) => {
|
||||
accum.insert(*symbol);
|
||||
args.iter().for_each(|arg| symbols_help(arg, accum));
|
||||
|
@ -830,6 +879,14 @@ fn variables_help(tipe: &Type, accum: &mut ImSet<Variable>) {
|
|||
}
|
||||
variables_help(actual, accum);
|
||||
}
|
||||
HostExposedAlias {
|
||||
arguments, actual, ..
|
||||
} => {
|
||||
for (_, arg) in arguments {
|
||||
variables_help(arg, accum);
|
||||
}
|
||||
variables_help(actual, accum);
|
||||
}
|
||||
Apply(_, args) => {
|
||||
for x in args {
|
||||
variables_help(x, accum);
|
||||
|
|
|
@ -17,3 +17,7 @@ roc_load = { path = "../compiler/load" }
|
|||
roc_builtins = { path = "../compiler/builtins" }
|
||||
roc_collections = { path = "../compiler/collections" }
|
||||
bumpalo = { version = "3.2", features = ["collections"] }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1"
|
||||
maplit = "1.0.1"
|
||||
|
|
284
docs/src/main.rs
284
docs/src/main.rs
|
@ -5,7 +5,10 @@ extern crate serde;
|
|||
extern crate serde_derive;
|
||||
extern crate pulldown_cmark;
|
||||
extern crate serde_json;
|
||||
use std::error::Error;
|
||||
use roc_builtins::std::StdLib;
|
||||
use roc_load::docs::Documentation;
|
||||
use roc_load::docs::ModuleDocumentation;
|
||||
|
||||
use std::fs;
|
||||
extern crate roc_load;
|
||||
use bumpalo::Bump;
|
||||
|
@ -22,7 +25,7 @@ pub struct Template {
|
|||
module_links: Vec<TemplateLink>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
#[derive(Serialize, Clone, Debug, PartialEq)]
|
||||
pub struct ModuleEntry {
|
||||
name: String,
|
||||
docs: String,
|
||||
|
@ -41,13 +44,9 @@ pub struct TemplateLinkEntry {
|
|||
name: String,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let std_lib = roc_builtins::std::standard_stdlib();
|
||||
let subs_by_module = MutMap::default();
|
||||
let arena = Bump::new();
|
||||
|
||||
let src_dir = Path::new("../compiler/builtins/docs");
|
||||
let files = vec![
|
||||
fn main() {
|
||||
generate(
|
||||
vec![
|
||||
PathBuf::from(r"../compiler/builtins/docs/Bool.roc"),
|
||||
PathBuf::from(r"../compiler/builtins/docs/Map.roc"),
|
||||
// Not working
|
||||
|
@ -56,76 +55,109 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
// PathBuf::from(r"../compiler/builtins/docs/Num.roc"),
|
||||
PathBuf::from(r"../compiler/builtins/docs/Set.roc"),
|
||||
PathBuf::from(r"../compiler/builtins/docs/Str.roc"),
|
||||
];
|
||||
],
|
||||
roc_builtins::std::standard_stdlib(),
|
||||
Path::new("../compiler/builtins/docs"),
|
||||
Path::new("./build"),
|
||||
)
|
||||
}
|
||||
|
||||
let mut modules_docs = vec![];
|
||||
pub fn generate(filenames: Vec<PathBuf>, std_lib: StdLib, src_dir: &Path, build_dir: &Path) {
|
||||
let files_docs = files_to_documentations(filenames, std_lib, src_dir);
|
||||
|
||||
// Load each file is files vector
|
||||
for filename in files {
|
||||
// TODO: get info from a file like "elm.json"
|
||||
let package = roc_load::docs::Documentation {
|
||||
name: "roc/builtins".to_string(),
|
||||
version: "1.0.0".to_string(),
|
||||
docs: "Package introduction or README.".to_string(),
|
||||
modules: files_docs,
|
||||
};
|
||||
|
||||
// Remove old build folder, if exists
|
||||
let _ = fs::remove_dir_all(build_dir);
|
||||
|
||||
let version_folder = build_dir
|
||||
.join(package.name.clone())
|
||||
.join(package.version.clone());
|
||||
|
||||
// Make sure the output directories exists
|
||||
fs::create_dir_all(&version_folder)
|
||||
.expect("TODO gracefully handle creating directories failing");
|
||||
|
||||
// Register handlebars template
|
||||
let mut handlebars = handlebars::Handlebars::new();
|
||||
handlebars
|
||||
.register_template_file("page", "./src/templates/page.hbs")
|
||||
.expect("TODO gracefully handle registering template failing");
|
||||
|
||||
// Write each package's module docs html file
|
||||
for module in &package.modules {
|
||||
let template = documentation_to_template_data(&package, module);
|
||||
|
||||
let handlebars_data = handlebars::to_json(&template);
|
||||
let filepath = version_folder.join(format!("{}.html", module.name));
|
||||
let mut output_file =
|
||||
fs::File::create(filepath).expect("TODO gracefully handle creating file failing");
|
||||
handlebars
|
||||
.render_to_write("page", &handlebars_data, &mut output_file)
|
||||
.expect("TODO gracefully handle writing file failing");
|
||||
}
|
||||
|
||||
// Copy /static folder content to /build
|
||||
let copy_options = fs_extra::dir::CopyOptions {
|
||||
overwrite: true,
|
||||
skip_exist: false,
|
||||
buffer_size: 64000, //64kb
|
||||
copy_inside: false,
|
||||
content_only: true,
|
||||
depth: 0,
|
||||
};
|
||||
fs_extra::dir::copy("./src/static/", &build_dir, ©_options)
|
||||
.expect("TODO gracefully handle copying static content failing");
|
||||
println!("Docs generated at {}", build_dir.display());
|
||||
}
|
||||
|
||||
fn files_to_documentations(
|
||||
filenames: Vec<PathBuf>,
|
||||
std_lib: StdLib,
|
||||
src_dir: &Path,
|
||||
) -> Vec<ModuleDocumentation> {
|
||||
let arena = Bump::new();
|
||||
let mut files_docs = vec![];
|
||||
|
||||
for filename in filenames {
|
||||
let mut loaded = roc_load::file::load_and_typecheck(
|
||||
&arena,
|
||||
filename,
|
||||
std_lib.clone(),
|
||||
src_dir,
|
||||
subs_by_module.clone(),
|
||||
MutMap::default(),
|
||||
)
|
||||
.expect("TODO gracefully handle load failing");
|
||||
modules_docs.extend(loaded.documentation.drain().map(|x| x.1));
|
||||
files_docs.extend(loaded.documentation.drain().map(|x| x.1));
|
||||
}
|
||||
files_docs
|
||||
}
|
||||
|
||||
let package = roc_load::docs::Documentation {
|
||||
name: "roc/builtins".to_string(),
|
||||
version: "1.0.0".to_string(),
|
||||
docs: "Package introduction or README.".to_string(),
|
||||
modules: modules_docs,
|
||||
};
|
||||
|
||||
// Remove old build folder
|
||||
fs::remove_dir_all("./build")?;
|
||||
|
||||
// Make sure the output directories exists
|
||||
fs::create_dir_all(format!("./build/{}/{}", package.name, package.version))?;
|
||||
|
||||
// Register handlebars template
|
||||
let mut handlebars = handlebars::Handlebars::new();
|
||||
assert!(handlebars
|
||||
.register_template_file("page", "./src/templates/page.hbs")
|
||||
.is_ok());
|
||||
|
||||
let markdown_options = pulldown_cmark::Options::all();
|
||||
|
||||
// Write each package's module docs
|
||||
for module in &package.modules {
|
||||
// Convert module docs from markdown to html
|
||||
let docs_parser = pulldown_cmark::Parser::new_ext(&module.docs, markdown_options);
|
||||
let mut docs_html: String = String::with_capacity(module.docs.len() * 3 / 2);
|
||||
pulldown_cmark::html::push_html(&mut docs_html, docs_parser);
|
||||
|
||||
let template = Template {
|
||||
package_name: package.name.clone(),
|
||||
package_version: package.version.clone(),
|
||||
fn documentation_to_template_data(doc: &Documentation, module: &ModuleDocumentation) -> Template {
|
||||
Template {
|
||||
package_name: doc.name.clone(),
|
||||
package_version: doc.version.clone(),
|
||||
module_name: module.name.clone(),
|
||||
module_docs: docs_html,
|
||||
module_docs: markdown_to_html(module.docs.clone()),
|
||||
module_entries: module
|
||||
.entries
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|entry| {
|
||||
// Convert entry docs from markdown to html
|
||||
let mut entry_docs_html: String = String::new();
|
||||
if let Some(docs) = entry.docs {
|
||||
let entry_docs_parser =
|
||||
pulldown_cmark::Parser::new_ext(&docs, markdown_options);
|
||||
pulldown_cmark::html::push_html(&mut entry_docs_html, entry_docs_parser);
|
||||
}
|
||||
|
||||
ModuleEntry {
|
||||
.map(|entry| ModuleEntry {
|
||||
name: entry.name.clone(),
|
||||
docs: entry_docs_html,
|
||||
}
|
||||
docs: match entry.docs {
|
||||
Some(docs) => markdown_to_html(docs),
|
||||
None => String::new(),
|
||||
},
|
||||
})
|
||||
.collect(),
|
||||
module_links: package
|
||||
module_links: doc
|
||||
.modules
|
||||
.clone()
|
||||
.into_iter()
|
||||
|
@ -140,26 +172,118 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
.collect(),
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
|
||||
let handlebars_data = handlebars::to_json(&template);
|
||||
let mut output_file = fs::File::create(format!(
|
||||
"./build/{}/{}/{}.html",
|
||||
package.name, package.version, module.name
|
||||
))?;
|
||||
handlebars.render_to_write("page", &handlebars_data, &mut output_file)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy /static folder content to /build
|
||||
let copy_options = fs_extra::dir::CopyOptions {
|
||||
overwrite: true,
|
||||
skip_exist: false,
|
||||
buffer_size: 64000, //64kb
|
||||
copy_inside: false,
|
||||
content_only: true,
|
||||
depth: 0,
|
||||
};
|
||||
fs_extra::dir::copy("./src/static/", "./build", ©_options)?;
|
||||
println!("Docs generated at /build");
|
||||
Ok(())
|
||||
fn markdown_to_html(markdown: String) -> String {
|
||||
use pulldown_cmark::CodeBlockKind::*;
|
||||
use pulldown_cmark::CowStr::*;
|
||||
use pulldown_cmark::Event::*;
|
||||
use pulldown_cmark::Tag::*;
|
||||
|
||||
let markdown_options = pulldown_cmark::Options::all();
|
||||
let mut docs_parser = vec![];
|
||||
let (_, _) = pulldown_cmark::Parser::new_ext(&markdown, markdown_options).fold(
|
||||
(0, 0),
|
||||
|(start_quote_count, end_quote_count), event| match event {
|
||||
// Replace this sequence (`>>>` syntax):
|
||||
// Start(BlockQuote)
|
||||
// Start(BlockQuote)
|
||||
// Start(BlockQuote)
|
||||
// Start(Paragraph)
|
||||
// For `Start(CodeBlock(Fenced(Borrowed("roc"))))`
|
||||
Start(BlockQuote) => {
|
||||
docs_parser.push(event);
|
||||
(start_quote_count + 1, 0)
|
||||
}
|
||||
Start(Paragraph) => {
|
||||
if start_quote_count == 3 {
|
||||
docs_parser.pop();
|
||||
docs_parser.pop();
|
||||
docs_parser.pop();
|
||||
docs_parser.push(Start(CodeBlock(Fenced(Borrowed("roc")))));
|
||||
} else {
|
||||
docs_parser.push(event);
|
||||
}
|
||||
(0, 0)
|
||||
}
|
||||
// Replace this sequence (`>>>` syntax):
|
||||
// End(Paragraph)
|
||||
// End(BlockQuote)
|
||||
// End(BlockQuote)
|
||||
// End(BlockQuote)
|
||||
// For `End(CodeBlock(Fenced(Borrowed("roc"))))`
|
||||
End(Paragraph) => {
|
||||
docs_parser.push(event);
|
||||
(0, 1)
|
||||
}
|
||||
End(BlockQuote) => {
|
||||
if end_quote_count == 3 {
|
||||
docs_parser.pop();
|
||||
docs_parser.pop();
|
||||
docs_parser.pop();
|
||||
docs_parser.push(End(CodeBlock(Fenced(Borrowed("roc")))));
|
||||
(0, 0)
|
||||
} else {
|
||||
docs_parser.push(event);
|
||||
(0, end_quote_count + 1)
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
docs_parser.push(event);
|
||||
(0, 0)
|
||||
}
|
||||
},
|
||||
);
|
||||
let mut docs_html = String::new();
|
||||
pulldown_cmark::html::push_html(&mut docs_html, docs_parser.into_iter());
|
||||
docs_html
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_docs {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn internal() {
|
||||
let files_docs = files_to_documentations(
|
||||
vec![PathBuf::from(r"tests/fixtures/Interface.roc")],
|
||||
roc_builtins::std::standard_stdlib(),
|
||||
Path::new("tests/fixtures"),
|
||||
);
|
||||
|
||||
let package = roc_load::docs::Documentation {
|
||||
name: "roc/builtins".to_string(),
|
||||
version: "1.0.0".to_string(),
|
||||
docs: "Package introduction or README.".to_string(),
|
||||
modules: files_docs,
|
||||
};
|
||||
|
||||
let expected_entries = vec![
|
||||
ModuleEntry {
|
||||
name: "singleline".to_string(),
|
||||
docs: "<p>Single line documentation.</p>\n".to_string(),
|
||||
},
|
||||
ModuleEntry {
|
||||
name: "multiline".to_string(),
|
||||
docs: "<p>Multiline documentation.\nWithout any complex syntax yet!</p>\n".to_string(),
|
||||
}, ModuleEntry {
|
||||
name: "multiparagraph".to_string(),
|
||||
docs: "<p>Multiparagraph documentation.</p>\n<p>Without any complex syntax yet!</p>\n".to_string(),
|
||||
}, ModuleEntry {
|
||||
name: "codeblock".to_string(),
|
||||
docs: "<p>Turns >>> into code block for now.</p>\n<pre><code class=\"language-roc\">codeblock</code></pre>\n".to_string(),
|
||||
},
|
||||
];
|
||||
|
||||
for module in &package.modules {
|
||||
let template = documentation_to_template_data(&package, module);
|
||||
assert_eq!(template.module_name, "Test");
|
||||
template
|
||||
.module_entries
|
||||
.iter()
|
||||
.zip(expected_entries.iter())
|
||||
.for_each(|(x, y)| assert_eq!(x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
23
docs/tests/fixtures/Interface.roc
vendored
Normal file
23
docs/tests/fixtures/Interface.roc
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
interface Test
|
||||
exposes [ singleline, multiline, multiparagraph, codeblock ]
|
||||
imports []
|
||||
|
||||
## Single line documentation.
|
||||
singleline : Bool -> Bool
|
||||
|
||||
## Multiline documentation.
|
||||
## Without any complex syntax yet!
|
||||
multiline : Bool -> Bool
|
||||
|
||||
## Multiparagraph documentation.
|
||||
##
|
||||
## Without any complex syntax yet!
|
||||
multiparagraph : Bool -> Bool
|
||||
|
||||
## No documentation for not exposed function.
|
||||
notExposed : Bool -> Bool
|
||||
|
||||
## Turns >>> into code block for now.
|
||||
##
|
||||
## >>> codeblock
|
||||
codeblock : Bool -> Bool
|
1
editor/.gitignore
vendored
Normal file
1
editor/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
*.spv
|
1
examples/.gitignore
vendored
1
examples/.gitignore
vendored
|
@ -1,4 +1,5 @@
|
|||
app
|
||||
host.o
|
||||
c_host.o
|
||||
roc_app.o
|
||||
app.dSYM
|
||||
|
|
9
examples/closure/Closure.roc
Normal file
9
examples/closure/Closure.roc
Normal file
|
@ -0,0 +1,9 @@
|
|||
app Closure provides [ makeClosure ] imports []
|
||||
|
||||
makeClosure : ({} -> Int) as MyClosure
|
||||
makeClosure =
|
||||
x = 42
|
||||
y = 42
|
||||
|
||||
\{} -> x + y
|
||||
|
23
examples/closure/platform/Cargo.lock
generated
Normal file
23
examples/closure/platform/Cargo.lock
generated
Normal file
|
@ -0,0 +1,23 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "host"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"roc_std 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "roc_std"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[metadata]
|
||||
"checksum libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)" = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743"
|
13
examples/closure/platform/Cargo.toml
Normal file
13
examples/closure/platform/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "host"
|
||||
version = "0.1.0"
|
||||
authors = ["Richard Feldman <oss@rtfeldman.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib"]
|
||||
|
||||
[dependencies]
|
||||
roc_std = { path = "../../../roc_std" }
|
||||
|
||||
[workspace]
|
7
examples/closure/platform/host.c
Normal file
7
examples/closure/platform/host.c
Normal file
|
@ -0,0 +1,7 @@
|
|||
#include <stdio.h>
|
||||
|
||||
extern int rust_main();
|
||||
|
||||
int main() {
|
||||
return rust_main();
|
||||
}
|
89
examples/closure/platform/src/lib.rs
Normal file
89
examples/closure/platform/src/lib.rs
Normal file
|
@ -0,0 +1,89 @@
|
|||
use roc_std::alloca;
|
||||
use roc_std::RocCallResult;
|
||||
use std::alloc::Layout;
|
||||
use std::time::SystemTime;
|
||||
|
||||
extern "C" {
|
||||
#[link_name = "makeClosure_1_exposed"]
|
||||
fn make_closure(output: *mut u8) -> ();
|
||||
|
||||
#[link_name = "makeClosure_1_size"]
|
||||
fn closure_size() -> i64;
|
||||
|
||||
#[link_name = "makeClosure_1_MyClosure_caller"]
|
||||
fn call_MyClosure(function_pointer: *const u8, closure_data: *const u8, output: *mut u8) -> ();
|
||||
|
||||
#[link_name = "makeClosure_1_MyClosure_size"]
|
||||
fn size_MyClosure() -> i64;
|
||||
}
|
||||
|
||||
unsafe fn call_the_closure(function_pointer: *const u8, closure_data_ptr: *const u8) -> i64 {
|
||||
let size = size_MyClosure() as usize;
|
||||
|
||||
alloca::with_stack_bytes(size, |buffer| {
|
||||
let buffer: *mut std::ffi::c_void = buffer;
|
||||
let buffer: *mut u8 = buffer as *mut u8;
|
||||
|
||||
call_MyClosure(
|
||||
function_pointer,
|
||||
closure_data_ptr as *const u8,
|
||||
buffer as *mut u8,
|
||||
);
|
||||
|
||||
let output = &*(buffer as *mut RocCallResult<i64>);
|
||||
|
||||
match output.into() {
|
||||
Ok(v) => v,
|
||||
Err(e) => panic!("failed with {}", e),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn rust_main() -> isize {
|
||||
println!("Running Roc closure");
|
||||
let start_time = SystemTime::now();
|
||||
|
||||
let size = unsafe { closure_size() } as usize;
|
||||
let layout = Layout::array::<u8>(size).unwrap();
|
||||
let answer = unsafe {
|
||||
let buffer = std::alloc::alloc(layout);
|
||||
|
||||
make_closure(buffer);
|
||||
|
||||
let output = &*(buffer as *mut RocCallResult<()>);
|
||||
|
||||
match output.into() {
|
||||
Ok(()) => {
|
||||
let function_pointer = {
|
||||
// this is a pointer to the location where the function pointer is stored
|
||||
// we pass just the function pointer
|
||||
let temp = buffer.offset(8) as *const i64;
|
||||
|
||||
(*temp) as *const u8
|
||||
};
|
||||
|
||||
let closure_data_ptr = buffer.offset(16);
|
||||
|
||||
call_the_closure(function_pointer as *const u8, closure_data_ptr as *const u8)
|
||||
}
|
||||
Err(msg) => {
|
||||
std::alloc::dealloc(buffer, layout);
|
||||
|
||||
panic!("Roc failed with message: {}", msg);
|
||||
}
|
||||
}
|
||||
};
|
||||
let end_time = SystemTime::now();
|
||||
let duration = end_time.duration_since(start_time).unwrap();
|
||||
|
||||
println!(
|
||||
"Roc closure took {:.4} ms to compute this answer: {:?}",
|
||||
duration.as_secs_f64() * 1000.0,
|
||||
// truncate the answer, so stdout is not swamped
|
||||
answer
|
||||
);
|
||||
|
||||
// Exit code
|
||||
0
|
||||
}
|
|
@ -1,17 +1,27 @@
|
|||
use roc_std::RocCallResult;
|
||||
use roc_std::RocStr;
|
||||
use std::str;
|
||||
|
||||
extern "C" {
|
||||
#[link_name = "main_1"]
|
||||
fn main() -> RocStr;
|
||||
#[link_name = "main_1_exposed"]
|
||||
fn say_hello(output: &mut RocCallResult<RocStr>) -> ();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn rust_main() -> isize {
|
||||
println!(
|
||||
"Roc says: {}",
|
||||
str::from_utf8(unsafe { main().as_slice() }).unwrap()
|
||||
);
|
||||
let answer = unsafe {
|
||||
use std::mem::MaybeUninit;
|
||||
let mut output: MaybeUninit<RocCallResult<RocStr>> = MaybeUninit::uninit();
|
||||
|
||||
say_hello(&mut *output.as_mut_ptr());
|
||||
|
||||
match output.assume_init().into() {
|
||||
Ok(value) => value,
|
||||
Err(msg) => panic!("roc failed with message {}", msg),
|
||||
}
|
||||
};
|
||||
|
||||
println!("Roc says: {}", str::from_utf8(answer.as_slice()).unwrap());
|
||||
|
||||
// Exit code
|
||||
0
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use roc_std::RocCallResult;
|
||||
use roc_std::RocList;
|
||||
use std::time::SystemTime;
|
||||
|
||||
extern "C" {
|
||||
#[link_name = "quicksort_1"]
|
||||
fn quicksort(list: RocList<i64>) -> RocList<i64>;
|
||||
#[link_name = "quicksort_1_exposed"]
|
||||
fn quicksort(list: RocList<i64>, output: &mut RocCallResult<RocList<i64>>) -> ();
|
||||
}
|
||||
|
||||
const NUM_NUMS: usize = 100;
|
||||
|
@ -24,7 +25,17 @@ pub fn rust_main() -> isize {
|
|||
|
||||
println!("Running Roc quicksort on {} numbers...", nums.len());
|
||||
let start_time = SystemTime::now();
|
||||
let answer = unsafe { quicksort(nums) };
|
||||
let answer = unsafe {
|
||||
use std::mem::MaybeUninit;
|
||||
let mut output = MaybeUninit::uninit();
|
||||
|
||||
quicksort(nums, &mut *output.as_mut_ptr());
|
||||
|
||||
match output.assume_init().into() {
|
||||
Ok(value) => value,
|
||||
Err(msg) => panic!("roc failed with message: {}", msg),
|
||||
}
|
||||
};
|
||||
let end_time = SystemTime::now();
|
||||
let duration = end_time.duration_since(start_time).unwrap();
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use roc_std::RocCallResult;
|
||||
use roc_std::RocList;
|
||||
use std::time::SystemTime;
|
||||
|
||||
extern "C" {
|
||||
#[link_name = "quicksort_1"]
|
||||
fn quicksort(list: RocList<i64>) -> RocList<i64>;
|
||||
#[link_name = "quicksort_1_exposed"]
|
||||
fn quicksort(list: RocList<i64>, output: &mut RocCallResult<RocList<i64>>) -> ();
|
||||
}
|
||||
|
||||
const NUM_NUMS: usize = 100;
|
||||
|
@ -24,7 +25,18 @@ pub fn rust_main() -> isize {
|
|||
|
||||
println!("Running Roc quicksort on {} numbers...", nums.len());
|
||||
let start_time = SystemTime::now();
|
||||
let answer = unsafe { quicksort(nums) };
|
||||
let answer = unsafe {
|
||||
use std::mem::MaybeUninit;
|
||||
let mut output = MaybeUninit::uninit();
|
||||
|
||||
quicksort(nums, &mut *output.as_mut_ptr());
|
||||
|
||||
match output.assume_init().into() {
|
||||
Ok(value) => value,
|
||||
Err(msg) => panic!("roc failed with message {}", msg),
|
||||
}
|
||||
};
|
||||
|
||||
let end_time = SystemTime::now();
|
||||
let duration = end_time.duration_since(start_time).unwrap();
|
||||
|
||||
|
|
23
examples/shared-quicksort/platform/Cargo.lock
generated
Normal file
23
examples/shared-quicksort/platform/Cargo.lock
generated
Normal file
|
@ -0,0 +1,23 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "host"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"roc_std 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "roc_std"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[metadata]
|
||||
"checksum libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)" = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743"
|
13
examples/shared-quicksort/platform/Cargo.toml
Normal file
13
examples/shared-quicksort/platform/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "host"
|
||||
version = "0.1.0"
|
||||
authors = ["Richard Feldman <oss@rtfeldman.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib"]
|
||||
|
||||
[dependencies]
|
||||
roc_std = { path = "../../../roc_std" }
|
||||
|
||||
[workspace]
|
|
@ -1,49 +0,0 @@
|
|||
# Rebuilding the host from source
|
||||
|
||||
Here are the current steps to rebuild this host. These
|
||||
steps can likely be moved into a `build.rs` script after
|
||||
turning `host.rs` into a `cargo` project, but that hasn't
|
||||
been attempted yet.
|
||||
|
||||
## Compile the Rust and C sources
|
||||
|
||||
Currently this host has both a `host.rs` and a `host.c`.
|
||||
This is only because we haven't figured out a way to convince
|
||||
Rust to emit a `.o` file that doesn't define a `main` entrypoint,
|
||||
but which is capable of being linked into one later.
|
||||
|
||||
As a workaround, we have `host.rs` expose a function called
|
||||
`rust_main` instead of `main`, and all `host.c` does is provide
|
||||
an actual `main` which imports and then calls `rust_main` from
|
||||
the compiled `host.rs`. It's not the most elegant workaround,
|
||||
but [asking on `users.rust-lang.org`](https://users.rust-lang.org/t/error-when-compiling-linking-with-o-files/49635/4)
|
||||
didn't turn up any nicer approaches. Maybe they're out there though!
|
||||
|
||||
To make this workaround happen, we need to compile both `host.rs`
|
||||
and `host.c`. First, `cd` into `platform/host/src/` and then run:
|
||||
|
||||
```
|
||||
$ clang -c host.c -o c_host.o
|
||||
$ rustc host.rs -o rust_host.o
|
||||
```
|
||||
|
||||
Now we should have `c_host.o` and `rust_host.o` in the curent directory.
|
||||
|
||||
## Link together the `.o` files
|
||||
|
||||
Next, combine `c_host.o` and `rust_host.o` into `host.o` using `ld -r` like so:
|
||||
|
||||
```
|
||||
$ ld -r c_host.o rust_host.o -o host.o
|
||||
```
|
||||
|
||||
Move `host.o` into the appropriate `platform/` subdirectory
|
||||
based on your architecture and operating system. For example,
|
||||
on macOS, you'd move `host.o` into the `platform/host/x86_64-unknown-darwin10/` directory.
|
||||
|
||||
## All done!
|
||||
|
||||
Congratulations! You now have an updated host.
|
||||
|
||||
It's now fine to delete `c_host.o` and `rust_host.o`,
|
||||
since they were only needed to produce `host.o`.
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue