Merge branch 'trunk' of github.com:rtfeldman/roc into str-split

This commit is contained in:
Chad Stearns 2020-11-07 02:07:52 -05:00
commit d41e940b7f
108 changed files with 3509 additions and 863 deletions

View file

@ -51,11 +51,6 @@ jobs:
command: clippy command: clippy
args: -- -D warnings args: -- -D warnings
- uses: actions-rs/cargo@v1
name: cargo test
with:
command: test
- uses: actions-rs/cargo@v1 - uses: actions-rs/cargo@v1
name: cargo test -- --ignored name: cargo test -- --ignored
continue-on-error: true continue-on-error: true

View file

@ -1,12 +1,13 @@
# Building the Roc compiler from source # 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: To build the compiler, you need these installed:
* `libunwind` (macOS should already have this one installed) * `libunwind` (macOS should already have this one installed)
* `libc++-dev` * `libc++-dev`
* Python 2.7 (Windows only), `python-is-python3` (Ubuntu)
* a particular version of Zig (see below) * a particular version of Zig (see below)
* a particular version of LLVM (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 tar xvf zig-linux-x86_64-0.6.0+0088efc4b.tar
# move the files into /opt: # move the files into /opt:
sudo mkdir -p /opt/zig 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`). Then add `/opt/zig/` to your `PATH` (e.g. in `~/.bashrc`).

7
Cargo.lock generated
View file

@ -644,6 +644,8 @@ dependencies = [
"bumpalo", "bumpalo",
"fs_extra", "fs_extra",
"handlebars", "handlebars",
"maplit",
"pretty_assertions",
"pulldown-cmark", "pulldown-cmark",
"roc_builtins", "roc_builtins",
"roc_collections", "roc_collections",
@ -661,9 +663,9 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
[[package]] [[package]]
name = "either" name = "either"
version = "1.6.0" version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd56b59865bce947ac5958779cfa508f6c3b9497cc762b7e24a12d11ccde2c4f" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]] [[package]]
name = "encode_unicode" name = "encode_unicode"
@ -2463,6 +2465,7 @@ name = "roc_gen"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"either",
"im", "im",
"im-rc", "im-rc",
"indoc", "indoc",

View file

@ -194,42 +194,40 @@ fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fa
exposed_types, exposed_types,
); );
let loaded = loaded.expect("failed to load module"); let mut loaded = loaded.expect("failed to load module");
use roc_load::file::MonomorphizedModule; use roc_load::file::MonomorphizedModule;
let MonomorphizedModule { let MonomorphizedModule {
can_problems,
type_problems,
mono_problems,
mut procedures, mut procedures,
interns, interns,
exposed_to_host, exposed_to_host,
mut subs, mut subs,
module_id: home, module_id: home,
sources,
.. ..
} = loaded; } = 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(); let error_count = can_problems.len() + type_problems.len() + mono_problems.len();
if error_count > 0 { if error_count == 0 {
// There were problems; report them and return. continue;
let src_lines: Vec<&str> = module_src.split('\n').collect(); }
// Used for reporting where an error came from. let src_lines: Vec<&str> = src.split('\n').collect();
//
// TODO: maybe Reporting should have this be an Option?
let path = PathBuf::new();
// Report problems
let palette = DEFAULT_PALETTE; let palette = DEFAULT_PALETTE;
// Report parsing and canonicalization problems // Report parsing and canonicalization problems
let alloc = RocDocAllocator::new(&src_lines, home, &interns); let alloc = RocDocAllocator::new(&src_lines, home, &interns);
let mut lines = Vec::with_capacity(error_count);
for problem in can_problems.into_iter() { 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(); let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette); 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); lines.push(buf);
} }
for problem in type_problems.into_iter() { for problem in type_problems {
let report = type_problem(&alloc, path.clone(), problem); let report = type_problem(&alloc, module_path.clone(), problem);
let mut buf = String::new(); let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette); 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); lines.push(buf);
} }
for problem in mono_problems.into_iter() { for problem in mono_problems {
let report = mono_problem(&alloc, path.clone(), problem); let report = mono_problem(&alloc, module_path.clone(), problem);
let mut buf = String::new(); let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette); report.render_color_terminal(&mut buf, &alloc, &palette);
lines.push(buf); lines.push(buf);
} }
}
if !lines.is_empty() {
Ok(ReplOutput::Problems(lines)) Ok(ReplOutput::Problems(lines))
} else { } else {
let context = Context::create(); let context = Context::create();

View file

@ -59,6 +59,11 @@ fn jit_to_ast_help<'a>(
content: &Content, content: &Content,
) -> Expr<'a> { ) -> Expr<'a> {
match layout { 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) => { Layout::Builtin(Builtin::Int64) => {
run_jit_function!(lib, main_fn_name, i64, |num| num_to_ast( run_jit_function!(lib, main_fn_name, i64, |num| num_to_ast(
env, env,
@ -433,6 +438,15 @@ fn i64_to_ast(arena: &Bump, num: i64) -> Expr<'_> {
Expr::Num(arena.alloc(format!("{}", num))) 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, /// This is centralized in case we want to format it differently later,
/// e.g. adding underscores for large numbers /// e.g. adding underscores for large numbers
fn f64_to_ast(arena: &Bump, num: f64) -> Expr<'_> { fn f64_to_ast(arena: &Bump, num: f64) -> Expr<'_> {

View file

@ -12,24 +12,13 @@ mod helpers;
#[cfg(test)] #[cfg(test)]
mod cli_run { mod cli_run {
use crate::helpers::{ 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 serial_test::serial;
use std::path::Path;
fn check_output( fn check_output(file: &Path, flags: &[&str], expected_ending: &str, use_valgrind: bool) {
folder: &str, let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags].concat());
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(),
);
if !compile_out.stderr.is_empty() { if !compile_out.stderr.is_empty() {
panic!(compile_out.stderr); panic!(compile_out.stderr);
} }
@ -37,14 +26,14 @@ mod cli_run {
let out = if use_valgrind { let out = if use_valgrind {
let (valgrind_out, raw_xml) = 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); let memory_errors = extract_valgrind_errors(&raw_xml);
if !memory_errors.is_empty() { if !memory_errors.is_empty() {
panic!("{:?}", memory_errors); panic!("{:?}", memory_errors);
} }
valgrind_out valgrind_out
} else { } 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) { if !&out.stdout.ends_with(expected_ending) {
panic!( panic!(
@ -59,8 +48,7 @@ mod cli_run {
#[serial(hello_world)] #[serial(hello_world)]
fn run_hello_world() { fn run_hello_world() {
check_output( check_output(
"hello-world", &example_file("hello-world", "Hello.roc"),
"Hello.roc",
&[], &[],
"Hello, World!!!!!!!!!!!!!\n", "Hello, World!!!!!!!!!!!!!\n",
true, true,
@ -71,8 +59,7 @@ mod cli_run {
#[serial(hello_world)] #[serial(hello_world)]
fn run_hello_world_optimized() { fn run_hello_world_optimized() {
check_output( check_output(
"hello-world", &example_file("hello-world", "Hello.roc"),
"Hello.roc",
&[], &[],
"Hello, World!!!!!!!!!!!!!\n", "Hello, World!!!!!!!!!!!!!\n",
true, true,
@ -83,8 +70,7 @@ mod cli_run {
#[serial(quicksort)] #[serial(quicksort)]
fn run_quicksort_not_optimized() { fn run_quicksort_not_optimized() {
check_output( check_output(
"quicksort", &example_file("quicksort", "Quicksort.roc"),
"Quicksort.roc",
&[], &[],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
false, false,
@ -95,8 +81,7 @@ mod cli_run {
#[serial(quicksort)] #[serial(quicksort)]
fn run_quicksort_optimized() { fn run_quicksort_optimized() {
check_output( check_output(
"quicksort", &example_file("quicksort", "Quicksort.roc"),
"Quicksort.roc",
&["--optimize"], &["--optimize"],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
false, false,
@ -109,8 +94,7 @@ mod cli_run {
#[ignore] #[ignore]
fn run_quicksort_valgrind() { fn run_quicksort_valgrind() {
check_output( check_output(
"quicksort", &example_file("quicksort", "Quicksort.roc"),
"Quicksort.roc",
&[], &[],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
true, true,
@ -123,8 +107,7 @@ mod cli_run {
#[ignore] #[ignore]
fn run_quicksort_optimized_valgrind() { fn run_quicksort_optimized_valgrind() {
check_output( check_output(
"quicksort", &example_file("quicksort", "Quicksort.roc"),
"Quicksort.roc",
&["--optimize"], &["--optimize"],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
true, true,
@ -135,8 +118,7 @@ mod cli_run {
#[serial(multi_module)] #[serial(multi_module)]
fn run_multi_module() { fn run_multi_module() {
check_output( check_output(
"multi-module", &example_file("multi-module", "Quicksort.roc"),
"Quicksort.roc",
&[], &[],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
false, false,
@ -147,8 +129,7 @@ mod cli_run {
#[serial(multi_module)] #[serial(multi_module)]
fn run_multi_module_optimized() { fn run_multi_module_optimized() {
check_output( check_output(
"multi-module", &example_file("multi-module", "Quicksort.roc"),
"Quicksort.roc",
&["--optimize"], &["--optimize"],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
false, false,
@ -161,8 +142,7 @@ mod cli_run {
#[ignore] #[ignore]
fn run_multi_module_valgrind() { fn run_multi_module_valgrind() {
check_output( check_output(
"multi-module", &example_file("multi-module", "Quicksort.roc"),
"Quicksort.roc",
&[], &[],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
true, true,
@ -175,11 +155,60 @@ mod cli_run {
#[ignore] #[ignore]
fn run_multi_module_optimized_valgrind() { fn run_multi_module_optimized_valgrind() {
check_output( check_output(
"multi-module", &example_file("multi-module", "Quicksort.roc"),
"Quicksort.roc",
&["--optimize"], &["--optimize"],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
true, 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
View file

@ -0,0 +1,4 @@
app
host.o
c_host.o
app.dSYM

View file

@ -0,0 +1,4 @@
interface Dep1 exposes [ str1 ] imports [ Dep2 ]
str1 : Str
str1 = Dep2.str2

View file

@ -0,0 +1,4 @@
interface Dep2 exposes [ str2 ] imports []
str2 : Str
str2 = "I am Dep2.str2"

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

View 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]

View 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.

View file

@ -0,0 +1,7 @@
#include <stdio.h>
extern int rust_main();
int main() {
return rust_main();
}

View 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
}

View file

@ -0,0 +1,4 @@
interface Dep1 exposes [ value1 ] imports [ Dep2 ]
value1 : {} -> Str
value1 = \_ -> Dep2.value2 {}

View file

@ -0,0 +1,4 @@
interface Dep2 exposes [ value2 ] imports []
value2 : {} -> Str
value2 = \_ -> "I am Dep2.value2"

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

View 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]

View 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.

View 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

View file

@ -0,0 +1,7 @@
#include <stdio.h>
extern int rust_main();
int main() {
return rust_main();
}

View 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
}

View file

@ -207,6 +207,40 @@ pub fn example_file(dir_name: &str, file_name: &str) -> PathBuf {
path 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)] #[allow(dead_code)]
pub fn repl_eval(input: &str) -> Out { pub fn repl_eval(input: &str) -> Out {
let mut cmd = Command::new(path_to_roc_binary()); let mut cmd = Command::new(path_to_roc_binary());

View file

@ -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] #[test]
fn basic_1_field_i64_record() { fn basic_1_field_i64_record() {
// Even though this gets unwrapped at runtime, the repl should still // Even though this gets unwrapped at runtime, the repl should still

View file

@ -6,7 +6,7 @@ use libloading::{Error, Library};
use roc_gen::llvm::build::OptLevel; use roc_gen::llvm::build::OptLevel;
use std::io; use std::io;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::{Child, Command}; use std::process::{Child, Command, Output};
use target_lexicon::{Architecture, OperatingSystem, Triple}; use target_lexicon::{Architecture, OperatingSystem, Triple};
use tempfile::tempdir; 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"); let host_dest = host_input_path.with_file_name("host.o");
// Compile host.c // Compile host.c
Command::new("clang") let output = Command::new("clang")
.env_clear() .env_clear()
.args(&[ .args(&[
"-c", "-c",
@ -58,18 +58,22 @@ pub fn rebuild_host(host_input_path: &Path) {
.output() .output()
.unwrap(); .unwrap();
validate_output("host.c", "clang", output);
if cargo_host_src.exists() { if cargo_host_src.exists() {
// Compile and link Cargo.toml, if it exists // Compile and link Cargo.toml, if it exists
let cargo_dir = host_input_path.parent().unwrap(); let cargo_dir = host_input_path.parent().unwrap();
let libhost_dir = cargo_dir.join("target").join("release"); let libhost_dir = cargo_dir.join("target").join("release");
Command::new("cargo") let output = Command::new("cargo")
.args(&["build", "--release"]) .args(&["build", "--release"])
.current_dir(cargo_dir) .current_dir(cargo_dir)
.output() .output()
.unwrap(); .unwrap();
Command::new("ld") validate_output("host.rs", "cargo build --release", output);
let output = Command::new("ld")
.env_clear() .env_clear()
.args(&[ .args(&[
"-r", "-r",
@ -82,9 +86,11 @@ pub fn rebuild_host(host_input_path: &Path) {
]) ])
.output() .output()
.unwrap(); .unwrap();
validate_output("c_host.o", "ld", output);
} else if rust_host_src.exists() { } else if rust_host_src.exists() {
// Compile and link host.rs, if it exists // Compile and link host.rs, if it exists
Command::new("rustc") let output = Command::new("rustc")
.args(&[ .args(&[
rust_host_src.to_str().unwrap(), rust_host_src.to_str().unwrap(),
"-o", "-o",
@ -93,7 +99,9 @@ pub fn rebuild_host(host_input_path: &Path) {
.output() .output()
.unwrap(); .unwrap();
Command::new("ld") validate_output("host.rs", "rustc", output);
let output = Command::new("ld")
.env_clear() .env_clear()
.args(&[ .args(&[
"-r", "-r",
@ -105,8 +113,10 @@ pub fn rebuild_host(host_input_path: &Path) {
.output() .output()
.unwrap(); .unwrap();
validate_output("rust_host.o", "ld", output);
// Clean up rust_host.o // Clean up rust_host.o
Command::new("rm") let output = Command::new("rm")
.env_clear() .env_clear()
.args(&[ .args(&[
"-f", "-f",
@ -115,13 +125,17 @@ pub fn rebuild_host(host_input_path: &Path) {
]) ])
.output() .output()
.unwrap(); .unwrap();
validate_output("rust_host.o", "rm", output);
} else { } else {
// Clean up rust_host.o // Clean up rust_host.o
Command::new("mv") let output = Command::new("mv")
.env_clear() .env_clear()
.args(&[c_host_dest, host_dest]) .args(&[c_host_dest, host_dest])
.output() .output()
.unwrap(); .unwrap();
validate_output("rust_host.o", "mv", output);
} }
} }
@ -303,3 +317,18 @@ pub fn module_to_dylib(
Library::new(path) 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
),
}
}
}

View file

@ -14,24 +14,26 @@ use target_lexicon::Triple;
#[allow(clippy::cognitive_complexity)] #[allow(clippy::cognitive_complexity)]
pub fn gen_from_mono_module( pub fn gen_from_mono_module(
arena: &Bump, arena: &Bump,
loaded: MonomorphizedModule, mut loaded: MonomorphizedModule,
file_path: PathBuf, _file_path: PathBuf,
target: Triple, target: Triple,
app_o_file: &Path, app_o_file: &Path,
opt_level: OptLevel, 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; for (home, (module_path, src)) in loaded.sources {
let home = loaded.module_id;
let src_lines: Vec<&str> = src.split('\n').collect(); let src_lines: Vec<&str> = src.split('\n').collect();
let palette = DEFAULT_PALETTE; let palette = DEFAULT_PALETTE;
// Report parsing and canonicalization problems // Report parsing and canonicalization problems
let alloc = RocDocAllocator::new(&src_lines, home, &loaded.interns); let alloc = RocDocAllocator::new(&src_lines, home, &loaded.interns);
for problem in loaded.can_problems.into_iter() { let problems = loaded.can_problems.remove(&home).unwrap_or_default();
let report = can_problem(&alloc, file_path.clone(), problem); for problem in problems.into_iter() {
let report = can_problem(&alloc, module_path.clone(), problem);
let mut buf = String::new(); let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette); report.render_color_terminal(&mut buf, &alloc, &palette);
@ -39,8 +41,9 @@ pub fn gen_from_mono_module(
println!("\n{}\n", buf); println!("\n{}\n", buf);
} }
for problem in loaded.type_problems.into_iter() { let problems = loaded.type_problems.remove(&home).unwrap_or_default();
let report = type_problem(&alloc, file_path.clone(), problem); for problem in problems {
let report = type_problem(&alloc, module_path.clone(), problem);
let mut buf = String::new(); let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette); report.render_color_terminal(&mut buf, &alloc, &palette);
@ -48,6 +51,17 @@ pub fn gen_from_mono_module(
println!("\n{}\n", buf); 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 // Generate the binary
let context = Context::create(); let context = Context::create();

View file

@ -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 // walkRight : List elem, (elem -> accum -> accum), accum -> accum
add_type( add_type(
Symbol::LIST_WALK_RIGHT, Symbol::LIST_WALK_RIGHT,

View file

@ -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))) // join : Attr * (List (Attr * (List a)))
// -> Attr * (List a) // -> Attr * (List a)
add_type(Symbol::LIST_JOIN, { add_type(Symbol::LIST_JOIN, {

View file

@ -1,6 +1,6 @@
use crate::env::Env; use crate::env::Env;
use crate::scope::Scope; 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::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_parse::ast::{AssignedField, Tag, TypeAnnotation}; use roc_parse::ast::{AssignedField, Tag, TypeAnnotation};
@ -31,6 +31,7 @@ pub struct IntroducedVariables {
pub wildcards: Vec<Variable>, pub wildcards: Vec<Variable>,
pub var_by_name: SendMap<Lowercase, Variable>, pub var_by_name: SendMap<Lowercase, Variable>,
pub name_by_var: SendMap<Variable, Lowercase>, pub name_by_var: SendMap<Variable, Lowercase>,
pub host_exposed_aliases: MutMap<Symbol, Variable>,
} }
impl IntroducedVariables { impl IntroducedVariables {
@ -43,10 +44,16 @@ impl IntroducedVariables {
self.wildcards.push(var); 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) { pub fn union(&mut self, other: &Self) {
self.wildcards.extend(other.wildcards.iter().cloned()); self.wildcards.extend(other.wildcards.iter().cloned());
self.var_by_name.extend(other.var_by_name.clone()); self.var_by_name.extend(other.var_by_name.clone());
self.name_by_var.extend(other.name_by_var.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> { pub fn var_by_name(&self, name: &Lowercase) -> Option<&Variable> {
@ -220,7 +227,15 @@ fn can_annotation_help(
// instantiate variables // instantiate variables
actual.substitute(&substitutions); 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 => { None => {
let mut args = Vec::new(); let mut args = Vec::new();
@ -352,7 +367,16 @@ fn can_annotation_help(
let alias = scope.lookup_alias(symbol).unwrap(); let alias = scope.lookup_alias(symbol).unwrap();
local_aliases.insert(symbol, alias.clone()); 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. // This is a syntactically invalid type alias.

View file

@ -63,6 +63,7 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
Symbol::LIST_REPEAT => list_repeat, Symbol::LIST_REPEAT => list_repeat,
Symbol::LIST_REVERSE => list_reverse, Symbol::LIST_REVERSE => list_reverse,
Symbol::LIST_CONCAT => list_concat, Symbol::LIST_CONCAT => list_concat,
Symbol::LIST_CONTAINS => list_contains,
Symbol::LIST_PREPEND => list_prepend, Symbol::LIST_PREPEND => list_prepend,
Symbol::LIST_JOIN => list_join, Symbol::LIST_JOIN => list_join,
Symbol::LIST_MAP => list_map, 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_ATAN => num_atan,
Symbol::NUM_ACOS => num_acos, Symbol::NUM_ACOS => num_acos,
Symbol::NUM_ASIN => num_asin, 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 /// List.map : List before, (before -> after) -> List after
fn list_map(symbol: Symbol, var_store: &mut VarStore) -> Def { fn list_map(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh(); let list_var = var_store.fresh();

View file

@ -1443,6 +1443,8 @@ fn to_pending_def<'a>(
SpaceBefore(sub_def, _) | SpaceAfter(sub_def, _) | Nested(sub_def) => { SpaceBefore(sub_def, _) | SpaceAfter(sub_def, _) | Nested(sub_def) => {
to_pending_def(env, var_store, sub_def, scope, pattern_type) to_pending_def(env, var_store, sub_def, scope, pattern_type)
} }
NotYetImplemented(s) => todo!("{}", s),
} }
} }

View file

@ -207,9 +207,8 @@ pub fn canonicalize_expr<'a>(
let (can_update, update_out) = let (can_update, update_out) =
canonicalize_expr(env, var_store, scope, loc_update.region, &loc_update.value); canonicalize_expr(env, var_store, scope, loc_update.region, &loc_update.value);
if let Var(symbol) = &can_update.value { if let Var(symbol) = &can_update.value {
let (can_fields, mut output) = match canonicalize_fields(env, var_store, scope, region, fields) {
canonicalize_fields(env, var_store, scope, region, fields); Ok((can_fields, mut output)) => {
output.references = output.references.union(update_out.references); output.references = output.references.union(update_out.references);
let answer = Update { let answer = Update {
@ -220,6 +219,20 @@ pub fn canonicalize_expr<'a>(
}; };
(answer, output) (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 { } else {
// only (optionally qualified) variables can be updated, not arbitrary expressions // only (optionally qualified) variables can be updated, not arbitrary expressions
@ -241,16 +254,27 @@ pub fn canonicalize_expr<'a>(
if fields.is_empty() { if fields.is_empty() {
(EmptyRecord, Output::default()) (EmptyRecord, Output::default())
} else { } else {
let (can_fields, output) = match canonicalize_fields(env, var_store, scope, region, fields) {
canonicalize_fields(env, var_store, scope, region, fields); Ok((can_fields, output)) => (
(
Record { Record {
record_var: var_store.fresh(), record_var: var_store.fresh(),
fields: can_fields, fields: can_fields,
}, },
output, 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), 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>( fn canonicalize_fields<'a>(
env: &mut Env<'a>, env: &mut Env<'a>,
var_store: &mut VarStore, var_store: &mut VarStore,
scope: &mut Scope, scope: &mut Scope,
region: Region, region: Region,
fields: &'a [Located<ast::AssignedField<'a, ast::Expr<'a>>>], 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 can_fields = SendMap::default();
let mut output = Output::default(); let mut output = Output::default();
for loc_field in fields.iter() { for loc_field in fields.iter() {
let (label, field_expr, field_out, field_var) = match canonicalize_field(env, var_store, scope, &loc_field.value, loc_field.region) {
canonicalize_field(env, var_store, scope, &loc_field.value, loc_field.region); Ok((label, field_expr, field_out, field_var)) => {
let field = Field { let field = Field {
var: field_var, var: field_var,
region: loc_field.region, region: loc_field.region,
@ -1004,17 +1034,40 @@ fn canonicalize_fields<'a>(
output.references = output.references.union(field_out.references); output.references = output.references.union(field_out.references);
} }
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,
});
}
}
}
(can_fields, output) Ok((can_fields, output))
} }
enum CanonicalizeFieldProblem {
InvalidOptionalValue {
field_name: Lowercase,
field_region: Region,
},
}
fn canonicalize_field<'a>( fn canonicalize_field<'a>(
env: &mut Env<'a>, env: &mut Env<'a>,
var_store: &mut VarStore, var_store: &mut VarStore,
scope: &mut Scope, scope: &mut Scope,
field: &'a ast::AssignedField<'a, ast::Expr<'a>>, field: &'a ast::AssignedField<'a, ast::Expr<'a>>,
region: Region, region: Region,
) -> (Lowercase, Located<Expr>, Output, Variable) { ) -> Result<(Lowercase, Located<Expr>, Output, Variable), CanonicalizeFieldProblem> {
use roc_parse::ast::AssignedField::*; use roc_parse::ast::AssignedField::*;
match field { match field {
@ -1024,17 +1077,18 @@ fn canonicalize_field<'a>(
let (loc_can_expr, output) = let (loc_can_expr, output) =
canonicalize_expr(env, var_store, scope, loc_expr.region, &loc_expr.value); canonicalize_expr(env, var_store, scope, loc_expr.region, &loc_expr.value);
( Ok((
Lowercase::from(label.value), Lowercase::from(label.value),
loc_can_expr, loc_can_expr,
output, output,
field_var, field_var,
) ))
} }
OptionalValue(_, _, _) => { OptionalValue(label, _, loc_expr) => Err(CanonicalizeFieldProblem::InvalidOptionalValue {
todo!("TODO gracefully handle an optional field being used in an Expr"); 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 }) // A label with no value, e.g. `{ name }` (this is sugar for { name: name })
LabelOnly(_) => { LabelOnly(_) => {

View file

@ -47,6 +47,8 @@ pub fn desugar_def<'a>(arena: &'a Bump, def: &'a Def<'a>) -> Def<'a> {
Nested(alias @ Alias { .. }) => Nested(alias), Nested(alias @ Alias { .. }) => Nested(alias),
ann @ Annotation(_, _) => Nested(ann), ann @ Annotation(_, _) => Nested(ann),
Nested(ann @ Annotation(_, _)) => Nested(ann), Nested(ann @ Annotation(_, _)) => Nested(ann),
Nested(NotYetImplemented(s)) => todo!("{}", s),
NotYetImplemented(s) => todo!("{}", s),
} }
} }

View file

@ -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 { fn get_closure(expr: &Expr, i: usize) -> roc_can::expr::Recursive {
match expr { match expr {
LetRec(assignments, body, _) => { LetRec(assignments, body, _) => {

View file

@ -2105,6 +2105,46 @@ fn annotation_to_attr_type(
let alias = Type::Alias(*symbol, new_fields, Box::new(actual_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, actual_vars,
crate::builtins::builtin_type(Symbol::ATTR_ATTR, vec![uniq_type, alias]), crate::builtins::builtin_type(Symbol::ATTR_ATTR, vec![uniq_type, alias]),

View file

@ -20,6 +20,7 @@ impl<'a> Formattable<'a> for Def<'a> {
spaces.iter().any(|s| is_comment(s)) || sub_def.is_multiline() spaces.iter().any(|s| is_comment(s)) || sub_def.is_multiline()
} }
Nested(def) => 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); fmt_spaces(buf, spaces.iter(), indent);
} }
Nested(def) => def.format(buf, indent), Nested(def) => def.format(buf, indent),
NotYetImplemented(s) => todo!("{}", s),
} }
} }
} }

View file

@ -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! im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.2", features = ["collections"] } bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1" inlinable_string = "0.1"
either = "1.6.1"
# NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything. # 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 # The reason for this fork is that the way Inkwell is designed, you have to use

View file

@ -1,8 +1,8 @@
use crate::layout_id::LayoutIds; use crate::layout_id::LayoutIds;
use crate::llvm::build_list::{ use crate::llvm::build_list::{
allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_get_unsafe, allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_contains,
list_join, list_keep_if, list_len, list_map, list_prepend, list_repeat, list_reverse, list_set, list_get_unsafe, list_join, list_keep_if, list_len, list_map, list_prepend, list_repeat,
list_single, list_walk_right, 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::build_str::{str_concat, str_len, str_split, CHAR_LAYOUT};
use crate::llvm::compare::{build_eq, build_neq}; 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::low_level::LowLevel;
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::ir::{JoinPointId, Wrapped}; 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; use target_lexicon::CallingConvention;
/// This is for Inkwell's FunctionValue::verify - we want to know the verification /// 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) 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>( pub fn build_proc_header<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
@ -1853,6 +2133,32 @@ pub fn build_proc_header<'a, 'ctx, 'env>(
let arena = env.arena; let arena = env.arena;
let context = &env.context; 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 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); 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_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 let fn_val = env
.module .module
.add_function(fn_name.as_str(), fn_type, Some(Linkage::Private)); .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); fn_val.set_call_conventions(FAST_CALL_CONV);
if env.exposed_to_host.contains(&symbol) {
expose_function_to_host(env, fn_val);
} }
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, &parameters, "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>( pub fn build_proc<'a, 'ctx, 'env>(
env: &'a Env<'a, 'ctx, 'env>, env: &'a Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, 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. // 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 // we need to cast that to the specific closure data layout that the body expects
let value = if let Symbol::ARG_CLOSURE = *arg_symbol { 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 // blindly trust that there is a layout available for the closure data
let layout = proc.closure_data_layout.clone().unwrap(); 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) panic!("Unrecognized builtin function: {:?}", fn_name)
} else { } else {
panic!( panic!(
"Unrecognized non-builtin function: {:?} {:?}", "Unrecognized non-builtin function: {:?} (symbol: {:?}, layout: {:?})",
fn_name, 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) 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 => { ListWalkRight => {
// List.walkRight : List elem, (elem -> accum -> accum), accum -> accum // List.walkRight : List elem, (elem -> accum -> accum), accum -> accum
debug_assert_eq!(args.len(), 3); 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> { 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 module = env.module;
let context = env.context; let context = env.context;

View file

@ -1,4 +1,5 @@
use crate::llvm::build::{Env, InPlace}; 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 crate::llvm::convert::{basic_type_from_layout, collection, get_ptr_type, ptr_int};
use inkwell::builder::Builder; use inkwell::builder::Builder;
use inkwell::context::Context; use inkwell::context::Context;
@ -819,6 +820,116 @@ pub fn list_walk_right<'a, 'ctx, 'env>(
builder.build_load(accum_alloca, "load_final_acum") 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 /// List.keepIf : List elem, (elem -> Bool) -> List elem
pub fn list_keep_if<'a, 'ctx, 'env>( pub fn list_keep_if<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
@ -849,7 +960,7 @@ pub fn list_keep_if<'a, 'ctx, 'env>(
elem_layout.clone(), 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); let list_type = basic_type_from_layout(env.arena, env.context, &list_layout, env.ptr_bytes);

View file

@ -285,6 +285,7 @@ mod gen_list {
r#" r#"
Bit : [ Zero, One ] Bit : [ Zero, One ]
byte : List Bit
byte = [ Zero, One, Zero, One, Zero, Zero, One, Zero ] byte = [ Zero, One, Zero, One, Zero, Zero, One, Zero ]
initialCounts = { zeroes: 0, ones: 0 } initialCounts = { zeroes: 0, ones: 0 }
@ -313,7 +314,7 @@ mod gen_list {
empty = empty =
[] []
List.keepIf empty (\x -> True) List.keepIf empty (\_ -> True)
"# "#
), ),
RocList::from_slice(&[]), RocList::from_slice(&[]),
@ -345,7 +346,7 @@ mod gen_list {
indoc!( indoc!(
r#" r#"
alwaysTrue : Int -> Bool alwaysTrue : Int -> Bool
alwaysTrue = \i -> alwaysTrue = \_ ->
True True
oneThroughEight : List Int oneThroughEight : List Int
@ -366,7 +367,7 @@ mod gen_list {
indoc!( indoc!(
r#" r#"
alwaysFalse : Int -> Bool alwaysFalse : Int -> Bool
alwaysFalse = \i -> alwaysFalse = \_ ->
False False
List.keepIf [1,2,3,4,5,6,7,8] alwaysFalse List.keepIf [1,2,3,4,5,6,7,8] alwaysFalse
@ -1602,4 +1603,13 @@ mod gen_list {
RocList<i64> 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);
}
} }

View file

@ -557,7 +557,7 @@ mod gen_num {
indoc!( indoc!(
r#" r#"
always42 : Num.Num Num.Integer -> Num.Num Num.Integer always42 : Num.Num Num.Integer -> Num.Num Num.Integer
always42 = \num -> 42 always42 = \_ -> 42
always42 5 always42 5
"# "#
@ -788,4 +788,30 @@ mod gen_num {
// f64 // 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
);
}
} }

View file

@ -294,7 +294,7 @@ mod gen_primitives {
r#" r#"
wrapper = \{} -> wrapper = \{} ->
alwaysFloatIdentity : Int -> (Float -> Float) alwaysFloatIdentity : Int -> (Float -> Float)
alwaysFloatIdentity = \num -> alwaysFloatIdentity = \_ ->
(\a -> a) (\a -> a)
(alwaysFloatIdentity 2) 3.14 (alwaysFloatIdentity 2) 3.14
@ -362,7 +362,7 @@ mod gen_primitives {
pi = 3.14 pi = 3.14
answer if pi > 3 then answer else answer
"# "#
), ),
42, 42,
@ -376,7 +376,7 @@ mod gen_primitives {
pi = 3.14 pi = 3.14
pi if answer > 3 then pi else pi
"# "#
), ),
3.14, 3.14,
@ -384,87 +384,89 @@ mod gen_primitives {
); );
} }
#[test] // These tests caught a bug in how Defs are converted to the mono IR
fn gen_chained_defs() { // but they have UnusedDef or UnusedArgument problems, and don't run any more
assert_evals_to!( // #[test]
indoc!( // fn gen_chained_defs() {
r#" // assert_evals_to!(
x = i1 // indoc!(
i3 = i2 // r#"
i1 = 1337 // x = i1
i2 = i1 // i3 = i2
y = 12.4 // i1 = 1337
// i2 = i1
i3 // y = 12.4
"# //
), // i3
1337, // "#
i64 // ),
); // 1337,
} // i64
// );
#[test] // }
fn gen_nested_defs_old() { //
assert_evals_to!( // #[test]
indoc!( // fn gen_nested_defs_old() {
r#" // assert_evals_to!(
x = 5 // indoc!(
// r#"
answer = // x = 5
i3 = i2 //
// answer =
nested = // i3 = i2
a = 1.0 //
b = 5 // nested =
// a = 1.0
i1 // b = 5
//
i1 = 1337 // i1
i2 = i1 //
// i1 = 1337
// i2 = i1
nested //
//
# None of this should affect anything, even though names // nested
# overlap with the previous nested defs //
unused = // # None of this should affect anything, even though names
nested = 17 // # overlap with the previous nested defs
// unused =
i1 = 84.2 // nested = 17
//
nested // i1 = 84.2
//
y = 12.4 // nested
//
answer // y = 12.4
"# //
), // answer
1337, // "#
i64 // ),
); // 1337,
} // i64
// );
#[test] // }
fn let_x_in_x() { //
assert_evals_to!( // #[test]
indoc!( // fn let_x_in_x() {
r#" // assert_evals_to!(
x = 5 // indoc!(
// r#"
answer = // x = 5
1337 //
// answer =
unused = // 1337
nested = 17 //
nested // unused =
// nested = 17
answer // nested
"# //
), // answer
1337, // "#
i64 // ),
); // 1337,
} // i64
// );
// }
#[test] #[test]
fn factorial() { fn factorial() {

View file

@ -254,11 +254,11 @@ mod gen_records {
r#" r#"
v = {} v = {}
1 v
"# "#
), ),
1, (),
i64 ()
); );
} }
#[test] #[test]

View file

@ -23,11 +23,12 @@ mod gen_tags {
x : Maybe Int x : Maybe Int
x = Nothing x = Nothing
0x1 x
"# "#
), ),
1, 1,
i64 (i64, i64),
|(tag, _)| tag
); );
} }
@ -41,11 +42,12 @@ mod gen_tags {
x : Maybe Int x : Maybe Int
x = Nothing x = Nothing
0x1 x
"# "#
), ),
1, 1,
i64 (i64, i64),
|(tag, _)| tag
); );
} }
@ -59,11 +61,11 @@ mod gen_tags {
y : Maybe Int y : Maybe Int
y = Just 0x4 y = Just 0x4
0x1 y
"# "#
), ),
1, (0, 0x4),
i64 (i64, i64)
); );
} }
@ -77,11 +79,11 @@ mod gen_tags {
y : Maybe Int y : Maybe Int
y = Just 0x4 y = Just 0x4
0x1 y
"# "#
), ),
1, (0, 0x4),
i64 (i64, i64)
); );
} }
@ -99,11 +101,11 @@ mod gen_tags {
y : Maybe Fruit y : Maybe Fruit
y = Just orange y = Just orange
0x1 y
"# "#
), ),
1, (0, 2),
i64 (i64, i64)
); );
} }
@ -350,7 +352,7 @@ mod gen_tags {
when x is when x is
These a b -> a + b These a b -> a + b
That v -> 8 That v -> v
This v -> v This v -> v
"# "#
), ),
@ -616,10 +618,10 @@ mod gen_tags {
x : [ Pair Int ] x : [ Pair Int ]
x = Pair 2 x = Pair 2
0x3 x
"# "#
), ),
3, 2,
i64 i64
); );
} }
@ -637,11 +639,11 @@ mod gen_tags {
x = Just (Just 41) x = Just (Just 41)
main = main =
5 x
"# "#
), ),
5, (0, (0, 41)),
i64 (i64, (i64, i64))
); );
} }
#[test] #[test]
@ -654,11 +656,11 @@ mod gen_tags {
v : Unit v : Unit
v = Unit v = Unit
1 v
"# "#
), ),
1, (),
i64 ()
); );
} }
@ -667,8 +669,6 @@ mod gen_tags {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
Maybe a : [ Nothing, Just a ]
x = { a : { b : 0x5 } } x = { a : { b : 0x5 } }
y = x.a y = x.a

View file

@ -50,14 +50,10 @@ pub fn helper<'a>(
exposed_types, exposed_types,
); );
let loaded = loaded.expect("failed to load module"); let mut loaded = loaded.expect("failed to load module");
use roc_load::file::MonomorphizedModule; use roc_load::file::MonomorphizedModule;
let MonomorphizedModule { let MonomorphizedModule {
module_id: home,
can_problems,
type_problems,
mono_problems,
mut procedures, mut procedures,
interns, interns,
exposed_to_host, exposed_to_host,
@ -76,47 +72,52 @@ pub fn helper<'a>(
let target = target_lexicon::Triple::host(); let target = target_lexicon::Triple::host();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; 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 mut lines = Vec::new();
let errors = can_problems // errors whose reporting we delay (so we can see that code gen generates runtime errors)
.into_iter() let mut delayed_errors = Vec::new();
.filter(|problem| {
use roc_problem::can::Problem::*;
// Ignore "unused" problems
match problem {
UnusedDef(_, _) | UnusedArgument(_, _, _) | UnusedImport(_, _) => false,
_ => true,
}
})
.collect::<Vec<roc_problem::can::Problem>>();
for (home, (module_path, src)) in loaded.sources {
use roc_reporting::report::{ use roc_reporting::report::{
can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE, can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE,
}; };
let error_count = errors.len() + type_problems.len() + mono_problems.len(); let can_problems = loaded.can_problems.remove(&home).unwrap_or_default();
let fatal_error_count = type_problems.len() + mono_problems.len(); 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 { let error_count = can_problems.len() + type_problems.len() + mono_problems.len();
// There were problems; report them and return.
let src_lines: Vec<&str> = module_src.split('\n').collect();
// Used for reporting where an error came from. if error_count == 0 {
// continue;
// 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; let palette = DEFAULT_PALETTE;
// Report parsing and canonicalization problems // Report parsing and canonicalization problems
let alloc = RocDocAllocator::new(&src_lines, home, &interns); let alloc = RocDocAllocator::new(&src_lines, home, &interns);
let mut lines = Vec::with_capacity(error_count); use roc_problem::can::Problem::*;
let can_problems = errors.clone();
for problem in can_problems.into_iter() { 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(); let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette); report.render_color_terminal(&mut buf, &alloc, &palette);
@ -124,31 +125,19 @@ pub fn helper<'a>(
lines.push(buf); lines.push(buf);
} }
for problem in type_problems.into_iter() { for problem in mono_problems {
let report = type_problem(&alloc, path.clone(), problem); let report = mono_problem(&alloc, module_path.clone(), problem);
let mut buf = String::new(); let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette); report.render_color_terminal(&mut buf, &alloc, &palette);
lines.push(buf); 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")); if !lines.is_empty() {
println!("{}", lines.join("\n"));
// we want to continue onward only for canonical problems at the moment, assert_eq!(0, 1, "Mistakes were made");
// to check that they codegen into runtime exceptions
if fatal_error_count > 0 {
assert_eq!(0, 1, "problems occured");
}
} }
let module = roc_gen::llvm::build::module_from_builtins(context, "app"); 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) let lib = module_to_dylib(&env.module, &target, opt_level)
.expect("Error loading compiled dylib for test"); .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 // TODO this is almost all code duplication with assert_llvm_evals_to

View file

@ -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 inlinable_string::InlinableString;
use roc_module::ident::ModuleName; use roc_module::ident::ModuleName;
use roc_module::symbol::IdentIds; use roc_module::symbol::IdentIds;
@ -71,7 +69,7 @@ fn generate_module_doc<'a>(
// If there are comments before, attach to this definition // If there are comments before, attach to this definition
generate_module_doc(exposed_ident_ids, acc, before_comments_or_new_lines, sub_def); 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)) (new_acc, Some(comments_or_new_lines))
} }
@ -98,9 +96,15 @@ fn generate_module_doc<'a>(
name: _, name: _,
vars: _, vars: _,
ann: _, ann: _,
} => (acc, None), } =>
// TODO
{
(acc, None)
}
Body(_, _) | Nested(_) => (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() { for comment_or_new_line in comments_or_new_lines.iter() {
match comment_or_new_line { match comment_or_new_line {
Newline => {} DocComment(doc_str) => {
LineComment(_) => {} docs.push_str(doc_str);
DocComment(doc_str) => docs.push_str(doc_str), docs.push_str("\n");
}
Newline | LineComment(_) => {}
} }
} }
if docs.is_empty() { if docs.is_empty() {

View file

@ -13,11 +13,10 @@ use roc_constrain::module::{
constrain_imports, pre_constrain_imports, ConstrainableImports, Import, constrain_imports, pre_constrain_imports, ConstrainableImports, Import,
}; };
use roc_constrain::module::{constrain_module, ExposedModuleTypes, SubsByModule}; 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_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
use roc_mono::ir::{ use roc_mono::ir::{
CapturedSymbols, ExternalSpecializations, MonoProblem, PartialProc, PendingSpecialization, CapturedSymbols, ExternalSpecializations, PartialProc, PendingSpecialization, Proc, Procs,
Proc, Procs,
}; };
use roc_mono::layout::{Layout, LayoutCache}; use roc_mono::layout::{Layout, LayoutCache};
use roc_parse::ast::{self, Attempting, ExposesEntry, ImportsEntry}; use roc_parse::ast::{self, Attempting, ExposesEntry, ImportsEntry};
@ -201,15 +200,24 @@ impl Dependencies {
#[derive(Debug, Default)] #[derive(Debug, Default)]
struct ModuleCache<'a> { struct ModuleCache<'a> {
module_names: MutMap<ModuleId, ModuleName>, module_names: MutMap<ModuleId, ModuleName>,
/// Phases
headers: MutMap<ModuleId, ModuleHeader<'a>>, headers: MutMap<ModuleId, ModuleHeader<'a>>,
parsed: MutMap<ModuleId, ParsedModule<'a>>, parsed: MutMap<ModuleId, ParsedModule<'a>>,
canonicalized: MutMap<ModuleId, CanonicalizedModule<'a>>, canonicalized: MutMap<ModuleId, CanonicalizedModule<'a>>,
aliases: MutMap<ModuleId, MutMap<Symbol, Alias>>, aliases: MutMap<ModuleId, MutMap<Symbol, Alias>>,
constrained: MutMap<ModuleId, ConstrainedModule<'a>>, constrained: MutMap<ModuleId, ConstrainedModule>,
typechecked: MutMap<ModuleId, TypeCheckedModule<'a>>, typechecked: MutMap<ModuleId, TypeCheckedModule<'a>>,
found_specializations: MutMap<ModuleId, FoundSpecializationsModule<'a>>, found_specializations: MutMap<ModuleId, FoundSpecializationsModule<'a>>,
external_specializations_requested: MutMap<ModuleId, ExternalSpecializations>, external_specializations_requested: MutMap<ModuleId, ExternalSpecializations>,
/// Various information
documentation: MutMap<ModuleId, ModuleDocumentation>, 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> { 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, module,
ident_ids, ident_ids,
module_timing, module_timing,
src,
constraint, constraint,
var_store, var_store,
imported_modules, imported_modules,
@ -326,7 +333,6 @@ fn start_phase<'a>(module_id: ModuleId, phase: Phase, state: &mut State<'a>) ->
module, module,
ident_ids, ident_ids,
module_timing, module_timing,
src,
constraint, constraint,
var_store, var_store,
imported_modules, imported_modules,
@ -344,7 +350,6 @@ fn start_phase<'a>(module_id: ModuleId, phase: Phase, state: &mut State<'a>) ->
module_timing, module_timing,
solved_subs, solved_subs,
decls, decls,
finished_info,
ident_ids, ident_ids,
} = typechecked; } = typechecked;
@ -354,7 +359,6 @@ fn start_phase<'a>(module_id: ModuleId, phase: Phase, state: &mut State<'a>) ->
module_timing, module_timing,
solved_subs, solved_subs,
decls, decls,
finished_info,
ident_ids, ident_ids,
exposed_to_host: state.exposed_to_host.clone(), 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, subs,
procs, procs,
layout_cache, layout_cache,
finished_info,
} = found_specializations; } = found_specializations;
BuildTask::MakeSpecializations { BuildTask::MakeSpecializations {
@ -388,7 +391,6 @@ fn start_phase<'a>(module_id: ModuleId, phase: Phase, state: &mut State<'a>) ->
procs, procs,
layout_cache, layout_cache,
specializations_we_must_make, specializations_we_must_make,
finished_info,
} }
} }
} }
@ -399,11 +401,11 @@ pub struct LoadedModule {
pub module_id: ModuleId, pub module_id: ModuleId,
pub interns: Interns, pub interns: Interns,
pub solved: Solved<Subs>, pub solved: Solved<Subs>,
pub can_problems: Vec<roc_problem::can::Problem>, pub can_problems: MutMap<ModuleId, Vec<roc_problem::can::Problem>>,
pub type_problems: Vec<solve::TypeError>, pub type_problems: MutMap<ModuleId, Vec<solve::TypeError>>,
pub declarations_by_id: MutMap<ModuleId, Vec<Declaration>>, pub declarations_by_id: MutMap<ModuleId, Vec<Declaration>>,
pub exposed_to_host: MutMap<Symbol, Variable>, pub exposed_to_host: MutMap<Symbol, Variable>,
pub src: Box<str>, pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
pub timings: MutMap<ModuleId, ModuleTiming>, pub timings: MutMap<ModuleId, ModuleTiming>,
pub documentation: MutMap<ModuleId, ModuleDocumentation>, pub documentation: MutMap<ModuleId, ModuleDocumentation>,
} }
@ -417,6 +419,7 @@ pub enum BuildProblem<'a> {
struct ModuleHeader<'a> { struct ModuleHeader<'a> {
module_id: ModuleId, module_id: ModuleId,
module_name: ModuleName, module_name: ModuleName,
module_path: PathBuf,
exposed_ident_ids: IdentIds, exposed_ident_ids: IdentIds,
deps_by_name: MutMap<ModuleName, ModuleId>, deps_by_name: MutMap<ModuleName, ModuleId>,
imported_modules: MutSet<ModuleId>, imported_modules: MutSet<ModuleId>,
@ -427,11 +430,10 @@ struct ModuleHeader<'a> {
} }
#[derive(Debug)] #[derive(Debug)]
struct ConstrainedModule<'a> { struct ConstrainedModule {
module: Module, module: Module,
declarations: Vec<Declaration>, declarations: Vec<Declaration>,
imported_modules: MutSet<ModuleId>, imported_modules: MutSet<ModuleId>,
src: &'a str,
constraint: Constraint, constraint: Constraint,
ident_ids: IdentIds, ident_ids: IdentIds,
var_store: VarStore, var_store: VarStore,
@ -446,7 +448,6 @@ pub struct TypeCheckedModule<'a> {
pub solved_subs: Solved<Subs>, pub solved_subs: Solved<Subs>,
pub decls: Vec<Declaration>, pub decls: Vec<Declaration>,
pub ident_ids: IdentIds, pub ident_ids: IdentIds,
pub finished_info: FinishedInfo<'a>,
} }
#[derive(Debug)] #[derive(Debug)]
@ -456,7 +457,6 @@ pub struct FoundSpecializationsModule<'a> {
pub layout_cache: LayoutCache<'a>, pub layout_cache: LayoutCache<'a>,
pub procs: Procs<'a>, pub procs: Procs<'a>,
pub subs: Subs, pub subs: Subs,
pub finished_info: FinishedInfo<'a>,
} }
#[derive(Debug)] #[derive(Debug)]
@ -464,19 +464,26 @@ pub struct MonomorphizedModule<'a> {
pub module_id: ModuleId, pub module_id: ModuleId,
pub interns: Interns, pub interns: Interns,
pub subs: Subs, pub subs: Subs,
pub can_problems: Vec<roc_problem::can::Problem>, pub can_problems: MutMap<ModuleId, Vec<roc_problem::can::Problem>>,
pub type_problems: Vec<solve::TypeError>, pub type_problems: MutMap<ModuleId, Vec<solve::TypeError>>,
pub mono_problems: Vec<roc_mono::ir::MonoProblem>, pub mono_problems: MutMap<ModuleId, Vec<roc_mono::ir::MonoProblem>>,
pub procedures: MutMap<(Symbol, Layout<'a>), Proc<'a>>, pub procedures: MutMap<(Symbol, Layout<'a>), Proc<'a>>,
pub exposed_to_host: MutMap<Symbol, Variable>, pub exposed_to_host: MutMap<Symbol, Variable>,
pub src: Box<str>, pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
pub timings: MutMap<ModuleId, ModuleTiming>, pub timings: MutMap<ModuleId, ModuleTiming>,
} }
#[derive(Debug, Default)]
pub struct VariablySizedLayouts<'a> {
rigids: MutMap<Lowercase, Layout<'a>>,
aliases: MutMap<Symbol, Layout<'a>>,
}
#[derive(Debug)] #[derive(Debug)]
struct ParsedModule<'a> { struct ParsedModule<'a> {
module_id: ModuleId, module_id: ModuleId,
module_name: ModuleName, module_name: ModuleName,
module_path: PathBuf,
src: &'a str, src: &'a str,
module_timing: ModuleTiming, module_timing: ModuleTiming,
deps_by_name: MutMap<ModuleName, ModuleId>, deps_by_name: MutMap<ModuleName, ModuleId>,
@ -498,12 +505,11 @@ enum Msg<'a> {
Header(ModuleHeader<'a>), Header(ModuleHeader<'a>),
Parsed(ParsedModule<'a>), Parsed(ParsedModule<'a>),
CanonicalizedAndConstrained { CanonicalizedAndConstrained {
constrained_module: ConstrainedModule<'a>, constrained_module: ConstrainedModule,
canonicalization_problems: Vec<roc_problem::can::Problem>, canonicalization_problems: Vec<roc_problem::can::Problem>,
module_docs: ModuleDocumentation, module_docs: ModuleDocumentation,
}, },
SolvedTypes { SolvedTypes {
src: &'a str,
module_id: ModuleId, module_id: ModuleId,
ident_ids: IdentIds, ident_ids: IdentIds,
solved_module: SolvedModule, solved_module: SolvedModule,
@ -515,7 +521,6 @@ enum Msg<'a> {
solved_subs: Solved<Subs>, solved_subs: Solved<Subs>,
exposed_vars_by_symbol: Vec<(Symbol, Variable)>, exposed_vars_by_symbol: Vec<(Symbol, Variable)>,
documentation: MutMap<ModuleId, ModuleDocumentation>, documentation: MutMap<ModuleId, ModuleDocumentation>,
src: &'a str,
}, },
FoundSpecializations { FoundSpecializations {
module_id: ModuleId, module_id: ModuleId,
@ -524,7 +529,6 @@ enum Msg<'a> {
procs: Procs<'a>, procs: Procs<'a>,
problems: Vec<roc_mono::ir::MonoProblem>, problems: Vec<roc_mono::ir::MonoProblem>,
solved_subs: Solved<Subs>, solved_subs: Solved<Subs>,
finished_info: FinishedInfo<'a>,
}, },
MadeSpecializations { MadeSpecializations {
module_id: ModuleId, module_id: ModuleId,
@ -534,7 +538,6 @@ enum Msg<'a> {
procedures: MutMap<(Symbol, Layout<'a>), Proc<'a>>, procedures: MutMap<(Symbol, Layout<'a>), Proc<'a>>,
problems: Vec<roc_mono::ir::MonoProblem>, problems: Vec<roc_mono::ir::MonoProblem>,
subs: Subs, subs: Subs,
finished_info: FinishedInfo<'a>,
}, },
/// The task is to only typecheck AND monomorphize modules /// The task is to only typecheck AND monomorphize modules
@ -542,16 +545,9 @@ enum Msg<'a> {
FinishedAllSpecialization { FinishedAllSpecialization {
subs: Subs, subs: Subs,
exposed_to_host: MutMap<Symbol, Variable>, 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)] #[derive(Debug)]
struct State<'a> { struct State<'a> {
pub root_id: ModuleId, pub root_id: ModuleId,
@ -559,10 +555,7 @@ struct State<'a> {
pub stdlib: StdLib, pub stdlib: StdLib,
pub exposed_types: SubsByModule, 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 headers_parsed: MutSet<ModuleId>,
pub type_problems: std::vec::Vec<solve::TypeError>,
pub module_cache: ModuleCache<'a>, pub module_cache: ModuleCache<'a>,
pub dependencies: Dependencies, pub dependencies: Dependencies,
@ -702,7 +695,6 @@ enum BuildTask<'a> {
constraint: Constraint, constraint: Constraint,
var_store: VarStore, var_store: VarStore,
declarations: Vec<Declaration>, declarations: Vec<Declaration>,
src: &'a str,
}, },
BuildPendingSpecializations { BuildPendingSpecializations {
module_timing: ModuleTiming, module_timing: ModuleTiming,
@ -711,7 +703,6 @@ enum BuildTask<'a> {
module_id: ModuleId, module_id: ModuleId,
ident_ids: IdentIds, ident_ids: IdentIds,
decls: Vec<Declaration>, decls: Vec<Declaration>,
finished_info: FinishedInfo<'a>,
exposed_to_host: MutMap<Symbol, Variable>, exposed_to_host: MutMap<Symbol, Variable>,
}, },
MakeSpecializations { MakeSpecializations {
@ -720,7 +711,6 @@ enum BuildTask<'a> {
subs: Subs, subs: Subs,
procs: Procs<'a>, procs: Procs<'a>,
layout_cache: LayoutCache<'a>, layout_cache: LayoutCache<'a>,
finished_info: FinishedInfo<'a>,
specializations_we_must_make: ExternalSpecializations, specializations_we_must_make: ExternalSpecializations,
}, },
} }
@ -1105,9 +1095,6 @@ where
exposed_types, exposed_types,
headers_parsed, headers_parsed,
loading_started, loading_started,
can_problems: std::vec::Vec::new(),
type_problems: std::vec::Vec::new(),
mono_problems: std::vec::Vec::new(),
arc_modules, arc_modules,
constrained_ident_ids: IdentIds::exposed_builtins(0), constrained_ident_ids: IdentIds::exposed_builtins(0),
ident_ids_by_module, ident_ids_by_module,
@ -1140,7 +1127,6 @@ where
solved_subs, solved_subs,
exposed_vars_by_symbol, exposed_vars_by_symbol,
documentation, documentation,
src,
} => { } => {
// We're done! There should be no more messages pending. // We're done! There should be no more messages pending.
debug_assert!(msg_rx.is_empty()); debug_assert!(msg_rx.is_empty());
@ -1157,13 +1143,11 @@ where
solved_subs, solved_subs,
exposed_vars_by_symbol, exposed_vars_by_symbol,
documentation, documentation,
src,
))); )));
} }
Msg::FinishedAllSpecialization { Msg::FinishedAllSpecialization {
subs, subs,
exposed_to_host, exposed_to_host,
src,
} => { } => {
// We're done! There should be no more messages pending. // We're done! There should be no more messages pending.
debug_assert!(msg_rx.is_empty()); debug_assert!(msg_rx.is_empty());
@ -1179,7 +1163,6 @@ where
state, state,
subs, subs,
exposed_to_host, exposed_to_host,
src,
))); )));
} }
msg => { msg => {
@ -1267,6 +1250,11 @@ fn update<'a>(
Ok(state) Ok(state)
} }
Parsed(parsed) => { Parsed(parsed) => {
state
.module_cache
.sources
.insert(parsed.module_id, (parsed.module_path.clone(), parsed.src));
let module_id = parsed.module_id; let module_id = parsed.module_id;
state.module_cache.parsed.insert(parsed.module_id, parsed); state.module_cache.parsed.insert(parsed.module_id, parsed);
@ -1288,7 +1276,10 @@ fn update<'a>(
} => { } => {
let module_id = constrained_module.module.module_id; let module_id = constrained_module.module.module_id;
log!("generated constraints for {:?}", 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 state
.module_cache .module_cache
@ -1318,7 +1309,6 @@ fn update<'a>(
Ok(state) Ok(state)
} }
SolvedTypes { SolvedTypes {
src,
module_id, module_id,
ident_ids, ident_ids,
solved_module, solved_module,
@ -1329,7 +1319,10 @@ fn update<'a>(
log!("solved types for {:?}", module_id); log!("solved types for {:?}", module_id);
module_timing.end_time = SystemTime::now(); 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); let work = state.dependencies.notify(module_id, Phase::SolveTypes);
@ -1357,7 +1350,6 @@ fn update<'a>(
solved_subs, solved_subs,
exposed_vars_by_symbol: solved_module.exposed_vars_by_symbol, exposed_vars_by_symbol: solved_module.exposed_vars_by_symbol,
documentation, documentation,
src,
}) })
.map_err(|_| LoadingProblem::MsgChannelDied)?; .map_err(|_| LoadingProblem::MsgChannelDied)?;
@ -1382,11 +1374,6 @@ fn update<'a>(
if state.goal_phase > Phase::SolveTypes { if state.goal_phase > Phase::SolveTypes {
let layout_cache = state.layout_caches.pop().unwrap_or_default(); 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 { let typechecked = TypeCheckedModule {
module_id, module_id,
decls, decls,
@ -1394,7 +1381,6 @@ fn update<'a>(
ident_ids, ident_ids,
module_timing, module_timing,
layout_cache, layout_cache,
finished_info,
}; };
state state
@ -1417,7 +1403,6 @@ fn update<'a>(
FoundSpecializations { FoundSpecializations {
module_id, module_id,
procs, procs,
finished_info,
solved_subs, solved_subs,
ident_ids, ident_ids,
layout_cache, layout_cache,
@ -1443,7 +1428,6 @@ fn update<'a>(
layout_cache, layout_cache,
module_id, module_id,
procs, procs,
finished_info,
ident_ids, ident_ids,
subs, subs,
}; };
@ -1468,7 +1452,6 @@ fn update<'a>(
module_id, module_id,
ident_ids, ident_ids,
subs, subs,
finished_info,
procedures, procedures,
external_specializations_requested, external_specializations_requested,
problems, problems,
@ -1476,7 +1459,7 @@ fn update<'a>(
} => { } => {
log!("made specializations for {:?}", module_id); 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 { for (module_id, requested) in external_specializations_requested {
let existing = match state let existing = match state
@ -1512,7 +1495,6 @@ fn update<'a>(
subs, subs,
// TODO thread through mono problems // TODO thread through mono problems
exposed_to_host: state.exposed_to_host.clone(), exposed_to_host: state.exposed_to_host.clone(),
src: finished_info.src,
}) })
.map_err(|_| LoadingProblem::MsgChannelDied)?; .map_err(|_| LoadingProblem::MsgChannelDied)?;
@ -1542,7 +1524,6 @@ fn finish_specialization<'a>(
state: State<'a>, state: State<'a>,
subs: Subs, subs: Subs,
exposed_to_host: MutMap<Symbol, Variable>, exposed_to_host: MutMap<Symbol, Variable>,
src: &'a str,
) -> MonomorphizedModule<'a> { ) -> MonomorphizedModule<'a> {
let module_ids = Arc::try_unwrap(state.arc_modules) let module_ids = Arc::try_unwrap(state.arc_modules)
.unwrap_or_else(|_| panic!("There were still outstanding Arc references to module_ids")) .unwrap_or_else(|_| panic!("There were still outstanding Arc references to module_ids"))
@ -1554,12 +1535,23 @@ fn finish_specialization<'a>(
}; };
let State { let State {
procedures,
module_cache,
..
} = state;
let ModuleCache {
mono_problems, mono_problems,
type_problems, type_problems,
can_problems, can_problems,
procedures, sources,
.. ..
} = state; } = module_cache;
let sources = sources
.into_iter()
.map(|(id, (path, src))| (id, (path, src.into())))
.collect();
MonomorphizedModule { MonomorphizedModule {
can_problems, can_problems,
@ -1570,7 +1562,7 @@ fn finish_specialization<'a>(
subs, subs,
interns, interns,
procedures, procedures,
src: src.into(), sources,
timings: state.timings, timings: state.timings,
} }
} }
@ -1580,7 +1572,6 @@ fn finish<'a>(
solved: Solved<Subs>, solved: Solved<Subs>,
exposed_vars_by_symbol: Vec<(Symbol, Variable)>, exposed_vars_by_symbol: Vec<(Symbol, Variable)>,
documentation: MutMap<ModuleId, ModuleDocumentation>, documentation: MutMap<ModuleId, ModuleDocumentation>,
src: &'a str,
) -> LoadedModule { ) -> LoadedModule {
let module_ids = Arc::try_unwrap(state.arc_modules) let module_ids = Arc::try_unwrap(state.arc_modules)
.unwrap_or_else(|_| panic!("There were still outstanding Arc references to module_ids")) .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, 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 { LoadedModule {
module_id: state.root_id, module_id: state.root_id,
interns, interns,
solved, solved,
can_problems: state.can_problems, can_problems: state.module_cache.can_problems,
type_problems: state.type_problems, type_problems: state.module_cache.type_problems,
declarations_by_id: state.declarations_by_id, declarations_by_id: state.declarations_by_id,
exposed_to_host: exposed_vars_by_symbol.into_iter().collect(), exposed_to_host: exposed_vars_by_symbol.into_iter().collect(),
src: src.into(), sources,
timings: state.timings, timings: state.timings,
documentation, documentation,
} }
@ -1693,6 +1691,7 @@ fn parse_header<'a>(
match parsed { match parsed {
Ok((ast::Module::Interface { header }, parse_state)) => Ok(send_header( Ok((ast::Module::Interface { header }, parse_state)) => Ok(send_header(
header.name, header.name,
filename,
header.exposes.into_bump_slice(), header.exposes.into_bump_slice(),
header.imports.into_bump_slice(), header.imports.into_bump_slice(),
parse_state, parse_state,
@ -1702,6 +1701,7 @@ fn parse_header<'a>(
)), )),
Ok((ast::Module::App { header }, parse_state)) => Ok(send_header( Ok((ast::Module::App { header }, parse_state)) => Ok(send_header(
header.name, header.name,
filename,
header.provides.into_bump_slice(), header.provides.into_bump_slice(),
header.imports.into_bump_slice(), header.imports.into_bump_slice(),
parse_state, parse_state,
@ -1776,6 +1776,7 @@ fn load_from_str<'a>(
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn send_header<'a>( fn send_header<'a>(
name: Located<roc_parse::header::ModuleName<'a>>, name: Located<roc_parse::header::ModuleName<'a>>,
filename: PathBuf,
exposes: &'a [Located<ExposesEntry<'a>>], exposes: &'a [Located<ExposesEntry<'a>>],
imports: &'a [Located<ImportsEntry<'a>>], imports: &'a [Located<ImportsEntry<'a>>],
parse_state: parser::State<'a>, parse_state: parser::State<'a>,
@ -1895,6 +1896,7 @@ fn send_header<'a>(
home, home,
Msg::Header(ModuleHeader { Msg::Header(ModuleHeader {
module_id: home, module_id: home,
module_path: filename,
exposed_ident_ids: ident_ids, exposed_ident_ids: ident_ids,
module_name: declared_name, module_name: declared_name,
imported_modules, imported_modules,
@ -1914,7 +1916,6 @@ impl<'a> BuildTask<'a> {
module: Module, module: Module,
ident_ids: IdentIds, ident_ids: IdentIds,
module_timing: ModuleTiming, module_timing: ModuleTiming,
src: &'a str,
constraint: Constraint, constraint: Constraint,
var_store: VarStore, var_store: VarStore,
imported_modules: MutSet<ModuleId>, imported_modules: MutSet<ModuleId>,
@ -1954,7 +1955,6 @@ impl<'a> BuildTask<'a> {
imported_symbols, imported_symbols,
constraint, constraint,
var_store, var_store,
src,
declarations, declarations,
module_timing, module_timing,
} }
@ -1970,7 +1970,6 @@ fn run_solve<'a>(
constraint: Constraint, constraint: Constraint,
mut var_store: VarStore, mut var_store: VarStore,
decls: Vec<Declaration>, decls: Vec<Declaration>,
src: &'a str,
) -> Msg<'a> { ) -> Msg<'a> {
// We have more constraining work to do now, so we'll add it to our timings. // We have more constraining work to do now, so we'll add it to our timings.
let constrain_start = SystemTime::now(); let constrain_start = SystemTime::now();
@ -2012,7 +2011,6 @@ fn run_solve<'a>(
// Send the subs to the main thread for processing, // Send the subs to the main thread for processing,
Msg::SolvedTypes { Msg::SolvedTypes {
src,
module_id, module_id,
solved_subs, solved_subs,
ident_ids, ident_ids,
@ -2041,7 +2039,6 @@ fn canonicalize_and_constrain<'a>(
exposed_imports, exposed_imports,
imported_modules, imported_modules,
mut module_timing, mut module_timing,
src,
.. ..
} = parsed; } = parsed;
@ -2094,7 +2091,6 @@ fn canonicalize_and_constrain<'a>(
module, module,
declarations: module_output.declarations, declarations: module_output.declarations,
imported_modules, imported_modules,
src,
var_store, var_store,
constraint, constraint,
ident_ids: module_output.ident_ids, 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, deps_by_name,
exposed_ident_ids, exposed_ident_ids,
exposed_imports, exposed_imports,
module_path,
.. ..
} = header; } = header;
let parsed = ParsedModule { let parsed = ParsedModule {
module_id, module_id,
module_name, module_name,
module_path,
deps_by_name, deps_by_name,
exposed_ident_ids, exposed_ident_ids,
exposed_imports, exposed_imports,
@ -2202,7 +2200,6 @@ fn make_specializations<'a>(
mut procs: Procs<'a>, mut procs: Procs<'a>,
mut layout_cache: LayoutCache<'a>, mut layout_cache: LayoutCache<'a>,
specializations_we_must_make: ExternalSpecializations, specializations_we_must_make: ExternalSpecializations,
finished_info: FinishedInfo<'a>,
) -> Msg<'a> { ) -> Msg<'a> {
let mut mono_problems = Vec::new(); let mut mono_problems = Vec::new();
// do the thing // do the thing
@ -2238,7 +2235,6 @@ fn make_specializations<'a>(
procedures, procedures,
problems: mono_problems, problems: mono_problems,
subs, subs,
finished_info,
external_specializations_requested, external_specializations_requested,
} }
} }
@ -2255,7 +2251,6 @@ fn build_pending_specializations<'a>(
mut layout_cache: LayoutCache<'a>, mut layout_cache: LayoutCache<'a>,
// TODO remove // TODO remove
exposed_to_host: MutMap<Symbol, Variable>, exposed_to_host: MutMap<Symbol, Variable>,
finished_info: FinishedInfo<'a>,
) -> Msg<'a> { ) -> Msg<'a> {
let mut procs = Procs::default(); let mut procs = Procs::default();
@ -2309,7 +2304,6 @@ fn build_pending_specializations<'a>(
layout_cache, layout_cache,
procs, procs,
problems, 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( 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) 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 { let proc = PartialProc {
@ -2457,7 +2463,6 @@ fn run_task<'a>(
var_store, var_store,
ident_ids, ident_ids,
declarations, declarations,
src,
} => Ok(run_solve( } => Ok(run_solve(
module, module,
ident_ids, ident_ids,
@ -2466,7 +2471,6 @@ fn run_task<'a>(
constraint, constraint,
var_store, var_store,
declarations, declarations,
src,
)), )),
BuildPendingSpecializations { BuildPendingSpecializations {
module_id, module_id,
@ -2475,7 +2479,6 @@ fn run_task<'a>(
module_timing, module_timing,
layout_cache, layout_cache,
solved_subs, solved_subs,
finished_info,
exposed_to_host, exposed_to_host,
} => Ok(build_pending_specializations( } => Ok(build_pending_specializations(
arena, arena,
@ -2486,7 +2489,6 @@ fn run_task<'a>(
module_timing, module_timing,
layout_cache, layout_cache,
exposed_to_host, exposed_to_host,
finished_info,
)), )),
MakeSpecializations { MakeSpecializations {
module_id, module_id,
@ -2495,7 +2497,6 @@ fn run_task<'a>(
procs, procs,
layout_cache, layout_cache,
specializations_we_must_make, specializations_we_must_make,
finished_info,
} => Ok(make_specializations( } => Ok(make_specializations(
arena, arena,
module_id, module_id,
@ -2504,7 +2505,6 @@ fn run_task<'a>(
procs, procs,
layout_cache, layout_cache,
specializations_we_must_make, specializations_we_must_make,
finished_info,
)), )),
}?; }?;

View file

@ -43,10 +43,21 @@ mod test_load {
src_dir.as_path(), src_dir.as_path(),
subs_by_module, 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()); let home = loaded_module.module_id;
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 expected_name = loaded_module let expected_name = loaded_module
.interns .interns
@ -87,8 +98,17 @@ mod test_load {
let home = loaded_module.module_id; let home = loaded_module.module_id;
let mut subs = loaded_module.solved.into_inner(); let mut subs = loaded_module.solved.into_inner();
assert_eq!(loaded_module.can_problems, Vec::new()); assert_eq!(
assert_eq!(loaded_module.type_problems, Vec::new()); 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() { for decl in loaded_module.declarations_by_id.remove(&home).unwrap() {
match decl { match decl {
@ -141,9 +161,19 @@ mod test_load {
); );
let mut loaded_module = loaded.expect("Test module failed to 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!(
assert_eq!(loaded_module.type_problems, Vec::new()); 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 let def_count: usize = loaded_module
.declarations_by_id .declarations_by_id

View file

@ -44,10 +44,21 @@ mod test_uniq_load {
src_dir.as_path(), src_dir.as_path(),
subs_by_module, 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()); let home = loaded_module.module_id;
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 expected_name = loaded_module let expected_name = loaded_module
.interns .interns
@ -88,8 +99,17 @@ mod test_uniq_load {
let home = loaded_module.module_id; let home = loaded_module.module_id;
let mut subs = loaded_module.solved.into_inner(); let mut subs = loaded_module.solved.into_inner();
assert_eq!(loaded_module.can_problems, Vec::new()); assert_eq!(
assert_eq!(loaded_module.type_problems, Vec::new()); 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() { for decl in loaded_module.declarations_by_id.remove(&home).unwrap() {
match decl { match decl {
@ -142,9 +162,19 @@ mod test_uniq_load {
); );
let mut loaded_module = loaded.expect("Test module failed to 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!(
assert_eq!(loaded_module.type_problems, Vec::new()); 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 let def_count: usize = loaded_module
.declarations_by_id .declarations_by_id

View file

@ -14,6 +14,7 @@ pub enum LowLevel {
ListRepeat, ListRepeat,
ListReverse, ListReverse,
ListConcat, ListConcat,
ListContains,
ListAppend, ListAppend,
ListPrepend, ListPrepend,
ListJoin, ListJoin,

View file

@ -691,6 +691,7 @@ define_builtins! {
15 LIST_PREPEND: "prepend" 15 LIST_PREPEND: "prepend"
16 LIST_JOIN: "join" 16 LIST_JOIN: "join"
17 LIST_KEEP_IF: "keepIf" 17 LIST_KEEP_IF: "keepIf"
18 LIST_CONTAINS: "contains"
} }
5 RESULT: "Result" => { 5 RESULT: "Result" => {
0 RESULT_RESULT: "Result" imported // the Result.Result type alias 0 RESULT_RESULT: "Result" imported // the Result.Result type alias

View file

@ -520,6 +520,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
ListJoin => arena.alloc_slice_copy(&[irrelevant]), ListJoin => arena.alloc_slice_copy(&[irrelevant]),
ListMap => arena.alloc_slice_copy(&[owned, irrelevant]), ListMap => arena.alloc_slice_copy(&[owned, irrelevant]),
ListKeepIf => 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]), ListWalkRight => arena.alloc_slice_copy(&[borrowed, irrelevant, owned]),
Eq | NotEq | And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumMul | NumGt Eq | NotEq | And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumMul | NumGt

View file

@ -147,7 +147,7 @@ type LiveVarSet = MutSet<Symbol>;
type JPLiveVarMap = MutMap<JoinPointId, LiveVarSet>; type JPLiveVarMap = MutMap<JoinPointId, LiveVarSet>;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Context<'a> { struct Context<'a> {
arena: &'a Bump, arena: &'a Bump,
vars: VarMap, vars: VarMap,
jp_live_vars: JPLiveVarMap, // map: join point => live variables jp_live_vars: JPLiveVarMap, // map: join point => live variables

View file

@ -28,6 +28,12 @@ pub struct PartialProc<'a> {
pub is_self_recursive: bool, 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)] #[derive(Clone, Debug, PartialEq)]
pub enum CapturedSymbols<'a> { pub enum CapturedSymbols<'a> {
None, None,
@ -46,12 +52,34 @@ impl<'a> CapturedSymbols<'a> {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct PendingSpecialization { pub struct PendingSpecialization {
solved_type: SolvedType, solved_type: SolvedType,
host_exposed_aliases: MutMap<Symbol, SolvedType>,
} }
impl PendingSpecialization { impl PendingSpecialization {
pub fn from_var(subs: &Subs, var: Variable) -> Self { pub fn from_var(subs: &Subs, var: Variable) -> Self {
let solved_type = SolvedType::from_var(subs, var); 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 closure_data_layout: Option<Layout<'a>>,
pub ret_layout: Layout<'a>, pub ret_layout: Layout<'a>,
pub is_self_recursive: SelfRecursive, 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)] #[derive(Clone, Debug, PartialEq)]
@ -398,8 +436,7 @@ impl<'a> Procs<'a> {
// Changing it to use .entry() would necessarily make it incorrect. // Changing it to use .entry() would necessarily make it incorrect.
#[allow(clippy::map_entry)] #[allow(clippy::map_entry)]
if !already_specialized { if !already_specialized {
let solved_type = SolvedType::from_var(env.subs, annotation); let pending = PendingSpecialization::from_var(env.subs, annotation);
let pending = PendingSpecialization { solved_type };
let pattern_symbols = pattern_symbols.into_bump_slice(); let pattern_symbols = pattern_symbols.into_bump_slice();
match &mut self.pending_specializations { match &mut self.pending_specializations {
@ -478,6 +515,7 @@ impl<'a> Procs<'a> {
name: Symbol, name: Symbol,
layout: Layout<'a>, layout: Layout<'a>,
subs: &Subs, subs: &Subs,
opt_annotation: Option<roc_can::def::Annotation>,
fn_var: Variable, fn_var: Variable,
) { ) {
let tuple = (name, layout); 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. // We're done with that tuple, so move layout back out to avoid cloning it.
let (name, layout) = tuple; 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. // This should only be called when pending_specializations is Some.
// Otherwise, it's being called in the wrong pass! // Otherwise, it's being called in the wrong pass!
@ -971,10 +1016,11 @@ impl<'a> Stmt<'a> {
pub fn new( pub fn new(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
can_expr: roc_can::expr::Expr, can_expr: roc_can::expr::Expr,
var: Variable,
procs: &mut Procs<'a>, procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>, layout_cache: &mut LayoutCache<'a>,
) -> Self { ) -> 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> 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, name,
layout_cache, layout_cache,
solved_type, solved_type,
MutMap::default(),
partial_proc, partial_proc,
) { ) {
Ok((proc, layout)) => { Ok((proc, layout)) => {
@ -1386,6 +1433,7 @@ fn specialize_external<'a>(
proc_name: Symbol, proc_name: Symbol,
layout_cache: &mut LayoutCache<'a>, layout_cache: &mut LayoutCache<'a>,
fn_var: Variable, fn_var: Variable,
host_exposed_variables: &[(Symbol, Variable)],
partial_proc: PartialProc<'a>, partial_proc: PartialProc<'a>,
) -> Result<Proc<'a>, LayoutProblem> { ) -> Result<Proc<'a>, LayoutProblem> {
let PartialProc { let PartialProc {
@ -1405,7 +1453,7 @@ fn specialize_external<'a>(
let is_valid = matches!(unified, roc_unify::unify::Unified::Success(_)); let is_valid = matches!(unified, roc_unify::unify::Unified::Success(_));
debug_assert!(is_valid); 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 // if this is a closure, add the closure record argument
let pattern_symbols = if let CapturedSymbols::Captured(_) = captured_symbols { 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 // reset subs, so we don't get type errors when specializing for a different signature
layout_cache.rollback_to(cache_snapshot); layout_cache.rollback_to(cache_snapshot);
env.subs.rollback_to(snapshot); env.subs.rollback_to(snapshot);
@ -1472,6 +1539,7 @@ fn specialize_external<'a>(
closure_data_layout, closure_data_layout,
ret_layout, ret_layout,
is_self_recursive: recursivity, is_self_recursive: recursivity,
host_exposed_layouts,
}; };
Ok(proc) Ok(proc)
@ -1692,51 +1760,80 @@ fn specialize<'a>(
pending: PendingSpecialization, pending: PendingSpecialization,
partial_proc: PartialProc<'a>, partial_proc: PartialProc<'a>,
) -> Result<(Proc<'a>, Layout<'a>), LayoutProblem> { ) -> Result<(Proc<'a>, Layout<'a>), LayoutProblem> {
let PendingSpecialization { solved_type } = pending; let PendingSpecialization {
solved_type,
host_exposed_aliases,
} = pending;
specialize_solved_type( specialize_solved_type(
env, env,
procs, procs,
proc_name, proc_name,
layout_cache, layout_cache,
solved_type, solved_type,
host_exposed_aliases,
partial_proc, 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>( fn specialize_solved_type<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
procs: &mut Procs<'a>, procs: &mut Procs<'a>,
proc_name: Symbol, proc_name: Symbol,
layout_cache: &mut LayoutCache<'a>, layout_cache: &mut LayoutCache<'a>,
solved_type: SolvedType, solved_type: SolvedType,
host_exposed_aliases: MutMap<Symbol, SolvedType>,
partial_proc: PartialProc<'a>, partial_proc: PartialProc<'a>,
) -> Result<(Proc<'a>, Layout<'a>), LayoutProblem> { ) -> Result<(Proc<'a>, Layout<'a>), LayoutProblem> {
// add the specializations that other modules require of us // add the specializations that other modules require of us
use roc_solve::solve::{insert_type_into_subs, instantiate_rigids}; use roc_solve::solve::instantiate_rigids;
use roc_types::solved_types::{to_type, FreeVars};
use roc_types::subs::VarStore;
let snapshot = env.subs.snapshot(); let snapshot = env.subs.snapshot();
let cache_snapshot = layout_cache.snapshot(); let cache_snapshot = layout_cache.snapshot();
let mut free_vars = FreeVars::default(); let fn_var = introduce_solved_type_to_subs(env, &solved_type);
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);
// make sure rigid variables in the annotation are converted to flex variables // make sure rigid variables in the annotation are converted to flex variables
instantiate_rigids(env.subs, partial_proc.annotation); 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) => { Ok(proc) => {
let layout = layout_cache let layout = layout_cache
.from_var(&env.arena, fn_var, env.subs) .from_var(&env.arena, fn_var, env.subs)
@ -1789,6 +1886,7 @@ impl<'a> FunctionLayouts<'a> {
pub fn with_hole<'a>( pub fn with_hole<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
can_expr: roc_can::expr::Expr, can_expr: roc_can::expr::Expr,
variable: Variable,
procs: &mut Procs<'a>, procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>, layout_cache: &mut LayoutCache<'a>,
assigned: Symbol, assigned: Symbol,
@ -1865,7 +1963,15 @@ pub fn with_hole<'a>(
return_type, 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( return with_hole(
env, env,
def.loc_expr.value, def.loc_expr.value,
def.expr_var,
procs, procs,
layout_cache, layout_cache,
assigned, assigned,
@ -1887,7 +1994,15 @@ pub fn with_hole<'a>(
} }
// continue with the default path // 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 // a variable is aliased
if let roc_can::expr::Expr::Var(original) = def.loc_expr.value { if let roc_can::expr::Expr::Var(original) = def.loc_expr.value {
@ -1898,6 +2013,7 @@ pub fn with_hole<'a>(
with_hole( with_hole(
env, env,
def.loc_expr.value, def.loc_expr.value,
def.expr_var,
procs, procs,
layout_cache, layout_cache,
symbol, symbol,
@ -1927,7 +2043,15 @@ pub fn with_hole<'a>(
} }
// convert the continuation // 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(); let outer_symbol = env.unique_symbol();
stmt = store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt) stmt = store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt)
@ -1937,6 +2061,7 @@ pub fn with_hole<'a>(
with_hole( with_hole(
env, env,
def.loc_expr.value, def.loc_expr.value,
def.expr_var,
procs, procs,
layout_cache, layout_cache,
outer_symbol, outer_symbol,
@ -1983,7 +2108,15 @@ pub fn with_hole<'a>(
unreachable!("recursive value does not have Identifier pattern") 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) => { Var(symbol) => {
if procs.module_thunks.contains(&symbol) { if procs.module_thunks.contains(&symbol) {
@ -2003,6 +2136,27 @@ pub fn with_hole<'a>(
); );
return result; 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 // 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 { Tag {
variant_var, variant_var,
name: tag_name, 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 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_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); let mut can_fields = Vec::with_capacity_in(fields.len(), env.arena);
for (label, layout) in sorted_fields.into_iter() { for (label, _, _) in sorted_fields.into_iter() {
field_layouts.push(layout);
// TODO how should function pointers be handled here? // TODO how should function pointers be handled here?
match fields.remove(&label) { match fields.remove(&label) {
Some(field) => match can_reuse_symbol(procs, &field.loc_expr.value) { Some(field) => match can_reuse_symbol(procs, &field.loc_expr.value) {
@ -2190,6 +2338,7 @@ pub fn with_hole<'a>(
stmt = with_hole( stmt = with_hole(
env, env,
field.loc_expr.value, field.loc_expr.value,
field.var,
procs, procs,
layout_cache, layout_cache,
*symbol, *symbol,
@ -2226,6 +2375,7 @@ pub fn with_hole<'a>(
let mut stmt = with_hole( let mut stmt = with_hole(
env, env,
final_else.value, final_else.value,
branch_var,
procs, procs,
layout_cache, layout_cache,
assigned, assigned,
@ -2237,6 +2387,7 @@ pub fn with_hole<'a>(
let then = with_hole( let then = with_hole(
env, env,
loc_then.value, loc_then.value,
branch_var,
procs, procs,
layout_cache, layout_cache,
assigned, assigned,
@ -2257,6 +2408,7 @@ pub fn with_hole<'a>(
stmt = with_hole( stmt = with_hole(
env, env,
loc_cond.value, loc_cond.value,
cond_var,
procs, procs,
layout_cache, layout_cache,
branching_symbol, branching_symbol,
@ -2275,6 +2427,7 @@ pub fn with_hole<'a>(
let mut stmt = with_hole( let mut stmt = with_hole(
env, env,
final_else.value, final_else.value,
branch_var,
procs, procs,
layout_cache, layout_cache,
assigned_in_jump, assigned_in_jump,
@ -2286,6 +2439,7 @@ pub fn with_hole<'a>(
let then = with_hole( let then = with_hole(
env, env,
loc_then.value, loc_then.value,
branch_var,
procs, procs,
layout_cache, layout_cache,
assigned_in_jump, assigned_in_jump,
@ -2306,6 +2460,7 @@ pub fn with_hole<'a>(
stmt = with_hole( stmt = with_hole(
env, env,
loc_cond.value, loc_cond.value,
cond_var,
procs, procs,
layout_cache, layout_cache,
branching_symbol, 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 field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena);
let mut current = 0; 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 { match opt_field_layout {
Err(_) => { Err(_) => {
// this was an optional field, and now does not exist! // 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 fields = Vec::with_capacity_in(sorted_fields.len(), env.arena);
let mut current = 0; 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 { match opt_field_layout {
Err(_) => { Err(_) => {
debug_assert!(!updates.contains_key(&label)); debug_assert!(!updates.contains_key(&label));
@ -2957,6 +3112,7 @@ pub fn with_hole<'a>(
result = with_hole( result = with_hole(
env, env,
loc_expr.value, loc_expr.value,
fn_var,
procs, procs,
layout_cache, layout_cache,
function_symbol, function_symbol,
@ -3004,6 +3160,7 @@ pub fn with_hole<'a>(
pub fn from_can<'a>( pub fn from_can<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
variable: Variable,
can_expr: roc_can::expr::Expr, can_expr: roc_can::expr::Expr,
procs: &mut Procs<'a>, procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>, layout_cache: &mut LayoutCache<'a>,
@ -3056,11 +3213,11 @@ pub fn from_can<'a>(
.from_var(env.arena, cond_var, env.subs) .from_var(env.arena, cond_var, env.subs)
.expect("invalid cond_layout"); .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() { for (loc_cond, loc_then) in branches.into_iter().rev() {
let branching_symbol = env.unique_symbol(); 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 { stmt = Stmt::Cond {
cond_symbol: branching_symbol, cond_symbol: branching_symbol,
@ -3076,6 +3233,7 @@ pub fn from_can<'a>(
stmt = with_hole( stmt = with_hole(
env, env,
loc_cond.value, loc_cond.value,
cond_var,
procs, procs,
layout_cache, layout_cache,
branching_symbol, branching_symbol,
@ -3128,7 +3286,7 @@ pub fn from_can<'a>(
unreachable!("recursive value does not have Identifier pattern") 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) => { LetNonRec(def, cont, outer_annotation) => {
if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value { if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value {
@ -3178,7 +3336,7 @@ pub fn from_can<'a>(
return_type, return_type,
); );
return from_can(env, cont.value, procs, layout_cache); return from_can(env, variable, cont.value, procs, layout_cache);
} }
_ => unreachable!(), _ => unreachable!(),
} }
@ -3186,7 +3344,7 @@ pub fn from_can<'a>(
match def.loc_expr.value { match def.loc_expr.value {
roc_can::expr::Expr::Var(original) => { 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 // a variable is aliased
substitute_in_exprs(env.arena, &mut rest, *symbol, original); substitute_in_exprs(env.arena, &mut rest, *symbol, original);
@ -3231,7 +3389,7 @@ pub fn from_can<'a>(
nested_annotation, 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) => { roc_can::expr::Expr::LetRec(nested_defs, nested_cont, nested_annotation) => {
use roc_can::expr::Expr::*; use roc_can::expr::Expr::*;
@ -3272,13 +3430,14 @@ pub fn from_can<'a>(
nested_annotation, 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( return with_hole(
env, env,
def.loc_expr.value, def.loc_expr.value,
def.expr_var,
procs, procs,
layout_cache, layout_cache,
*symbol, *symbol,
@ -3292,11 +3451,19 @@ pub fn from_can<'a>(
let mono_pattern = from_can_pattern(env, layout_cache, &def.loc_pattern.value); let mono_pattern = from_can_pattern(env, layout_cache, &def.loc_pattern.value);
if let Pattern::Identifier(symbol) = mono_pattern { if let Pattern::Identifier(symbol) = mono_pattern {
let hole = env let hole =
.arena env.arena
.alloc(from_can(env, cont.value, procs, layout_cache)); .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 { } else {
let context = crate::exhaustive::Context::BadDestruct; let context = crate::exhaustive::Context::BadDestruct;
match crate::exhaustive::check( match crate::exhaustive::check(
@ -3317,7 +3484,7 @@ pub fn from_can<'a>(
} }
// convert the continuation // 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 { if let roc_can::expr::Expr::Var(outer_symbol) = def.loc_expr.value {
store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt) store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt)
@ -3332,6 +3499,7 @@ pub fn from_can<'a>(
with_hole( with_hole(
env, env,
def.loc_expr.value, def.loc_expr.value,
def.expr_var,
procs, procs,
layout_cache, layout_cache,
outer_symbol, outer_symbol,
@ -3344,7 +3512,7 @@ pub fn from_can<'a>(
_ => { _ => {
let symbol = env.unique_symbol(); let symbol = env.unique_symbol();
let hole = env.arena.alloc(Stmt::Ret(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() .into_iter()
.map(|(pattern, opt_guard, can_expr)| { .map(|(pattern, opt_guard, can_expr)| {
let branch_stmt = match join_point { 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) => { Some(id) => {
let symbol = env.unique_symbol(); let symbol = env.unique_symbol();
let arguments = bumpalo::vec![in env.arena; symbol].into_bump_slice(); let arguments = bumpalo::vec![in env.arena; symbol].into_bump_slice();
let jump = env.arena.alloc(Stmt::Jump(id, arguments)); 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 symbol = env.unique_symbol();
let jump = env.arena.alloc(Stmt::Jump(id, env.arena.alloc([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) { match store_pattern(env, procs, layout_cache, &pattern, cond_symbol, guard_stmt) {
Ok(new_guard_stmt) => ( Ok(new_guard_stmt) => (
@ -4026,6 +4202,7 @@ fn store_record_destruct<'a>(
stmt = with_hole( stmt = with_hole(
env, env,
expr.clone(), expr.clone(),
destruct.variable,
procs, procs,
layout_cache, layout_cache,
destruct.symbol, destruct.symbol,
@ -4250,6 +4427,7 @@ fn assign_to_symbol<'a>(
with_hole( with_hole(
env, env,
loc_arg.value, loc_arg.value,
arg_var,
procs, procs,
layout_cache, layout_cache,
symbol, symbol,
@ -4275,6 +4453,24 @@ where
result 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)] #[allow(clippy::too_many_arguments)]
fn call_by_name<'a>( fn call_by_name<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
@ -4358,6 +4554,12 @@ fn call_by_name<'a>(
// exactly once. // exactly once.
match &mut procs.pending_specializations { match &mut procs.pending_specializations {
Some(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 // register the pending specialization, so this gets code genned later
add_pending( add_pending(
pending_specializations, pending_specializations,
@ -4365,6 +4567,7 @@ fn call_by_name<'a>(
full_layout.clone(), full_layout.clone(),
pending, pending,
); );
}
let call = Expr::FunctionCall { let call = Expr::FunctionCall {
call_type: CallType::ByName(proc_name), call_type: CallType::ByName(proc_name),
@ -4447,21 +4650,7 @@ fn call_by_name<'a>(
} }
None if assigned.module_id() != proc_name.module_id() => { None if assigned.module_id() != proc_name.module_id() => {
let fn_var = original_fn_var; add_needed_external(procs, env, original_fn_var, proc_name);
// 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);
let call = Expr::FunctionCall { let call = Expr::FunctionCall {
call_type: CallType::ByName(proc_name), call_type: CallType::ByName(proc_name),
@ -4548,6 +4737,7 @@ pub enum Pattern<'a> {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct RecordDestruct<'a> { pub struct RecordDestruct<'a> {
pub label: Lowercase, pub label: Lowercase,
pub variable: Variable,
pub layout: Layout<'a>, pub layout: Layout<'a>,
pub symbol: Symbol, pub symbol: Symbol,
pub typ: DestructType<'a>, pub typ: DestructType<'a>,
@ -4774,7 +4964,7 @@ pub fn from_can_pattern<'a>(
loop { loop {
match (opt_sorted, opt_destruct) { 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 { if destruct.value.label == label {
mono_destructs.push(from_can_record_destruct( mono_destructs.push(from_can_record_destruct(
env, env,
@ -4790,6 +4980,7 @@ pub fn from_can_pattern<'a>(
mono_destructs.push(RecordDestruct { mono_destructs.push(RecordDestruct {
label: label.clone(), label: label.clone(),
symbol: env.unique_symbol(), symbol: env.unique_symbol(),
variable,
layout: field_layout.clone(), layout: field_layout.clone(),
typ: DestructType::Guard(Pattern::Underscore), typ: DestructType::Guard(Pattern::Underscore),
}); });
@ -4798,7 +4989,7 @@ pub fn from_can_pattern<'a>(
} }
field_layouts.push(field_layout); 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 { if destruct.value.label == label {
opt_destruct = it2.next(); opt_destruct = it2.next();
@ -4806,6 +4997,7 @@ pub fn from_can_pattern<'a>(
label: destruct.value.label.clone(), label: destruct.value.label.clone(),
symbol: destruct.value.symbol, symbol: destruct.value.symbol,
layout: field_layout, layout: field_layout,
variable,
typ: match &destruct.value.typ { typ: match &destruct.value.typ {
roc_can::pattern::DestructType::Optional(_, loc_expr) => { roc_can::pattern::DestructType::Optional(_, loc_expr) => {
// if we reach this stage, the optional field is not present // 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(); 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 // the remainder of the fields (from the type) is not matched on in
// this pattern; to fill it out, we put underscores // this pattern; to fill it out, we put underscores
mono_destructs.push(RecordDestruct { mono_destructs.push(RecordDestruct {
label: label.clone(), label: label.clone(),
symbol: env.unique_symbol(), symbol: env.unique_symbol(),
variable,
layout: field_layout.clone(), layout: field_layout.clone(),
typ: DestructType::Guard(Pattern::Underscore), typ: DestructType::Guard(Pattern::Underscore),
}); });
@ -4834,12 +5027,13 @@ pub fn from_can_pattern<'a>(
opt_sorted = it1.next(); 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 // the remainder of the fields (from the type) is not matched on in
// this pattern; to fill it out, we put underscores // this pattern; to fill it out, we put underscores
mono_destructs.push(RecordDestruct { mono_destructs.push(RecordDestruct {
label: label.clone(), label: label.clone(),
symbol: env.unique_symbol(), symbol: env.unique_symbol(),
variable,
layout: field_layout.clone(), layout: field_layout.clone(),
typ: DestructType::Guard(Pattern::Underscore), typ: DestructType::Guard(Pattern::Underscore),
}); });
@ -4861,6 +5055,7 @@ pub fn from_can_pattern<'a>(
mono_destructs.push(RecordDestruct { mono_destructs.push(RecordDestruct {
label: destruct.value.label.clone(), label: destruct.value.label.clone(),
symbol: destruct.value.symbol, symbol: destruct.value.symbol,
variable: destruct.value.var,
layout: field_layout, layout: field_layout,
typ: DestructType::Optional(loc_expr.value.clone()), typ: DestructType::Optional(loc_expr.value.clone()),
}) })
@ -4894,6 +5089,7 @@ fn from_can_record_destruct<'a>(
RecordDestruct { RecordDestruct {
label: can_rd.label.clone(), label: can_rd.label.clone(),
symbol: can_rd.symbol, symbol: can_rd.symbol,
variable: can_rd.var,
layout: field_layout, layout: field_layout,
typ: match &can_rd.typ { typ: match &can_rd.typ {
roc_can::pattern::DestructType::Required => DestructType::Required, roc_can::pattern::DestructType::Required => DestructType::Required,

View file

@ -822,7 +822,7 @@ pub fn sort_record_fields<'a>(
arena: &'a Bump, arena: &'a Bump,
var: Variable, var: Variable,
subs: &Subs, 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 fields_map = MutMap::default();
let mut env = Env { let mut env = Env {
@ -844,7 +844,7 @@ pub fn sort_record_fields<'a>(
RecordField::Optional(v) => { RecordField::Optional(v) => {
let layout = let layout =
Layout::from_var(&mut env, v).expect("invalid layout from var"); 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; continue;
} }
}; };
@ -853,11 +853,11 @@ pub fn sort_record_fields<'a>(
// Drop any zero-sized fields like {} // Drop any zero-sized fields like {}
if !layout.is_zero_sized() { 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 sorted_fields
} }

View file

@ -62,18 +62,20 @@ mod test_mono {
exposed_types, exposed_types,
); );
let loaded = loaded.expect("failed to load module"); let mut loaded = loaded.expect("failed to load module");
use roc_load::file::MonomorphizedModule; use roc_load::file::MonomorphizedModule;
let MonomorphizedModule { let MonomorphizedModule {
can_problems, module_id: home,
type_problems,
mono_problems,
procedures, procedures,
exposed_to_host, exposed_to_host,
.. ..
} = loaded; } = 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() { if !can_problems.is_empty() {
println!("Ignoring {} canonicalization problems", can_problems.len()); println!("Ignoring {} canonicalization problems", can_problems.len());
} }

4
compiler/parse/fuzz/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
target
corpus
artifacts

182
compiler/parse/fuzz/Cargo.lock generated Normal file
View 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",
]

View 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

View 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.

View file

@ -0,0 +1,36 @@
"if"
"then"
"else"
"when"
"as"
"is"
"expect"
"app"
"platform"
"provides"
"requires"
"exposes"
"imports"
"effects"
"interface"
"|>"
"=="
"!="
"&&"
"||"
"+"
"*"
"-"
"//"
"/"
"<="
"<"
">="
">"
"^"
"%%"
"%"
"->"

View 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());
}
});

View 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());
}
});

View 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());
}
});

View file

@ -277,6 +277,8 @@ pub enum Def<'a> {
/// This is used only to avoid cloning when reordering expressions (e.g. in desugar()). /// 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. /// It lets us take a (&Def) and create a plain (Def) from it.
Nested(&'a Def<'a>), Nested(&'a Def<'a>),
NotYetImplemented(&'static str),
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]

View file

@ -317,12 +317,27 @@ fn spaces<'a>(
'\n' => { '\n' => {
state = state.newline()?; state = state.newline()?;
// This was a newline, so end this line comment. match (comment_line_buf.len(), comment_line_buf.chars().next())
space_list.push(LineComment(comment_line_buf.into_bump_str())); {
(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); comment_line_buf = String::new_in(arena);
line_state = LineState::Normal; 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 => { nonblank => {
// Chars can have btye lengths of more than 1! // Chars can have btye lengths of more than 1!
state = state.advance_without_indenting(nonblank.len_utf8())?; state = state.advance_without_indenting(nonblank.len_utf8())?;

View file

@ -561,7 +561,7 @@ fn annotation_or_alias<'a>(
ann: loc_ann, ann: loc_ann,
}, },
Apply(_, _) => { 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( SpaceAfter(value, spaces_before) => Def::SpaceAfter(
arena.alloc(annotation_or_alias(arena, value, region, loc_ann)), 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), Nested(value) => annotation_or_alias(arena, value, region, loc_ann),
PrivateTag(_) => { 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 { .. } => { 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(_) => { NumLiteral(_) | NonBase10Literal { .. } | FloatLiteral(_) | StrLiteral(_) => {
panic!("TODO gracefully handle trying to annotate a litera"); Def::NotYetImplemented("TODO gracefully handle trying to annotate a litera")
} }
Underscore => { 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(_) => { Malformed(_) => {
panic!("TODO translate a malformed pattern into a malformed annotation"); Def::NotYetImplemented("TODO translate a malformed pattern into a malformed annotation")
} }
Identifier(ident) => { Identifier(ident) => {
// This is a regular Annotation // 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 // `<` because '=' should be same indent (or greater) as the entire def-expr
} else if equals_sign_indent < def_start_col { } 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 { } else {
// Indented more beyond the original indent of the entire def-expr. // Indented more beyond the original indent of the entire def-expr.
let indented_more = def_start_col + 1; let indented_more = def_start_col + 1;
@ -720,7 +726,15 @@ fn parse_def_signature<'a>(
)) ))
// `<` because ':' should be same indent or greater // `<` because ':' should be same indent or greater
} else if colon_indent < original_indent { } 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 { } else {
// Indented more beyond the original indent. // Indented more beyond the original indent.
let indented_more = original_indent + 1; 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 { } else {
format!("{}.{}", module_name, parts.join(".")) format!("{}.{}", module_name, parts.join("."))
}; };
Ok(( Ok((
Located { Located {
region: loc_ident.region, 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, state,
)) ))
@ -1120,7 +1135,15 @@ mod when {
), ),
move |arena, state, (case_indent, loc_condition)| { move |arena, state, (case_indent, loc_condition)| {
if case_indent < min_indent { 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. // 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) { if alternatives_indented_correctly(&loc_patterns, original_indent) {
Ok(((loc_patterns, loc_guard), state)) Ok(((loc_patterns, loc_guard), state))
} else { } else {
panic!( Err((
"TODO additional branch didn't have same indentation as first branch" 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) => { Err(malformed) => {
panic!( return Err((
Fail {
attempting: state.attempting,
reason: FailReason::NotYetImplemented(format!(
"TODO early return malformed pattern {:?}", "TODO early return malformed pattern {:?}",
malformed malformed
); )),
},
state,
));
} }
} }
} }

View file

@ -24,4 +24,5 @@ pub mod number_literal;
pub mod pattern; pub mod pattern;
pub mod problems; pub mod problems;
pub mod string_literal; pub mod string_literal;
pub mod test_helpers;
pub mod type_annotation; pub mod type_annotation;

View file

@ -224,6 +224,7 @@ pub enum FailReason {
BadUtf8, BadUtf8,
ReservedKeyword(Region), ReservedKeyword(Region),
ArgumentsBeforeEquals(Region), ArgumentsBeforeEquals(Region),
NotYetImplemented(String),
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]

View file

@ -1,8 +1,8 @@
use crate::ast::{Attempting, EscapedChar, StrLiteral, StrSegment}; use crate::ast::{Attempting, EscapedChar, StrLiteral, StrSegment};
use crate::expr; use crate::expr;
use crate::parser::{ use crate::parser::{
allocated, ascii_char, ascii_hex_digits, loc, parse_utf8, unexpected, unexpected_eof, allocated, ascii_char, ascii_hex_digits, loc, parse_utf8, unexpected, unexpected_eof, Fail,
ParseResult, Parser, State, FailReason, ParseResult, Parser, State,
}; };
use bumpalo::collections::vec::Vec; use bumpalo::collections::vec::Vec;
use bumpalo::Bump; use bumpalo::Bump;
@ -279,7 +279,16 @@ where
// lines.push(line); // lines.push(line);
// Ok((StrLiteral::Block(lines.into_bump_slice()), state)) // 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), Err(reason) => state.fail(reason),
}; };

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

View file

@ -4,8 +4,8 @@ use crate::expr::{global_tag, private_tag};
use crate::ident::join_module_parts; use crate::ident::join_module_parts;
use crate::keyword; use crate::keyword;
use crate::parser::{ use crate::parser::{
allocated, ascii_char, ascii_string, not, optional, peek_utf8_char, unexpected, Either, allocated, ascii_char, ascii_string, not, optional, peek_utf8_char, unexpected, Either, Fail,
ParseResult, Parser, State, FailReason, ParseResult, Parser, State,
}; };
use bumpalo::collections::string::String; use bumpalo::collections::string::String;
use bumpalo::collections::vec::Vec; use bumpalo::collections::vec::Vec;
@ -239,7 +239,13 @@ fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>
Ok((first, state)) Ok((first, state))
} else { } else {
// e.g. `Int,Int` without an arrow and return type // 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,
))
} }
} }
} }

View file

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

View file

@ -11,11 +11,8 @@ extern crate quickcheck_macros;
extern crate roc_module; extern crate roc_module;
extern crate roc_parse; extern crate roc_parse;
mod helpers;
#[cfg(test)] #[cfg(test)]
mod test_parse { mod test_parse {
use crate::helpers::parse_with;
use bumpalo::collections::vec::Vec; use bumpalo::collections::vec::Vec;
use bumpalo::{self, Bump}; use bumpalo::{self, Bump};
use roc_module::operator::BinOp::*; use roc_module::operator::BinOp::*;
@ -33,19 +30,20 @@ mod test_parse {
use roc_parse::header::ModuleName; use roc_parse::header::ModuleName;
use roc_parse::module::{interface_header, module_defs}; use roc_parse::module::{interface_header, module_defs};
use roc_parse::parser::{Fail, FailReason, Parser, State}; use roc_parse::parser::{Fail, FailReason, Parser, State};
use roc_parse::test_helpers::parse_expr_with;
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use std::{f64, i64}; use std::{f64, i64};
fn assert_parses_to<'a>(input: &'a str, expected_expr: Expr<'a>) { fn assert_parses_to<'a>(input: &'a str, expected_expr: Expr<'a>) {
let arena = Bump::new(); 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); assert_eq!(Ok(expected_expr), actual);
} }
fn assert_parsing_fails<'a>(input: &'a str, reason: FailReason, attempting: Attempting) { fn assert_parsing_fails<'a>(input: &'a str, reason: FailReason, attempting: Attempting) {
let arena = Bump::new(); let arena = Bump::new();
let actual = parse_with(&arena, input); let actual = parse_expr_with(&arena, input);
let expected_fail = Fail { reason, attempting }; let expected_fail = Fail { reason, attempting };
assert_eq!(Err(expected_fail), actual); 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) { fn assert_segments<E: Fn(&Bump) -> Vec<'_, ast::StrSegment<'_>>>(input: &str, to_expected: E) {
let arena = Bump::new(); 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_slice = to_expected(&arena);
let expected_expr = Expr::Str(Line(&expected_slice)); let expected_expr = Expr::Str(Line(&expected_slice));
@ -77,7 +75,7 @@ mod test_parse {
("\\t", EscapedChar::Tab), ("\\t", EscapedChar::Tab),
("\\\"", EscapedChar::Quote), ("\\\"", 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_slice = to_expected(*escaped, &arena);
let expected_expr = Expr::Str(Line(&expected_slice)); let expected_expr = Expr::Str(Line(&expected_slice));
@ -423,7 +421,7 @@ mod test_parse {
fields: &[], fields: &[],
update: None, update: None,
}; };
let actual = parse_with(&arena, "{}"); let actual = parse_expr_with(&arena, "{}");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -455,7 +453,7 @@ mod test_parse {
fields, 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); assert_eq!(Ok(expected), actual);
} }
@ -470,7 +468,7 @@ mod test_parse {
Located::new(0, 0, 2, 3, Num("2")), Located::new(0, 0, 2, 3, Num("2")),
)); ));
let expected = BinOp(tuple); let expected = BinOp(tuple);
let actual = parse_with(&arena, "1+2"); let actual = parse_expr_with(&arena, "1+2");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -484,7 +482,7 @@ mod test_parse {
Located::new(0, 0, 2, 3, Num("2")), Located::new(0, 0, 2, 3, Num("2")),
)); ));
let expected = BinOp(tuple); let expected = BinOp(tuple);
let actual = parse_with(&arena, "1-2"); let actual = parse_expr_with(&arena, "1-2");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -498,7 +496,7 @@ mod test_parse {
Located::new(0, 0, 7, 8, Num("2")), Located::new(0, 0, 7, 8, Num("2")),
)); ));
let expected = BinOp(tuple); let expected = BinOp(tuple);
let actual = parse_with(&arena, "1 + 2"); let actual = parse_expr_with(&arena, "1 + 2");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -512,7 +510,7 @@ mod test_parse {
Located::new(0, 0, 7, 8, Num("2")), Located::new(0, 0, 7, 8, Num("2")),
)); ));
let expected = BinOp(tuple); let expected = BinOp(tuple);
let actual = parse_with(&arena, "1 - 2"); let actual = parse_expr_with(&arena, "1 - 2");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -535,7 +533,7 @@ mod test_parse {
Located::new(0, 0, 4, 5, Num("2")), Located::new(0, 0, 4, 5, Num("2")),
)); ));
let expected = BinOp(tuple); let expected = BinOp(tuple);
let actual = parse_with(&arena, "x + 2"); let actual = parse_expr_with(&arena, "x + 2");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -557,7 +555,7 @@ mod test_parse {
Located::new(0, 0, 4, 5, Num("2")), Located::new(0, 0, 4, 5, Num("2")),
)); ));
let expected = BinOp(tuple); let expected = BinOp(tuple);
let actual = parse_with(&arena, "x - 2"); let actual = parse_expr_with(&arena, "x - 2");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -572,7 +570,7 @@ mod test_parse {
Located::new(1, 1, 2, 3, Num("4")), Located::new(1, 1, 2, 3, Num("4")),
)); ));
let expected = BinOp(tuple); 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); assert_eq!(Ok(expected), actual);
} }
@ -587,7 +585,7 @@ mod test_parse {
Located::new(1, 1, 2, 3, Num("4")), Located::new(1, 1, 2, 3, Num("4")),
)); ));
let expected = BinOp(tuple); 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); assert_eq!(Ok(expected), actual);
} }
@ -602,7 +600,7 @@ mod test_parse {
Located::new(1, 1, 2, 3, spaced_int), Located::new(1, 1, 2, 3, spaced_int),
)); ));
let expected = BinOp(tuple); 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); assert_eq!(Ok(expected), actual);
} }
@ -617,7 +615,7 @@ mod test_parse {
Located::new(1, 1, 2, 3, spaced_int), Located::new(1, 1, 2, 3, spaced_int),
)); ));
let expected = BinOp(tuple); 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); assert_eq!(Ok(expected), actual);
} }
@ -632,7 +630,7 @@ mod test_parse {
Located::new(1, 1, 2, 3, Num("4")), Located::new(1, 1, 2, 3, Num("4")),
)); ));
let expected = BinOp(tuple); 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); assert_eq!(Ok(expected), actual);
} }
@ -647,7 +645,7 @@ mod test_parse {
Located::new(1, 1, 2, 3, Num("4")), Located::new(1, 1, 2, 3, Num("4")),
)); ));
let expected = BinOp(tuple); 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); assert_eq!(Ok(expected), actual);
} }
@ -662,7 +660,7 @@ mod test_parse {
Located::new(1, 1, 1, 3, spaced_int), Located::new(1, 1, 1, 3, spaced_int),
)); ));
let expected = BinOp(tuple); 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); assert_eq!(Ok(expected), actual);
} }
@ -678,7 +676,7 @@ mod test_parse {
Located::new(3, 3, 2, 3, spaced_int2), Located::new(3, 3, 2, 3, spaced_int2),
)); ));
let expected = BinOp(tuple); 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); assert_eq!(Ok(expected), actual);
} }
@ -702,7 +700,7 @@ mod test_parse {
Located::new(0, 0, 3, 4, var2), Located::new(0, 0, 3, 4, var2),
)); ));
let expected = BinOp(tuple); let expected = BinOp(tuple);
let actual = parse_with(&arena, "x- y"); let actual = parse_expr_with(&arena, "x- y");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -716,7 +714,7 @@ mod test_parse {
Located::new(0, 0, 4, 5, Num("5")), Located::new(0, 0, 4, 5, Num("5")),
)); ));
let expected = BinOp(tuple); let expected = BinOp(tuple);
let actual = parse_with(&arena, "-12-5"); let actual = parse_expr_with(&arena, "-12-5");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -730,7 +728,7 @@ mod test_parse {
Located::new(0, 0, 3, 5, Num("11")), Located::new(0, 0, 3, 5, Num("11")),
)); ));
let expected = BinOp(tuple); let expected = BinOp(tuple);
let actual = parse_with(&arena, "10*11"); let actual = parse_expr_with(&arena, "10*11");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -749,7 +747,7 @@ mod test_parse {
Located::new(0, 0, 3, 9, BinOp(inner)), Located::new(0, 0, 3, 9, BinOp(inner)),
)); ));
let expected = BinOp(outer); 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); assert_eq!(Ok(expected), actual);
} }
@ -771,7 +769,7 @@ mod test_parse {
Located::new(0, 0, 3, 4, var2), Located::new(0, 0, 3, 4, var2),
)); ));
let expected = BinOp(tuple); let expected = BinOp(tuple);
let actual = parse_with(&arena, "x==y"); let actual = parse_expr_with(&arena, "x==y");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -793,7 +791,7 @@ mod test_parse {
Located::new(0, 0, 5, 6, var2), Located::new(0, 0, 5, 6, var2),
)); ));
let expected = BinOp(tuple); let expected = BinOp(tuple);
let actual = parse_with(&arena, "x == y"); let actual = parse_expr_with(&arena, "x == y");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -807,7 +805,7 @@ mod test_parse {
module_name: "", module_name: "",
ident: "whee", ident: "whee",
}; };
let actual = parse_with(&arena, "whee"); let actual = parse_expr_with(&arena, "whee");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -819,7 +817,7 @@ mod test_parse {
module_name: "", module_name: "",
ident: "whee", ident: "whee",
})); }));
let actual = parse_with(&arena, "(whee)"); let actual = parse_expr_with(&arena, "(whee)");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -831,7 +829,7 @@ mod test_parse {
module_name: "One.Two", module_name: "One.Two",
ident: "whee", ident: "whee",
}; };
let actual = parse_with(&arena, "One.Two.whee"); let actual = parse_expr_with(&arena, "One.Two.whee");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -842,7 +840,7 @@ mod test_parse {
fn basic_global_tag() { fn basic_global_tag() {
let arena = Bump::new(); let arena = Bump::new();
let expected = Expr::GlobalTag("Whee"); let expected = Expr::GlobalTag("Whee");
let actual = parse_with(&arena, "Whee"); let actual = parse_expr_with(&arena, "Whee");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -851,7 +849,7 @@ mod test_parse {
fn basic_private_tag() { fn basic_private_tag() {
let arena = Bump::new(); let arena = Bump::new();
let expected = Expr::PrivateTag("@Whee"); let expected = Expr::PrivateTag("@Whee");
let actual = parse_with(&arena, "@Whee"); let actual = parse_expr_with(&arena, "@Whee");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -867,7 +865,7 @@ mod test_parse {
args, args,
CalledVia::Space, CalledVia::Space,
); );
let actual = parse_with(&arena, "@Whee 12 34"); let actual = parse_expr_with(&arena, "@Whee 12 34");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -883,7 +881,7 @@ mod test_parse {
args, args,
CalledVia::Space, CalledVia::Space,
); );
let actual = parse_with(&arena, "Whee 12 34"); let actual = parse_expr_with(&arena, "Whee 12 34");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -901,7 +899,7 @@ mod test_parse {
args, args,
CalledVia::Space, CalledVia::Space,
); );
let actual = parse_with(&arena, "Whee (12) (34)"); let actual = parse_expr_with(&arena, "Whee (12) (34)");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -910,7 +908,7 @@ mod test_parse {
fn qualified_global_tag() { fn qualified_global_tag() {
let arena = Bump::new(); let arena = Bump::new();
let expected = Expr::MalformedIdent("One.Two.Whee"); 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); assert_eq!(Ok(expected), actual);
} }
@ -920,7 +918,7 @@ mod test_parse {
// fn qualified_private_tag() { // fn qualified_private_tag() {
// let arena = Bump::new(); // let arena = Bump::new();
// let expected = Expr::MalformedIdent("One.Two.@Whee"); // 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); // assert_eq!(Ok(expected), actual);
// } // }
@ -931,7 +929,7 @@ mod test_parse {
let pattern = Located::new(0, 0, 1, 6, Pattern::GlobalTag("Thing")); let pattern = Located::new(0, 0, 1, 6, Pattern::GlobalTag("Thing"));
let patterns = &[pattern]; let patterns = &[pattern];
let expected = Closure(patterns, arena.alloc(Located::new(0, 0, 10, 12, Num("42")))); 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); assert_eq!(Ok(expected), actual);
} }
@ -940,7 +938,7 @@ mod test_parse {
fn private_qualified_tag() { fn private_qualified_tag() {
let arena = Bump::new(); let arena = Bump::new();
let expected = Expr::MalformedIdent("@One.Two.Whee"); 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); assert_eq!(Ok(expected), actual);
} }
@ -952,7 +950,7 @@ mod test_parse {
let arena = Bump::new(); let arena = Bump::new();
let elems = &[]; let elems = &[];
let expected = List(elems); let expected = List(elems);
let actual = parse_with(&arena, "[]"); let actual = parse_expr_with(&arena, "[]");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -963,7 +961,7 @@ mod test_parse {
let arena = Bump::new(); let arena = Bump::new();
let elems = &[]; let elems = &[];
let expected = List(elems); let expected = List(elems);
let actual = parse_with(&arena, "[ ]"); let actual = parse_expr_with(&arena, "[ ]");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -973,7 +971,7 @@ mod test_parse {
let arena = Bump::new(); let arena = Bump::new();
let elems = &[&*arena.alloc(Located::new(0, 0, 1, 2, Num("1")))]; let elems = &[&*arena.alloc(Located::new(0, 0, 1, 2, Num("1")))];
let expected = List(elems); let expected = List(elems);
let actual = parse_with(&arena, "[1]"); let actual = parse_expr_with(&arena, "[1]");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -983,7 +981,7 @@ mod test_parse {
let arena = Bump::new(); let arena = Bump::new();
let elems = &[&*arena.alloc(Located::new(0, 0, 2, 3, Num("1")))]; let elems = &[&*arena.alloc(Located::new(0, 0, 2, 3, Num("1")))];
let expected = List(elems); let expected = List(elems);
let actual = parse_with(&arena, "[ 1 ]"); let actual = parse_expr_with(&arena, "[ 1 ]");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -998,7 +996,7 @@ mod test_parse {
ident: "rec", ident: "rec",
}; };
let expected = Access(arena.alloc(var), "field"); 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); assert_eq!(Ok(expected), actual);
} }
@ -1011,7 +1009,7 @@ mod test_parse {
ident: "rec", ident: "rec",
})); }));
let expected = Access(arena.alloc(paren_var), "field"); 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); assert_eq!(Ok(expected), actual);
} }
@ -1024,7 +1022,7 @@ mod test_parse {
ident: "rec", ident: "rec",
})); }));
let expected = Access(arena.alloc(paren_var), "field"); 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); assert_eq!(Ok(expected), actual);
} }
@ -1040,7 +1038,7 @@ mod test_parse {
arena.alloc(Access(arena.alloc(Access(arena.alloc(var), "abc")), "def")), arena.alloc(Access(arena.alloc(Access(arena.alloc(var), "abc")), "def")),
"ghi", "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); assert_eq!(Ok(expected), actual);
} }
@ -1056,7 +1054,7 @@ mod test_parse {
arena.alloc(Access(arena.alloc(Access(arena.alloc(var), "abc")), "def")), arena.alloc(Access(arena.alloc(Access(arena.alloc(var), "abc")), "def")),
"ghi", "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); assert_eq!(Ok(expected), actual);
} }
@ -1077,7 +1075,7 @@ mod test_parse {
args, args,
CalledVia::Space, CalledVia::Space,
); );
let actual = parse_with(&arena, "whee 1"); let actual = parse_expr_with(&arena, "whee 1");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -1102,7 +1100,7 @@ mod test_parse {
args, args,
CalledVia::Space, CalledVia::Space,
); );
let actual = parse_with(&arena, "whee 12 34"); let actual = parse_expr_with(&arena, "whee 12 34");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -1155,7 +1153,7 @@ mod test_parse {
args, args,
CalledVia::Space, 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); assert_eq!(Ok(expected), actual);
} }
@ -1174,7 +1172,7 @@ mod test_parse {
args, args,
CalledVia::Space, CalledVia::Space,
); );
let actual = parse_with(&arena, "(whee) 1"); let actual = parse_expr_with(&arena, "(whee) 1");
assert_eq!(Ok(expected), actual); 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 loc_arg1_expr = Located::new(0, 0, 1, 4, arg1_expr);
let expected = UnaryOp(arena.alloc(loc_arg1_expr), loc_op); 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); 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 loc_arg1_expr = Located::new(0, 0, 1, 5, arg1_expr);
let expected = UnaryOp(arena.alloc(loc_arg1_expr), loc_op); 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); assert_eq!(Ok(expected), actual);
} }
@ -1242,7 +1240,7 @@ mod test_parse {
CalledVia::Space, CalledVia::Space,
); );
let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 13, apply_expr)), loc_op); 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); assert_eq!(Ok(expected), actual);
} }
@ -1278,7 +1276,7 @@ mod test_parse {
CalledVia::Space, CalledVia::Space,
); );
let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 13, apply_expr)), loc_op); 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); assert_eq!(Ok(expected), actual);
} }
@ -1314,7 +1312,7 @@ mod test_parse {
CalledVia::Space, CalledVia::Space,
))); )));
let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 15, apply_expr)), loc_op); 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); assert_eq!(Ok(expected), actual);
} }
@ -1350,7 +1348,7 @@ mod test_parse {
CalledVia::Space, CalledVia::Space,
))); )));
let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 15, apply_expr)), loc_op); 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); assert_eq!(Ok(expected), actual);
} }
@ -1377,7 +1375,7 @@ mod test_parse {
args, args,
CalledVia::Space, CalledVia::Space,
); );
let actual = parse_with(&arena, "whee 12 -foo"); let actual = parse_expr_with(&arena, "whee 12 -foo");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -1394,7 +1392,7 @@ mod test_parse {
let access = Access(arena.alloc(var), "field"); let access = Access(arena.alloc(var), "field");
let loc_access = Located::new(0, 0, 1, 11, access); let loc_access = Located::new(0, 0, 1, 11, access);
let expected = UnaryOp(arena.alloc(loc_access), loc_op); 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); assert_eq!(Ok(expected), actual);
} }
@ -1407,7 +1405,7 @@ mod test_parse {
let pattern = Located::new(0, 0, 1, 2, Identifier("a")); let pattern = Located::new(0, 0, 1, 2, Identifier("a"));
let patterns = &[pattern]; let patterns = &[pattern];
let expected = Closure(patterns, arena.alloc(Located::new(0, 0, 6, 8, Num("42")))); 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); assert_eq!(Ok(expected), actual);
} }
@ -1418,7 +1416,7 @@ mod test_parse {
let pattern = Located::new(0, 0, 1, 2, Underscore); let pattern = Located::new(0, 0, 1, 2, Underscore);
let patterns = &[pattern]; let patterns = &[pattern];
let expected = Closure(patterns, arena.alloc(Located::new(0, 0, 6, 8, Num("42")))); 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); assert_eq!(Ok(expected), actual);
} }
@ -1429,7 +1427,7 @@ mod test_parse {
// underscore in an argument name, it would parse as three arguments // underscore in an argument name, it would parse as three arguments
// (and would ignore the underscore as if it had been blank space). // (and would ignore the underscore as if it had been blank space).
let arena = Bump::new(); 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); assert_eq!(Ok(MalformedClosure), actual);
} }
@ -1441,7 +1439,7 @@ mod test_parse {
let arg2 = Located::new(0, 0, 4, 5, Identifier("b")); let arg2 = Located::new(0, 0, 4, 5, Identifier("b"));
let patterns = &[arg1, arg2]; let patterns = &[arg1, arg2];
let expected = Closure(patterns, arena.alloc(Located::new(0, 0, 9, 11, Num("42")))); 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); assert_eq!(Ok(expected), actual);
} }
@ -1457,7 +1455,7 @@ mod test_parse {
arena.alloc(patterns), arena.alloc(patterns),
arena.alloc(Located::new(0, 0, 12, 14, Num("42"))), 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); assert_eq!(Ok(expected), actual);
} }
@ -1472,7 +1470,7 @@ mod test_parse {
arena.alloc(patterns), arena.alloc(patterns),
arena.alloc(Located::new(0, 0, 9, 11, Num("42"))), 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); assert_eq!(Ok(expected), actual);
} }
@ -2040,7 +2038,7 @@ mod test_parse {
}; };
let loc_cond = Located::new(0, 0, 5, 6, var); let loc_cond = Located::new(0, 0, 5, 6, var);
let expected = Expr::When(arena.alloc(loc_cond), branches); let expected = Expr::When(arena.alloc(loc_cond), branches);
let actual = parse_with( let actual = parse_expr_with(
&arena, &arena,
indoc!( indoc!(
r#" r#"
@ -2084,7 +2082,7 @@ mod test_parse {
}; };
let loc_cond = Located::new(0, 0, 5, 6, var); let loc_cond = Located::new(0, 0, 5, 6, var);
let expected = Expr::When(arena.alloc(loc_cond), branches); let expected = Expr::When(arena.alloc(loc_cond), branches);
let actual = parse_with( let actual = parse_expr_with(
&arena, &arena,
indoc!( indoc!(
r#" r#"
@ -2133,7 +2131,7 @@ mod test_parse {
}; };
let loc_cond = Located::new(0, 0, 5, 6, var); let loc_cond = Located::new(0, 0, 5, 6, var);
let expected = Expr::When(arena.alloc(loc_cond), branches); let expected = Expr::When(arena.alloc(loc_cond), branches);
let actual = parse_with( let actual = parse_expr_with(
&arena, &arena,
indoc!( indoc!(
r#" r#"
@ -2183,7 +2181,7 @@ mod test_parse {
}; };
let loc_cond = Located::new(0, 0, 5, 6, var); let loc_cond = Located::new(0, 0, 5, 6, var);
let expected = Expr::When(arena.alloc(loc_cond), branches); let expected = Expr::When(arena.alloc(loc_cond), branches);
let actual = parse_with( let actual = parse_expr_with(
&arena, &arena,
indoc!( indoc!(
r#" r#"
@ -2347,18 +2345,20 @@ mod test_parse {
let arena = Bump::new(); let arena = Bump::new();
let newlines = &[Newline, Newline]; let newlines = &[Newline, Newline];
let def = Def::Body( let def = Def::Body(
arena.alloc(Located::new(4, 4, 0, 1, Identifier("x"))), arena.alloc(Located::new(6, 6, 0, 1, Identifier("x"))),
arena.alloc(Located::new(4, 4, 4, 5, Num("5"))), 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 defs = &[loc_def];
let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines); 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 = &[ let reset_indentation = &[
DocComment("first line of docs"), DocComment("first line of docs"),
DocComment(" second line"), DocComment(" second line"),
DocComment(" third line"), DocComment(" third line"),
DocComment("fourth line"), DocComment("fourth line"),
DocComment(""),
DocComment("sixth line after doc new line"),
]; ];
let expected = Expr::SpaceBefore( let expected = Expr::SpaceBefore(
arena.alloc(Defs(defs, arena.alloc(loc_ret))), arena.alloc(Defs(defs, arena.alloc(loc_ret))),
@ -2372,6 +2372,8 @@ mod test_parse {
## second line ## second line
## third line ## third line
## fourth line ## fourth line
##
## sixth line after doc new line
x = 5 x = 5
42 42
@ -2490,7 +2492,7 @@ mod test_parse {
}; };
let loc_cond = Located::new(0, 0, 5, 6, var); let loc_cond = Located::new(0, 0, 5, 6, var);
let expected = Expr::When(arena.alloc(loc_cond), branches); let expected = Expr::When(arena.alloc(loc_cond), branches);
let actual = parse_with( let actual = parse_expr_with(
&arena, &arena,
indoc!( indoc!(
r#" r#"
@ -2535,7 +2537,7 @@ mod test_parse {
}; };
let loc_cond = Located::new(0, 0, 5, 6, var); let loc_cond = Located::new(0, 0, 5, 6, var);
let expected = Expr::When(arena.alloc(loc_cond), branches); let expected = Expr::When(arena.alloc(loc_cond), branches);
let actual = parse_with( let actual = parse_expr_with(
&arena, &arena,
indoc!( indoc!(
r#" r#"

View file

@ -40,6 +40,12 @@ pub enum Problem {
field_region: Region, field_region: Region,
replaced_region: Region, replaced_region: Region,
}, },
InvalidOptionalValue {
field_name: Lowercase,
record_region: Region,
field_region: Region,
},
DuplicateTag { DuplicateTag {
tag_name: TagName, tag_name: TagName,
tag_union_region: Region, tag_union_region: Region,
@ -101,6 +107,11 @@ pub enum RuntimeError {
original_region: Region, original_region: Region,
shadow: Located<Ident>, 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! // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
UnsupportedPattern(Region), UnsupportedPattern(Region),
// Example: when 1 is 1.X -> 32 // Example: when 1 is 1.X -> 32

View file

@ -179,6 +179,25 @@ pub fn can_problem<'b>(
alloc.reflow(" definitions from this record."), 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 { Problem::DuplicateRecordFieldType {
field_name, field_name,
field_region, field_region,
@ -534,6 +553,25 @@ fn pretty_runtime_error<'b>(
tip, 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![ RuntimeError::InvalidRecordUpdate { region } => alloc.stack(vec![
alloc.concat(vec![ alloc.concat(vec![
alloc.reflow("This expression cannot be updated"), alloc.reflow("This expression cannot be updated"),

View file

@ -95,8 +95,13 @@ mod test_reporting {
home, home,
ident_ids: &mut ident_ids, ident_ids: &mut ident_ids,
}; };
let _mono_expr = let _mono_expr = Stmt::new(
Stmt::new(&mut mono_env, loc_expr.value, &mut procs, &mut layout_cache); &mut mono_env,
loc_expr.value,
var,
&mut procs,
&mut layout_cache,
);
} }
Ok((unify_problems, can_problems, mono_problems, home, interns)) Ok((unify_problems, can_problems, 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] #[test]
fn first_wildcard_is_required() { fn first_wildcard_is_required() {
report_problem_as( report_problem_as(

View file

@ -813,6 +813,45 @@ fn type_to_variable(
result 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) => { Erroneous(problem) => {
let content = Content::Structure(FlatType::Erroneous(problem.clone())); let content = Content::Structure(FlatType::Erroneous(problem.clone()));

View file

@ -75,13 +75,16 @@ mod solve_expr {
let LoadedModule { let LoadedModule {
module_id: home, module_id: home,
mut can_problems, mut can_problems,
type_problems, mut type_problems,
interns, interns,
mut solved, mut solved,
exposed_to_host, exposed_to_host,
.. ..
} = loaded; } = 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(); let mut subs = solved.inner_mut();
// assert!(can_problems.is_empty()); // assert!(can_problems.is_empty());

View file

@ -53,6 +53,13 @@ pub enum SolvedType {
/// A type alias /// A type alias
Alias(Symbol, Vec<(Lowercase, SolvedType)>, Box<SolvedType>), Alias(Symbol, Vec<(Lowercase, SolvedType)>, Box<SolvedType>),
HostExposedAlias {
name: Symbol,
arguments: Vec<(Lowercase, SolvedType)>,
actual_var: VarId,
actual: Box<SolvedType>,
},
/// a boolean algebra Bool /// a boolean algebra Bool
Boolean(SolvedBool), Boolean(SolvedBool),
@ -194,6 +201,26 @@ impl SolvedType {
SolvedType::Alias(*symbol, solved_args, Box::new(solved_type)) 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())), Boolean(val) => SolvedType::Boolean(SolvedBool::from_bool(&val, solved_subs.inner())),
Variable(var) => Self::from_var(solved_subs.inner(), *var), 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)) 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), Error => Type::Erroneous(Problem::SolvedTypeError),
Erroneous(problem) => Type::Erroneous(problem.clone()), Erroneous(problem) => Type::Erroneous(problem.clone()),
} }

View file

@ -143,6 +143,12 @@ pub enum Type {
Record(SendMap<Lowercase, RecordField<Type>>, Box<Type>), Record(SendMap<Lowercase, RecordField<Type>>, Box<Type>),
TagUnion(Vec<(TagName, Vec<Type>)>, Box<Type>), TagUnion(Vec<(TagName, Vec<Type>)>, Box<Type>),
Alias(Symbol, Vec<(Lowercase, 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>), RecursiveTagUnion(Variable, Vec<(TagName, Vec<Type>)>, Box<Type>),
/// Applying a type to some arguments (e.g. Map.Map String Int) /// Applying a type to some arguments (e.g. Map.Map String Int)
Apply(Symbol, Vec<Type>), Apply(Symbol, Vec<Type>),
@ -206,6 +212,20 @@ impl fmt::Debug for Type {
Ok(()) 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) => { Type::Record(fields, ext) => {
write!(f, "{{")?; write!(f, "{{")?;
@ -405,6 +425,16 @@ impl Type {
} }
actual_type.substitute(substitutions); actual_type.substitute(substitutions);
} }
HostExposedAlias {
arguments,
actual: actual_type,
..
} => {
for (_, value) in arguments.iter_mut() {
value.substitute(substitutions);
}
actual_type.substitute(substitutions);
}
Apply(_, args) => { Apply(_, args) => {
for arg in args { for arg in args {
arg.substitute(substitutions); arg.substitute(substitutions);
@ -453,6 +483,12 @@ impl Type {
Alias(_, _, actual_type) => { Alias(_, _, actual_type) => {
actual_type.substitute_alias(rep_symbol, actual); actual_type.substitute_alias(rep_symbol, actual);
} }
HostExposedAlias {
actual: actual_type,
..
} => {
actual_type.substitute_alias(rep_symbol, actual);
}
Apply(symbol, _) if *symbol == rep_symbol => { Apply(symbol, _) if *symbol == rep_symbol => {
*self = actual.clone(); *self = actual.clone();
@ -496,6 +532,9 @@ impl Type {
Alias(alias_symbol, _, actual_type) => { Alias(alias_symbol, _, actual_type) => {
alias_symbol == &rep_symbol || actual_type.contains_symbol(rep_symbol) 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(symbol, _) if *symbol == rep_symbol => true,
Apply(_, args) => args.iter().any(|arg| arg.contains_symbol(rep_symbol)), Apply(_, args) => args.iter().any(|arg| arg.contains_symbol(rep_symbol)),
EmptyRec | EmptyTagUnion | Erroneous(_) | Variable(_) | Boolean(_) => false, EmptyRec | EmptyTagUnion | Erroneous(_) | Variable(_) | Boolean(_) => false,
@ -528,6 +567,7 @@ impl Type {
.any(|arg| arg.contains_variable(rep_variable)) .any(|arg| arg.contains_variable(rep_variable))
} }
Alias(_, _, actual_type) => actual_type.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)), Apply(_, args) => args.iter().any(|arg| arg.contains_variable(rep_variable)),
EmptyRec | EmptyTagUnion | Erroneous(_) | Boolean(_) => false, EmptyRec | EmptyTagUnion | Erroneous(_) | Boolean(_) => false,
} }
@ -579,7 +619,12 @@ impl Type {
} }
ext.instantiate_aliases(region, aliases, var_store, introduced); 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 { for arg in type_args {
arg.1 arg.1
.instantiate_aliases(region, aliases, var_store, introduced); .instantiate_aliases(region, aliases, var_store, introduced);
@ -761,6 +806,10 @@ fn symbols_help(tipe: &Type, accum: &mut ImSet<Symbol>) {
accum.insert(*alias_symbol); accum.insert(*alias_symbol);
symbols_help(&actual_type, accum); symbols_help(&actual_type, accum);
} }
HostExposedAlias { name, actual, .. } => {
accum.insert(*name);
symbols_help(&actual, accum);
}
Apply(symbol, args) => { Apply(symbol, args) => {
accum.insert(*symbol); accum.insert(*symbol);
args.iter().for_each(|arg| symbols_help(arg, accum)); 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); variables_help(actual, accum);
} }
HostExposedAlias {
arguments, actual, ..
} => {
for (_, arg) in arguments {
variables_help(arg, accum);
}
variables_help(actual, accum);
}
Apply(_, args) => { Apply(_, args) => {
for x in args { for x in args {
variables_help(x, accum); variables_help(x, accum);

View file

@ -17,3 +17,7 @@ roc_load = { path = "../compiler/load" }
roc_builtins = { path = "../compiler/builtins" } roc_builtins = { path = "../compiler/builtins" }
roc_collections = { path = "../compiler/collections" } roc_collections = { path = "../compiler/collections" }
bumpalo = { version = "3.2", features = ["collections"] } bumpalo = { version = "3.2", features = ["collections"] }
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"

View file

@ -5,7 +5,10 @@ extern crate serde;
extern crate serde_derive; extern crate serde_derive;
extern crate pulldown_cmark; extern crate pulldown_cmark;
extern crate serde_json; 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; use std::fs;
extern crate roc_load; extern crate roc_load;
use bumpalo::Bump; use bumpalo::Bump;
@ -22,7 +25,7 @@ pub struct Template {
module_links: Vec<TemplateLink>, module_links: Vec<TemplateLink>,
} }
#[derive(Serialize, Clone)] #[derive(Serialize, Clone, Debug, PartialEq)]
pub struct ModuleEntry { pub struct ModuleEntry {
name: String, name: String,
docs: String, docs: String,
@ -41,13 +44,9 @@ pub struct TemplateLinkEntry {
name: String, name: String,
} }
fn main() -> Result<(), Box<dyn Error>> { fn main() {
let std_lib = roc_builtins::std::standard_stdlib(); generate(
let subs_by_module = MutMap::default(); vec![
let arena = Bump::new();
let src_dir = Path::new("../compiler/builtins/docs");
let files = vec![
PathBuf::from(r"../compiler/builtins/docs/Bool.roc"), PathBuf::from(r"../compiler/builtins/docs/Bool.roc"),
PathBuf::from(r"../compiler/builtins/docs/Map.roc"), PathBuf::from(r"../compiler/builtins/docs/Map.roc"),
// Not working // 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/Num.roc"),
PathBuf::from(r"../compiler/builtins/docs/Set.roc"), PathBuf::from(r"../compiler/builtins/docs/Set.roc"),
PathBuf::from(r"../compiler/builtins/docs/Str.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 // TODO: get info from a file like "elm.json"
for filename in files { 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, &copy_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( let mut loaded = roc_load::file::load_and_typecheck(
&arena, &arena,
filename, filename,
std_lib.clone(), std_lib.clone(),
src_dir, src_dir,
subs_by_module.clone(), MutMap::default(),
) )
.expect("TODO gracefully handle load failing"); .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 { fn documentation_to_template_data(doc: &Documentation, module: &ModuleDocumentation) -> Template {
name: "roc/builtins".to_string(), Template {
version: "1.0.0".to_string(), package_name: doc.name.clone(),
docs: "Package introduction or README.".to_string(), package_version: doc.version.clone(),
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(),
module_name: module.name.clone(), module_name: module.name.clone(),
module_docs: docs_html, module_docs: markdown_to_html(module.docs.clone()),
module_entries: module module_entries: module
.entries .entries
.clone() .clone()
.into_iter() .into_iter()
.map(|entry| { .map(|entry| ModuleEntry {
// 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 {
name: entry.name.clone(), name: entry.name.clone(),
docs: entry_docs_html, docs: match entry.docs {
} Some(docs) => markdown_to_html(docs),
None => String::new(),
},
}) })
.collect(), .collect(),
module_links: package module_links: doc
.modules .modules
.clone() .clone()
.into_iter() .into_iter()
@ -140,26 +172,118 @@ fn main() -> Result<(), Box<dyn Error>> {
.collect(), .collect(),
}) })
.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 { fn markdown_to_html(markdown: String) -> String {
overwrite: true, use pulldown_cmark::CodeBlockKind::*;
skip_exist: false, use pulldown_cmark::CowStr::*;
buffer_size: 64000, //64kb use pulldown_cmark::Event::*;
copy_inside: false, use pulldown_cmark::Tag::*;
content_only: true,
depth: 0, let markdown_options = pulldown_cmark::Options::all();
}; let mut docs_parser = vec![];
fs_extra::dir::copy("./src/static/", "./build", &copy_options)?; let (_, _) = pulldown_cmark::Parser::new_ext(&markdown, markdown_options).fold(
println!("Docs generated at /build"); (0, 0),
Ok(()) |(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 &gt;&gt;&gt; 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
View 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
View file

@ -0,0 +1 @@
*.spv

1
examples/.gitignore vendored
View file

@ -1,4 +1,5 @@
app app
host.o host.o
c_host.o c_host.o
roc_app.o
app.dSYM app.dSYM

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

View 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]

View file

@ -0,0 +1,7 @@
#include <stdio.h>
extern int rust_main();
int main() {
return rust_main();
}

View 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
}

View file

@ -1,17 +1,27 @@
use roc_std::RocCallResult;
use roc_std::RocStr; use roc_std::RocStr;
use std::str; use std::str;
extern "C" { extern "C" {
#[link_name = "main_1"] #[link_name = "main_1_exposed"]
fn main() -> RocStr; fn say_hello(output: &mut RocCallResult<RocStr>) -> ();
} }
#[no_mangle] #[no_mangle]
pub fn rust_main() -> isize { pub fn rust_main() -> isize {
println!( let answer = unsafe {
"Roc says: {}", use std::mem::MaybeUninit;
str::from_utf8(unsafe { main().as_slice() }).unwrap() 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 // Exit code
0 0

View file

@ -1,9 +1,10 @@
use roc_std::RocCallResult;
use roc_std::RocList; use roc_std::RocList;
use std::time::SystemTime; use std::time::SystemTime;
extern "C" { extern "C" {
#[link_name = "quicksort_1"] #[link_name = "quicksort_1_exposed"]
fn quicksort(list: RocList<i64>) -> RocList<i64>; fn quicksort(list: RocList<i64>, output: &mut RocCallResult<RocList<i64>>) -> ();
} }
const NUM_NUMS: usize = 100; const NUM_NUMS: usize = 100;
@ -24,7 +25,17 @@ pub fn rust_main() -> isize {
println!("Running Roc quicksort on {} numbers...", nums.len()); println!("Running Roc quicksort on {} numbers...", nums.len());
let start_time = SystemTime::now(); 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 end_time = SystemTime::now();
let duration = end_time.duration_since(start_time).unwrap(); let duration = end_time.duration_since(start_time).unwrap();

View file

@ -1,9 +1,10 @@
use roc_std::RocCallResult;
use roc_std::RocList; use roc_std::RocList;
use std::time::SystemTime; use std::time::SystemTime;
extern "C" { extern "C" {
#[link_name = "quicksort_1"] #[link_name = "quicksort_1_exposed"]
fn quicksort(list: RocList<i64>) -> RocList<i64>; fn quicksort(list: RocList<i64>, output: &mut RocCallResult<RocList<i64>>) -> ();
} }
const NUM_NUMS: usize = 100; const NUM_NUMS: usize = 100;
@ -24,7 +25,18 @@ pub fn rust_main() -> isize {
println!("Running Roc quicksort on {} numbers...", nums.len()); println!("Running Roc quicksort on {} numbers...", nums.len());
let start_time = SystemTime::now(); 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 end_time = SystemTime::now();
let duration = end_time.duration_since(start_time).unwrap(); let duration = end_time.duration_since(start_time).unwrap();

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

View 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]

View file

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