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
args: -- -D warnings
- uses: actions-rs/cargo@v1
name: cargo test
with:
command: test
- uses: actions-rs/cargo@v1
name: cargo test -- --ignored
continue-on-error: true

View file

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

7
Cargo.lock generated
View file

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

View file

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

View file

@ -59,6 +59,11 @@ fn jit_to_ast_help<'a>(
content: &Content,
) -> Expr<'a> {
match layout {
Layout::Builtin(Builtin::Int1) => {
// TODO this will not handle the case where this bool was really
// a 1-element recored, e.g. { x: True }, correctly
run_jit_function!(lib, main_fn_name, bool, |num| i1_to_ast(num))
}
Layout::Builtin(Builtin::Int64) => {
run_jit_function!(lib, main_fn_name, i64, |num| num_to_ast(
env,
@ -433,6 +438,15 @@ fn i64_to_ast(arena: &Bump, num: i64) -> Expr<'_> {
Expr::Num(arena.alloc(format!("{}", num)))
}
/// This is centralized in case we want to format it differently later,
/// e.g. adding underscores for large numbers
fn i1_to_ast<'a>(num: bool) -> Expr<'a> {
match num {
true => Expr::GlobalTag(&"True"),
false => Expr::GlobalTag(&"False"),
}
}
/// This is centralized in case we want to format it differently later,
/// e.g. adding underscores for large numbers
fn f64_to_ast(arena: &Bump, num: f64) -> Expr<'_> {

View file

@ -12,24 +12,13 @@ mod helpers;
#[cfg(test)]
mod cli_run {
use crate::helpers::{
example_file, extract_valgrind_errors, run_cmd, run_roc, run_with_valgrind,
example_file, extract_valgrind_errors, fixture_file, run_cmd, run_roc, run_with_valgrind,
};
use serial_test::serial;
use std::path::Path;
fn check_output(
folder: &str,
file: &str,
flags: &[&str],
expected_ending: &str,
use_valgrind: bool,
) {
let compile_out = run_roc(
&[
&["build", example_file(folder, file).to_str().unwrap()],
flags,
]
.concat(),
);
fn check_output(file: &Path, flags: &[&str], expected_ending: &str, use_valgrind: bool) {
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags].concat());
if !compile_out.stderr.is_empty() {
panic!(compile_out.stderr);
}
@ -37,14 +26,14 @@ mod cli_run {
let out = if use_valgrind {
let (valgrind_out, raw_xml) =
run_with_valgrind(&[example_file(folder, "app").to_str().unwrap()]);
run_with_valgrind(&[file.with_file_name("app").to_str().unwrap()]);
let memory_errors = extract_valgrind_errors(&raw_xml);
if !memory_errors.is_empty() {
panic!("{:?}", memory_errors);
}
valgrind_out
} else {
run_cmd(example_file(folder, "app").to_str().unwrap(), &[])
run_cmd(file.with_file_name("app").to_str().unwrap(), &[])
};
if !&out.stdout.ends_with(expected_ending) {
panic!(
@ -59,8 +48,7 @@ mod cli_run {
#[serial(hello_world)]
fn run_hello_world() {
check_output(
"hello-world",
"Hello.roc",
&example_file("hello-world", "Hello.roc"),
&[],
"Hello, World!!!!!!!!!!!!!\n",
true,
@ -71,8 +59,7 @@ mod cli_run {
#[serial(hello_world)]
fn run_hello_world_optimized() {
check_output(
"hello-world",
"Hello.roc",
&example_file("hello-world", "Hello.roc"),
&[],
"Hello, World!!!!!!!!!!!!!\n",
true,
@ -83,8 +70,7 @@ mod cli_run {
#[serial(quicksort)]
fn run_quicksort_not_optimized() {
check_output(
"quicksort",
"Quicksort.roc",
&example_file("quicksort", "Quicksort.roc"),
&[],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
false,
@ -95,8 +81,7 @@ mod cli_run {
#[serial(quicksort)]
fn run_quicksort_optimized() {
check_output(
"quicksort",
"Quicksort.roc",
&example_file("quicksort", "Quicksort.roc"),
&["--optimize"],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
false,
@ -109,8 +94,7 @@ mod cli_run {
#[ignore]
fn run_quicksort_valgrind() {
check_output(
"quicksort",
"Quicksort.roc",
&example_file("quicksort", "Quicksort.roc"),
&[],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
true,
@ -123,8 +107,7 @@ mod cli_run {
#[ignore]
fn run_quicksort_optimized_valgrind() {
check_output(
"quicksort",
"Quicksort.roc",
&example_file("quicksort", "Quicksort.roc"),
&["--optimize"],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
true,
@ -135,8 +118,7 @@ mod cli_run {
#[serial(multi_module)]
fn run_multi_module() {
check_output(
"multi-module",
"Quicksort.roc",
&example_file("multi-module", "Quicksort.roc"),
&[],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
false,
@ -147,8 +129,7 @@ mod cli_run {
#[serial(multi_module)]
fn run_multi_module_optimized() {
check_output(
"multi-module",
"Quicksort.roc",
&example_file("multi-module", "Quicksort.roc"),
&["--optimize"],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
false,
@ -161,8 +142,7 @@ mod cli_run {
#[ignore]
fn run_multi_module_valgrind() {
check_output(
"multi-module",
"Quicksort.roc",
&example_file("multi-module", "Quicksort.roc"),
&[],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
true,
@ -175,11 +155,60 @@ mod cli_run {
#[ignore]
fn run_multi_module_optimized_valgrind() {
check_output(
"multi-module",
"Quicksort.roc",
&example_file("multi-module", "Quicksort.roc"),
&["--optimize"],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
true,
);
}
#[test]
#[serial(multi_dep_str)]
fn run_multi_dep_str_unoptimized() {
// if true {
// todo!(
// "fix this test so it no longer deadlocks and hangs during monomorphization! The test never shows the error; to see the panic error, run this: cargo run run cli/tests/fixtures/multi-dep-str/Main.roc"
// );
// }
check_output(
&fixture_file("multi-dep-str", "Main.roc"),
&[],
"I am Dep2.str2\n",
true,
);
}
#[test]
#[serial(multi_dep_str)]
fn run_multi_dep_str_optimized() {
check_output(
&fixture_file("multi-dep-str", "Main.roc"),
&[],
"I am Dep2.str2\n",
true,
);
}
#[test]
#[serial(multi_dep_thunk)]
fn run_multi_dep_thunk_unoptimized() {
check_output(
&fixture_file("multi-dep-thunk", "Main.roc"),
&[],
"I am Dep2.value2\n",
true,
);
}
#[test]
#[serial(multi_dep_str)]
fn run_multi_dep_thunk_optimized() {
check_output(
&fixture_file("multi-dep-thunk", "Main.roc"),
&[],
"I am Dep2.value2\n",
true,
);
}
}

4
cli/tests/fixtures/.gitignore vendored Normal file
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
}
#[allow(dead_code)]
pub fn fixtures_dir(dir_name: &str) -> PathBuf {
let mut path = env::current_exe().ok().unwrap();
// Get rid of the filename in target/debug/deps/cli_run-99c65e4e9a1fbd06
path.pop();
// If we're in deps/ get rid of deps/ in target/debug/deps/
if path.ends_with("deps") {
path.pop();
}
// Get rid of target/debug/ so we're back at the project root
path.pop();
path.pop();
// Descend into cli/tests/fixtures/{dir_name}
path.push("cli");
path.push("tests");
path.push("fixtures");
path.push(dir_name);
path
}
#[allow(dead_code)]
pub fn fixture_file(dir_name: &str, file_name: &str) -> PathBuf {
let mut path = fixtures_dir(dir_name);
path.push(file_name);
path
}
#[allow(dead_code)]
pub fn repl_eval(input: &str) -> Out {
let mut cmd = Command::new(path_to_roc_binary());

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]
fn basic_1_field_i64_record() {
// 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 std::io;
use std::path::{Path, PathBuf};
use std::process::{Child, Command};
use std::process::{Child, Command, Output};
use target_lexicon::{Architecture, OperatingSystem, Triple};
use tempfile::tempdir;
@ -47,7 +47,7 @@ pub fn rebuild_host(host_input_path: &Path) {
let host_dest = host_input_path.with_file_name("host.o");
// Compile host.c
Command::new("clang")
let output = Command::new("clang")
.env_clear()
.args(&[
"-c",
@ -58,18 +58,22 @@ pub fn rebuild_host(host_input_path: &Path) {
.output()
.unwrap();
validate_output("host.c", "clang", output);
if cargo_host_src.exists() {
// Compile and link Cargo.toml, if it exists
let cargo_dir = host_input_path.parent().unwrap();
let libhost_dir = cargo_dir.join("target").join("release");
Command::new("cargo")
let output = Command::new("cargo")
.args(&["build", "--release"])
.current_dir(cargo_dir)
.output()
.unwrap();
Command::new("ld")
validate_output("host.rs", "cargo build --release", output);
let output = Command::new("ld")
.env_clear()
.args(&[
"-r",
@ -82,9 +86,11 @@ pub fn rebuild_host(host_input_path: &Path) {
])
.output()
.unwrap();
validate_output("c_host.o", "ld", output);
} else if rust_host_src.exists() {
// Compile and link host.rs, if it exists
Command::new("rustc")
let output = Command::new("rustc")
.args(&[
rust_host_src.to_str().unwrap(),
"-o",
@ -93,7 +99,9 @@ pub fn rebuild_host(host_input_path: &Path) {
.output()
.unwrap();
Command::new("ld")
validate_output("host.rs", "rustc", output);
let output = Command::new("ld")
.env_clear()
.args(&[
"-r",
@ -105,8 +113,10 @@ pub fn rebuild_host(host_input_path: &Path) {
.output()
.unwrap();
validate_output("rust_host.o", "ld", output);
// Clean up rust_host.o
Command::new("rm")
let output = Command::new("rm")
.env_clear()
.args(&[
"-f",
@ -115,13 +125,17 @@ pub fn rebuild_host(host_input_path: &Path) {
])
.output()
.unwrap();
validate_output("rust_host.o", "rm", output);
} else {
// Clean up rust_host.o
Command::new("mv")
let output = Command::new("mv")
.env_clear()
.args(&[c_host_dest, host_dest])
.output()
.unwrap();
validate_output("rust_host.o", "mv", output);
}
}
@ -303,3 +317,18 @@ pub fn module_to_dylib(
Library::new(path)
}
fn validate_output(file_name: &str, cmd_name: &str, output: Output) {
if !output.status.success() {
match std::str::from_utf8(&output.stderr) {
Ok(stderr) => panic!(
"Failed to rebuild {} - stderr of the `{}` command was:\n{}",
file_name, cmd_name, stderr
),
Err(utf8_err) => panic!(
"Failed to rebuild {} - stderr of the `{}` command was invalid utf8 ({:?})",
file_name, cmd_name, utf8_err
),
}
}
}

View file

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

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

View file

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

View file

@ -63,6 +63,7 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
Symbol::LIST_REPEAT => list_repeat,
Symbol::LIST_REVERSE => list_reverse,
Symbol::LIST_CONCAT => list_concat,
Symbol::LIST_CONTAINS => list_contains,
Symbol::LIST_PREPEND => list_prepend,
Symbol::LIST_JOIN => list_join,
Symbol::LIST_MAP => list_map,
@ -101,6 +102,36 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
Symbol::NUM_ATAN => num_atan,
Symbol::NUM_ACOS => num_acos,
Symbol::NUM_ASIN => num_asin,
Symbol::NUM_MAX_INT => num_max_int,
Symbol::NUM_MIN_INT => num_min_int,
}
}
/// Num.maxInt : Int
fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
let int_var = var_store.fresh();
let body = Int(int_var, i64::MAX);
Def {
annotation: None,
expr_var: int_var,
loc_expr: Located::at_zero(body),
loc_pattern: Located::at_zero(Pattern::Identifier(symbol)),
pattern_vars: SendMap::default(),
}
}
/// Num.minInt : Int
fn num_min_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
let int_var = var_store.fresh();
let body = Int(int_var, i64::MIN);
Def {
annotation: None,
expr_var: int_var,
loc_expr: Located::at_zero(body),
loc_pattern: Located::at_zero(Pattern::Identifier(symbol)),
pattern_vars: SendMap::default(),
}
}
@ -1291,6 +1322,30 @@ fn list_keep_if(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
// List.contains : List elem, elem, -> Bool
fn list_contains(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
let elem_var = var_store.fresh();
let bool_var = var_store.fresh();
let body = RunLowLevel {
op: LowLevel::ListContains,
args: vec![
(list_var, Var(Symbol::ARG_1)),
(elem_var, Var(Symbol::ARG_2)),
],
ret_var: bool_var,
};
defn(
symbol,
vec![(list_var, Symbol::ARG_1), (elem_var, Symbol::ARG_2)],
var_store,
body,
bool_var,
)
}
/// List.map : List before, (before -> after) -> List after
fn list_map(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();

View file

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

View file

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

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),
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 {
match expr {
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));
(
actual_vars,
crate::builtins::builtin_type(Symbol::ATTR_ATTR, vec![uniq_type, alias]),
)
} else {
panic!("lifted type is not Attr")
}
}
HostExposedAlias {
name: symbol,
arguments: fields,
actual_var,
actual,
} => {
let (mut actual_vars, lifted_actual) =
annotation_to_attr_type(var_store, actual, rigids, change_var_kind);
if let Type::Apply(attr_symbol, args) = lifted_actual {
debug_assert!(attr_symbol == Symbol::ATTR_ATTR);
let uniq_type = args[0].clone();
let actual_type = args[1].clone();
let mut new_fields = Vec::with_capacity(fields.len());
for (name, tipe) in fields {
let (lifted_vars, lifted) =
annotation_to_attr_type(var_store, tipe, rigids, change_var_kind);
actual_vars.extend(lifted_vars);
new_fields.push((name.clone(), lifted));
}
let alias = Type::HostExposedAlias {
name: *symbol,
arguments: new_fields,
actual_var: *actual_var,
actual: Box::new(actual_type),
};
(
actual_vars,
crate::builtins::builtin_type(Symbol::ATTR_ATTR, vec![uniq_type, alias]),

View file

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

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

View file

@ -1,8 +1,8 @@
use crate::layout_id::LayoutIds;
use crate::llvm::build_list::{
allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_get_unsafe,
list_join, list_keep_if, list_len, list_map, list_prepend, list_repeat, list_reverse, list_set,
list_single, list_walk_right,
allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_contains,
list_get_unsafe, list_join, list_keep_if, list_len, list_map, list_prepend, list_repeat,
list_reverse, list_set, list_single, list_walk_right,
};
use crate::llvm::build_str::{str_concat, str_len, str_split, CHAR_LAYOUT};
use crate::llvm::compare::{build_eq, build_neq};
@ -34,7 +34,7 @@ use roc_collections::all::{ImMap, MutSet};
use roc_module::low_level::LowLevel;
use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::ir::{JoinPointId, Wrapped};
use roc_mono::layout::{Builtin, Layout, MemoryMode};
use roc_mono::layout::{Builtin, ClosureLayout, Layout, MemoryMode};
use target_lexicon::CallingConvention;
/// This is for Inkwell's FunctionValue::verify - we want to know the verification
@ -1842,6 +1842,286 @@ pub fn create_entry_block_alloca<'a, 'ctx>(
builder.build_alloca(basic_type, name)
}
fn expose_function_to_host<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
roc_function: FunctionValue<'ctx>,
) {
use inkwell::types::BasicType;
let roc_wrapper_function = make_exception_catching_wrapper(env, roc_function);
let roc_function_type = roc_wrapper_function.get_type();
// STEP 1: turn `f : a,b,c -> d` into `f : a,b,c, &d -> {}`
let mut argument_types = roc_function_type.get_param_types();
let return_type = roc_function_type.get_return_type().unwrap();
let output_type = return_type.ptr_type(AddressSpace::Generic);
argument_types.push(output_type.into());
let c_function_type = env.context.void_type().fn_type(&argument_types, false);
let c_function_name: String = format!("{}_exposed", roc_function.get_name().to_str().unwrap());
let c_function = env.module.add_function(
c_function_name.as_str(),
c_function_type,
Some(Linkage::External),
);
// STEP 2: build the exposed function's body
let builder = env.builder;
let context = env.context;
let entry = context.append_basic_block(c_function, "entry");
builder.position_at_end(entry);
// drop the final argument, which is the pointer we write the result into
let args = c_function.get_params();
let output_arg_index = args.len() - 1;
let args = &args[..args.len() - 1];
debug_assert_eq!(args.len(), roc_function.get_params().len());
debug_assert_eq!(args.len(), roc_wrapper_function.get_params().len());
let call_wrapped = builder.build_call(roc_wrapper_function, args, "call_wrapped_function");
call_wrapped.set_call_convention(FAST_CALL_CONV);
let call_result = call_wrapped.try_as_basic_value().left().unwrap();
let output_arg = c_function
.get_nth_param(output_arg_index as u32)
.unwrap()
.into_pointer_value();
builder.build_store(output_arg, call_result);
builder.build_return(None);
// STEP 3: build a {} -> u64 function that gives the size of the return type
let size_function_type = env.context.i64_type().fn_type(&[], false);
let size_function_name: String = format!("{}_size", roc_function.get_name().to_str().unwrap());
let size_function = env.module.add_function(
size_function_name.as_str(),
size_function_type,
Some(Linkage::External),
);
let entry = context.append_basic_block(size_function, "entry");
builder.position_at_end(entry);
let size: BasicValueEnum = return_type.size_of().unwrap().into();
builder.build_return(Some(&size));
}
fn invoke_and_catch<'a, 'ctx, 'env, F, T>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
function: F,
arguments: &[BasicValueEnum<'ctx>],
return_type: T,
) -> BasicValueEnum<'ctx>
where
F: Into<either::Either<FunctionValue<'ctx>, PointerValue<'ctx>>>,
T: inkwell::types::BasicType<'ctx>,
{
let context = env.context;
let builder = env.builder;
let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic);
let call_result_type = context.struct_type(
&[context.i64_type().into(), return_type.as_basic_type_enum()],
false,
);
let then_block = context.append_basic_block(parent, "then_block");
let catch_block = context.append_basic_block(parent, "catch_block");
let cont_block = context.append_basic_block(parent, "cont_block");
let result_alloca = builder.build_alloca(call_result_type, "result");
// invoke instead of call, so that we can catch any exeptions thrown in Roc code
let call_result = {
let call = builder.build_invoke(
function,
&arguments,
then_block,
catch_block,
"call_roc_function",
);
call.set_call_convention(FAST_CALL_CONV);
call.try_as_basic_value().left().unwrap()
};
// exception handling
{
builder.position_at_end(catch_block);
let landing_pad_type = {
let exception_ptr = context.i8_type().ptr_type(AddressSpace::Generic).into();
let selector_value = context.i32_type().into();
context.struct_type(&[exception_ptr, selector_value], false)
};
let info = builder
.build_catch_all_landing_pad(
&landing_pad_type,
&BasicValueEnum::IntValue(context.i8_type().const_zero()),
context.i8_type().ptr_type(AddressSpace::Generic),
"main_landing_pad",
)
.into_struct_value();
let exception_ptr = builder
.build_extract_value(info, 0, "exception_ptr")
.unwrap();
let thrown = cxa_begin_catch(env, exception_ptr);
let error_msg = {
let exception_type = u8_ptr;
let ptr = builder.build_bitcast(
thrown,
exception_type.ptr_type(AddressSpace::Generic),
"cast",
);
builder.build_load(ptr.into_pointer_value(), "error_msg")
};
let return_type = context.struct_type(&[context.i64_type().into(), u8_ptr.into()], false);
let return_value = {
let v1 = return_type.const_zero();
// flag is non-zero, indicating failure
let flag = context.i64_type().const_int(1, false);
let v2 = builder
.build_insert_value(v1, flag, 0, "set_error")
.unwrap();
let v3 = builder
.build_insert_value(v2, error_msg, 1, "set_exception")
.unwrap();
v3
};
// bitcast result alloca so we can store our concrete type { flag, error_msg } in there
let result_alloca_bitcast = builder
.build_bitcast(
result_alloca,
return_type.ptr_type(AddressSpace::Generic),
"result_alloca_bitcast",
)
.into_pointer_value();
// store our return value
builder.build_store(result_alloca_bitcast, return_value);
cxa_end_catch(env);
builder.build_unconditional_branch(cont_block);
}
{
builder.position_at_end(then_block);
let return_value = {
let v1 = call_result_type.const_zero();
let v2 = builder
.build_insert_value(v1, context.i64_type().const_zero(), 0, "set_no_error")
.unwrap();
let v3 = builder
.build_insert_value(v2, call_result, 1, "set_call_result")
.unwrap();
v3
};
let ptr = builder.build_bitcast(
result_alloca,
call_result_type.ptr_type(AddressSpace::Generic),
"name",
);
builder.build_store(ptr.into_pointer_value(), return_value);
builder.build_unconditional_branch(cont_block);
}
builder.position_at_end(cont_block);
let result = builder.build_load(result_alloca, "result");
// MUST set the personality at the very end;
// doing it earlier can cause the personality to be ignored
let personality_func = get_gxx_personality_v0(env);
parent.set_personality_function(personality_func);
result
}
fn make_exception_catching_wrapper<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
roc_function: FunctionValue<'ctx>,
) -> FunctionValue<'ctx> {
// build the C calling convention wrapper
let context = env.context;
let builder = env.builder;
let roc_function_type = roc_function.get_type();
let argument_types = roc_function_type.get_param_types();
let wrapper_function_name = format!("{}_catcher", roc_function.get_name().to_str().unwrap());
let wrapper_return_type = context.struct_type(
&[
context.i64_type().into(),
roc_function_type.get_return_type().unwrap(),
],
false,
);
let wrapper_function_type = wrapper_return_type.fn_type(&argument_types, false);
// Add main to the module.
let wrapper_function =
env.module
.add_function(&wrapper_function_name, wrapper_function_type, None);
// our exposed main function adheres to the C calling convention
wrapper_function.set_call_conventions(FAST_CALL_CONV);
// invoke instead of call, so that we can catch any exeptions thrown in Roc code
let arguments = wrapper_function.get_params();
let basic_block = context.append_basic_block(wrapper_function, "entry");
builder.position_at_end(basic_block);
let result = invoke_and_catch(
env,
wrapper_function,
roc_function,
&arguments,
roc_function_type.get_return_type().unwrap(),
);
builder.build_return(Some(&result));
// MUST set the personality at the very end;
// doing it earlier can cause the personality to be ignored
let personality_func = get_gxx_personality_v0(env);
wrapper_function.set_personality_function(personality_func);
wrapper_function
}
pub fn build_proc_header<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
@ -1853,6 +2133,32 @@ pub fn build_proc_header<'a, 'ctx, 'env>(
let arena = env.arena;
let context = &env.context;
let fn_name = layout_ids
.get(symbol, layout)
.to_symbol_string(symbol, &env.interns);
use roc_mono::ir::HostExposedLayouts;
match &proc.host_exposed_layouts {
HostExposedLayouts::NotHostExposed => {}
HostExposedLayouts::HostExposed { rigids: _, aliases } => {
for (name, layout) in aliases {
match layout {
Layout::Closure(arguments, closure, result) => {
build_closure_caller(env, &fn_name, *name, arguments, closure, result)
}
Layout::FunctionPointer(_arguments, _result) => {
// TODO should this be considered a closure of size 0?
// or do we let the host call it directly?
// then we have no RocCallResult wrapping though
}
_ => {
// TODO
}
}
}
}
}
let ret_type = basic_type_from_layout(arena, context, &proc.ret_layout, env.ptr_bytes);
let mut arg_basic_types = Vec::with_capacity_in(args.len(), arena);
@ -1864,26 +2170,199 @@ pub fn build_proc_header<'a, 'ctx, 'env>(
let fn_type = get_fn_type(&ret_type, &arg_basic_types);
let fn_name = layout_ids
.get(symbol, layout)
.to_symbol_string(symbol, &env.interns);
let fn_val = env
.module
.add_function(fn_name.as_str(), fn_type, Some(Linkage::Private));
if env.exposed_to_host.contains(&symbol) {
// If this is an external-facing function, it'll use the C calling convention
// and external linkage.
fn_val.set_linkage(Linkage::External);
fn_val.set_call_conventions(C_CALL_CONV);
} else {
// If it's an internal-only function, it should use the fast calling conention.
fn_val.set_call_conventions(FAST_CALL_CONV);
if env.exposed_to_host.contains(&symbol) {
expose_function_to_host(env, fn_val);
}
fn_val
}
pub fn build_closure_caller<'a, 'ctx, 'env>(
env: &'a Env<'a, 'ctx, 'env>,
def_name: &str,
alias_symbol: Symbol,
arguments: &[Layout<'a>],
closure: &ClosureLayout<'a>,
result: &Layout<'a>,
) {
use inkwell::types::BasicType;
let arena = env.arena;
let context = &env.context;
let builder = env.builder;
// STEP 1: build function header
let function_name = format!(
"{}_{}_caller",
def_name,
alias_symbol.ident_string(&env.interns)
);
let mut argument_types = Vec::with_capacity_in(arguments.len() + 3, env.arena);
for layout in arguments {
argument_types.push(basic_type_from_layout(
arena,
context,
layout,
env.ptr_bytes,
));
}
let function_pointer_type = {
let function_layout =
ClosureLayout::extend_function_layout(arena, arguments, closure.clone(), result);
// this is already a (function) pointer type
basic_type_from_layout(arena, context, &function_layout, env.ptr_bytes)
};
argument_types.push(function_pointer_type);
let closure_argument_type = {
let basic_type = basic_type_from_layout(
arena,
context,
&closure.as_block_of_memory_layout(),
env.ptr_bytes,
);
basic_type.ptr_type(AddressSpace::Generic)
};
argument_types.push(closure_argument_type.into());
let result_type = basic_type_from_layout(arena, context, result, env.ptr_bytes);
let roc_call_result_type =
context.struct_type(&[context.i64_type().into(), result_type], false);
let output_type = { roc_call_result_type.ptr_type(AddressSpace::Generic) };
argument_types.push(output_type.into());
let function_type = context.void_type().fn_type(&argument_types, false);
let function_value = env.module.add_function(
function_name.as_str(),
function_type,
Some(Linkage::External),
);
function_value.set_call_conventions(C_CALL_CONV);
// STEP 2: build function body
let entry = context.append_basic_block(function_value, "entry");
builder.position_at_end(entry);
let mut parameters = function_value.get_params();
let output = parameters.pop().unwrap().into_pointer_value();
let closure_data_ptr = parameters.pop().unwrap().into_pointer_value();
let function_ptr = parameters.pop().unwrap().into_pointer_value();
let closure_data = builder.build_load(closure_data_ptr, "load_closure_data");
let mut arguments = parameters;
arguments.push(closure_data);
let result = invoke_and_catch(env, function_value, function_ptr, &arguments, result_type);
builder.build_store(output, result);
builder.build_return(None);
// STEP 3: build a {} -> u64 function that gives the size of the return type
let size_function_type = env.context.i64_type().fn_type(&[], false);
let size_function_name: String = format!(
"{}_{}_size",
def_name,
alias_symbol.ident_string(&env.interns)
);
let size_function = env.module.add_function(
size_function_name.as_str(),
size_function_type,
Some(Linkage::External),
);
let entry = context.append_basic_block(size_function, "entry");
builder.position_at_end(entry);
let size: BasicValueEnum = roc_call_result_type.size_of().unwrap().into();
builder.build_return(Some(&size));
}
#[allow(dead_code)]
pub fn build_closure_caller_old<'a, 'ctx, 'env>(
env: &'a Env<'a, 'ctx, 'env>,
closure_function: FunctionValue<'ctx>,
) {
let context = env.context;
let builder = env.builder;
// asuming the closure has type `a, b, closure_data -> c`
// change that into `a, b, *const closure_data, *mut output -> ()`
// a function `a, b, closure_data -> RocCallResult<c>`
let wrapped_function = make_exception_catching_wrapper(env, closure_function);
let closure_function_type = closure_function.get_type();
let wrapped_function_type = wrapped_function.get_type();
let mut arguments = closure_function_type.get_param_types();
// require that the closure data is passed by reference
let closure_data_type = arguments.pop().unwrap();
let closure_data_ptr_type = get_ptr_type(&closure_data_type, AddressSpace::Generic);
arguments.push(closure_data_ptr_type.into());
// require that a pointer is passed in to write the result into
let output_type = get_ptr_type(
&wrapped_function_type.get_return_type().unwrap(),
AddressSpace::Generic,
);
arguments.push(output_type.into());
let caller_function_type = env.context.void_type().fn_type(&arguments, false);
let caller_function_name: String =
format!("{}_caller", closure_function.get_name().to_str().unwrap());
let caller_function = env.module.add_function(
caller_function_name.as_str(),
caller_function_type,
Some(Linkage::External),
);
caller_function.set_call_conventions(C_CALL_CONV);
let entry = context.append_basic_block(caller_function, "entry");
builder.position_at_end(entry);
let mut parameters = caller_function.get_params();
let output = parameters.pop().unwrap();
let closure_data_ptr = parameters.pop().unwrap();
let closure_data =
builder.build_load(closure_data_ptr.into_pointer_value(), "load_closure_data");
parameters.push(closure_data);
let call = builder.build_call(wrapped_function, &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>(
env: &'a Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
@ -1907,6 +2386,10 @@ pub fn build_proc<'a, 'ctx, 'env>(
// the closure argument (if any) comes in as an opaque sequence of bytes.
// we need to cast that to the specific closure data layout that the body expects
let value = if let Symbol::ARG_CLOSURE = *arg_symbol {
// generate a caller function (to be used by the host)
// build_closure_caller(env, fn_val);
// builder.position_at_end(entry);
// blindly trust that there is a layout available for the closure data
let layout = proc.closure_data_layout.clone().unwrap();
@ -1971,8 +2454,8 @@ fn call_with_args<'a, 'ctx, 'env>(
panic!("Unrecognized builtin function: {:?}", fn_name)
} else {
panic!(
"Unrecognized non-builtin function: {:?} {:?}",
fn_name, layout
"Unrecognized non-builtin function: {:?} (symbol: {:?}, layout: {:?})",
fn_name, symbol, layout
)
}
});
@ -2126,6 +2609,16 @@ fn run_low_level<'a, 'ctx, 'env>(
list_keep_if(env, inplace, parent, func, func_layout, list, list_layout)
}
ListContains => {
// List.contains : List elem, elem -> Bool
debug_assert_eq!(args.len(), 2);
let (list, list_layout) = load_symbol_and_layout(env, scope, &args[0]);
let (elem, elem_layout) = load_symbol_and_layout(env, scope, &args[1]);
list_contains(env, parent, elem, elem_layout, list, list_layout)
}
ListWalkRight => {
// List.walkRight : List elem, (elem -> accum -> accum), accum -> accum
debug_assert_eq!(args.len(), 3);
@ -2920,7 +3413,7 @@ fn cxa_rethrow_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValu
}
fn get_gxx_personality_v0<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> FunctionValue<'ctx> {
let name = "__cxa_rethrow";
let name = "__gxx_personality_v0";
let module = env.module;
let context = env.context;

View file

@ -1,4 +1,5 @@
use crate::llvm::build::{Env, InPlace};
use crate::llvm::compare::build_eq;
use crate::llvm::convert::{basic_type_from_layout, collection, get_ptr_type, ptr_int};
use inkwell::builder::Builder;
use inkwell::context::Context;
@ -819,6 +820,116 @@ pub fn list_walk_right<'a, 'ctx, 'env>(
builder.build_load(accum_alloca, "load_final_acum")
}
/// List.contains : List elem, elem -> Bool
pub fn list_contains<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
elem: BasicValueEnum<'ctx>,
elem_layout: &Layout<'a>,
list: BasicValueEnum<'ctx>,
list_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
use inkwell::types::BasicType;
let builder = env.builder;
let wrapper_struct = list.into_struct_value();
let list_elem_layout = match &list_layout {
// this pointer will never actually be dereferenced
Layout::Builtin(Builtin::EmptyList) => &Layout::Builtin(Builtin::Int64),
Layout::Builtin(Builtin::List(_, element_layout)) => element_layout,
_ => unreachable!("Invalid layout {:?} in List.contains", list_layout),
};
let list_elem_type =
basic_type_from_layout(env.arena, env.context, list_elem_layout, env.ptr_bytes);
let list_ptr = load_list_ptr(
builder,
wrapper_struct,
list_elem_type.ptr_type(AddressSpace::Generic),
);
let length = list_len(builder, list.into_struct_value());
list_contains_help(
env,
parent,
length,
list_ptr,
list_elem_layout,
elem,
elem_layout,
)
}
pub fn list_contains_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
length: IntValue<'ctx>,
source_ptr: PointerValue<'ctx>,
list_elem_layout: &Layout<'a>,
elem: BasicValueEnum<'ctx>,
elem_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let ctx = env.context;
let bool_alloca = builder.build_alloca(ctx.bool_type(), "bool_alloca");
let index_alloca = builder.build_alloca(ctx.i64_type(), "index_alloca");
let next_free_index_alloca = builder.build_alloca(ctx.i64_type(), "next_free_index_alloca");
builder.build_store(bool_alloca, ctx.bool_type().const_zero());
builder.build_store(index_alloca, ctx.i64_type().const_zero());
builder.build_store(next_free_index_alloca, ctx.i64_type().const_zero());
let condition_bb = ctx.append_basic_block(parent, "condition");
builder.build_unconditional_branch(condition_bb);
builder.position_at_end(condition_bb);
let index = builder.build_load(index_alloca, "index").into_int_value();
let condition = builder.build_int_compare(IntPredicate::SGT, length, index, "loopcond");
let body_bb = ctx.append_basic_block(parent, "body");
let cont_bb = ctx.append_basic_block(parent, "cont");
builder.build_conditional_branch(condition, body_bb, cont_bb);
// loop body
builder.position_at_end(body_bb);
let current_elem_ptr = unsafe { builder.build_in_bounds_gep(source_ptr, &[index], "elem_ptr") };
let current_elem = builder.build_load(current_elem_ptr, "load_elem");
let has_found = build_eq(env, current_elem, elem, list_elem_layout, elem_layout);
builder.build_store(bool_alloca, has_found.into_int_value());
let one = ctx.i64_type().const_int(1, false);
let next_free_index = builder
.build_load(next_free_index_alloca, "load_next_free")
.into_int_value();
builder.build_store(
next_free_index_alloca,
builder.build_int_add(next_free_index, one, "incremented_next_free_index"),
);
builder.build_store(
index_alloca,
builder.build_int_add(index, one, "incremented_index"),
);
builder.build_conditional_branch(has_found.into_int_value(), cont_bb, condition_bb);
// continuation
builder.position_at_end(cont_bb);
builder.build_load(bool_alloca, "answer")
}
/// List.keepIf : List elem, (elem -> Bool) -> List elem
pub fn list_keep_if<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
@ -849,7 +960,7 @@ pub fn list_keep_if<'a, 'ctx, 'env>(
elem_layout.clone(),
),
_ => unreachable!("Invalid layout {:?} in List.reverse", list_layout),
_ => unreachable!("Invalid layout {:?} in List.keepIf", list_layout),
};
let list_type = basic_type_from_layout(env.arena, env.context, &list_layout, env.ptr_bytes);

View file

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

View file

@ -557,7 +557,7 @@ mod gen_num {
indoc!(
r#"
always42 : Num.Num Num.Integer -> Num.Num Num.Integer
always42 = \num -> 42
always42 = \_ -> 42
always42 5
"#
@ -788,4 +788,30 @@ mod gen_num {
// f64
// );
// }
#[test]
fn num_max_int() {
assert_evals_to!(
indoc!(
r#"
Num.maxInt
"#
),
i64::MAX,
i64
);
}
#[test]
fn num_min_int() {
assert_evals_to!(
indoc!(
r#"
Num.minInt
"#
),
i64::MIN,
i64
);
}
}

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

4
compiler/parse/fuzz/.gitignore vendored Normal file
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()).
/// It lets us take a (&Def) and create a plain (Def) from it.
Nested(&'a Def<'a>),
NotYetImplemented(&'static str),
}
#[derive(Debug, Clone, PartialEq)]

View file

@ -317,12 +317,27 @@ fn spaces<'a>(
'\n' => {
state = state.newline()?;
// This was a newline, so end this line comment.
space_list.push(LineComment(comment_line_buf.into_bump_str()));
match (comment_line_buf.len(), comment_line_buf.chars().next())
{
(1, Some('#')) => {
// This is a line with `##` - that is,
// a doc comment new line.
space_list.push(DocComment(""));
comment_line_buf = String::new_in(arena);
line_state = LineState::Normal;
}
_ => {
// This was a newline, so end this line comment.
space_list.push(LineComment(
comment_line_buf.into_bump_str(),
));
comment_line_buf = String::new_in(arena);
line_state = LineState::Normal;
}
}
}
nonblank => {
// Chars can have btye lengths of more than 1!
state = state.advance_without_indenting(nonblank.len_utf8())?;

View file

@ -561,7 +561,7 @@ fn annotation_or_alias<'a>(
ann: loc_ann,
},
Apply(_, _) => {
panic!("TODO gracefully handle invalid Apply in type annotation");
Def::NotYetImplemented("TODO gracefully handle invalid Apply in type annotation")
}
SpaceAfter(value, spaces_before) => Def::SpaceAfter(
arena.alloc(annotation_or_alias(arena, value, region, loc_ann)),
@ -574,19 +574,19 @@ fn annotation_or_alias<'a>(
Nested(value) => annotation_or_alias(arena, value, region, loc_ann),
PrivateTag(_) => {
panic!("TODO gracefully handle trying to use a private tag as an annotation.");
Def::NotYetImplemented("TODO gracefully handle trying to use a private tag as an annotation.")
}
QualifiedIdentifier { .. } => {
panic!("TODO gracefully handle trying to annotate a qualified identifier, e.g. `Foo.bar : ...`");
Def::NotYetImplemented("TODO gracefully handle trying to annotate a qualified identifier, e.g. `Foo.bar : ...`")
}
NumLiteral(_) | NonBase10Literal { .. } | FloatLiteral(_) | StrLiteral(_) => {
panic!("TODO gracefully handle trying to annotate a litera");
Def::NotYetImplemented("TODO gracefully handle trying to annotate a litera")
}
Underscore => {
panic!("TODO gracefully handle trying to give a type annotation to an undrscore");
Def::NotYetImplemented("TODO gracefully handle trying to give a type annotation to an undrscore")
}
Malformed(_) => {
panic!("TODO translate a malformed pattern into a malformed annotation");
Def::NotYetImplemented("TODO translate a malformed pattern into a malformed annotation")
}
Identifier(ident) => {
// This is a regular Annotation
@ -633,7 +633,13 @@ fn parse_def_expr<'a>(
))
// `<` because '=' should be same indent (or greater) as the entire def-expr
} else if equals_sign_indent < def_start_col {
todo!("TODO the = in this declaration seems outdented. equals_sign_indent was {} and def_start_col was {}", equals_sign_indent, def_start_col);
Err((
Fail {
attempting: state.attempting,
reason: FailReason::NotYetImplemented(format!("TODO the = in this declaration seems outdented. equals_sign_indent was {} and def_start_col was {}", equals_sign_indent, def_start_col)),
},
state,
))
} else {
// Indented more beyond the original indent of the entire def-expr.
let indented_more = def_start_col + 1;
@ -720,7 +726,15 @@ fn parse_def_signature<'a>(
))
// `<` because ':' should be same indent or greater
} else if colon_indent < original_indent {
panic!("TODO the : in this declaration seems outdented");
Err((
Fail {
attempting: state.attempting,
reason: FailReason::NotYetImplemented(
"TODO the : in this declaration seems outdented".to_string(),
),
},
state,
))
} else {
// Indented more beyond the original indent.
let indented_more = original_indent + 1;
@ -1069,11 +1083,12 @@ fn loc_ident_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>
} else {
format!("{}.{}", module_name, parts.join("."))
};
Ok((
Located {
region: loc_ident.region,
value: Pattern::Malformed(arena.alloc(malformed_str)),
value: Pattern::Malformed(
String::from_str_in(&malformed_str, &arena).into_bump_str(),
),
},
state,
))
@ -1120,7 +1135,15 @@ mod when {
),
move |arena, state, (case_indent, loc_condition)| {
if case_indent < min_indent {
panic!("TODO case wasn't indented enough");
return Err((
Fail {
attempting: state.attempting,
reason: FailReason::NotYetImplemented(
"TODO case wasn't indented enough".to_string(),
),
},
state,
));
}
// Everything in the branches must be indented at least as much as the case itself.
@ -1178,9 +1201,15 @@ mod when {
if alternatives_indented_correctly(&loc_patterns, original_indent) {
Ok(((loc_patterns, loc_guard), state))
} else {
panic!(
"TODO additional branch didn't have same indentation as first branch"
);
Err((
Fail {
attempting: state.attempting,
reason: FailReason::NotYetImplemented(
"TODO additional branch didn't have same indentation as first branch".to_string(),
),
},
state,
))
}
},
),
@ -1490,10 +1519,16 @@ fn ident_etc<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
});
}
Err(malformed) => {
panic!(
return Err((
Fail {
attempting: state.attempting,
reason: FailReason::NotYetImplemented(format!(
"TODO early return malformed pattern {:?}",
malformed
);
)),
},
state,
));
}
}
}

View file

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

View file

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

View file

@ -1,8 +1,8 @@
use crate::ast::{Attempting, EscapedChar, StrLiteral, StrSegment};
use crate::expr;
use crate::parser::{
allocated, ascii_char, ascii_hex_digits, loc, parse_utf8, unexpected, unexpected_eof,
ParseResult, Parser, State,
allocated, ascii_char, ascii_hex_digits, loc, parse_utf8, unexpected, unexpected_eof, Fail,
FailReason, ParseResult, Parser, State,
};
use bumpalo::collections::vec::Vec;
use bumpalo::Bump;
@ -279,7 +279,16 @@ where
// lines.push(line);
// Ok((StrLiteral::Block(lines.into_bump_slice()), state))
todo!("TODO parse this line in a block string: {:?}", line);
Err((
Fail {
attempting: state.attempting,
reason: FailReason::NotYetImplemented(format!(
"TODO parse this line in a block string: {:?}",
line
)),
},
state,
))
}
Err(reason) => state.fail(reason),
};

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::keyword;
use crate::parser::{
allocated, ascii_char, ascii_string, not, optional, peek_utf8_char, unexpected, Either,
ParseResult, Parser, State,
allocated, ascii_char, ascii_string, not, optional, peek_utf8_char, unexpected, Either, Fail,
FailReason, ParseResult, Parser, State,
};
use bumpalo::collections::string::String;
use bumpalo::collections::vec::Vec;
@ -239,7 +239,13 @@ fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>
Ok((first, state))
} else {
// e.g. `Int,Int` without an arrow and return type
panic!("Invalid function signature")
Err((
Fail {
attempting: state.attempting,
reason: FailReason::NotYetImplemented("TODO: Decide the correct error to return for 'Invalid function signature'".to_string()),
},
state,
))
}
}
}

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

View file

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

View file

@ -179,6 +179,25 @@ pub fn can_problem<'b>(
alloc.reflow(" definitions from this record."),
]),
]),
Problem::InvalidOptionalValue {
field_name,
field_region,
record_region,
} => alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This record uses an optional value for the "),
alloc.record_field(field_name),
alloc.reflow(" field in an incorrect context!"),
]),
alloc.region_all_the_things(
record_region,
field_region,
field_region,
Annotation::Error,
),
alloc.reflow("You can only use optional values in record destructuring, for example in affectation:"),
alloc.reflow("{ answer ? 42, otherField } = myRecord").indent(4),
]),
Problem::DuplicateRecordFieldType {
field_name,
field_region,
@ -534,6 +553,25 @@ fn pretty_runtime_error<'b>(
tip,
])
}
RuntimeError::InvalidOptionalValue {
field_name,
field_region,
record_region,
} => alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This record uses an optional value for the "),
alloc.record_field(field_name),
alloc.reflow(" field in an incorrect context!"),
]),
alloc.region_all_the_things(
record_region,
field_region,
field_region,
Annotation::Error,
),
alloc.reflow("You can only use optional values in record destructuring, for exemple in affectation:"),
alloc.reflow("{ answer ? 42, otherField } = myRecord"),
]),
RuntimeError::InvalidRecordUpdate { region } => alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This expression cannot be updated"),

View file

@ -95,8 +95,13 @@ mod test_reporting {
home,
ident_ids: &mut ident_ids,
};
let _mono_expr =
Stmt::new(&mut mono_env, loc_expr.value, &mut procs, &mut layout_cache);
let _mono_expr = Stmt::new(
&mut mono_env,
loc_expr.value,
var,
&mut procs,
&mut layout_cache,
);
}
Ok((unify_problems, can_problems, mono_problems, home, interns))
@ -3806,6 +3811,32 @@ mod test_reporting {
)
}
#[test]
fn incorrect_optional_field() {
report_problem_as(
indoc!(
r#"
{ x: 5, y ? 42 }
"#
),
indoc!(
r#"
SYNTAX PROBLEM
This record uses an optional value for the `.y` field in an incorrect
context!
1 { x: 5, y ? 42 }
^^^^^^
You can only use optional values in record destructuring, for example
in affectation:
{ answer ? 42, otherField } = myRecord
"#
),
)
}
#[test]
fn first_wildcard_is_required() {
report_problem_as(

View file

@ -813,6 +813,45 @@ fn type_to_variable(
result
}
HostExposedAlias {
name: symbol,
arguments: args,
actual: alias_type,
actual_var,
..
} => {
let mut arg_vars = Vec::with_capacity(args.len());
let mut new_aliases = ImMap::default();
for (arg, arg_type) in args {
let arg_var = type_to_variable(subs, rank, pools, cached, arg_type);
arg_vars.push((arg.clone(), arg_var));
new_aliases.insert(arg.clone(), arg_var);
}
let alias_var = type_to_variable(subs, rank, pools, cached, alias_type);
// unify the actual_var with the result var
// this can be used to access the type of the actual_var
// to determine its layout later
// subs.set_content(*actual_var, descriptor.content);
//subs.set(*actual_var, descriptor.clone());
let content = Content::Alias(*symbol, arg_vars, alias_var);
let result = register(subs, rank, pools, content);
// We only want to unify the actual_var with the alias once
// if it's already redirected (and therefore, redundant)
// don't do it again
if !subs.redundant(*actual_var) {
let descriptor = subs.get(result);
subs.union(result, *actual_var, descriptor);
}
result
}
Erroneous(problem) => {
let content = Content::Structure(FlatType::Erroneous(problem.clone()));

View file

@ -75,13 +75,16 @@ mod solve_expr {
let LoadedModule {
module_id: home,
mut can_problems,
type_problems,
mut type_problems,
interns,
mut solved,
exposed_to_host,
..
} = loaded;
let mut can_problems = can_problems.remove(&home).unwrap_or_default();
let type_problems = type_problems.remove(&home).unwrap_or_default();
let mut subs = solved.inner_mut();
// assert!(can_problems.is_empty());

View file

@ -53,6 +53,13 @@ pub enum SolvedType {
/// A type alias
Alias(Symbol, Vec<(Lowercase, SolvedType)>, Box<SolvedType>),
HostExposedAlias {
name: Symbol,
arguments: Vec<(Lowercase, SolvedType)>,
actual_var: VarId,
actual: Box<SolvedType>,
},
/// a boolean algebra Bool
Boolean(SolvedBool),
@ -194,6 +201,26 @@ impl SolvedType {
SolvedType::Alias(*symbol, solved_args, Box::new(solved_type))
}
HostExposedAlias {
name,
arguments,
actual_var,
actual,
} => {
let solved_type = Self::from_type(solved_subs, actual);
let mut solved_args = Vec::with_capacity(arguments.len());
for (name, var) in arguments {
solved_args.push((name.clone(), Self::from_type(solved_subs, var)));
}
SolvedType::HostExposedAlias {
name: *name,
arguments: solved_args,
actual_var: VarId::from_var(*actual_var, solved_subs.inner()),
actual: Box::new(solved_type),
}
}
Boolean(val) => SolvedType::Boolean(SolvedBool::from_bool(&val, solved_subs.inner())),
Variable(var) => Self::from_var(solved_subs.inner(), *var),
}
@ -486,6 +513,27 @@ pub fn to_type(
Type::Alias(*symbol, type_variables, Box::new(actual))
}
HostExposedAlias {
name,
arguments: solved_type_variables,
actual_var,
actual: solved_actual,
} => {
let mut type_variables = Vec::with_capacity(solved_type_variables.len());
for (lowercase, solved_arg) in solved_type_variables {
type_variables.push((lowercase.clone(), to_type(solved_arg, free_vars, var_store)));
}
let actual = to_type(solved_actual, free_vars, var_store);
Type::HostExposedAlias {
name: *name,
arguments: type_variables,
actual_var: var_id_to_flex_var(*actual_var, free_vars, var_store),
actual: Box::new(actual),
}
}
Error => Type::Erroneous(Problem::SolvedTypeError),
Erroneous(problem) => Type::Erroneous(problem.clone()),
}

View file

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

View file

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

View file

@ -5,7 +5,10 @@ extern crate serde;
extern crate serde_derive;
extern crate pulldown_cmark;
extern crate serde_json;
use std::error::Error;
use roc_builtins::std::StdLib;
use roc_load::docs::Documentation;
use roc_load::docs::ModuleDocumentation;
use std::fs;
extern crate roc_load;
use bumpalo::Bump;
@ -22,7 +25,7 @@ pub struct Template {
module_links: Vec<TemplateLink>,
}
#[derive(Serialize, Clone)]
#[derive(Serialize, Clone, Debug, PartialEq)]
pub struct ModuleEntry {
name: String,
docs: String,
@ -41,13 +44,9 @@ pub struct TemplateLinkEntry {
name: String,
}
fn main() -> Result<(), Box<dyn Error>> {
let std_lib = roc_builtins::std::standard_stdlib();
let subs_by_module = MutMap::default();
let arena = Bump::new();
let src_dir = Path::new("../compiler/builtins/docs");
let files = vec![
fn main() {
generate(
vec![
PathBuf::from(r"../compiler/builtins/docs/Bool.roc"),
PathBuf::from(r"../compiler/builtins/docs/Map.roc"),
// Not working
@ -56,76 +55,109 @@ fn main() -> Result<(), Box<dyn Error>> {
// PathBuf::from(r"../compiler/builtins/docs/Num.roc"),
PathBuf::from(r"../compiler/builtins/docs/Set.roc"),
PathBuf::from(r"../compiler/builtins/docs/Str.roc"),
];
],
roc_builtins::std::standard_stdlib(),
Path::new("../compiler/builtins/docs"),
Path::new("./build"),
)
}
let mut modules_docs = vec![];
pub fn generate(filenames: Vec<PathBuf>, std_lib: StdLib, src_dir: &Path, build_dir: &Path) {
let files_docs = files_to_documentations(filenames, std_lib, src_dir);
// Load each file is files vector
for filename in files {
// TODO: get info from a file like "elm.json"
let package = roc_load::docs::Documentation {
name: "roc/builtins".to_string(),
version: "1.0.0".to_string(),
docs: "Package introduction or README.".to_string(),
modules: files_docs,
};
// Remove old build folder, if exists
let _ = fs::remove_dir_all(build_dir);
let version_folder = build_dir
.join(package.name.clone())
.join(package.version.clone());
// Make sure the output directories exists
fs::create_dir_all(&version_folder)
.expect("TODO gracefully handle creating directories failing");
// Register handlebars template
let mut handlebars = handlebars::Handlebars::new();
handlebars
.register_template_file("page", "./src/templates/page.hbs")
.expect("TODO gracefully handle registering template failing");
// Write each package's module docs html file
for module in &package.modules {
let template = documentation_to_template_data(&package, module);
let handlebars_data = handlebars::to_json(&template);
let filepath = version_folder.join(format!("{}.html", module.name));
let mut output_file =
fs::File::create(filepath).expect("TODO gracefully handle creating file failing");
handlebars
.render_to_write("page", &handlebars_data, &mut output_file)
.expect("TODO gracefully handle writing file failing");
}
// Copy /static folder content to /build
let copy_options = fs_extra::dir::CopyOptions {
overwrite: true,
skip_exist: false,
buffer_size: 64000, //64kb
copy_inside: false,
content_only: true,
depth: 0,
};
fs_extra::dir::copy("./src/static/", &build_dir, &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(
&arena,
filename,
std_lib.clone(),
src_dir,
subs_by_module.clone(),
MutMap::default(),
)
.expect("TODO gracefully handle load failing");
modules_docs.extend(loaded.documentation.drain().map(|x| x.1));
files_docs.extend(loaded.documentation.drain().map(|x| x.1));
}
files_docs
}
let package = roc_load::docs::Documentation {
name: "roc/builtins".to_string(),
version: "1.0.0".to_string(),
docs: "Package introduction or README.".to_string(),
modules: modules_docs,
};
// Remove old build folder
fs::remove_dir_all("./build")?;
// Make sure the output directories exists
fs::create_dir_all(format!("./build/{}/{}", package.name, package.version))?;
// Register handlebars template
let mut handlebars = handlebars::Handlebars::new();
assert!(handlebars
.register_template_file("page", "./src/templates/page.hbs")
.is_ok());
let markdown_options = pulldown_cmark::Options::all();
// Write each package's module docs
for module in &package.modules {
// Convert module docs from markdown to html
let docs_parser = pulldown_cmark::Parser::new_ext(&module.docs, markdown_options);
let mut docs_html: String = String::with_capacity(module.docs.len() * 3 / 2);
pulldown_cmark::html::push_html(&mut docs_html, docs_parser);
let template = Template {
package_name: package.name.clone(),
package_version: package.version.clone(),
fn documentation_to_template_data(doc: &Documentation, module: &ModuleDocumentation) -> Template {
Template {
package_name: doc.name.clone(),
package_version: doc.version.clone(),
module_name: module.name.clone(),
module_docs: docs_html,
module_docs: markdown_to_html(module.docs.clone()),
module_entries: module
.entries
.clone()
.into_iter()
.map(|entry| {
// Convert entry docs from markdown to html
let mut entry_docs_html: String = String::new();
if let Some(docs) = entry.docs {
let entry_docs_parser =
pulldown_cmark::Parser::new_ext(&docs, markdown_options);
pulldown_cmark::html::push_html(&mut entry_docs_html, entry_docs_parser);
}
ModuleEntry {
.map(|entry| ModuleEntry {
name: entry.name.clone(),
docs: entry_docs_html,
}
docs: match entry.docs {
Some(docs) => markdown_to_html(docs),
None => String::new(),
},
})
.collect(),
module_links: package
module_links: doc
.modules
.clone()
.into_iter()
@ -140,26 +172,118 @@ fn main() -> Result<(), Box<dyn Error>> {
.collect(),
})
.collect(),
};
let handlebars_data = handlebars::to_json(&template);
let mut output_file = fs::File::create(format!(
"./build/{}/{}/{}.html",
package.name, package.version, module.name
))?;
handlebars.render_to_write("page", &handlebars_data, &mut output_file)?;
}
}
// Copy /static folder content to /build
let copy_options = fs_extra::dir::CopyOptions {
overwrite: true,
skip_exist: false,
buffer_size: 64000, //64kb
copy_inside: false,
content_only: true,
depth: 0,
};
fs_extra::dir::copy("./src/static/", "./build", &copy_options)?;
println!("Docs generated at /build");
Ok(())
fn markdown_to_html(markdown: String) -> String {
use pulldown_cmark::CodeBlockKind::*;
use pulldown_cmark::CowStr::*;
use pulldown_cmark::Event::*;
use pulldown_cmark::Tag::*;
let markdown_options = pulldown_cmark::Options::all();
let mut docs_parser = vec![];
let (_, _) = pulldown_cmark::Parser::new_ext(&markdown, markdown_options).fold(
(0, 0),
|(start_quote_count, end_quote_count), event| match event {
// Replace this sequence (`>>>` syntax):
// Start(BlockQuote)
// Start(BlockQuote)
// Start(BlockQuote)
// Start(Paragraph)
// For `Start(CodeBlock(Fenced(Borrowed("roc"))))`
Start(BlockQuote) => {
docs_parser.push(event);
(start_quote_count + 1, 0)
}
Start(Paragraph) => {
if start_quote_count == 3 {
docs_parser.pop();
docs_parser.pop();
docs_parser.pop();
docs_parser.push(Start(CodeBlock(Fenced(Borrowed("roc")))));
} else {
docs_parser.push(event);
}
(0, 0)
}
// Replace this sequence (`>>>` syntax):
// End(Paragraph)
// End(BlockQuote)
// End(BlockQuote)
// End(BlockQuote)
// For `End(CodeBlock(Fenced(Borrowed("roc"))))`
End(Paragraph) => {
docs_parser.push(event);
(0, 1)
}
End(BlockQuote) => {
if end_quote_count == 3 {
docs_parser.pop();
docs_parser.pop();
docs_parser.pop();
docs_parser.push(End(CodeBlock(Fenced(Borrowed("roc")))));
(0, 0)
} else {
docs_parser.push(event);
(0, end_quote_count + 1)
}
}
_ => {
docs_parser.push(event);
(0, 0)
}
},
);
let mut docs_html = String::new();
pulldown_cmark::html::push_html(&mut docs_html, docs_parser.into_iter());
docs_html
}
#[cfg(test)]
mod test_docs {
use super::*;
#[test]
fn internal() {
let files_docs = files_to_documentations(
vec![PathBuf::from(r"tests/fixtures/Interface.roc")],
roc_builtins::std::standard_stdlib(),
Path::new("tests/fixtures"),
);
let package = roc_load::docs::Documentation {
name: "roc/builtins".to_string(),
version: "1.0.0".to_string(),
docs: "Package introduction or README.".to_string(),
modules: files_docs,
};
let expected_entries = vec![
ModuleEntry {
name: "singleline".to_string(),
docs: "<p>Single line documentation.</p>\n".to_string(),
},
ModuleEntry {
name: "multiline".to_string(),
docs: "<p>Multiline documentation.\nWithout any complex syntax yet!</p>\n".to_string(),
}, ModuleEntry {
name: "multiparagraph".to_string(),
docs: "<p>Multiparagraph documentation.</p>\n<p>Without any complex syntax yet!</p>\n".to_string(),
}, ModuleEntry {
name: "codeblock".to_string(),
docs: "<p>Turns &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
host.o
c_host.o
roc_app.o
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 std::str;
extern "C" {
#[link_name = "main_1"]
fn main() -> RocStr;
#[link_name = "main_1_exposed"]
fn say_hello(output: &mut RocCallResult<RocStr>) -> ();
}
#[no_mangle]
pub fn rust_main() -> isize {
println!(
"Roc says: {}",
str::from_utf8(unsafe { main().as_slice() }).unwrap()
);
let answer = unsafe {
use std::mem::MaybeUninit;
let mut output: MaybeUninit<RocCallResult<RocStr>> = MaybeUninit::uninit();
say_hello(&mut *output.as_mut_ptr());
match output.assume_init().into() {
Ok(value) => value,
Err(msg) => panic!("roc failed with message {}", msg),
}
};
println!("Roc says: {}", str::from_utf8(answer.as_slice()).unwrap());
// Exit code
0

View file

@ -1,9 +1,10 @@
use roc_std::RocCallResult;
use roc_std::RocList;
use std::time::SystemTime;
extern "C" {
#[link_name = "quicksort_1"]
fn quicksort(list: RocList<i64>) -> RocList<i64>;
#[link_name = "quicksort_1_exposed"]
fn quicksort(list: RocList<i64>, output: &mut RocCallResult<RocList<i64>>) -> ();
}
const NUM_NUMS: usize = 100;
@ -24,7 +25,17 @@ pub fn rust_main() -> isize {
println!("Running Roc quicksort on {} numbers...", nums.len());
let start_time = SystemTime::now();
let answer = unsafe { quicksort(nums) };
let answer = unsafe {
use std::mem::MaybeUninit;
let mut output = MaybeUninit::uninit();
quicksort(nums, &mut *output.as_mut_ptr());
match output.assume_init().into() {
Ok(value) => value,
Err(msg) => panic!("roc failed with message: {}", msg),
}
};
let end_time = SystemTime::now();
let duration = end_time.duration_since(start_time).unwrap();

View file

@ -1,9 +1,10 @@
use roc_std::RocCallResult;
use roc_std::RocList;
use std::time::SystemTime;
extern "C" {
#[link_name = "quicksort_1"]
fn quicksort(list: RocList<i64>) -> RocList<i64>;
#[link_name = "quicksort_1_exposed"]
fn quicksort(list: RocList<i64>, output: &mut RocCallResult<RocList<i64>>) -> ();
}
const NUM_NUMS: usize = 100;
@ -24,7 +25,18 @@ pub fn rust_main() -> isize {
println!("Running Roc quicksort on {} numbers...", nums.len());
let start_time = SystemTime::now();
let answer = unsafe { quicksort(nums) };
let answer = unsafe {
use std::mem::MaybeUninit;
let mut output = MaybeUninit::uninit();
quicksort(nums, &mut *output.as_mut_ptr());
match output.assume_init().into() {
Ok(value) => value,
Err(msg) => panic!("roc failed with message {}", msg),
}
};
let end_time = SystemTime::now();
let duration = end_time.duration_since(start_time).unwrap();

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